1const CURRENT_URL = new URL(self.location.href); 2const BUILD_HASH = CURRENT_URL.searchParams.get('build') || 'dev-build'; 3const CACHE_PREFIX = 'halo-pwa'; 4const SHELL_CACHE = `${CACHE_PREFIX}-shell-${BUILD_HASH}`; 5const ASSET_CACHE = `${CACHE_PREFIX}-assets-${BUILD_HASH}`; 6 7const PRECACHE_URLS = [ 8 '/', 9 '/settings', 10 '/settings/interface', 11 '/manifest.json', 12 '/static/favicon.png', 13 '/static/favicon-dark.png', 14 '/static/favicon-96x96.png', 15 '/static/apple-touch-icon.png', 16 '/static/web-app-manifest-192x192.png', 17 '/static/web-app-manifest-512x512.png', 18 '/static/web-app-manifest-maskable-192x192.png', 19 '/static/web-app-manifest-maskable-512x512.png' 20]; 21 22const EXCLUDED_PREFIXES = [ 23 '/api', 24 '/ws', 25 '/cache', 26 '/openai', 27 '/ollama', 28 '/gemini', 29 '/grok', 30 '/anthropic' 31]; 32 33const isCacheableAsset = (pathname) => 34 pathname.startsWith('/_app/') || 35 pathname.startsWith('/assets/') || 36 pathname.startsWith('/static/'); 37 38const shouldBypass = (request, url) => { 39 if (request.method !== 'GET') { 40 return true; 41 } 42 43 if (url.origin !== self.location.origin) { 44 return true; 45 } 46 47 return EXCLUDED_PREFIXES.some((prefix) => url.pathname.startsWith(prefix)); 48}; 49 50const precacheUrl = async (cache, url) => { 51 try { 52 const response = await fetch(url, { cache: 'no-store' }); 53 if (response.ok) { 54 await cache.put(url, response.clone()); 55 } 56 } catch (error) { 57 // Ignore individual precache failures so one missing asset doesn't block install. 58 } 59}; 60 61self.addEventListener('install', (event) => { 62 event.waitUntil( 63 (async () => { 64 const cache = await caches.open(SHELL_CACHE); 65 await Promise.allSettled(PRECACHE_URLS.map((url) => precacheUrl(cache, url))); 66 await self.skipWaiting(); 67 })() 68 ); 69}); 70 71self.addEventListener('activate', (event) => { 72 event.waitUntil( 73 (async () => { 74 const cacheNames = await caches.keys(); 75 await Promise.all( 76 cacheNames.map((name) => { 77 if (name.startsWith(CACHE_PREFIX) && name !== SHELL_CACHE && name !== ASSET_CACHE) { 78 return caches.delete(name); 79 } 80 return Promise.resolve(false); 81 }) 82 ); 83 await self.clients.claim(); 84 })() 85 ); 86}); 87 88const handleNavigationRequest = async (request) => { 89 try { 90 const response = await fetch(request); 91 const cache = await caches.open(SHELL_CACHE); 92 if (response.ok) { 93 await cache.put(request, response.clone()); 94 } 95 return response; 96 } catch (error) { 97 const cache = await caches.open(SHELL_CACHE); 98 return ( 99 (await cache.match(request, { ignoreSearch: true })) || 100 (await cache.match('/')) || 101 (await cache.match('/settings')) 102 ); 103 } 104}; 105 106const handleAssetRequest = async (request) => { 107 const cache = await caches.open(ASSET_CACHE); 108 const cached = await cache.match(request); 109 if (cached) { 110 return cached; 111 } 112 113 const response = await fetch(request); 114 if (response.ok) { 115 await cache.put(request, response.clone()); 116 } 117 return response; 118}; 119 120const handleManifestRequest = async (request) => { 121 try { 122 const response = await fetch(request, { cache: 'no-store' }); 123 const cache = await caches.open(SHELL_CACHE); 124 if (response.ok) { 125 await cache.put(request, response.clone()); 126 } 127 return response; 128 } catch (error) { 129 const cache = await caches.open(SHELL_CACHE); 130 return (await cache.match(request)) || (await cache.match('/manifest.json')); 131 } 132}; 133 134self.addEventListener('fetch', (event) => { 135 const { request } = event; 136 const url = new URL(request.url); 137 138 if (shouldBypass(request, url)) { 139 return; 140 } 141 142 if (request.mode === 'navigate') { 143 event.respondWith(handleNavigationRequest(request)); 144 return; 145 } 146 147 if (url.pathname === '/manifest.json') { 148 event.respondWith(handleManifestRequest(request)); 149 return; 150 } 151 152 if (isCacheableAsset(url.pathname)) { 153 event.respondWith(handleAssetRequest(request)); 154 } 155});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.