// 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 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/' ]; // Установка Service Worker self.addEventListener('install', (event) => { console.log('[SW] Установка Service Worker'); event.waitUntil( caches.open(STATIC_CACHE_NAME) .then((cache) => { console.log('[SW] Кэширование статических файлов'); return cache.addAll(STATIC_FILES); }) .then(() => { console.log('[SW] Статические файлы закэшированы'); return self.skipWaiting(); }) .catch((error) => { console.error('[SW] Ошибка при кэшировании статических файлов:', error); }) ); }); // Активация Service Worker self.addEventListener('activate', (event) => { console.log('[SW] Активация Service Worker'); event.waitUntil( caches.keys() .then((cacheNames) => { return Promise.all( cacheNames.map((cacheName) => { // Удаляем старые кэши if (cacheName !== STATIC_CACHE_NAME && cacheName !== DYNAMIC_CACHE_NAME) { console.log('[SW] Удаление старого кэша:', cacheName); return caches.delete(cacheName); } }) ); }) .then(() => { console.log('[SW] Service Worker активирован'); return self.clients.claim(); }) ); }); // Перехват запросов self.addEventListener('fetch', (event) => { const { request } = event; const url = new URL(request.url); // Пропускаем запросы к API и загрузкам if (EXCLUDE_FROM_CACHE.some(pattern => url.pathname.startsWith(pattern))) { return; } // Стратегия кэширования: Cache First для статических файлов, Network First для HTML if (request.method === 'GET') { event.respondWith( handleRequest(request) ); } }); 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); } // Для статических ресурсов используем 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); const networkResponse = await fetch(request); // Кэшируем успешные ответы if (networkResponse.ok) { const cache = await caches.open(DYNAMIC_CACHE_NAME); cache.put(request, networkResponse.clone()); } return networkResponse; } catch (error) { console.log('[SW] Сеть недоступна, поиск в кэше:', request.url); const cachedResponse = await caches.match(request); if (cachedResponse) { return cachedResponse; } // Fallback на главную страницу if (request.headers.get('accept')?.includes('text/html')) { return await caches.match('/index.html'); } 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); if (event.data && event.data.type === 'SKIP_WAITING') { self.skipWaiting(); } 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] Очистка старых кэшей завершена'); }