- 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
44 lines
1.7 KiB
JavaScript
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;
|
|
})
|
|
);
|
|
});
|