const CACHE = "puppy-tracker-v9"; const PHOTO_CACHE = "puppy-tracker-photos-v1"; const ASSETS = [ "./", "./index.html", "./style.css", "./app.js", "./manifest.json", "./icon.svg", ]; self.addEventListener("install", (event) => { event.waitUntil( caches.open(CACHE).then((cache) => cache.addAll(ASSETS)) ); // No skipWaiting() here: a new worker stays in "waiting" while an old one is // controlling a tab, so the page can prompt before swapping assets out from // under it. The page tells us to activate via a SKIP_WAITING message. }); self.addEventListener("message", (event) => { if (event.data && event.data.type === "SKIP_WAITING") self.skipWaiting(); }); self.addEventListener("activate", (event) => { event.waitUntil( caches.keys().then((keys) => Promise.all( keys .filter((k) => k !== CACHE && k !== PHOTO_CACHE) .map((k) => caches.delete(k)) ) ) ); self.clients.claim(); }); self.addEventListener("fetch", (event) => { const req = event.request; if (req.method !== "GET") return; const url = new URL(req.url); // Photos are immutable per UUID — cache-first forever and stuff every // successful fetch into a dedicated cache so they survive between visits // and are available offline once seen. if (url.pathname.includes("/api/photos/")) { event.respondWith( caches.open(PHOTO_CACHE).then(async (cache) => { const cached = await cache.match(req); if (cached) return cached; try { const res = await fetch(req); if (res && res.ok) cache.put(req, res.clone()); return res; } catch (err) { return cached || Response.error(); } }) ); return; } // Other API calls: never cache — sync must reflect live server state. if (url.pathname.includes("/api/")) return; event.respondWith( caches.match(req).then((cached) => { if (cached) return cached; return fetch(req) .then((res) => { if (res && res.ok && url.origin === self.location.origin) { const clone = res.clone(); caches.open(CACHE).then((c) => c.put(req, clone)); } return res; }) .catch(() => caches.match("./index.html")); }) ); });