/* amerc service worker — offline app shell + asset caching. Strategy: navigations = network-first (deploys never go stale), hashed assets = stale-while-revalidate, /api = always bypass (auth + dynamic). */ const CACHE = 'amerc-v1'; const SHELL = '/index.html'; self.addEventListener('install', (e) => { e.waitUntil(caches.open(CACHE).then((c) => c.addAll([SHELL, '/favicon.svg', '/app-192.png'])).catch(() => {})); 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()) ); }); self.addEventListener('fetch', (e) => { const req = e.request; const url = new URL(req.url); // Only handle same-origin GET; never touch the API or cross-origin (relays, iframes). if (req.method !== 'GET' || url.origin !== self.location.origin || url.pathname.startsWith('/api')) return; // Navigations: network-first, fall back to cached shell when offline. if (req.mode === 'navigate') { e.respondWith( fetch(req).then((res) => { const copy = res.clone(); caches.open(CACHE).then((c) => c.put(SHELL, copy)); return res; }) .catch(() => caches.match(SHELL).then((m) => m || caches.match(req))) ); return; } // Static assets: serve from cache fast, refresh in the background. e.respondWith( caches.match(req).then((cached) => { const network = fetch(req).then((res) => { if (res && res.status === 200 && res.type === 'basic') { const copy = res.clone(); caches.open(CACHE).then((c) => c.put(req, copy)); } return res; }).catch(() => cached); return cached || network; }) ); });