amerc/public/sw.js
artheru 4ad339a94f installable PWA + branded app icons (0.40.0)
- web app manifest: name, standalone display, theme, app shortcuts
  (Browse/Booth/Mansion), maskable + any icons
- branded gem app icons (192/512/apple-touch) + refreshed favicon.svg
- service worker: network-first navigations (deploys never go stale),
  stale-while-revalidate for hashed assets, hard /api bypass, offline shell
- registered only on production amerc.ai hosts (dev HMR untouched)
- nginx: serve .webmanifest as application/manifest+json (all vhosts)
- verified live: installable criteria met, SW controls page, 0 console errors
2026-06-10 06:17:16 +08:00

44 lines
1.7 KiB
JavaScript

/* amerc service worker — offline app shell + asset caching.
Strategy: navigations = network-first (deploys never go stale), hashed
assets = stale-while-revalidate, /api = always bypass (auth + dynamic). */
const CACHE = 'amerc-v1';
const SHELL = '/index.html';
self.addEventListener('install', (e) => {
e.waitUntil(caches.open(CACHE).then((c) => c.addAll([SHELL, '/favicon.svg', '/app-192.png'])).catch(() => {}));
self.skipWaiting();
});
self.addEventListener('activate', (e) => {
e.waitUntil(
caches.keys().then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))).then(() => self.clients.claim())
);
});
self.addEventListener('fetch', (e) => {
const req = e.request;
const url = new URL(req.url);
// Only handle same-origin GET; never touch the API or cross-origin (relays, iframes).
if (req.method !== 'GET' || url.origin !== self.location.origin || url.pathname.startsWith('/api')) return;
// Navigations: network-first, fall back to cached shell when offline.
if (req.mode === 'navigate') {
e.respondWith(
fetch(req).then((res) => { const copy = res.clone(); caches.open(CACHE).then((c) => c.put(SHELL, copy)); return res; })
.catch(() => caches.match(SHELL).then((m) => m || caches.match(req)))
);
return;
}
// Static assets: serve from cache fast, refresh in the background.
e.respondWith(
caches.match(req).then((cached) => {
const network = fetch(req).then((res) => {
if (res && res.status === 200 && res.type === 'basic') { const copy = res.clone(); caches.open(CACHE).then((c) => c.put(req, copy)); }
return res;
}).catch(() => cached);
return cached || network;
})
);
});