// Served from / (not /static/) so its scope covers /admin/today/. const CACHE = "osiris-v1"; self.addEventListener("install", (event) => { self.skipWaiting(); }); self.addEventListener("activate", (event) => { event.waitUntil( caches .keys() .then((keys) => Promise.all(keys.filter((key) => key !== CACHE).map((key) => caches.delete(key))) ) .then(() => self.clients.claim()) ); }); self.addEventListener("push", (event) => { let payload = {}; try { payload = event.data ? event.data.json() : {}; } catch (e) { payload = { title: "Osiris", body: event.data ? event.data.text() : "" }; } event.waitUntil( self.registration.showNotification(payload.title || "Osiris", { body: payload.body || "", icon: "/static/icons/icon-192.png", badge: "/static/icons/icon-192.png", // Same tag replaces the previous notification instead of stacking. tag: payload.tag || "osiris", renotify: true, requireInteraction: true, // A dose reminder should wait, not fade away. data: { url: payload.url || "/admin/today/" }, }) ); }); self.addEventListener("notificationclick", (event) => { event.notification.close(); const url = (event.notification.data && event.notification.data.url) || "/admin/today/"; // Focus the app if it is already open rather than opening a second window. event.waitUntil( self.clients .matchAll({ type: "window", includeUncontrolled: true }) .then((windows) => { for (const client of windows) { if (client.url.includes(url) && "focus" in client) return client.focus(); } return self.clients.openWindow(url); }) ); }); self.addEventListener("fetch", (event) => { const { request } = event; // Never touch writes or the API: a stale or replayed dose would be a lie. if (request.method !== "GET" || new URL(request.url).pathname.startsWith("/api/")) { return; } // Static assets change rarely: serve from cache, refresh in the background. if (new URL(request.url).pathname.startsWith("/static/")) { event.respondWith( caches.open(CACHE).then(async (cache) => { const cached = await cache.match(request); const network = fetch(request) .then((response) => { if (response.ok) cache.put(request, response.clone()); return response; }) .catch(() => cached); return cached || network; }) ); return; } // Pages: always prefer the live version, fall back to the last one seen so the // app still opens (read-only) with no signal. event.respondWith( fetch(request) .then((response) => { if (response.ok) { const copy = response.clone(); caches.open(CACHE).then((cache) => cache.put(request, copy)); } return response; }) .catch(async () => { const cached = await caches.match(request); return ( cached || new Response( "" + "
" + "Osiris needs a connection to load. Try again once you're back online.
", { headers: { "Content-Type": "text/html; charset=utf-8" } } ) ); }) ); });