/// /// import type { ApiEnvelope } from '@owlboard/api-schema-types'; import { build, files, version } from '$service-worker'; const sw = self as unknown as ServiceWorkerGlobalScope; const CACHE_NAME = `owlboard-cache-${version}`; const ASSETS = [ ...build, ...files.filter((file) => !file.endsWith('manifest.json') && !file.startsWith('/icons/')), '/' ]; sw.addEventListener('install', (event) => { sw.skipWaiting(); event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSETS))); }); sw.addEventListener('activate', (event) => { event.waitUntil( Promise.all([ caches.keys().then((keys) => { return Promise.all( keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key)) ); }), sw.clients.claim() ]) ); }); sw.addEventListener('fetch', (event) => { const { request } = event; if (request.method !== 'GET') return; const url = new URL(request.url); // Uncached static assets if (url.pathname.endsWith('manifest.json') || url.pathname.includes('/icons/')) { event.respondWith(fetch(request)); return; } // Static Assets (Cache-First) if (ASSETS.includes(url.pathname) || url.pathname.startsWith('/_app/')) { event.respondWith(caches.match(request).then((res) => res || fetch(request))); return; } // Cachable API Responses - stale-while-revalidate if (url.pathname === '/api/v3/locationFilter/data') { event.respondWith( caches.open('ob-dynamic-cache').then(async (cache) => { const cachedResponse = await cache.match(request); // Ensure fallback response exists const makeOfflineFallback = () => { const errorObject: ApiEnvelope.Envelope = { e: { code: 'NETWORK_DISCONNECTED', msg: 'Cannot connect to the OwlBoard server' }, t: Math.floor(Date.now() / 1000) }; return new Response(JSON.stringify(errorObject), { status: 503, headers: { 'Content-Type': 'application/json' } }); }; const networkFetch = fetch(request) .then((networkResponse) => { if (networkResponse.ok) { cache.put(request, networkResponse.clone()); } return networkResponse; }) .catch(() => { // If offline & cache empty, return fallback return makeOfflineFallback(); }); // If cachedResponse is undefined, networkFetch safely resolves to a Response return cachedResponse || networkFetch; }) ); return; } // Offline API Fallback (Network-First) if (url.pathname.startsWith('/api') || url.hostname.includes('api')) { event.respondWith( fetch(request).catch(() => { const errorObject: ApiEnvelope.Envelope = { e: { code: 'NETWORK_DISCONNECTED', msg: 'Cannot connect to the OwlBoard server' }, t: Math.floor(Date.now() / 1000) }; return new Response(JSON.stringify(errorObject), { status: 503, headers: { 'Content-Type': 'application/json' } }); }) ); return; } // Nav fallback - return fallback page, if offline and page requested if (request.mode === 'navigate' || request.headers.get('accept')?.includes('text/html')) { event.respondWith( fetch(request).catch(() => { // Serve svelte fallback page return caches.match('/') as Promise; }) ); return; } });