1/* Browser-only worker: no fetch handler, no article caching. */ 2self.addEventListener('install', () => self.skipWaiting()); 3self.addEventListener('activate', (event) => 4 event.waitUntil(self.clients.claim()), 5); 6function claim(id) { 7 return new Promise((resolve, reject) => { 8 const request = indexedDB.open('vervecode-push', 1); 9 request.onupgradeneeded = () => 10 request.result.createObjectStore('seen', { keyPath: 'id' }); 11 request.onerror = () => reject(request.error); 12 request.onsuccess = () => { 13 const db = request.result; 14 const tx = db.transaction('seen', 'readwrite'); 15 let fresh = false; 16 const store = tx.objectStore('seen'); 17 const get = store.get(id); 18 get.onsuccess = () => { 19 if (!get.result) { 20 store.add({ id, at: Date.now() }); 21 fresh = true; 22 } 23 }; 24 tx.oncomplete = () => { 25 db.close(); 26 resolve(fresh); 27 }; 28 tx.onerror = () => { 29 db.close(); 30 reject(tx.error); 31 }; 32 }; 33 }); 34} 35self.addEventListener('push', (event) => { 36 event.waitUntil( 37 (async () => { 38 let data;
39 try { 40 data = event.data.json(); 41 } catch { 42 return; 43 } 44 if ( 45 !data || 46 typeof data.id !== 'string' || 47 data.id.length > 64 || 48 typeof data.title !== 'string' || 49 typeof data.body !== 'string' || 50 !Number.isFinite(data.expires) || 51 data.expires <= Date.now() 52 ) 53 return; 54 if ( 55 typeof data.path !== 'string' || 56 !data.path.startsWith('/') || 57 data.path.startsWith('//') || 58 /[\\?#]/.test(data.path) 59 ) 60 return; 61 const url = new URL(data.path, self.location.origin); 62 if (url.origin !== self.location.origin || !(await claim(data.id))) 63 return; 64 await self.registration.showNotification(data.title.slice(0, 100), { 65 body: data.body.slice(0, 160), 66 tag: `vervecode-${data.id}`, 67 renotify: false, 68 data: { url: url.href, id: data.id }, 69 }); 70 })(), 71 ); 72}); 73self.addEventListener('notificationclick', (event) => { 74 event.notification.close(); 75 event.waitUntil( 76 (async () => { 77 const url = new URL( 78 event.notification.data?.url || '/', 79 self.location.origin, 80 ); 81 if (url.origin !== self.location.origin) return; 82 const windows = await self.clients.matchAll({ 83 type: 'window', 84 includeUncontrolled: true, 85 }); 86 const existing = windows.find((client) => client.url === url.href); 87 if (existing) await existing.focus(); 88 else await self.clients.openWindow(url.href); 89 })(), 90 ); 91});
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.