// const VER = 18 const CACHE_NAME = 'cache-v1' const TEMP_PAGE_URL = '/preloader.html' const STATIC_ASSETS = [TEMP_PAGE_URL, '/css/preloader.css'] self.addEventListener('install', (event) => { event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(STATIC_ASSETS))) }) self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((cacheNames) => Promise.all( cacheNames.map((cache) => { if (cache !== CACHE_NAME) { return caches.delete(cache) } }), ), ), ) }) self.addEventListener('fetch', (event) => { const url = new URL(event.request.url) // Обрабатываем только HTTP/HTTPS запросы if (url.protocol !== 'http:' && url.protocol !== 'https:') { return } if (event.request.mode === 'navigate') { // Для навигационных запросов сначала отдаём `/preloader.html`, пока основная страница загружается event.respondWith(handleNavigationRequest(event.request)) } else if (STATIC_ASSETS.includes(url.pathname)) { // Кэшируем только preloader.html и его CSS event.respondWith(caches.match(event.request)) } }) const TEMP_CACHE = new Map() // Используем карту для временного хранения async function handleNavigationRequest(request) { if (TEMP_CACHE.has(request.url) && navigator.onLine) { return TEMP_CACHE.get(request.url).clone() } const cache = await caches.open(CACHE_NAME) // Отдаём временную страницу из кеша const tempResponse = await cache.match(TEMP_PAGE_URL) // Попытка загрузить основную страницу в фоновом режиме fetch(request) .then(async (networkResponse) => { if (networkResponse && networkResponse.ok) { // Сохраняем клон в TEMP_CACHE TEMP_CACHE.set(request.url, networkResponse) // Сообщаем клиенту, что основная страница готова console.log('notifying clients about MAIN_PAGE_READY after fetch') const allClients = await clients.matchAll({ type: 'window' }) allClients.forEach((client) => client.postMessage({ type: 'MAIN_PAGE_READY', }), ) // Удаляем из TEMP_CACHE через минимальное время setTimeout(() => { TEMP_CACHE.delete(request.url) }, 5000) } else { throw new Error('Failed to fetch main page') } }) .catch(async (error) => { console.error('Error loading main page:', error) // Сообщаем клиенту об ошибке сети const allClients = await clients.matchAll({ type: 'window' }) allClients.forEach((client) => client.postMessage({ type: 'NETWORK_ERROR', }), ) }) // Возвращаем временную страницу return tempResponse } async function updateStaticAssetsCache() { const cache = await caches.open(CACHE_NAME) // Обновляем ресурсы try { await Promise.all( STATIC_ASSETS.map(async (asset) => { const response = await fetch(asset, { cache: 'no-store' }) if (response.ok) { await cache.put(asset, response.clone()) } else { console.error(`Failed to fetch ${asset}:`, response.status) } }), ) console.log('STATIC_ASSETS обновлены.') } catch (error) { console.error('Ошибка обновления STATIC_ASSETS:', error) } } self.addEventListener('message', async (event) => { if (event.data && event.data.type === 'SKIP_WAITING') { self.skipWaiting() } else if (event.data && event.data.type === 'CHECK_MAIN_PAGE_READY') { console.log('CHECK_MAIN_PAGE_READY:', TEMP_CACHE.size ? 'ready' : 'not ready') if (TEMP_CACHE.size === 0) { event.source.postMessage({ type: 'MAIN_PAGE_IS_NOT_READY' }) } else { event.source.postMessage({ type: 'MAIN_PAGE_READY' }) } } else if (event.data && event.data.type === 'CHECK_ONLINE_STATUS') { const isOnline = navigator.onLine // Проверяем состояние сети event.ports[0].postMessage({ online: isOnline }) // Отправляем результат обратно console.log('sent CHECK_ONLINE_STATUS online:', isOnline) } else if (event.data && event.data.type === 'UPDATE_STATIC_ASSETS') { event.waitUntil(updateStaticAssetsCache()) } else { event.waitUntil(debug(Object.assign({ answer: true }, event.data))) } }) self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME).then((cache) => { return cache.addAll(STATIC_ASSETS) }), ) self.skipWaiting().then(() => debug({ installed: true })) }) self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((cacheNames) => Promise.all( cacheNames.map((cache) => { if (cache !== CACHE_NAME) { return caches.delete(cache) } }), ), ), ) clients.claim().then(() => debug(event.source, { activated: true })) }) self.addEventListener('push', async (event) => { debug({ event: 'push', }) // PushData keys structure standart https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/showNotification let pushData = event.data.json() if (!pushData || !pushData.notification.title) { debug({ error: 'Received WebPush with an empty title. Received body', }) } self.registration .showNotification(pushData.notification.title, pushData.notification.options) .then(function () { // You can save to your analytics fact that push was shown // fetch('https://your_backend_server.com/track_show?message_id=' + pushData.data.message_id); }) .catch((error) => { debug({ errorShowNotification: error.message, }) }) }) self.addEventListener('notificationclick', (event) => { debug({ event: 'notificationclick', }) event.waitUntil(handleNotificationClick(event)) }) async function handleNotificationClick(event) { event.notification.close() if (!event.notification.data) { await debug({ error: 'Click on WebPush with empty data, where url should be. Notification', }) return } if (!event.notification.data.url) { await debug({ error: 'Click on WebPush without url. Notification' }) return } await openURL(event.notification.data.url) } async function openURL(urlToOpen) { let matchedClients try { const urlToOpenObj = new URL(urlToOpen, location.origin) matchedClients = await clients.matchAll({ type: 'window', includeUncontrolled: true }) try { for (const client of matchedClients) { const clientUrlObj = new URL(client.url) try { await debug(client, { urlToOpen, 'client.url': client.url, }) if ( urlToOpenObj.pathname + urlToOpenObj.search !== clientUrlObj.pathname + clientUrlObj.search ) { continue } if (client.focused) { debug({ 'client.focused': true }) return } await client.focus() debug({ 'client.focus()': true }) return } catch (error) { await debug({ error: error.message }) } } } catch (error) { debug({ 'get client error': error }) } } catch (error) { debug({ 'openURL error': error }) } if (matchedClients && matchedClients.length) { debug({ 'no matched clients': true }) } try { const client = await clients.openWindow(urlToOpen) if (!client) { await debug({ error: 'client is null' }) } debug({ 'clients.openWindow()': true }) } catch (error) { await debug(matchedClients && matchedClients[0], { error: error.message }) } } function debug() {} // async function debug(...args) { // return fetch('https://hereconnect-debug.condev.ru/debug', { // method: 'POST', // body: JSON.stringify(args), // }) // }