/// /// /// /// declare const self: ServiceWorkerGlobalScope; import { build, files, version } from '$service-worker'; const CACHE_NAME = `cache-${version}`; const ASSETS = [...build, ...files]; const OFFLINE_URL = '/offline'; // Install: pre-cache all static assets and the offline fallback page self.addEventListener('install', (event: ExtendableEvent) => { event.waitUntil( (async () => { const cache = await caches.open(CACHE_NAME); await cache.addAll(ASSETS); // Cache offline fallback page await cache.add(OFFLINE_URL); await self.skipWaiting(); })() ); }); // Activate: clean up old caches self.addEventListener('activate', (event: ExtendableEvent) => { event.waitUntil( (async () => { const keys = await caches.keys(); const deletions = keys .filter((key) => key !== CACHE_NAME) .map((key) => caches.delete(key)); await Promise.all(deletions); await self.clients.claim(); })() ); }); // Fetch: cache-first for static assets, network-first for API/pages self.addEventListener('fetch', (event: FetchEvent) => { const { request } = event; const url = new URL(request.url); // Skip non-GET requests if (request.method !== 'GET') return; // Skip cross-origin requests if (url.origin !== self.location.origin) return; // Sensitive API paths: never cache, always go to network const sensitiveApiPrefixes = ['/api/users/', '/api/admin/', '/api/auth/']; if (sensitiveApiPrefixes.some((prefix) => url.pathname.startsWith(prefix))) { event.respondWith(fetch(request)); return; } // API calls: network-first with cache fallback if (url.pathname.startsWith('/api/')) { event.respondWith(networkFirst(request)); return; } // Static assets (build artifacts + static files): cache-first if (ASSETS.includes(url.pathname)) { event.respondWith(cacheFirst(request)); return; } // Navigation requests (HTML pages): network-first with offline fallback if (request.mode === 'navigate') { event.respondWith(navigationHandler(request)); return; } // Everything else: network-first event.respondWith(networkFirst(request)); }); /** * Cache-first strategy: serve from cache, fall back to network. */ async function cacheFirst(request: Request): Promise { const cached = await caches.match(request); if (cached) return cached; try { const response = await fetch(request); if (response.ok) { const cache = await caches.open(CACHE_NAME); cache.put(request, response.clone()); } return response; } catch { return new Response('Offline', { status: 503, statusText: 'Service Unavailable' }); } } /** * Network-first strategy: try network, fall back to cache. */ async function networkFirst(request: Request): Promise { try { const response = await fetch(request); if (response.ok) { const cache = await caches.open(CACHE_NAME); cache.put(request, response.clone()); } return response; } catch { const cached = await caches.match(request); if (cached) return cached; return new Response('Offline', { status: 503, statusText: 'Service Unavailable' }); } } /** * Navigation handler: network-first with offline fallback page. */ async function navigationHandler(request: Request): Promise { try { return await fetch(request); } catch { const cached = await caches.match(request); if (cached) return cached; const offlinePage = await caches.match(OFFLINE_URL); if (offlinePage) return offlinePage; return new Response('Offline', { status: 503, statusText: 'Service Unavailable', headers: { 'Content-Type': 'text/html' } }); } }