diff --git a/web/public/sw.js b/web/public/sw.js index b932022..e9260ff 100644 --- a/web/public/sw.js +++ b/web/public/sw.js @@ -1,17 +1,42 @@ -/* Offline shell: network-first for navigations, cache fallback when offline. */ -const CACHE = "homelab-shell-v1"; +/* Offline shell: navigations are network-first, static assets cache-first. */ +const CACHE = "homelab-shell-v2"; self.addEventListener("install", (e) => { e.waitUntil(caches.open(CACHE).then((c) => c.addAll(["/", "/index.html"]))); + self.skipWaiting(); }); self.addEventListener("activate", (e) => { e.waitUntil( - caches.keys().then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))), + caches + .keys() + .then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))) + .then(() => self.clients.claim()), ); }); self.addEventListener("fetch", (e) => { - if (e.request.mode !== "navigate") return; - e.respondWith(fetch(e.request).catch(() => caches.match("/index.html"))); + if (e.request.method !== "GET") return; // RPC = POST, always network + + // Navigations: network-first, cached shell on failure. + if (e.request.mode === "navigate") { + e.respondWith(fetch(e.request).catch(() => caches.match("/index.html"))); + return; + } + + // Same-origin assets (hashed filenames): cache-first, backfill cache on success. + const url = new URL(e.request.url); + if (url.origin === location.origin && (url.pathname.startsWith("/assets/") || url.pathname === "/manifest.webmanifest" || url.pathname.startsWith("/icon"))) { + e.respondWith( + caches.match(e.request).then( + (hit) => + hit || + fetch(e.request).then((resp) => { + const copy = resp.clone(); + caches.open(CACHE).then((c) => c.put(e.request, copy)); + return resp; + }), + ), + ); + } });