✨ Обновлены зависимости и улучшена поддержка PWA
- Добавлена библиотека pngjs для работы с PNG изображениями - Добавлены мета-теги для улучшения поддержки PWA на страницах: index.html, notes.html, profile.html, register.html - Обновлен сервисный работник для улучшенного кэширования и обработки запросов - Добавлены функции для отладки PWA в консоли
This commit is contained in:
parent
4600dc61b7
commit
9ecc787719
136
PWA-TESTING.md
Normal file
136
PWA-TESTING.md
Normal file
@ -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-уведомления (опционально)
|
||||
9
package-lock.json
generated
9
package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="description" content="NoteJS - современная система заметок с поддержкой Markdown, изображений, тегов и календаря" />
|
||||
<meta name="theme-color" content="#007bff" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<meta name="apple-mobile-web-app-title" content="NoteJS" />
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="description" content="NoteJS - современная система заметок с поддержкой Markdown, изображений, тегов и календаря" />
|
||||
<meta name="theme-color" content="#007bff" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<meta name="apple-mobile-web-app-title" content="NoteJS" />
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="description" content="NoteJS - современная система заметок с поддержкой Markdown, изображений, тегов и календаря" />
|
||||
<meta name="theme-color" content="#007bff" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<meta name="apple-mobile-web-app-title" content="NoteJS" />
|
||||
|
||||
@ -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());
|
||||
};
|
||||
@ -8,6 +8,7 @@
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="description" content="NoteJS - современная система заметок с поддержкой Markdown, изображений, тегов и календаря" />
|
||||
<meta name="theme-color" content="#007bff" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<meta name="apple-mobile-web-app-title" content="NoteJS" />
|
||||
|
||||
187
public/sw.js
187
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);
|
||||
@ -242,25 +127,3 @@ self.addEventListener('message', (event) => {
|
||||
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] Очистка старых кэшей завершена');
|
||||
}
|
||||
|
||||
217
public/test-pwa.html
Normal file
217
public/test-pwa.html
Normal file
@ -0,0 +1,217 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Тест PWA - NoteJS</title>
|
||||
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="description" content="Тест PWA для NoteJS" />
|
||||
<meta name="theme-color" content="#007bff" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<meta name="apple-mobile-web-app-title" content="NoteJS" />
|
||||
|
||||
<!-- Icons -->
|
||||
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/icons/icon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/icons/icon-16x16.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/icons/icon-192x192.png" />
|
||||
|
||||
<!-- Manifest -->
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
.status {
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.success { background: #d4edda; color: #155724; }
|
||||
.error { background: #f8d7da; color: #721c24; }
|
||||
.info { background: #d1ecf1; color: #0c5460; }
|
||||
button {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
margin: 5px;
|
||||
}
|
||||
button:hover { background: #0056b3; }
|
||||
button:disabled { background: #6c757d; cursor: not-allowed; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🔧 Тест PWA для NoteJS</h1>
|
||||
|
||||
<div id="status-container">
|
||||
<div class="status info">Проверяем требования PWA...</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button onclick="checkPWAStatus()">Проверить статус PWA</button>
|
||||
<button onclick="installPWA()" id="install-btn" disabled>Установить приложение</button>
|
||||
<button onclick="clearCache()">Очистить кэш</button>
|
||||
</div>
|
||||
|
||||
<div id="debug-info" style="margin-top: 20px; padding: 15px; background: #f8f9fa; border-radius: 5px; font-family: monospace; font-size: 12px;">
|
||||
<h3>Отладочная информация:</h3>
|
||||
<div id="debug-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PWA Script -->
|
||||
<script src="/pwa.js"></script>
|
||||
|
||||
<script>
|
||||
let deferredPrompt;
|
||||
|
||||
// Проверка статуса PWA
|
||||
function checkPWAStatus() {
|
||||
const statusContainer = document.getElementById('status-container');
|
||||
const debugContent = document.getElementById('debug-content');
|
||||
|
||||
// Очищаем предыдущие статусы
|
||||
statusContainer.innerHTML = '';
|
||||
debugContent.innerHTML = '';
|
||||
|
||||
// Проверяем требования PWA
|
||||
const checks = [
|
||||
{
|
||||
name: 'HTTPS или localhost',
|
||||
status: location.protocol === 'https:' || location.hostname === 'localhost',
|
||||
description: `Протокол: ${location.protocol}, Хост: ${location.hostname}`
|
||||
},
|
||||
{
|
||||
name: 'Service Worker',
|
||||
status: 'serviceWorker' in navigator,
|
||||
description: 'Поддержка Service Worker API'
|
||||
},
|
||||
{
|
||||
name: 'Manifest',
|
||||
status: document.querySelector('link[rel="manifest"]') !== null,
|
||||
description: 'Манифест PWA подключен'
|
||||
},
|
||||
{
|
||||
name: 'Иконки',
|
||||
status: document.querySelector('link[rel="icon"]') !== null,
|
||||
description: 'Иконки приложения подключены'
|
||||
},
|
||||
{
|
||||
name: 'Уже установлено',
|
||||
status: window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone === true,
|
||||
description: 'Приложение уже установлено как PWA'
|
||||
}
|
||||
];
|
||||
|
||||
let allPassed = true;
|
||||
|
||||
checks.forEach(check => {
|
||||
const statusDiv = document.createElement('div');
|
||||
statusDiv.className = `status ${check.status ? 'success' : 'error'}`;
|
||||
statusDiv.innerHTML = `${check.status ? '✅' : '❌'} ${check.name}: ${check.description}`;
|
||||
statusContainer.appendChild(statusDiv);
|
||||
|
||||
if (!check.status) allPassed = false;
|
||||
});
|
||||
|
||||
// Общая оценка
|
||||
const overallDiv = document.createElement('div');
|
||||
overallDiv.className = `status ${allPassed ? 'success' : 'error'}`;
|
||||
overallDiv.innerHTML = `${allPassed ? '✅' : '❌'} Общий статус: ${allPassed ? 'PWA готово к установке' : 'Есть проблемы с PWA'}`;
|
||||
statusContainer.appendChild(overallDiv);
|
||||
|
||||
// Отладочная информация
|
||||
const debugInfo = {
|
||||
userAgent: navigator.userAgent,
|
||||
isOnline: navigator.onLine,
|
||||
hasServiceWorker: 'serviceWorker' in navigator,
|
||||
isStandalone: window.matchMedia('(display-mode: standalone)').matches,
|
||||
isIOSStandalone: window.navigator.standalone === true,
|
||||
hasDeferredPrompt: deferredPrompt !== null,
|
||||
pwaInfo: window.PWAManager ? window.PWAManager.getPWAInfo() : 'PWA Manager не доступен'
|
||||
};
|
||||
|
||||
debugContent.innerHTML = JSON.stringify(debugInfo, null, 2);
|
||||
|
||||
// Активируем кнопку установки если доступно
|
||||
const installBtn = document.getElementById('install-btn');
|
||||
if (deferredPrompt && !debugInfo.isStandalone && !debugInfo.isIOSStandalone) {
|
||||
installBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Установка PWA
|
||||
function installPWA() {
|
||||
if (deferredPrompt) {
|
||||
deferredPrompt.prompt();
|
||||
deferredPrompt.userChoice.then((choiceResult) => {
|
||||
console.log('Результат установки:', choiceResult.outcome);
|
||||
if (choiceResult.outcome === 'accepted') {
|
||||
alert('Приложение установлено!');
|
||||
}
|
||||
deferredPrompt = null;
|
||||
document.getElementById('install-btn').disabled = true;
|
||||
});
|
||||
} else {
|
||||
alert('Установка недоступна. Возможно, приложение уже установлено или браузер не поддерживает установку PWA.');
|
||||
}
|
||||
}
|
||||
|
||||
// Очистка кэша
|
||||
function clearCache() {
|
||||
if ('caches' in window) {
|
||||
caches.keys().then(cacheNames => {
|
||||
return Promise.all(
|
||||
cacheNames.map(cacheName => {
|
||||
console.log('Удаление кэша:', cacheName);
|
||||
return caches.delete(cacheName);
|
||||
})
|
||||
);
|
||||
}).then(() => {
|
||||
alert('Кэш очищен! Перезагрузите страницу.');
|
||||
});
|
||||
} else {
|
||||
alert('Кэш не поддерживается в этом браузере.');
|
||||
}
|
||||
}
|
||||
|
||||
// Обработка события beforeinstallprompt
|
||||
window.addEventListener('beforeinstallprompt', (e) => {
|
||||
console.log('beforeinstallprompt событие получено');
|
||||
e.preventDefault();
|
||||
deferredPrompt = e;
|
||||
document.getElementById('install-btn').disabled = false;
|
||||
});
|
||||
|
||||
// Обработка успешной установки
|
||||
window.addEventListener('appinstalled', () => {
|
||||
console.log('PWA установлено успешно');
|
||||
alert('Приложение установлено успешно!');
|
||||
document.getElementById('install-btn').disabled = true;
|
||||
});
|
||||
|
||||
// Автоматическая проверка при загрузке
|
||||
window.addEventListener('load', () => {
|
||||
setTimeout(checkPWAStatus, 1000);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user