// Ten31 Portal service worker. Deliberately conservative so it never serves a stale app: // - navigations are network-first (always get the latest index.html), cache only as offline fallback // - content-hashed /assets/* are cache-first (immutable, safe forever) // - /api/* is never cached // Bump CACHE on each release so old entries are purged. const CACHE = 'ten31-portal-0.2.45' self.addEventListener('install', () => self.skipWaiting()) self.addEventListener('activate', (event) => { event.waitUntil( (async () => { const keys = await caches.keys() await Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k))) await self.clients.claim() })(), ) }) self.addEventListener('fetch', (event) => { const req = event.request if (req.method !== 'GET') return const url = new URL(req.url) if (url.origin !== self.location.origin) return if (url.pathname.startsWith('/api/')) return if (req.mode === 'navigate') { event.respondWith( (async () => { try { const fresh = await fetch(req) const cache = await caches.open(CACHE) cache.put('/', fresh.clone()) return fresh } catch { const cache = await caches.open(CACHE) return (await cache.match('/')) || Response.error() } })(), ) return } if (url.pathname.startsWith('/assets/')) { event.respondWith( (async () => { const cache = await caches.open(CACHE) const hit = await cache.match(req) if (hit) return hit const res = await fetch(req) if (res.ok) cache.put(req, res.clone()) return res })(), ) } })