fix(sw): point service worker at React shell + bump cache version
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).
This commit is contained in:
parent
39d764e1ff
commit
990cbf97ac
1 changed files with 31 additions and 59 deletions
90
public/sw.js
90
public/sw.js
|
|
@ -1,31 +1,29 @@
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// SERVICE WORKER — Cache shell, network-first for API
|
// SERVICE WORKER — Cache the React shell + manifest.
|
||||||
// Provides offline fallback for app shell while keeping
|
// Network-first for API (medical data must always be fresh).
|
||||||
// API calls always fresh (critical for medical data accuracy)
|
// 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 = [
|
var SHELL_ASSETS = [
|
||||||
'/',
|
'/',
|
||||||
'/index.html',
|
'/manifest.json',
|
||||||
'/css/styles.css',
|
|
||||||
'/js/app.js',
|
|
||||||
'/js/auth.js',
|
|
||||||
'/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) {
|
self.addEventListener('install', function(event) {
|
||||||
event.waitUntil(
|
event.waitUntil(
|
||||||
caches.open(CACHE_NAME).then(function(cache) {
|
caches.open(CACHE_NAME).then(function(cache) {
|
||||||
return cache.addAll(SHELL_ASSETS);
|
return Promise.allSettled(SHELL_ASSETS.map(function(url) {
|
||||||
}).then(function() {
|
return cache.add(url).catch(function() {});
|
||||||
return self.skipWaiting();
|
}));
|
||||||
})
|
}).then(function() { return self.skipWaiting(); })
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Activate: clear old caches
|
// Activate: clear old caches (deletes pedscribe-v12 and earlier).
|
||||||
self.addEventListener('activate', function(event) {
|
self.addEventListener('activate', function(event) {
|
||||||
event.waitUntil(
|
event.waitUntil(
|
||||||
caches.keys().then(function(names) {
|
caches.keys().then(function(names) {
|
||||||
|
|
@ -33,61 +31,35 @@ self.addEventListener('activate', function(event) {
|
||||||
names.filter(function(name) { return name !== CACHE_NAME; })
|
names.filter(function(name) { return name !== CACHE_NAME; })
|
||||||
.map(function(name) { return caches.delete(name); })
|
.map(function(name) { return caches.delete(name); })
|
||||||
);
|
);
|
||||||
}).then(function() {
|
}).then(function() { return self.clients.claim(); })
|
||||||
return self.clients.claim();
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch: network-first for API, cache-first for static assets
|
|
||||||
self.addEventListener('fetch', function(event) {
|
self.addEventListener('fetch', function(event) {
|
||||||
|
if (event.request.method !== 'GET') return;
|
||||||
var url = new URL(event.request.url);
|
var url = new URL(event.request.url);
|
||||||
|
// Same-origin only; skip cross-origin (CDN, fonts, etc.)
|
||||||
// Only handle same-origin requests
|
|
||||||
if (url.origin !== self.location.origin) return;
|
if (url.origin !== self.location.origin) return;
|
||||||
|
// API: network only — never cache medical data.
|
||||||
// API calls — always network, never cache (medical data must be fresh)
|
if (url.pathname.startsWith('/api/')) return;
|
||||||
if (url.pathname.startsWith('/api/')) {
|
// Hashed Vite assets: serve from network, fall back to cache if offline.
|
||||||
event.respondWith(fetch(event.request));
|
if (url.pathname.startsWith('/app/assets/')) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Component HTML — network-first with cache fallback
|
|
||||||
if (url.pathname.startsWith('/components/')) {
|
|
||||||
event.respondWith(
|
event.respondWith(
|
||||||
fetch(event.request).then(function(response) {
|
fetch(event.request).catch(function() { return caches.match(event.request); })
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
|
// SPA shell + everything else: cache-first with network update.
|
||||||
// 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
|
|
||||||
event.respondWith(
|
event.respondWith(
|
||||||
fetch(event.request).then(function(response) {
|
caches.match(event.request).then(function(cached) {
|
||||||
var clone = response.clone();
|
var fetchPromise = fetch(event.request).then(function(resp) {
|
||||||
caches.open(CACHE_NAME).then(function(cache) { cache.put(event.request, clone); });
|
if (resp && resp.status === 200) {
|
||||||
return response;
|
var copy = resp.clone();
|
||||||
}).catch(function() {
|
caches.open(CACHE_NAME).then(function(cache) { cache.put(event.request, copy); });
|
||||||
return caches.match(event.request) || caches.match('/index.html');
|
}
|
||||||
|
return resp;
|
||||||
|
}).catch(function() { return cached; });
|
||||||
|
return cached || fetchPromise;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue