const CACHE = 'klassenportal-v2'; const PRECACHE = [ '/', '/login.html', '/app.html', '/manifest.json', '/favicon.svg', '/icons/icon-192.png', '/icons/icon-512.png', '/icons/apple-touch-icon.png', ]; self.addEventListener('install', e => { e.waitUntil( caches.open(CACHE) .then(c => c.addAll(PRECACHE)) .then(() => self.skipWaiting()) ); }); self.addEventListener('activate', e => { e.waitUntil( caches.keys() .then(keys => Promise.all( keys.filter(k => k !== CACHE).map(k => caches.delete(k)) )) .then(() => self.clients.claim()) ); }); self.addEventListener('fetch', e => { const url = new URL(e.request.url); // Only handle same-origin GET requests if (url.origin !== self.location.origin) return; if (e.request.method !== 'GET') return; // API calls: always network, never cache if (url.pathname.startsWith('/api/')) return; // Page navigations: network-first, fallback to cache if (e.request.mode === 'navigate') { e.respondWith( fetch(e.request) .then(r => { const clone = r.clone(); caches.open(CACHE).then(c => c.put(e.request, clone)); return r; }) .catch(() => caches.match(e.request).then(r => r || caches.match('/login.html')) ) ); return; } // Static assets: cache-first, populate on miss e.respondWith( caches.match(e.request).then(cached => { if (cached) return cached; return fetch(e.request).then(r => { if (r.ok) { const clone = r.clone(); caches.open(CACHE).then(c => c.put(e.request, clone)); } return r; }); }) ); });