// Web Push handling, imported into the generated service worker (workbox importScripts). self.addEventListener("push", (event) => { let data = {}; try { data = event.data ? event.data.json() : {}; } catch { data = { body: event.data ? event.data.text() : "" }; } const title = data.title || "CodeCraft"; const de = String((self.navigator && self.navigator.language) || "").toLowerCase().startsWith("de"); // Approvals can be answered right from the notification. const actions = data.kind === "permission" && data.permission_id ? [ { action: "allow", title: de ? "Erlauben" : "Allow" }, { action: "reject", title: de ? "Ablehnen" : "Reject" }, ] : []; const prefix = { permission: "🔐 ", question: "❓ ", done: "✅ " }[data.kind] || ""; event.waitUntil( self.registration.showNotification(prefix + title, { body: data.kind === "done" ? data.body || "Finished" : data.body || "", tag: data.tag || "codecraft", renotify: true, icon: "/icon-192.png", badge: "/icon-192.png", actions, requireInteraction: data.kind === "permission", data: { url: data.url || "/", permission_id: data.permission_id || null }, }), ); }); self.addEventListener("notificationclick", (event) => { event.notification.close(); const info = event.notification.data || {}; if ((event.action === "allow" || event.action === "reject") && info.permission_id) { const de = String((self.navigator && self.navigator.language) || "").toLowerCase().startsWith("de"); const target = new URL(info.url || "/", self.location.origin).href; event.waitUntil( fetch("/api/permissions/" + encodeURIComponent(info.permission_id), { method: "POST", credentials: "same-origin", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ reply: event.action === "allow" ? "once" : "reject" }), }) .then((r) => { if (!r.ok) throw new Error(String(r.status)); // Short confirmation, so the tap does not feel lost. return self.registration.showNotification(event.action === "allow" ? (de ? "Erlaubt" : "Allowed") : (de ? "Abgelehnt" : "Rejected"), { tag: "perm-" + info.permission_id, icon: "/icon-192.png", silent: true, data: { url: info.url }, }).then(() => new Promise((res) => setTimeout(res, 2500))).then(() => self.registration.getNotifications({ tag: "perm-" + info.permission_id })).then((l) => l.forEach((n) => n.close())); }) .catch(() => self.clients.openWindow(target)), // expired, answered elsewhere or signed out: open the app ); return; } const url = new URL((event.notification.data && event.notification.data.url) || "/", self.location.origin).href; event.waitUntil( self.clients.matchAll({ type: "window", includeUncontrolled: true }).then((list) => { for (const client of list) { if (new URL(client.url).origin === self.location.origin) { return client.focus().then((c) => (c && "navigate" in c ? c.navigate(url) : c)); } } return self.clients.openWindow(url); }), ); });