diff --git a/apps/web/src/service-worker.ts b/apps/web/src/service-worker.ts new file mode 100644 index 0000000..565fecd --- /dev/null +++ b/apps/web/src/service-worker.ts @@ -0,0 +1,77 @@ +/// +/// + +import { build, files, version } from '$service-worker'; + +// Give `self` the correct type +const selfRef = globalThis.self as unknown as ServiceWorkerGlobalScope; + +// Unique cache key per deployment +const CACHE = `gt-cache-${version}`; + +// Precache application shell (built assets) and static files +const ASSETS = [ + ...build, + ...files +]; + +selfRef.addEventListener('install', (event) => { + event.waitUntil( + (async () => { + const cache = await caches.open(CACHE); + await cache.addAll(ASSETS); + })() + ); +}); + +selfRef.addEventListener('activate', (event) => { + event.waitUntil( + (async () => { + const keys = await caches.keys(); + await Promise.all(keys.map((k) => (k === CACHE ? undefined : caches.delete(k)))); + // Claim clients so updated SW takes control immediately on refresh + await selfRef.clients.claim(); + })() + ); +}); + +selfRef.addEventListener('fetch', (event: FetchEvent) => { + // Only handle GET requests + if (event.request.method !== 'GET') return; + + const url = new URL(event.request.url); + + // Serve precached ASSETS from cache directly + if (ASSETS.includes(url.pathname)) { + event.respondWith( + (async () => { + const cache = await caches.open(CACHE); + const cached = await cache.match(url.pathname); + if (cached) return cached; + // Fallback to network if somehow missing + const res = await fetch(event.request); + if (res.ok) cache.put(url.pathname, res.clone()); + return res; + })() + ); + return; + } + + // Runtime: network-first with cache fallback for other GETs + event.respondWith( + (async () => { + const cache = await caches.open(CACHE); + try { + const res = await fetch(event.request); + if (res instanceof Response && res.status === 200) { + cache.put(event.request, res.clone()); + } + return res; + } catch (err) { + const cached = await cache.match(event.request); + if (cached) return cached; + throw err; // propagate if nothing cached + } + })() + ); +}); diff --git a/apps/web/svelte.config.js b/apps/web/svelte.config.js index 5c6b098..1d0ff18 100644 --- a/apps/web/svelte.config.js +++ b/apps/web/svelte.config.js @@ -8,7 +8,10 @@ const config = { adapter: adapter({ fallback: '200.html' }), - // Service worker wiring comes in T008 + serviceWorker: { + // keep default auto-registration explicit + register: true + }, paths: { // supports GitHub Pages-like hosting later; keep default for now } diff --git a/specs/001-glowtrack-a-mood/tasks.md b/specs/001-glowtrack-a-mood/tasks.md index e90df09..2c3aa41 100644 --- a/specs/001-glowtrack-a-mood/tasks.md +++ b/specs/001-glowtrack-a-mood/tasks.md @@ -60,7 +60,7 @@ Paths below are absolute to this repo. - Root CI scripts in tools/ci (stub) and package scripts wiring - Dependencies: T005 -- [ ] T008 PWA service worker wiring (SvelteKit) +- [X] T008 PWA service worker wiring (SvelteKit) - Enable service worker in SvelteKit config and add minimal SW handler - Ensure static asset caching strategy is defined (runtime-minimal) - Dependencies: T005