const CACHE_NAME = 'bagg-v1'; const STATIC_ASSETS = [ '/login.html', '/style.css', '/app.js', '/manifest.json', ]; // ── Install: cache static assets ───────────────────────────────────────────── self.addEventListener('install', e => { e.waitUntil( caches.open(CACHE_NAME).then(cache => cache.addAll(STATIC_ASSETS)) ); self.skipWaiting(); }); // ── Activate: clean old caches ──────────────────────────────────────────────── self.addEventListener('activate', e => { e.waitUntil( caches.keys().then(keys => Promise.all(keys.filter(k => k !== CACHE_NAME).map(k => caches.delete(k))) ) ); self.clients.claim(); }); // ── Fetch strategy ──────────────────────────────────────────────────────────── self.addEventListener('fetch', e => { const { request } = e; const url = new URL(request.url); // Skip non-GET and cross-origin requests if (request.method !== 'GET' || url.origin !== location.origin) return; // API requests: network-first, no cache if (url.pathname.startsWith('/web/api/') || url.pathname.startsWith('/api/')) { e.respondWith(fetch(request).catch(() => new Response('{"error":"offline"}', { headers: { 'Content-Type': 'application/json' } }))); return; } // Static assets: cache-first if (STATIC_ASSETS.includes(url.pathname) || url.pathname.startsWith('/icons/')) { e.respondWith( caches.match(request).then(cached => cached || fetch(request).then(res => { if (res.ok) { const clone = res.clone(); caches.open(CACHE_NAME).then(c => c.put(request, clone)); } return res; })) ); return; } // Navigation (HTML pages): network-first, fallback to cache if (request.mode === 'navigate') { e.respondWith( fetch(request) .then(res => { if (res.ok) { const clone = res.clone(); caches.open(CACHE_NAME).then(c => c.put(request, clone)); } return res; }) .catch(() => caches.match(request).then(cached => cached || caches.match('/login.html'))) ); return; } });