raycer/frontend/public/sw.js

112 lines
2.8 KiB
JavaScript
Raw Permalink Normal View History

/* 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 });
}
})()
);
});