const CACHE = 'ns-v69'; const PRECACHE = ['/', 'index.html', 'manifest.json', 'icon.svg']; const SKIP_PATHS = ['/rest/stream', '/rest/download', '/config.js']; // Auth token params stripped from cache keys so artwork survives across sessions/token refreshes. // The actual fetch still uses the full URL (with token); only the stored key is normalized. const STRIP_PARAMS = ['t', 's', 'u', 'v', 'c', 'f']; self.addEventListener('install', e => { e.waitUntil(caches.open(CACHE).then(c => c.addAll(PRECACHE))); self.skipWaiting(); }); self.addEventListener('activate', e => { e.waitUntil( caches.keys() .then(keys => Promise.all(keys.filter(k => k !== CACHE).map(k => caches.delete(k)))) .then(() => self.clients.claim()) .then(() => self.clients.matchAll({ type: 'window', includeUncontrolled: true })) .then(clients => clients.forEach(c => c.postMessage({ type: 'SW_UPDATED', version: CACHE }))) ); }); self.addEventListener('message', e => { if (e.data?.type === 'GET_VERSION') { e.source.postMessage({ type: 'VERSION', version: CACHE }); } }); // Build a stable cache key by stripping Navidrome auth params function _cacheKey(url) { try { const u = new URL(url); STRIP_PARAMS.forEach(p => u.searchParams.delete(p)); return u.toString(); } catch { return url; } } self.addEventListener('fetch', e => { if (!e.request.url.startsWith(self.location.origin)) return; if (SKIP_PATHS.some(p => e.request.url.includes(p))) return; const isCoverArt = e.request.url.includes('/rest/getCoverArt'); if (isCoverArt) { // Cache-first for artwork — serves instantly from SW cache even when Doze blocks network. // Key is token-stripped so it survives session restarts with new auth tokens. const keyUrl = _cacheKey(e.request.url); const keyReq = new Request(keyUrl); e.respondWith( caches.open(CACHE).then(cache => cache.match(keyReq).then(cached => { if (cached) return cached; return fetch(e.request).then(res => { if (res.status === 200) cache.put(keyReq, res.clone()); return res; }).catch(() => Response.error()); }) ) ); return; } // All other requests: network-first, fall back to cache e.respondWith( fetch(e.request) .then(res => { if (res.status === 200 && e.request.method === 'GET') { const clone = res.clone(); caches.open(CACHE).then(c => c.put(e.request, clone)); } return res; }) .catch(() => caches.match(e.request).then(r => r || Response.error())) ); });