1// SPDX-License-Identifier: AGPL-3.0-or-later2// The cache name and precache list are filled in by scripts/package.py.3const CACHE='__CACHE__';4const FILES=[__FILES__];5const PAGES={'/':'./','/perspective':'./perspective','/fuji':'./fuji','/social':'./social','/ela':'./ela'};6// Paths are taken relative to the worker's directory, so the app also works under a path.7const BASE=new URL('./',self.location).pathname;8self.addEventListener('install',e=>{self.skipWaiting();e.waitUntil(caches.open(CACHE).then(c=>c.addAll(FILES)));});9self.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())));10self.addEventListener('fetch',e=>{11 const url=new URL(e.request.url);12 if(e.request.method!=='GET'||url.origin!==self.location.origin)return;13 const rel=url.pathname.startsWith(BASE)?'/'+url.pathname.slice(BASE.length):url.pathname;14 const path=rel.replace(/\/+$/,'')||'/';15 if(e.request.mode==='navigate'||path in PAGES){16 // Pages: network first so a new deploy is picked up on the next open; cache only when offline.17 e.respondWith(fetch(e.request).then(r=>{if(r.ok)caches.open(CACHE).then(c=>c.put(PAGES[path]||e.request,r.clone()));return r;})18 .catch(async()=>{const c=await caches.open(CACHE);return (await c.match(PAGES[path]||'./'))||c.match('./');}));19 return;20 }21 // Hashed assets never change under the same name: cache first.22 e.respondWith(caches.open(CACHE).then(async c=>{23 const cached=await c.match(e.request,{ignoreSearch:true});if(cached)return cached;24 const r=await fetch(e.request);if(r.ok&&rel.startsWith('/static/'))c.put(e.request,r.clone());return r;25 }));26});