1/* 2 * VidaPeps Web Push handlers. 3 * 4 * Hand-authored (NOT bundled) and pulled into the Workbox-generated service 5 * worker via `workbox.importScripts: ["/push-sw.js"]` in vite.config.ts. This 6 * keeps the app on the `generateSW` strategy (preserving the tuned precache + 7 * catalog runtime caching) while still adding the custom `push` and 8 * `notificationclick` handlers that `generateSW` cannot emit on its own. 9 * 10 * NOTE: this file is served verbatim from /public, so it MUST stay free of em 11 * dashes (the storefront build guard only strips them from bundled chunks). 12 */ 13 14// VidaTools (tools.vidapeps.com) shares this service worker + precache with the 15// storefront but must keep its OWN installed-app identity. The Workbox 16// NavigationRoute serves the precached document for navigations; on the tools 17// host that must be the VidaTools index.html (its own manifest + apple icon + 18// title), never the storefront one. This handler is imported into the generated 19// SW BEFORE the Workbox routes, so for tools-host top-level navigations it wins 20// and goes to the network (where the server emits the VidaTools document), 21// falling back to cache offline. Non-tools requests are left to Workbox. 22self.addEventListener("fetch", (event) => { 23 if (event.request.mode !== "navigate") return; 24 let host = ""; 25 try { 26 host = new URL(event.request.url).hostname; 27 } catch (e) { 28 return; 29 } 30 if (!host.startsWith("tools.")) return; 31 event.respondWith( 32 fetch(event.request).catch(() => 33 caches.match(event.request).then((cached) => cached || caches.match("/index.html")), 34 ), 35 ); 36}); 37 38self.addEventListener("push", (event) => { 39 let payload = {}; 40 try { 41 payload = event.data ? event.data.json() : {}; 42 } catch (e) { 43 payload = { title: "VidaPeps", body: event.data ? event.data.text() : "" }; 44 } 45 46 const title = payload.title || "VidaPeps"; 47 const data = Object.assign({}, payload.data, { 48 url: (payload.data && payload.data.url) || payload.url || "/", 49 }); 50 const options = { 51 body: payload.body || "", 52 icon: payload.icon || "/icons/pwa-192.png", 53 badge: payload.badge || "/icons/badge-72.png", 54 tag: payload.tag || undefined, 55 renotify: Boolean(payload.tag), 56 data: data, 57 }; 58 59 // iOS requires every push to show a user-visible notification. 60 event.waitUntil(self.registration.showNotification(title, options)); 61}); 62 63self.addEventListener("notificationclick", (event) => { 64 event.notification.close(); 65 const targetUrl = (event.notification.data && event.notification.data.url) || "/"; 66 67 event.waitUntil( 68 (async () => { 69 const allClients = await self.clients.matchAll({ 70 type: "window", 71 includeUncontrolled: true, 72 }); 73 // Focus an already-open tab on this origin and route it in-app. 74 for (const client of allClients) { 75 try { 76 if (new URL(client.url).origin === self.location.origin) { 77 await client.focus(); 78 if ("navigate" in client && targetUrl) { 79 try { 80 await client.navigate(targetUrl); 81 } catch (e) { 82 /* navigation across SW scope can throw; focus is enough */ 83 } 84 } 85 return; 86 } 87 } catch (e) { 88 /* malformed client url, skip */ 89 } 90 } 91 if (self.clients.openWindow) { 92 await self.clients.openWindow(targetUrl); 93 } 94 })(), 95 ); 96});
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.