VERSION was stuck at raycer-v1 so mobile PWAs never picked up the streak fix (cache-first served the stale stats.js forever). - Bump to raycer-v2 → triggers install, purges old caches - Switch same-origin assets from cache-first to network-first so future deploys propagate immediately; cache is only used as an offline fallback - Simplify to a single SHELL_CACHE (no separate runtime cache) - Cross-origin vendor assets (Dexie CDN) stay cache-first
111 lines
2.8 KiB
JavaScript
111 lines
2.8 KiB
JavaScript
/* raycer service worker
|
|
* - app shell: network-first (so deploys propagate immediately), cache fallback for offline
|
|
* - /api/*: network-only (data lives in Dexie)
|
|
* - cross-origin (Dexie CDN): cache-first (immutable vendor assets)
|
|
*/
|
|
|
|
const VERSION = 'raycer-v2';
|
|
const SHELL_CACHE = `${VERSION}-shell`;
|
|
|
|
const PRECACHE = [
|
|
'/',
|
|
'/index.html',
|
|
'/manifest.webmanifest',
|
|
'/css/styles.css',
|
|
'/js/app.js',
|
|
'/js/api.js',
|
|
'/js/db.js',
|
|
'/js/theme.js',
|
|
'/js/stats.js',
|
|
'/js/calendar.js',
|
|
'/icons/favicon.svg',
|
|
'/icons/icon-192.png',
|
|
'/icons/icon-512.png',
|
|
];
|
|
|
|
self.addEventListener('install', (event) => {
|
|
event.waitUntil(
|
|
(async () => {
|
|
const cache = await caches.open(SHELL_CACHE);
|
|
await cache.addAll(PRECACHE);
|
|
self.skipWaiting();
|
|
})()
|
|
);
|
|
});
|
|
|
|
self.addEventListener('activate', (event) => {
|
|
event.waitUntil(
|
|
(async () => {
|
|
const keys = await caches.keys();
|
|
await Promise.all(
|
|
keys
|
|
.filter((k) => k !== SHELL_CACHE)
|
|
.map((k) => caches.delete(k))
|
|
);
|
|
await self.clients.claim();
|
|
})()
|
|
);
|
|
});
|
|
|
|
self.addEventListener('fetch', (event) => {
|
|
const req = event.request;
|
|
if (req.method !== 'GET') return;
|
|
|
|
const url = new URL(req.url);
|
|
|
|
// API: network-only, never cache
|
|
if (url.pathname.startsWith('/api/')) {
|
|
event.respondWith(
|
|
fetch(req).catch(
|
|
() => new Response(JSON.stringify({ offline: true }), {
|
|
status: 503,
|
|
headers: { 'content-type': 'application/json' },
|
|
})
|
|
)
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Same-origin (app shell + assets): network-first, cache fallback for offline
|
|
if (url.origin === self.location.origin) {
|
|
event.respondWith(
|
|
(async () => {
|
|
try {
|
|
const fresh = await fetch(req);
|
|
if (fresh.ok) {
|
|
const cache = await caches.open(SHELL_CACHE);
|
|
cache.put(req, fresh.clone()).catch(() => {});
|
|
}
|
|
return fresh;
|
|
} catch {
|
|
const cached = await caches.match(req);
|
|
if (cached) return cached;
|
|
if (req.mode === 'navigate') {
|
|
const shell = await caches.match('/index.html');
|
|
if (shell) return shell;
|
|
}
|
|
return new Response('offline', { status: 503 });
|
|
}
|
|
})()
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Cross-origin (e.g. Dexie CDN): cache-first (immutable vendor assets)
|
|
event.respondWith(
|
|
(async () => {
|
|
const cached = await caches.match(req);
|
|
if (cached) return cached;
|
|
try {
|
|
const fresh = await fetch(req);
|
|
if (fresh.ok) {
|
|
const cache = await caches.open(SHELL_CACHE);
|
|
cache.put(req, fresh.clone()).catch(() => {});
|
|
}
|
|
return fresh;
|
|
} catch {
|
|
return new Response('offline', { status: 503 });
|
|
}
|
|
})()
|
|
);
|
|
});
|