/* AquaFlow service worker: lets the cashier terminal open and run with no network. * * - Page loads (navigations): network first, falling back to the cached copy, * and to the cached /terminal page for anything else, so a cashier who opens * the app offline still lands on the till. * - Next.js build assets (/_next/static/*): cache first. They are content-hashed * and immutable, so a cached copy is always correct. * - API calls (/api/*) and every non-GET request: never cached. Sales go through * the IndexedDB queue in the page, not through this worker. * * Bump CACHE_VERSION to drop every cached page and asset on the next visit. */ const CACHE_VERSION = "aquaflow-v1"; const APP_SHELL = ["/terminal", "/login"]; async function cacheShell() { const cache = await caches.open(CACHE_VERSION); for (const path of APP_SHELL) { try { const res = await fetch(path, { credentials: "same-origin" }); if (!res.ok) continue; await cache.put(path, res.clone()); // Also cache the scripts and styles the page needs to boot. const html = await res.text(); const assets = Array.from(html.matchAll(/(?:src|href)="(\/_next\/static\/[^"]+)"/g), (m) => m[1]); await Promise.all( Array.from(new Set(assets)).map((url) => cache.match(url).then((hit) => hit || fetch(url).then((r) => r.ok && cache.put(url, r))) ) ); } catch (e) { // Offline during install: the runtime handlers below fill the cache later. } } } self.addEventListener("install", (event) => { event.waitUntil(cacheShell().then(() => self.skipWaiting())); }); self.addEventListener("activate", (event) => { event.waitUntil( caches .keys() .then((keys) => Promise.all(keys.filter((k) => k !== CACHE_VERSION).map((k) => caches.delete(k)))) .then(() => self.clients.claim()) ); }); self.addEventListener("fetch", (event) => { const req = event.request; if (req.method !== "GET") return; const url = new URL(req.url); if (url.origin !== self.location.origin) return; if (url.pathname.startsWith("/api/")) return; if (req.mode === "navigate") { event.respondWith( fetch(req) .then((res) => { if (res.ok) { const copy = res.clone(); caches.open(CACHE_VERSION).then((c) => c.put(url.pathname, copy)); } return res; }) .catch(async () => { const cache = await caches.open(CACHE_VERSION); return (await cache.match(url.pathname)) || (await cache.match("/terminal")) || Response.error(); }) ); return; } if (url.pathname.startsWith("/_next/static/")) { event.respondWith( caches.open(CACHE_VERSION).then(async (cache) => { const hit = await cache.match(req); if (hit) return hit; const res = await fetch(req); if (res.ok) cache.put(req, res.clone()); return res; }) ); } });