1const CACHE_NAME = 'final-red-v9'; 2const STATIC_ASSETS = ['/', '/manifest.json', '/favicon.png']; 3 4self.addEventListener('install', (event) => { 5 event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(STATIC_ASSETS))); 6 self.skipWaiting(); 7}); 8 9self.addEventListener('activate', (event) => { 10 event.waitUntil( 11 caches.keys().then((names) => 12 Promise.all(names.filter((n) => n !== CACHE_NAME).map((n) => caches.delete(n))) 13 ) 14 ); 15 self.clients.claim(); 16}); 17 18self.addEventListener('fetch', (event) => { 19 const url = new URL(event.request.url); 20 // Only handle http/https â skip chrome-extension, data, blob, etc. 21 if (!url.protocol.startsWith('http')) return; 22 if (event.request.method !== 'GET') return; 23 24 if (url.pathname.startsWith('/api/')) { 25 event.respondWith( 26 fetch(event.request).catch(() => 27 new Response(JSON.stringify({ error: 'Offline' }), { 28 status: 503, 29 headers: { 'Content-Type': 'application/json' }, 30 }) 31 ) 32 ); 33 return; 34 } 35 36 event.respondWith( 37 fetch(event.request) 38 .then((response) => { 39 if (response.ok && response.type === 'basic') { 40 const clone = response.clone(); 41 caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone)); 42 } 43 return response; 44 }) 45 .catch(() => caches.match(event.request)) 46 ); 47}); 48 49// ââ PUSH NOTIFICATIONS ââââââââââââââââââââââââââââââââââââââââââââââââââââââ 50 51self.addEventListener('push', (event) => { 52 let data = { title: 'final.red', body: 'A crisis estimate has changed.', url: '/' }; 53 try { data = { ...data, ...event.data.json() }; } catch {} 54 event.waitUntil( 55 self.registration.showNotification(data.title, { 56 body: data.body, 57 icon: '/icons/icon-192x192.png', 58 badge: '/icons/icon-72x72.png', 59 data: { url: data.url }, 60 vibrate: [200, 100, 200], 61 tag: 'finalred-update', 62 renotify: true, 63 }) 64 ); 65}); 66 67self.addEventListener('notificationclick', (event) => { 68 event.notification.close(); 69 const url = (event.notification.data && event.notification.data.url) || '/'; 70 event.waitUntil( 71 clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => { 72 for (const client of clientList) { 73 if (client.url.includes(self.location.origin) && 'focus' in client) { 74 client.navigate(url); 75 return client.focus(); 76 } 77 } 78 if (clients.openWindow) return clients.openWindow(url); 79 }) 80 ); 81});
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.