/* dsh-sync service worker: offline app shell + last-known API responses. * Served at /sw.js (scope /). Registration only happens in secure contexts; * plain-HTTP LAN deployments simply skip it and behave as before. */ "use strict"; const SHELL_CACHE = "dsh-shell-v1"; const API_CACHE = "dsh-api-v1"; self.addEventListener("install", (e) => { e.waitUntil( caches.open(SHELL_CACHE) .then((c) => c.addAll(["/app"])) .then(() => self.skipWaiting()) ); }); self.addEventListener("activate", (e) => { e.waitUntil((async () => { for (const k of await caches.keys()) { if (k !== SHELL_CACHE && k !== API_CACHE) await caches.delete(k); } await self.clients.claim(); })()); }); self.addEventListener("fetch", (e) => { const req = e.request; if (req.method !== "GET") return; const url = new URL(req.url); if (url.origin !== self.location.origin) return; if (req.mode === "navigate") { // App shell: fresh when online, cached copy when the server is unreachable. e.respondWith((async () => { try { const resp = await fetch(req); if (resp.ok) (await caches.open(SHELL_CACHE)).put("/app", resp.clone()); return resp; } catch { return (await caches.match("/app")) || Response.error(); } })()); return; } if (url.pathname.startsWith("/v1/")) { // API: network-first, fall back to the last successful response. e.respondWith((async () => { try { const resp = await fetch(req); if (resp.ok) (await caches.open(API_CACHE)).put(req, resp.clone()); return resp; } catch { const hit = await caches.match(req); if (hit) return hit; return new Response(JSON.stringify({ detail: "offline" }), { status: 503, headers: { "Content-Type": "application/json" }, }); } })()); } });