From 990cbf97acb604705f98c616f49219d5dd9e57c2 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 24 Apr 2026 02:57:23 +0200 Subject: [PATCH] fix(sw): point service worker at React shell + bump cache version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous SW (pedscribe-v12) precached /js/app.js, /js/auth.js, and /css/styles.css — all deleted in the previous commit. Without this fix, every existing PWA install would 404 on the next SW install attempt and serve stale cached vanilla pages forever. Changes: • CACHE_NAME bumped to pedscribe-v13 → activate event evicts the v12 cache (and any earlier). • SHELL_ASSETS narrowed to [/, /manifest.json]. Hashed Vite assets are not precached because their filenames change on every build — they're served fresh from the network with Vite's immutable Cache-Control as backup. • Fetch handler: - /api/* → bypass SW (medical data must always be fresh). - /app/assets/* → network-first, cache fallback for offline. - everything else → cache-first with stale-while-revalidate. • Promise.allSettled around cache.add() so a single missing asset can't break the whole SW install. Existing PWA users get the new shell on their next visit (skipWaiting + clients.claim). --- public/sw.js | 90 ++++++++++++++++++---------------------------------- 1 file changed, 31 insertions(+), 59 deletions(-) diff --git a/public/sw.js b/public/sw.js index b047ac1..5712fff 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,31 +1,29 @@ // ============================================================ -// SERVICE WORKER — Cache shell, network-first for API -// Provides offline fallback for app shell while keeping -// API calls always fresh (critical for medical data accuracy) +// SERVICE WORKER — Cache the React shell + manifest. +// Network-first for API (medical data must always be fresh). +// Hashed Vite assets are NOT precached — they change every build, +// the browser HTTP cache plus immutable Cache-Control handles them. // ============================================================ -var CACHE_NAME = 'pedscribe-v12'; +var CACHE_NAME = 'pedscribe-v13'; var SHELL_ASSETS = [ '/', - '/index.html', - '/css/styles.css', - '/js/app.js', - '/js/auth.js', - '/manifest.json' + '/manifest.json', ]; -// Install: precache app shell +// Install: precache shell. Errors swallowed so a missing single +// asset doesn't break SW install entirely. self.addEventListener('install', function(event) { event.waitUntil( caches.open(CACHE_NAME).then(function(cache) { - return cache.addAll(SHELL_ASSETS); - }).then(function() { - return self.skipWaiting(); - }) + return Promise.allSettled(SHELL_ASSETS.map(function(url) { + return cache.add(url).catch(function() {}); + })); + }).then(function() { return self.skipWaiting(); }) ); }); -// Activate: clear old caches +// Activate: clear old caches (deletes pedscribe-v12 and earlier). self.addEventListener('activate', function(event) { event.waitUntil( caches.keys().then(function(names) { @@ -33,61 +31,35 @@ self.addEventListener('activate', function(event) { names.filter(function(name) { return name !== CACHE_NAME; }) .map(function(name) { return caches.delete(name); }) ); - }).then(function() { - return self.clients.claim(); - }) + }).then(function() { return self.clients.claim(); }) ); }); -// Fetch: network-first for API, cache-first for static assets self.addEventListener('fetch', function(event) { + if (event.request.method !== 'GET') return; var url = new URL(event.request.url); - - // Only handle same-origin requests + // Same-origin only; skip cross-origin (CDN, fonts, etc.) if (url.origin !== self.location.origin) return; - - // API calls — always network, never cache (medical data must be fresh) - if (url.pathname.startsWith('/api/')) { - event.respondWith(fetch(event.request)); - return; - } - - // Component HTML — network-first with cache fallback - if (url.pathname.startsWith('/components/')) { + // API: network only — never cache medical data. + if (url.pathname.startsWith('/api/')) return; + // Hashed Vite assets: serve from network, fall back to cache if offline. + if (url.pathname.startsWith('/app/assets/')) { event.respondWith( - fetch(event.request).then(function(response) { - var clone = response.clone(); - caches.open(CACHE_NAME).then(function(cache) { cache.put(event.request, clone); }); - return response; - }).catch(function() { - return caches.match(event.request); - }) + fetch(event.request).catch(function() { return caches.match(event.request); }) ); return; } - - // Static assets (JS, CSS, icons) — network-first so code updates apply immediately - if (url.pathname.match(/\.(js|css|png|ico|woff2?)$/)) { - event.respondWith( - fetch(event.request).then(function(response) { - var clone = response.clone(); - caches.open(CACHE_NAME).then(function(cache) { cache.put(event.request, clone); }); - return response; - }).catch(function() { - return caches.match(event.request); - }) - ); - return; - } - - // HTML pages — network-first + // SPA shell + everything else: cache-first with network update. event.respondWith( - fetch(event.request).then(function(response) { - var clone = response.clone(); - caches.open(CACHE_NAME).then(function(cache) { cache.put(event.request, clone); }); - return response; - }).catch(function() { - return caches.match(event.request) || caches.match('/index.html'); + caches.match(event.request).then(function(cached) { + var fetchPromise = fetch(event.request).then(function(resp) { + if (resp && resp.status === 200) { + var copy = resp.clone(); + caches.open(CACHE_NAME).then(function(cache) { cache.put(event.request, copy); }); + } + return resp; + }).catch(function() { return cached; }); + return cached || fetchPromise; }) ); });