/** * HomeOS service worker. Deliberately conservative for an authenticated Livewire app: * - never intercepts non-GET (login, Livewire updates, broadcasting/auth stay online-only) * - cache-first only for fingerprinted, immutable assets under /build/ and static icons * - navigations are network-first with an offline fallback, so authenticated HTML is never * served stale from cache * Bump VERSION to invalidate old caches on the next activate. */ const VERSION = 'homeos-v1'; const STATIC_CACHE = `${VERSION}-static`; const OFFLINE_URL = '/offline.html'; const PRECACHE = [OFFLINE_URL, '/icons/icon-192.png', '/icons/icon-512.png']; self.addEventListener('install', (event) => { event.waitUntil( (async () => { const cache = await caches.open(STATIC_CACHE); await cache.addAll(PRECACHE); await self.skipWaiting(); })(), ); }); self.addEventListener('activate', (event) => { event.waitUntil( (async () => { const keys = await caches.keys(); await Promise.all(keys.filter((k) => !k.startsWith(VERSION)).map((k) => caches.delete(k))); await self.clients.claim(); })(), ); }); self.addEventListener('message', (event) => { if (event.data === 'skipWaiting') self.skipWaiting(); }); self.addEventListener('fetch', (event) => { const req = event.request; if (req.method !== 'GET') return; // login / Livewire / broadcasting are POST — stay online const url = new URL(req.url); if (url.origin !== self.location.origin) return; // don't touch cross-origin // Immutable, fingerprinted build output + static icons → cache-first. if (url.pathname.startsWith('/build/') || url.pathname.startsWith('/icons/')) { event.respondWith(cacheFirst(req)); return; } // Page loads → network-first, fall back to the offline shell only when truly offline. if (req.mode === 'navigate') { event.respondWith(networkFirst(req)); } }); async function cacheFirst(req) { const cache = await caches.open(STATIC_CACHE); const hit = await cache.match(req); if (hit) return hit; const res = await fetch(req); // Await the write: respondWith() keeps the worker alive for this promise, so the asset is // actually persisted before the worker can be terminated (otherwise offline use loses it). if (res && res.ok) await cache.put(req, res.clone()); return res; } async function networkFirst(req) { try { return await fetch(req); } catch (e) { const cache = await caches.open(STATIC_CACHE); const offline = await cache.match(OFFLINE_URL); return offline || Response.error(); } }