feat: Versand per Web-Push und E-Mail
Push ueber VAPID, Mail ueber SMTP, beide unabhaengig zuschaltbar. Die Schluessel eines Push-Geraets liegen verschluesselt in der Datenbank. Ein Geraet, das der Push-Dienst mit 404 oder 410 ablehnt, wird sofort entfernt; andere Fehler zaehlen hoch und fuehren nach fuenf Versuchen zum selben Ergebnis. Eine Liste toter Abos putzt sich so von allein. Der Nachweis wird vor dem Senden geschrieben, nicht danach: lieber eine verpasste Nachricht als eine, die im Minutentakt erneut versucht wird. Weder Endpunkt noch Schluessel noch Mailinhalt landen im Protokoll. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ddb13d7dd0
commit
e0e11c85b2
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Service Worker des Planers.
|
||||
*
|
||||
* Er tut genau eine Sache: Push-Nachrichten anzeigen und beim Klick die
|
||||
* richtige Seite öffnen. Kein Zwischenspeichern von Seiten — ein halb
|
||||
* veralteter Planer wäre schlimmer als gar keiner.
|
||||
*/
|
||||
|
||||
self.addEventListener('install', () => {
|
||||
// Sofort übernehmen, nicht auf das Schließen aller Tabs warten.
|
||||
self.skipWaiting()
|
||||
})
|
||||
|
||||
self.addEventListener('activate', (ereignis) => {
|
||||
ereignis.waitUntil(self.clients.claim())
|
||||
})
|
||||
|
||||
self.addEventListener('push', (ereignis) => {
|
||||
let daten = { titel: 'Planer', text: '', pfad: '/' }
|
||||
try {
|
||||
if (ereignis.data) daten = { ...daten, ...ereignis.data.json() }
|
||||
} catch {
|
||||
// Kaputte Nutzlast: lieber eine karge Meldung als gar keine.
|
||||
}
|
||||
|
||||
ereignis.waitUntil(
|
||||
self.registration.showNotification(daten.titel, {
|
||||
body: daten.text,
|
||||
lang: 'de',
|
||||
// Gleiche Anlässe überschreiben einander, statt sich zu stapeln.
|
||||
tag: daten.anlass || 'planer',
|
||||
data: { pfad: daten.pfad },
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
self.addEventListener('notificationclick', (ereignis) => {
|
||||
ereignis.notification.close()
|
||||
const pfad = (ereignis.notification.data && ereignis.notification.data.pfad) || '/'
|
||||
|
||||
ereignis.waitUntil(
|
||||
self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((fenster) => {
|
||||
// Ist der Planer schon offen, dorthin wechseln statt ein zweites
|
||||
// Fenster aufzumachen.
|
||||
for (const f of fenster) {
|
||||
if (f.url.includes(self.location.origin)) {
|
||||
return f.focus().then(() => f.navigate(pfad))
|
||||
}
|
||||
}
|
||||
return self.clients.openWindow(pfad)
|
||||
}),
|
||||
)
|
||||
})
|
||||
Reference in New Issue
Block a user