// Service worker cache strategy for player pages, assets, media, and playlists. const CACHE_VERSION = 'v38'; const PAGE_CACHE = `pulse-signage-player-pages-${CACHE_VERSION}`; const ASSET_CACHE = `pulse-signage-player-assets-${CACHE_VERSION}`; const MEDIA_CACHE = `pulse-signage-player-media-${CACHE_VERSION}`; const PLAYLIST_CACHE = `pulse-signage-player-playlists-${CACHE_VERSION}`; function normalizeRequest(request) { const url = new URL(request.url); return new Request(`${url.origin}${url.pathname}`, { method: 'GET', headers: request.headers, mode: 'same-origin', credentials: 'same-origin' }); } function shouldBypassCache(request) { if (!request) { return false; } if (request.cache === 'reload' || request.cache === 'no-store') { return true; } const cacheControl = String(request.headers.get('cache-control') || '').toLowerCase(); const pragma = String(request.headers.get('pragma') || '').toLowerCase(); return cacheControl.includes('no-cache') || cacheControl.includes('max-age=0') || pragma.includes('no-cache'); } async function cacheResponse(cacheName, request, response, cacheKeyRequest) { if (!response || !response.ok) { return; } const cache = await caches.open(cacheName); await cache.put(cacheKeyRequest || request, response.clone()); } async function networkFirst(request, cacheName, cacheKeyRequest) { try { const response = await fetch(request); if (response && response.ok) { await cacheResponse(cacheName, request, response, cacheKeyRequest); return response; } if (response && response.status === 304) { const cached = await caches.match(cacheKeyRequest || request); if (cached) { return cached; } } const cached = await caches.match(cacheKeyRequest || request); if (cached) { return cached; } return response; } catch (_error) { const cached = await caches.match(cacheKeyRequest || request); if (cached) { return cached; } return new Response('', { status: 504, statusText: 'Offline' }); } } async function cacheFirst(request, cacheName) { const cached = await caches.match(request); if (cached) { return cached; } const response = await fetch(request); if (response && response.ok) { await cacheResponse(cacheName, request, response); } return response; } async function staleWhileRevalidate(request, cacheName) { const cached = await caches.match(request); const networkPromise = fetch(request).then(async function (response) { if (response && response.ok) { await cacheResponse(cacheName, request, response); } return response; }).catch(function () { return null; }); if (cached) { networkPromise.catch(function () { return null; }); return cached; } const networkResponse = await networkPromise; if (networkResponse) { return networkResponse; } return new Response('', { status: 504, statusText: 'Offline' }); } async function networkOnly(request) { return fetch(request); } self.addEventListener('install', function (event) { self.skipWaiting(); event.waitUntil(Promise.resolve()); }); self.addEventListener('activate', function (event) { event.waitUntil((async function () { const expected = [PAGE_CACHE, ASSET_CACHE, MEDIA_CACHE, PLAYLIST_CACHE]; const keys = await caches.keys(); await Promise.all(keys.filter(function (key) { return expected.indexOf(key) === -1; }).map(function (key) { return caches.delete(key); })); await self.clients.claim(); })()); }); self.addEventListener('fetch', function (event) { const request = event.request; if (request.method !== 'GET') { return; } const url = new URL(request.url); if (url.origin !== self.location.origin) { return; } if (url.pathname === '/sw.js') { return; } if (shouldBypassCache(request)) { event.respondWith(networkOnly(request)); return; } if (url.pathname.startsWith('/assets/')) { event.respondWith(cacheFirst(request, ASSET_CACHE)); return; } if (url.pathname.startsWith('/media/')) { event.respondWith(networkOnly(request)); return; } if (request.mode === 'navigate' || url.pathname === '/' || url.pathname === '/onboard' || /^\/screen\/[^/]+$/.test(url.pathname)) { event.respondWith(networkFirst(request, PAGE_CACHE, normalizeRequest(request))); return; } if (url.pathname.startsWith('/api/screens/') && url.pathname.endsWith('/playlist')) { event.respondWith(networkFirst(request, PLAYLIST_CACHE, normalizeRequest(request))); } });