feat(tickets): Web-Push-Benachrichtigungen (PWA)
- Die Züchterin kann auf der Tickets-Seite Push-Benachrichtigungen aktivieren (🔔-Schalter). Service Worker ist BEWUSST push-only (kein fetch/Caching), damit das Laden der App nie beeinträchtigt wird. - Backend: WebPush-Bibliothek + VAPID; PushNotifier sendet an alle Abos, räumt veraltete (404/410) auf. Endpoints: GET /push/vapid-public-key, POST /push/subscribe|unsubscribe. - Ausgelöst über ein notify-Flag auf PUT /feedback: NUR die KI setzt es (z. B. neue Rückfrage / Ticket gelöst) → keine Selbst-Pushes durch Aktionen der Züchterin. Text wird aus dem Status abgeleitet, Klick öffnet das Ticket (?focus=). - Schalter/Logik blenden sich aus, wenn das Gerät kein Push kann oder der Server keine VAPID-Schlüssel hat (z. B. e2e-Mock). Migration WebPushSubscriptions. VAPID-Schlüssel in appsettings.json (lokale Single-User-App im Heimnetz — bewusste, vertretbare Vereinfachung). Tests: 264 Backend grün (+Push-Endpoints), e2e Tickets Desktop grün, vitest 149. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
49
gerbil-manager-web/public/sw.js
Normal file
49
gerbil-manager-web/public/sw.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Service Worker — NUR für Web-Push-Benachrichtigungen.
|
||||
* BEWUSST OHNE fetch-Handler/Caching, damit das Laden der App niemals beeinflusst wird.
|
||||
*/
|
||||
self.addEventListener('install', () => self.skipWaiting())
|
||||
self.addEventListener('activate', (event) => event.waitUntil(self.clients.claim()))
|
||||
|
||||
self.addEventListener('push', (event) => {
|
||||
let data = {}
|
||||
try {
|
||||
data = event.data ? event.data.json() : {}
|
||||
} catch {
|
||||
data = {}
|
||||
}
|
||||
const title = data.title || 'Zucht der kleinen Chaoten'
|
||||
const body = data.body || ''
|
||||
const url = data.url || '/hilfe/tickets'
|
||||
event.waitUntil(
|
||||
self.registration.showNotification(title, {
|
||||
body,
|
||||
icon: '/icon-192.png',
|
||||
badge: '/icon-192.png',
|
||||
data: { url },
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
self.addEventListener('notificationclick', (event) => {
|
||||
event.notification.close()
|
||||
const url = (event.notification.data && event.notification.data.url) || '/hilfe/tickets'
|
||||
event.waitUntil(
|
||||
self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clients) => {
|
||||
for (const client of clients) {
|
||||
if ('focus' in client) {
|
||||
if ('navigate' in client) {
|
||||
try {
|
||||
client.navigate(url)
|
||||
} catch {
|
||||
/* ignorieren */
|
||||
}
|
||||
}
|
||||
return client.focus()
|
||||
}
|
||||
}
|
||||
if (self.clients.openWindow) return self.clients.openWindow(url)
|
||||
return undefined
|
||||
}),
|
||||
)
|
||||
})
|
||||
Reference in New Issue
Block a user