Skip to content
67 linesCodeBlameRaw
1// g1t's service worker: browser notifications, nothing else. It caches
2// nothing and answers no requests, so pages load exactly as without it.
3// Pushes come from the notify service (services/notify), encrypted for
4// this browser: { title, body, href, tag, kind, urgent, workspace, actions,
5// card }. A notification's buttons are links: each opens its page. Actions
6// without a link (from cards that were pressed in place) are left off.
7
8self.addEventListener("install", () => self.skipWaiting());
9self.addEventListener("activate", (event) => event.waitUntil(self.clients.claim()));
10
11self.addEventListener("push", (event) => {
12 let data = {};
13 try {
14 data = event.data ? event.data.json() : {};
15 } catch {
16 data = { title: "g1t", body: event.data ? event.data.text() : "" };
17 }
18 const title = data.title || "g1t";
19 const safe = (href) => (typeof href === "string" && href.startsWith("/") && !href.startsWith("//") ? href : null);
20 const actions = Array.isArray(data.actions)
21 ? data.actions
22 .filter((a) => a && typeof a.id === "string" && typeof a.label === "string" && safe(a.href))
23 .slice(0, 2)
24 .map((a) => ({ id: a.id, label: a.label, href: safe(a.href) }))
25 : [];
26 event.waitUntil(
27 self.registration.showNotification(title, {
28 body: data.body || "",
29 icon: "/icon-192.png",
30 badge: "/badge-96.png",
31 // One notification per conversation: a newer message replaces the last.
32 tag: data.tag || undefined,
33 renotify: Boolean(data.tag),
34 actions: actions.map((a) => ({ action: a.id, title: a.label })),
35 data: { href: safe(data.href) || "/", actions },
36 }),
37 );
38});
39
40self.addEventListener("notificationclick", (event) => {
41 event.notification.close();
42 const data = event.notification.data || {};
43 const pressed = event.action && Array.isArray(data.actions) ? data.actions.find((a) => a.id === event.action) : null;
44 const href = (pressed && pressed.href) || data.href || "/";
45 const target = new URL(href, self.location.origin).href;
46 event.waitUntil(
47 (async () => {
48 const open = await self.clients.matchAll({ type: "window", includeUncontrolled: true });
49 // A tab already on g1t: brought forward and moved there.
50 const tab = open.find((client) => new URL(client.url).origin === self.location.origin);
51 if (tab) {
52 await tab.focus();
53 if ("navigate" in tab) {
54 try {
55 await tab.navigate(target);
56 return;
57 } catch {
58 // Not ours to navigate (uncontrolled): told instead.
59 }
60 }
61 tab.postMessage({ type: "g1t:navigate", href });
62 return;
63 }
64 await self.clients.openWindow(target);
65 })(),
66 );
67});