From b5e17ecab24c1496953a92127e04a15d074df676 Mon Sep 17 00:00:00 2001 From: Google AI Agent Date: Mon, 30 Mar 2026 21:13:25 +0000 Subject: [PATCH] feat: add Service Worker for offline resilience --- sw.js | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 sw.js diff --git a/sw.js b/sw.js new file mode 100644 index 0000000..b535288 --- /dev/null +++ b/sw.js @@ -0,0 +1,68 @@ +const CACHE_NAME = 'the-door-v1'; +const ASSETS = [ + '/', + '/index.html', + '/about' +]; + +// Install event - cache core assets +self.addEventListener('install', (event) => { + event.waitUntil( + caches.open(CACHE_NAME).then((cache) => { + return cache.addAll(ASSETS); + }) + ); + self.skipWaiting(); +}); + +// Activate event - cleanup old caches +self.addEventListener('activate', (event) => { + event.waitUntil( + caches.keys().then((keys) => { + return Promise.all( + keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key)) + ); + }) + ); + self.clients.claim(); +}); + +// Fetch event - network first, fallback to cache for static, +// but for the crisis front door, we want to ensure the shell is ALWAYS available. +self.addEventListener('fetch', (event) => { + const url = new URL(event.request.url); + + // Skip API calls - they should always go to network + if (url.pathname.startsWith('/api/')) { + return; + } + + event.respondWith( + fetch(event.request) + .then((response) => { + // If we got a valid response, cache it for next time + if (response.ok && ASSETS.includes(url.pathname)) { + const copy = response.clone(); + caches.open(CACHE_NAME).then((cache) => cache.put(event.request, copy)); + } + return response; + }) + .catch(() => { + // If network fails, try cache + return caches.match(event.request).then((cached) => { + if (cached) return cached; + + // If it's a navigation request and we're offline, return index.html + if (event.request.mode === 'navigate') { + return caches.match('/index.html'); + } + + return new Response('Offline and resource not cached.', { + status: 503, + statusText: 'Service Unavailable', + headers: new Headers({ 'Content-Type': 'text/plain' }) + }); + }); + }) + ); +});