News that finds you, including when nothing of ours is open
The dots covered Reports and Messages from the day those sections existed. The announcement did not: only a chat reply produced an HX-Trigger, so a scheduled run that filed a report or posted into Messages lit a green dot in a corner and said nothing at all. That is precisely the arrival nobody is watching for -- a chat reply is one you asked for a moment ago and are probably looking at. So every kind announces, each with its own once-only flag, and the payload is a list of items rather than of titles, because a notification is a thing you click and a title cannot say where. One arrival, three channels, and they must not all fire. A toast for somebody looking at the page; a count in the tab title while it is hidden, cleared on focus; a system notification for somebody elsewhere entirely. The service worker is the only place that can tell them apart -- the server cannot see whether a window is focused and the page cannot see a push it did not receive -- so it stays quiet when one of its own windows has focus. And web push, hand-rolled against RFC 8291 and RFC 8292 with the cryptography already here for Fernet. It exists because everything else is polled by an open page, and the arrival worth interrupting somebody for is a schedule firing at seven in the morning with the laptop shut. The trade is real and is written down rather than glossed: the POST goes to Google's or Mozilla's push service, the payload is sealed end to end so they cannot read it, and what they do learn is that this server sent something and when. Opt-in per device, off until asked for, and the rest of the system works without it. Nothing else in LLeMbas contacts an outside service on its own. The encryption is tested by decrypting it back with an independent implementation of the specification's other half. There is no other way to know: a push service accepts the POST and forwards bytes it cannot read, so a wrong derivation is a notification that never appears, with a 201 in the log. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
feca8861a1
commit
fb54a236ae
@@ -120,3 +120,72 @@ self.addEventListener("fetch", function (event) {
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
/*
|
||||
Notifications that arrive with no page open.
|
||||
|
||||
This is the only part of LLeMbas that runs when nothing of ours is on screen,
|
||||
and it is why web push exists here at all: everything else is polled by an open
|
||||
page, which is exactly what is missing at seven in the morning when a schedule
|
||||
fires and the laptop is shut.
|
||||
|
||||
The payload was encrypted end to end (see services/push.py), so what arrives
|
||||
here is the first plaintext anybody but this browser and that server has seen.
|
||||
*/
|
||||
self.addEventListener("push", function (event) {
|
||||
var payload = {};
|
||||
try {
|
||||
payload = event.data ? event.data.json() : {};
|
||||
} catch (error) {
|
||||
payload = { title: "LLeMbas", body: "Something new arrived." };
|
||||
}
|
||||
|
||||
event.waitUntil(
|
||||
self.clients.matchAll({ type: "window", includeUncontrolled: true }).then(function (clients) {
|
||||
/* Somebody is looking at it. The page has its own toast and its own
|
||||
count in the tab title, and a system notification on top of those is
|
||||
the same news three times -- which is how notifications come to be
|
||||
switched off for good. This is the only place that can be known: the
|
||||
server cannot see whether a window is focused, and the page cannot see
|
||||
a push that it did not receive. */
|
||||
for (var i = 0; i < clients.length; i++) {
|
||||
if (clients[i].focused) return null;
|
||||
}
|
||||
return self.registration.showNotification(payload.title || "LLeMbas", {
|
||||
body: payload.body || "",
|
||||
/* One at a time. A browser left closed all day must not be opened to a
|
||||
stack of twelve. */
|
||||
tag: "lembas-" + (payload.kind || "unread"),
|
||||
renotify: true,
|
||||
icon: "/static/img/icon-192.png",
|
||||
badge: "/static/img/icon-192.png",
|
||||
data: { url: payload.url || "/" },
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
/*
|
||||
Clicking one.
|
||||
|
||||
Focus a window that is already open rather than opening a second: somebody
|
||||
with LLeMbas open in a tab wants that tab, and `openWindow` would give them
|
||||
two. `navigate` moves the one they have to whatever arrived.
|
||||
*/
|
||||
self.addEventListener("notificationclick", function (event) {
|
||||
event.notification.close();
|
||||
var target = (event.notification.data && event.notification.data.url) || "/";
|
||||
|
||||
event.waitUntil(
|
||||
self.clients.matchAll({ type: "window", includeUncontrolled: true }).then(function (clients) {
|
||||
for (var i = 0; i < clients.length; i++) {
|
||||
var client = clients[i];
|
||||
if (new URL(client.url).origin !== self.location.origin) continue;
|
||||
return client.focus().then(function (focused) {
|
||||
return focused && focused.navigate ? focused.navigate(target) : focused;
|
||||
});
|
||||
}
|
||||
return self.clients.openWindow(target);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user