// The service worker lets the site be installed as an app, and shows staff notifications. It caches // nothing but the offline page: every request still goes to the network, so nobody sees stale bans // or ranks. const CACHE = "offline-v1"; const OFFLINE = "/static/offline.html"; self.addEventListener("install", (e) => { e.waitUntil(caches.open(CACHE).then((c) => c.add(OFFLINE)).then(() => self.skipWaiting())); }); self.addEventListener("activate", (e) => { e.waitUntil( caches.keys() .then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))) .then(() => self.clients.claim()), ); }); // Only page loads get the offline page; API calls fail as they always have, and the pages say so. self.addEventListener("fetch", (e) => { if (e.request.mode !== "navigate") return; e.respondWith(fetch(e.request).catch(() => caches.match(OFFLINE))); }); // Staff notifications (internal/web/notify.go). The panel sends title, body, icon, tag, url and // actions; a notification with the same tag replaces the last one quietly. self.addEventListener("push", (e) => { let d = {}; try { d = e.data ? e.data.json() : {}; } catch { d = { body: e.data.text() }; } e.waitUntil(self.registration.showNotification(d.title || "zio.sh", { body: d.body || "", icon: d.icon || "/static/icon-192.png", badge: "/static/badge-96.png", tag: d.tag, timestamp: d.timestamp, actions: d.actions || [], data: { url: d.url || "/" }, })); }); // Tapping the notification or its "View player" button opens its page, in an open window if // there is one. self.addEventListener("notificationclick", (e) => { e.notification.close(); const url = new URL(e.notification.data?.url || "/", self.location.origin).href; e.waitUntil((async () => { const wins = await self.clients.matchAll({ type: "window", includeUncontrolled: true }); const win = wins.find((w) => new URL(w.url).origin === self.location.origin); if (win) { try { await win.focus(); await win.navigate(url); return; } catch { /* not ours to navigate; open a new one */ } } await self.clients.openWindow(url); })()); }); // Browsers sometimes replace a subscription. Hand the new one to the panel so notifications keep // coming; this only works while the staff member's session cookie is still valid. self.addEventListener("pushsubscriptionchange", (e) => { e.waitUntil((async () => { const opts = e.oldSubscription?.options; const sub = e.newSubscription || (opts && await self.registration.pushManager.subscribe(opts)); if (!sub) return; await fetch("/api/admin/notifications/subscribe", { method: "POST", credentials: "same-origin", headers: { "Content-Type": "application/json", "X-Requested-With": "simpleadmin-web" }, body: JSON.stringify({ ...sub.toJSON(), userAgent: navigator.userAgent }), }); })()); });