// Service worker for the /m boss-dashboard PWA. Precaches the // mobile shell, network-first for navigations, never caches // boss-dashboard API responses (those are real-time glance data). const CACHE_NAME = "wfdash-mobile-v1"; const OFFLINE_URL = "/offline"; const PRECACHE_ASSETS = [ "/m", "/manifest-mobile.json", "/offline", "/android-chrome-192x192.png", "/android-chrome-512x512.png", "/apple-touch-icon.png", ]; self.addEventListener("install", (event) => { event.waitUntil( caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE_ASSETS)) ); self.skipWaiting(); }); self.addEventListener("activate", (event) => { event.waitUntil( caches.keys().then((keys) => Promise.all( keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k)) ) ) ); self.clients.claim(); }); self.addEventListener("fetch", (event) => { const { request } = event; if (request.method !== "GET") return; const url = new URL(request.url); // Never cache API traffic — boss-dashboard is real-time glance data // and serving a stale /api/v1/boss-dashboard or rep-activity would // actively mislead the boss. if (url.pathname.startsWith("/api/")) return; // Navigation requests: network-first, fall back to the cached /m // shell, then the generic /offline page. if (request.mode === "navigate") { event.respondWith( fetch(request) .then((response) => { const clone = response.clone(); caches.open(CACHE_NAME).then((cache) => cache.put(request, clone)); return response; }) .catch(async () => { const cached = await caches.match(request); if (cached) return cached; const mShell = await caches.match("/m"); if (mShell) return mShell; return caches.match(OFFLINE_URL); }) ); return; } // Static assets: stale-while-revalidate. event.respondWith( caches.match(request).then((cached) => { const networkFetch = fetch(request) .then((response) => { const clone = response.clone(); caches.open(CACHE_NAME).then((cache) => cache.put(request, clone)); return response; }) .catch(() => cached); return cached || networkFetch; }) ); });