/* Prabhupāda Says — Web Push service worker. * * Handles incoming push messages (shows a notification) and clicks (focuses * an existing same-origin tab and navigates it, or opens a new one). The push * payload is the JSON the BFF sends: * { title, body, url, icon, tag, require_interaction } */ // --- Offline caching (Save-offline feature) --------------------------------- // Bump CACHE_VERSION to invalidate old asset/page caches on the next activate. const CACHE_VERSION = "v1"; const PAGES_CACHE = `offline-pages-${CACHE_VERSION}`; // saved page HTML documents const ASSETS_CACHE = `offline-assets-${CACHE_VERSION}`; // /_next/* + static files const OFFLINE_CACHES = [PAGES_CACHE, ASSETS_CACHE]; // The library page doubles as the navigation fallback when offline. const OFFLINE_FALLBACK = "/downloads"; self.addEventListener("install", () => self.skipWaiting()); self.addEventListener("activate", (event) => event.waitUntil( (async () => { // Drop stale offline caches from previous CACHE_VERSIONs. const names = await caches.keys(); await Promise.all( names .filter( (n) => n.startsWith("offline-") && !OFFLINE_CACHES.includes(n), ) .map((n) => caches.delete(n)), ); await self.clients.claim(); })(), ), ); // Treat content-hashed Next assets and static files as immutable → cache-first. function isCacheableAsset(url) { if (url.origin !== self.location.origin) return false; if (url.pathname.startsWith("/_next/")) return true; return /\.(?:js|css|woff2?|ttf|otf|png|jpe?g|webp|avif|svg|ico|gif)$/.test( url.pathname, ); } self.addEventListener("fetch", (event) => { const req = event.request; if (req.method !== "GET") return; // never touch POSTs (push subscribe etc.) const url = new URL(req.url); // 1. Page navigations. // ONLINE: do NOT intercept — let the browser navigate natively. Proxying // a redirected response (the app server-`redirect()`s every book chapter // index to its first verse) or Next's streamed SSR through the SW makes // iOS/WebKit standalone PWAs render a blank page. Native handling is // identical to how the app behaved before this SW had a fetch handler. // OFFLINE: serve a saved copy, then the offline library, then a stub — // all plain stored/constructed Responses, which WebKit accepts. if (req.mode === "navigate") { if (self.navigator.onLine) return; event.respondWith( (async () => { const pages = await caches.open(PAGES_CACHE); const saved = (await pages.match(req.url)) || (await pages.match(url.pathname)); if (saved) return saved; const fallback = await pages.match(OFFLINE_FALLBACK); if (fallback) return fallback; return new Response( "
This page hasn't been saved for offline use.
", { headers: { "Content-Type": "text/html; charset=utf-8" }, status: 503 }, ); })(), ); return; } // 2. Immutable same-origin assets: cache-first so the app shell boots offline. if (isCacheableAsset(url)) { event.respondWith( (async () => { const cache = await caches.open(ASSETS_CACHE); const hit = await cache.match(req); if (hit) return hit; const res = await fetch(req); // Only cache full, same-origin 200s. Guard put() so a 206/opaque/quota // rejection can never fail the request (which could drop a JS chunk). try { if (res && res.status === 200 && res.type === "basic") { await cache.put(req, res.clone()); } } catch { /* serve the response uncached */ } return res; })(), ); return; } // 3. Everything else (BFF data, RSC, /api): straight to network. }); // The page registers us as /sw.js?bff=