// Clawmates service worker (ยง16 PWA). Hand-rolled on purpose: ~50 lines we // fully control beats a build-tool plugin. Strategy: // /api/** -> NEVER touched (approvals + SSE must be live) // /_next/static/** -> cache-first (content-hashed, immutable) // navigations -> network-first, cache fallback for offline shell const STATIC_CACHE = "tc-static-v2"; const PAGE_CACHE = "tc-pages-v2"; self.addEventListener("install", () => { self.skipWaiting(); }); self.addEventListener("activate", (event) => { event.waitUntil( (async () => { const keep = [STATIC_CACHE, PAGE_CACHE]; for (const key of await caches.keys()) { if (!keep.includes(key)) await caches.delete(key); } await self.clients.claim(); })(), ); }); self.addEventListener("fetch", (event) => { const url = new URL(event.request.url); if (url.origin !== self.location.origin) return; if (url.pathname.startsWith("/api/")) return; if (url.pathname.startsWith("/_next/static/")) { event.respondWith( (async () => { const cache = await caches.open(STATIC_CACHE); const hit = await cache.match(event.request); if (hit) return hit; const response = await fetch(event.request); if (response.ok) cache.put(event.request, response.clone()); return response; })(), ); return; } if (event.request.mode === "navigate") { event.respondWith( (async () => { const cache = await caches.open(PAGE_CACHE); try { const response = await fetch(event.request); if (response.ok) cache.put(event.request, response.clone()); return response; } catch { const hit = await cache.match(event.request); if (hit) return hit; throw new Error("offline and not cached"); } })(), ); } });