- Реализованы маршруты для обслуживания PWA файлов: manifest.json, sw.js и browserconfig.xml - Добавлены мета-теги и иконки для PWA в HTML страницах - Внедрена регистрация сервисного работника для кэширования и оффлайн-доступа - Обновлены страницы входа, регистрации, профиля и заметок для поддержки PWA
267 lines
7.6 KiB
JavaScript
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] Очистка старых кэшей завершена');
|
|
}
|