// Service worker for the /rep visit-logging PWA. // // Unlike the boss-phone SW, this one exists primarily so the app SHELL // loads with no signal. A rep at a mine shaft has GPS but often no // data, and the whole check-in flow is built to complete offline: the // customer list is cached in localStorage, matching runs on the phone, // and the submission sits in an outbox until there's a connection. // // So the shell must open from cache, every time, or none of that is // reachable. const CACHE_NAME = "wfdash-rep-v1"; const OFFLINE_URL = "/offline"; const PRECACHE_ASSETS = [ "/rep", "/manifest-rep.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. Serving a stale customer snapshot would // hide newly-added customers from a rep standing in front of one, // and the app maintains its own snapshot cache in localStorage where // it can reason about the age of the data. if (url.pathname.startsWith("/api/")) return; // Navigations: cache-first for the shell. The opposite of the boss // dashboard's network-first, and deliberately so — the boss wants // fresh numbers, the rep wants the app to open at all. Freshness // comes from the snapshot refresh once there's signal. if (request.mode === "navigate") { event.respondWith( caches .match(request) .then((cached) => { if (cached) { // Refresh in the background for the next launch. event.waitUntil( fetch(request) .then((response) => caches .open(CACHE_NAME) .then((cache) => cache.put(request, response.clone())) ) .catch(() => {}) ); return cached; } return fetch(request).then((response) => { const clone = response.clone(); caches.open(CACHE_NAME).then((cache) => cache.put(request, clone)); return response; }); }) .catch(async () => { const shell = await caches.match("/rep"); return shell || 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; }) ); });