/* Announcements signage app-shell worker. * * Scope is deliberately narrow: this worker makes the /display application * shell available for a future offline start. Slide bytes are NOT its job — * those are owned explicitly by the display asset manager in Cache Storage, so * the two caches never compete for the same responsibility. */ const SHELL_CACHE = "announcements-display-shell-v1"; const SHELL_FALLBACK = "/display"; // Paths the worker must never handle. Media is the asset manager's; the API and // socket are live server state and must never be answered from a cache. const BYPASS_PREFIXES = ["/api/", "/socket.io/", "/media/", "/health"]; function isBypassed(url) { return BYPASS_PREFIXES.some( (prefix) => url.pathname === prefix.replace(/\/$/, "") || url.pathname.startsWith(prefix), ); } self.addEventListener("install", (event) => { event.waitUntil( (async () => { const cache = await caches.open(SHELL_CACHE); try { await cache.add(new Request(SHELL_FALLBACK, { cache: "reload" })); } catch { // A first install while the network is unavailable still succeeds; the // shell is captured on the next successful navigation instead. } // A kiosk must never be trapped on a stale application version. await self.skipWaiting(); })(), ); }); self.addEventListener("activate", (event) => { event.waitUntil( (async () => { for (const name of await caches.keys()) { // Only this worker's own shell generations are removed. The display // asset cache is left strictly alone. if (name.startsWith("announcements-display-shell-") && name !== SHELL_CACHE) { await caches.delete(name); } } await self.clients.claim(); })(), ); }); async function networkFirst(request) { const cache = await caches.open(SHELL_CACHE); try { const response = await fetch(request); if (response && response.ok) { // Keep the shell fallback fresh so an online kiosk always updates. await cache.put(SHELL_FALLBACK, response.clone()); } return response; } catch (error) { const cached = (await cache.match(SHELL_FALLBACK)) || (await cache.match("/index.html")); if (cached) return cached; throw error; } } async function cacheFirst(request) { const cache = await caches.open(SHELL_CACHE); const cached = await cache.match(request); if (cached) return cached; const response = await fetch(request); if (response && response.ok) await cache.put(request, response.clone()); return response; } self.addEventListener("fetch", (event) => { const { request } = event; if (request.method !== "GET") return; const url = new URL(request.url); if (url.origin !== self.location.origin) return; if (isBypassed(url)) return; if (request.mode === "navigate") { // Online kiosks always get the newest application; offline ones get the // last known good shell. event.respondWith(networkFirst(request)); return; } // Vite emits content-hashed asset filenames, so these are safely immutable. if (url.pathname.startsWith("/assets/")) { event.respondWith(cacheFirst(request)); } });