NoteJS/public/sw.js
Fovway 4600dc61b7 Добавлена поддержка PWA для улучшения пользовательского опыта
- Реализованы маршруты для обслуживания PWA файлов: manifest.json, sw.js и browserconfig.xml
- Добавлены мета-теги и иконки для PWA в HTML страницах
- Внедрена регистрация сервисного работника для кэширования и оффлайн-доступа
- Обновлены страницы входа, регистрации, профиля и заметок для поддержки PWA
2025-10-20 08:53:59 +07:00

267 lines
7.6 KiB
JavaScript

// 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] Очистка старых кэшей завершена');
}