Spaces:
Running
Running
| /* ShopStack service worker β Phase 4 #5 PWA shell. | |
| * | |
| * Strategy: cache-first for the app shell (HTML, CSS, JS, icons) so the | |
| * app can boot offline. Network-first for everything else (Gradio API | |
| * calls, market data, OCR) β those will fail offline and the user will | |
| * see the cached "offline" UI. | |
| * | |
| * This is intentionally minimal. Real production PWA would also | |
| * implement background sync, push notifications, and stale-while-revalidate. | |
| */ | |
| const CACHE_NAME = "shopstack-shell-v1"; | |
| const SHELL_ASSETS = [ | |
| "/", | |
| "/static/manifest.json", | |
| "/static/icon-192.svg", | |
| "/static/icon-512.svg", | |
| ]; | |
| self.addEventListener("install", (event) => { | |
| event.waitUntil( | |
| caches.open(CACHE_NAME).then((cache) => { | |
| // Best-effort: add each shell asset individually so a single | |
| // 404 doesn't abort the whole install. | |
| return Promise.all( | |
| SHELL_ASSETS.map((url) => | |
| cache.add(url).catch((err) => { | |
| console.warn("[sw] failed to cache", url, err); | |
| }) | |
| ) | |
| ); | |
| }) | |
| ); | |
| 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 req = event.request; | |
| if (req.method !== "GET") return; | |
| const url = new URL(req.url); | |
| // Don't intercept Gradio's WebSocket / API / SSE endpoints β those need | |
| // network. Only cache static assets and the app shell. | |
| const isShellAsset = | |
| SHELL_ASSETS.includes(url.pathname) || | |
| url.pathname.startsWith("/static/") || | |
| url.pathname === "/"; | |
| if (!isShellAsset) { | |
| return; // let the browser fetch normally | |
| } | |
| event.respondWith( | |
| caches.match(req).then((cached) => { | |
| if (cached) { | |
| return cached; | |
| } | |
| return fetch(req) | |
| .then((resp) => { | |
| // Opportunistically cache successful responses | |
| if (resp.ok && resp.status === 200) { | |
| const copy = resp.clone(); | |
| caches.open(CACHE_NAME).then((cache) => cache.put(req, copy)); | |
| } | |
| return resp; | |
| }) | |
| .catch(() => { | |
| // Offline fallback: return the cached root if available | |
| return caches.match("/").then((fallback) => { | |
| return ( | |
| fallback || | |
| new Response("Offline β ShopStack shell not cached.", { | |
| status: 503, | |
| statusText: "Service Unavailable", | |
| }) | |
| ); | |
| }); | |
| }); | |
| }) | |
| ); | |
| }); | |