const CACHE_NAME = 'coastal-timesheet-v1'; const STATIC_ASSETS = [ '/', '/manifest.json', '/icons/icon-192.svg', '/icons/icon-512.svg', ]; // Install: cache shell 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: network-first for API, cache-first for assets self.addEventListener('fetch', (e) => { const { request } = e; const url = new URL(request.url); // Skip non-GET if (request.method !== 'GET') return; // API calls: network-first with offline fallback if (url.pathname.startsWith('/api/')) { e.respondWith( fetch(request) .then((res) => { // Cache successful GET API responses for offline use if (res.ok) { const clone = res.clone(); caches.open(CACHE_NAME).then((cache) => cache.put(request, clone)); } return res; }) .catch(() => caches.match(request).then((cached) => cached || offlineResponse())) ); return; } // Static assets: cache-first e.respondWith( caches.match(request).then((cached) => { if (cached) { // Background refresh fetch(request).then((res) => { if (res.ok) caches.open(CACHE_NAME).then((cache) => cache.put(request, res)); }).catch(() => {}); return cached; } return fetch(request).then((res) => { if (res.ok) { const clone = res.clone(); caches.open(CACHE_NAME).then((cache) => cache.put(request, clone)); } return res; }).catch(() => caches.match('/')); }) ); }); function offlineResponse() { return new Response( JSON.stringify({ error: 'offline', message: 'You are currently offline. Data will sync when connection is restored.' }), { status: 503, headers: { 'Content-Type': 'application/json' } } ); }