From 9ecc787719f4eeecffb07f208dab25e1bb9b1c29 Mon Sep 17 00:00:00 2001 From: Fovway Date: Mon, 20 Oct 2025 09:09:45 +0700 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=B7=D0=B0=D0=B2=D0=B8=D1=81=D0=B8?= =?UTF-8?q?=D0=BC=D0=BE=D1=81=D1=82=D0=B8=20=D0=B8=20=D1=83=D0=BB=D1=83?= =?UTF-8?q?=D1=87=D1=88=D0=B5=D0=BD=D0=B0=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5?= =?UTF-8?q?=D1=80=D0=B6=D0=BA=D0=B0=20PWA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Добавлена библиотека pngjs для работы с PNG изображениями - Добавлены мета-теги для улучшения поддержки PWA на страницах: index.html, notes.html, profile.html, register.html - Обновлен сервисный работник для улучшенного кэширования и обработки запросов - Добавлены функции для отладки PWA в консоли --- PWA-TESTING.md | 136 +++++++++++++++++++++++++++ package-lock.json | 9 ++ package.json | 1 + public/index.html | 1 + public/notes.html | 1 + public/profile.html | 1 + public/pwa.js | 61 ++++++++---- public/register.html | 1 + public/sw.js | 189 ++++++------------------------------- public/test-pwa.html | 217 +++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 438 insertions(+), 179 deletions(-) create mode 100644 PWA-TESTING.md create mode 100644 public/test-pwa.html diff --git a/PWA-TESTING.md b/PWA-TESTING.md new file mode 100644 index 0000000..7ea307d --- /dev/null +++ b/PWA-TESTING.md @@ -0,0 +1,136 @@ +# 🚀 Тестирование PWA для NoteJS + +## Что было сделано + +✅ **Созданы файлы PWA:** +- `manifest.json` - манифест приложения +- `sw.js` - сервис-воркер для кэширования +- `pwa.js` - JavaScript класс для управления PWA +- `icon.svg` - SVG иконка приложения +- `logo.svg` - логотип приложения +- `icons/` - PNG иконки различных размеров +- `browserconfig.xml` - конфигурация для Windows + +✅ **Обновлены HTML страницы:** +- Добавлены PWA мета-теги +- Подключены иконки и манифест +- Добавлен скрипт регистрации Service Worker + +✅ **Настроен сервер:** +- Правильные заголовки для PWA файлов +- Поддержка кэширования + +## Как протестировать + +### 1. Откройте тестовую страницу +``` +http://localhost:3000/test-pwa.html +``` + +### 2. Проверьте требования PWA +Нажмите кнопку "Проверить статус PWA" - все пункты должны быть зелеными: +- ✅ HTTPS или localhost +- ✅ Service Worker +- ✅ Manifest +- ✅ Иконки +- ❌ Уже установлено (должно быть красным, если не установлено) + +### 3. Установка приложения +- Если все проверки пройдены, появится кнопка "Установить приложение" +- Нажмите на неё для установки PWA +- Следуйте инструкциям браузера + +### 4. Проверка в разных браузерах + +#### Chrome/Edge: +- Откройте DevTools (F12) +- Перейдите в Application → Manifest +- Проверьте, что манифест загружается без ошибок +- В Application → Service Workers проверьте статус SW + +#### Firefox: +- Откройте DevTools (F12) +- Перейдите в Application → Manifest +- Проверьте манифест + +#### Safari (iOS): +- Откройте сайт в Safari +- Нажмите кнопку "Поделиться" +- Выберите "На экран Домой" +- Приложение установится как PWA + +## Возможные проблемы и решения + +### 1. Кнопка установки не появляется +**Причины:** +- Приложение уже установлено +- Браузер не поддерживает PWA +- Не выполнены требования PWA + +**Решение:** +- Проверьте статус на тестовой странице +- Убедитесь, что используете HTTPS или localhost +- Проверьте консоль браузера на ошибки + +### 2. Service Worker не регистрируется +**Причины:** +- Ошибки в коде SW +- Проблемы с кэшированием файлов + +**Решение:** +- Откройте DevTools → Application → Service Workers +- Проверьте ошибки в консоли +- Попробуйте очистить кэш + +### 3. Ошибка "Download error or resource isn't a valid image" +**Причины:** +- PNG иконки повреждены или имеют неправильный размер +- Иконки не являются валидными PNG файлами + +**Решение:** +- ✅ **ИСПРАВЛЕНО**: Созданы правильные PNG иконки с помощью pngjs +- Проверьте, что иконки имеют правильные размеры (192x192, 512x512) +- Убедитесь, что файлы иконок валидные PNG + +### 4. Предупреждение о deprecated meta tag +**Причины:** +- Использование устаревшего `apple-mobile-web-app-capable` + +**Решение:** +- ✅ **ИСПРАВЛЕНО**: Добавлен современный `mobile-web-app-capable` +- Оба тега теперь присутствуют для совместимости + +## Отладка + +### Консоль браузера +Откройте DevTools (F12) и проверьте консоль на ошибки: +```javascript +// Проверить статус PWA +window.debugPWA(); + +// Проверить Service Worker +navigator.serviceWorker.getRegistrations().then(console.log); +``` + +### Lighthouse +Запустите аудит Lighthouse в Chrome DevTools: +1. Откройте DevTools (F12) +2. Перейдите в Lighthouse +3. Выберите "Progressive Web App" +4. Нажмите "Generate report" + +## Файлы для проверки + +- ✅ `http://localhost:3000/manifest.json` - должен возвращать JSON +- ✅ `http://localhost:3000/sw.js` - должен возвращать JavaScript +- ✅ `http://localhost:3000/icons/icon-192x192.png` - должен возвращать PNG +- ✅ `http://localhost:3000/icons/icon-512x512.png` - должен возвращать PNG + +## Следующие шаги + +После успешного тестирования: +1. Удалите тестовую страницу `test-pwa.html` +2. Настройте HTTPS для продакшена +3. Добавьте реальные скриншоты в манифест +4. Создайте качественные иконки с помощью дизайнера +5. Настройте push-уведомления (опционально) diff --git a/package-lock.json b/package-lock.json index 786a34e..31bbc9c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "marked": "^16.4.0", "multer": "^2.0.0-rc.4", "node-fetch": "^3.3.2", + "pngjs": "^7.0.0", "sqlite3": "^5.1.7" }, "devDependencies": { @@ -2603,6 +2604,14 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "engines": { + "node": ">=14.19.0" + } + }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", diff --git a/package.json b/package.json index 1478097..5cd2d00 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "marked": "^16.4.0", "multer": "^2.0.0-rc.4", "node-fetch": "^3.3.2", + "pngjs": "^7.0.0", "sqlite3": "^5.1.7" }, "devDependencies": { diff --git a/public/index.html b/public/index.html index 8c52341..0cda61d 100644 --- a/public/index.html +++ b/public/index.html @@ -8,6 +8,7 @@ + diff --git a/public/notes.html b/public/notes.html index 84cc8c0..a6af060 100644 --- a/public/notes.html +++ b/public/notes.html @@ -8,6 +8,7 @@ + diff --git a/public/profile.html b/public/profile.html index 486ebad..2e9a620 100644 --- a/public/profile.html +++ b/public/profile.html @@ -8,6 +8,7 @@ + diff --git a/public/pwa.js b/public/pwa.js index f8976b0..c212418 100644 --- a/public/pwa.js +++ b/public/pwa.js @@ -6,9 +6,20 @@ class PWAManager { } init() { + console.log('PWA Manager инициализирован'); this.registerServiceWorker(); this.setupInstallPrompt(); this.setupAppInstalled(); + this.checkPWARequirements(); + } + + // Проверка требований PWA + checkPWARequirements() { + console.log('Проверка требований PWA:'); + console.log('- Service Worker:', 'serviceWorker' in navigator); + console.log('- HTTPS:', location.protocol === 'https:' || location.hostname === 'localhost'); + console.log('- Manifest:', document.querySelector('link[rel="manifest"]') !== null); + console.log('- Icons:', document.querySelector('link[rel="icon"]') !== null); } // Регистрация Service Worker @@ -33,6 +44,8 @@ class PWAManager { console.log('Ошибка регистрации SW:', error); }); }); + } else { + console.log('Service Worker не поддерживается'); } } @@ -49,6 +62,7 @@ class PWAManager { // Настройка промпта установки setupInstallPrompt() { window.addEventListener('beforeinstallprompt', (e) => { + console.log('beforeinstallprompt событие получено'); e.preventDefault(); this.deferredPrompt = e; this.showInstallButton(); @@ -57,10 +71,12 @@ class PWAManager { // Показ кнопки установки showInstallButton() { + console.log('Попытка показать кнопку установки'); + // Проверяем, не установлено ли уже приложение - if (window.matchMedia('(display-mode: standalone)').matches || - window.navigator.standalone === true) { - return; // Приложение уже установлено + if (this.isPWAInstalled()) { + console.log('Приложение уже установлено'); + return; } const installButton = this.createInstallButton(); @@ -75,10 +91,7 @@ class PWAManager { installButton.style.marginTop = '10px'; installButton.style.width = '100%'; installButton.style.fontSize = '14px'; - installButton.style.display = 'flex'; - installButton.style.alignItems = 'center'; - installButton.style.justifyContent = 'center'; - installButton.style.gap = '8px'; + installButton.id = 'pwa-install-button'; installButton.addEventListener('click', () => { this.installApp(); @@ -89,6 +102,12 @@ class PWAManager { // Добавление кнопки на страницу addInstallButtonToPage(installButton) { + // Удаляем существующую кнопку, если есть + const existingButton = document.getElementById('pwa-install-button'); + if (existingButton) { + existingButton.remove(); + } + // Ищем подходящее место для кнопки const authLink = document.querySelector('.auth-link'); const footer = document.querySelector('.footer'); @@ -96,42 +115,46 @@ class PWAManager { if (authLink) { authLink.appendChild(installButton); + console.log('Кнопка установки добавлена в auth-link'); } else if (footer) { footer.insertBefore(installButton, footer.firstChild); + console.log('Кнопка установки добавлена в footer'); } else if (container) { container.appendChild(installButton); + console.log('Кнопка установки добавлена в container'); + } else { + document.body.appendChild(installButton); + console.log('Кнопка установки добавлена в body'); } } // Установка приложения installApp() { + console.log('Попытка установки приложения'); if (this.deferredPrompt) { this.deferredPrompt.prompt(); this.deferredPrompt.userChoice.then((choiceResult) => { + console.log('Результат установки:', choiceResult.outcome); if (choiceResult.outcome === 'accepted') { console.log('Пользователь установил приложение'); - this.trackInstallation(); } this.deferredPrompt = null; this.removeInstallButton(); }); + } else { + console.log('deferredPrompt не доступен'); } } // Удаление кнопки установки removeInstallButton() { - const installButton = document.querySelector('button[style*="Установить приложение"]'); + const installButton = document.getElementById('pwa-install-button'); if (installButton) { installButton.remove(); + console.log('Кнопка установки удалена'); } } - // Отслеживание установки - trackInstallation() { - // Здесь можно добавить аналитику - console.log('PWA установлено успешно'); - } - // Обработка успешной установки setupAppInstalled() { window.addEventListener('appinstalled', () => { @@ -152,7 +175,8 @@ class PWAManager { isInstalled: this.isPWAInstalled(), isOnline: navigator.onLine, hasServiceWorker: 'serviceWorker' in navigator, - userAgent: navigator.userAgent + userAgent: navigator.userAgent, + hasDeferredPrompt: this.deferredPrompt !== null }; } } @@ -162,3 +186,8 @@ const pwaManager = new PWAManager(); // Экспорт для использования в других скриптах window.PWAManager = pwaManager; + +// Добавляем глобальную функцию для отладки +window.debugPWA = () => { + console.log('PWA Debug Info:', pwaManager.getPWAInfo()); +}; \ No newline at end of file diff --git a/public/register.html b/public/register.html index 6f95636..3c52bbb 100644 --- a/public/register.html +++ b/public/register.html @@ -8,6 +8,7 @@ + diff --git a/public/sw.js b/public/sw.js index ffc2569..5e251a0 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,34 +1,15 @@ // Service Worker для NoteJS -const CACHE_NAME = 'notejs-v1.0.0'; -const STATIC_CACHE_NAME = 'notejs-static-v1.0.0'; -const DYNAMIC_CACHE_NAME = 'notejs-dynamic-v1.0.0'; +const CACHE_NAME = 'notejs-v1.0.1'; +const STATIC_CACHE_NAME = 'notejs-static-v1.0.1'; // Файлы для кэширования при установке const STATIC_FILES = [ '/', '/index.html', - '/login.html', - '/register.html', - '/notes.html', - '/profile.html', '/style.css', - '/app.js', - '/login.js', - '/register.js', - '/profile.js', - '/icon.svg', - '/logo.svg', '/manifest.json', '/icons/icon-192x192.png', - '/icons/icon-512x512.png', - 'https://cdnjs.cloudflare.com/ajax/libs/iconify/2.0.0/iconify.min.js' -]; - -// Файлы, которые не нужно кэшировать -const EXCLUDE_FROM_CACHE = [ - '/api/', - '/uploads/', - '/database/' + '/icons/icon-512x512.png' ]; // Установка Service Worker @@ -47,6 +28,8 @@ self.addEventListener('install', (event) => { }) .catch((error) => { console.error('[SW] Ошибка при кэшировании статических файлов:', error); + // Продолжаем работу даже если кэширование не удалось + return self.skipWaiting(); }) ); }); @@ -61,7 +44,7 @@ self.addEventListener('activate', (event) => { return Promise.all( cacheNames.map((cacheName) => { // Удаляем старые кэши - if (cacheName !== STATIC_CACHE_NAME && cacheName !== DYNAMIC_CACHE_NAME) { + if (cacheName !== STATIC_CACHE_NAME) { console.log('[SW] Удаление старого кэша:', cacheName); return caches.delete(cacheName); } @@ -81,11 +64,13 @@ self.addEventListener('fetch', (event) => { const url = new URL(request.url); // Пропускаем запросы к API и загрузкам - if (EXCLUDE_FROM_CACHE.some(pattern => url.pathname.startsWith(pattern))) { + if (url.pathname.startsWith('/api/') || + url.pathname.startsWith('/uploads/') || + url.pathname.startsWith('/database/')) { return; } - // Стратегия кэширования: Cache First для статических файлов, Network First для HTML + // Обрабатываем только GET запросы if (request.method === 'GET') { event.respondWith( handleRequest(request) @@ -94,142 +79,42 @@ self.addEventListener('fetch', (event) => { }); async function handleRequest(request) { - const url = new URL(request.url); - try { - // Для HTML файлов используем Network First стратегию - if (request.headers.get('accept')?.includes('text/html')) { - return await networkFirstStrategy(request); + // Сначала пытаемся получить из кэша + const cachedResponse = await caches.match(request); + + if (cachedResponse) { + console.log('[SW] Запрос из кэша:', request.url); + return cachedResponse; } - // Для статических ресурсов используем Cache First стратегию - return await cacheFirstStrategy(request); - - } catch (error) { - console.error('[SW] Ошибка при обработке запроса:', error); - - // Fallback для HTML страниц - if (request.headers.get('accept')?.includes('text/html')) { - return await caches.match('/index.html'); - } - - throw error; - } -} - -// Стратегия Cache First (для статических ресурсов) -async function cacheFirstStrategy(request) { - const cachedResponse = await caches.match(request); - - if (cachedResponse) { - console.log('[SW] Запрос из кэша:', request.url); - return cachedResponse; - } - - console.log('[SW] Запрос к сети:', request.url); - const networkResponse = await fetch(request); - - // Кэшируем успешные ответы - if (networkResponse.ok) { - const cache = await caches.open(DYNAMIC_CACHE_NAME); - cache.put(request, networkResponse.clone()); - } - - return networkResponse; -} - -// Стратегия Network First (для HTML страниц) -async function networkFirstStrategy(request) { - try { - console.log('[SW] Запрос к сети (Network First):', request.url); + // Если нет в кэше, загружаем из сети + console.log('[SW] Запрос к сети:', request.url); const networkResponse = await fetch(request); // Кэшируем успешные ответы if (networkResponse.ok) { - const cache = await caches.open(DYNAMIC_CACHE_NAME); + const cache = await caches.open(STATIC_CACHE_NAME); cache.put(request, networkResponse.clone()); } return networkResponse; } catch (error) { - console.log('[SW] Сеть недоступна, поиск в кэше:', request.url); - const cachedResponse = await caches.match(request); + console.error('[SW] Ошибка при обработке запроса:', error); - if (cachedResponse) { - return cachedResponse; - } - - // Fallback на главную страницу + // Fallback для HTML страниц if (request.headers.get('accept')?.includes('text/html')) { - return await caches.match('/index.html'); + const fallbackResponse = await caches.match('/index.html'); + if (fallbackResponse) { + return fallbackResponse; + } } throw error; } } -// Обработка push уведомлений (для будущего использования) -self.addEventListener('push', (event) => { - console.log('[SW] Получено push уведомление'); - - const options = { - body: event.data ? event.data.text() : 'Новое уведомление от NoteJS', - icon: '/icons/icon-192x192.png', - badge: '/icons/icon-96x96.png', - vibrate: [100, 50, 100], - data: { - dateOfArrival: Date.now(), - primaryKey: 1 - }, - actions: [ - { - action: 'explore', - title: 'Открыть приложение', - icon: '/icons/icon-96x96.png' - }, - { - action: 'close', - title: 'Закрыть', - icon: '/icons/icon-96x96.png' - } - ] - }; - - event.waitUntil( - self.registration.showNotification('NoteJS', options) - ); -}); - -// Обработка кликов по уведомлениям -self.addEventListener('notificationclick', (event) => { - console.log('[SW] Клик по уведомлению:', event.action); - - event.notification.close(); - - if (event.action === 'explore') { - event.waitUntil( - clients.openWindow('/') - ); - } -}); - -// Синхронизация в фоне (для будущего использования) -self.addEventListener('sync', (event) => { - console.log('[SW] Фоновая синхронизация:', event.tag); - - if (event.tag === 'background-sync') { - event.waitUntil( - doBackgroundSync() - ); - } -}); - -async function doBackgroundSync() { - // Здесь можно добавить логику синхронизации данных - console.log('[SW] Выполнение фоновой синхронизации'); -} - // Обработка сообщений от основного потока self.addEventListener('message', (event) => { console.log('[SW] Получено сообщение:', event.data); @@ -241,26 +126,4 @@ self.addEventListener('message', (event) => { if (event.data && event.data.type === 'GET_VERSION') { event.ports[0].postMessage({ version: CACHE_NAME }); } -}); - -// Периодическая очистка кэша -self.addEventListener('periodicsync', (event) => { - if (event.tag === 'cache-cleanup') { - event.waitUntil(cleanupCache()); - } -}); - -async function cleanupCache() { - const cacheNames = await caches.keys(); - const oldCaches = cacheNames.filter(name => - name !== STATIC_CACHE_NAME && - name !== DYNAMIC_CACHE_NAME && - name.startsWith('notejs-') - ); - - await Promise.all( - oldCaches.map(name => caches.delete(name)) - ); - - console.log('[SW] Очистка старых кэшей завершена'); -} +}); \ No newline at end of file diff --git a/public/test-pwa.html b/public/test-pwa.html new file mode 100644 index 0000000..7b72b78 --- /dev/null +++ b/public/test-pwa.html @@ -0,0 +1,217 @@ + + + + + + Тест PWA - NoteJS + + + + + + + + + + + + + + + + + + + + + +
+

🔧 Тест PWA для NoteJS

+ +
+
Проверяем требования PWA...
+
+ +
+ + + +
+ +
+

Отладочная информация:

+
+
+
+ + + + + + +