/* raycer service worker * - app shell: network-first (so deploys propagate immediately), cache fallback for offline * - /api/*: network-only (data lives in Dexie) * - cross-origin (Dexie CDN): cache-first (immutable vendor assets) */ const VERSION = 'raycer-v2'; const SHELL_CACHE = `${VERSION}-shell`; const PRECACHE = [ '/', '/index.html', '/manifest.webmanifest', '/css/styles.css', '/js/app.js', '/js/api.js', '/js/db.js', '/js/theme.js', '/js/stats.js', '/js/calendar.js', '/icons/favicon.svg', '/icons/icon-192.png', '/icons/icon-512.png', ]; self.addEventListener('install', (event) => { event.waitUntil( (async () => { const cache = await caches.open(SHELL_CACHE); await cache.addAll(PRECACHE); self.skipWaiting(); })() ); }); self.addEventListener('activate', (event) => { event.waitUntil( (async () => { const keys = await caches.keys(); await Promise.all( keys .filter((k) => k !== SHELL_CACHE) .map((k) => caches.delete(k)) ); await self.clients.claim(); })() ); }); self.addEventListener('fetch', (event) => { const req = event.request; if (req.method !== 'GET') return; const url = new URL(req.url); // API: network-only, never cache if (url.pathname.startsWith('/api/')) { event.respondWith( fetch(req).catch( () => new Response(JSON.stringify({ offline: true }), { status: 503, headers: { 'content-type': 'application/json' }, }) ) ); return; } // Same-origin (app shell + assets): network-first, cache fallback for offline if (url.origin === self.location.origin) { event.respondWith( (async () => { try { const fresh = await fetch(req); if (fresh.ok) { const cache = await caches.open(SHELL_CACHE); cache.put(req, fresh.clone()).catch(() => {}); } return fresh; } catch { const cached = await caches.match(req); if (cached) return cached; if (req.mode === 'navigate') { const shell = await caches.match('/index.html'); if (shell) return shell; } return new Response('offline', { status: 503 }); } })() ); return; } // Cross-origin (e.g. Dexie CDN): cache-first (immutable vendor assets) event.respondWith( (async () => { const cached = await caches.match(req); if (cached) return cached; try { const fresh = await fetch(req); if (fresh.ok) { const cache = await caches.open(SHELL_CACHE); cache.put(req, fresh.clone()).catch(() => {}); } return fresh; } catch { return new Response('offline', { status: 503 }); } })() ); });