Files
Live_Navi/sw.js
T
Claude 4da99c913c Navigation an iOS-Tabs angleichen, Benachrichtigungscenter, Einfacher Modus
Phase 2-4 der WebApp-Angleichung an die iOS-App:

- Navigation: Favoriten und Störungen sind jetzt eigene Top-Level-Tabs
  (Desktop-Tableiste + mobile Bottom-Nav) statt nur über das Mehr-Grid bzw.
  den Stern im Header erreichbar. Neue Header-Icons für Einstellungen (⚙)
  und Benachrichtigungen (🔔, mit rotem Punkt bei ungelesenen Einträgen).
  "Mehr" ist jetzt schlanker (In der Nähe, Tickets, Verlauf, App-Info mit
  Verbindungstest gegen /health/full) und die Sprachauswahl in einen neuen
  eigenen "Einstellungen"-Tab umgezogen.

- Benachrichtigungscenter (js/notifications.js, neu): persistenter
  Verlauf über IndexedDB, sowohl vom Service Worker (eingehende Web-Push-
  Nachrichten, auch bei geschlossenem Tab) als auch von der Seite selbst
  (Störungs-Matches bei Favoriten, Fahrtalarm) beschrieben. Neuer
  "Benachrichtigungen"-Tab zeigt den Verlauf, Badge am Glocken-Icon zeigt
  ungelesene Einträge, per postMessage vom Service Worker live aktualisiert.

- Einfacher Modus: neuer Toggle im Einstellungen-Tab, persistiert in
  localStorage, setzt eine .simple-mode-Klasse auf <html> - moderat
  größere Schrift plus Ausblenden von Verkehrsmittel-Filter/Fahrrad-Option/
  "Zwischenhalt hinzufügen" im Routenplaner, analog zur iOS-App (bewusst
  keine Accessibility-Schriftgrößenstufe, die ist für Screenreader gedacht).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01De7H8hR1Q9DdgyuS2ukN6y
2026-09-28 11:58:15 +02:00

75 lines
2.7 KiB
JavaScript

// Service Worker für Web-Push. Läuft unabhängig vom offenen Tab im
// Hintergrund und zeigt Systembenachrichtigungen, sobald der Server (siehe
// backend/src/utils/pushJobs.js) eine Push-Nachricht schickt.
//
// WICHTIG: diese Datei muss im Root des Frontends liegen (dort, wo auch
// index.html liegt), NICHT unter js/ - der Geltungsbereich ("scope") eines
// Service Workers ist standardmäßig auf sein eigenes Verzeichnis und alles
// darunter beschränkt.
// Schreibt jede eingehende Push-Nachricht zusätzlich in den persistenten
// Benachrichtigungsverlauf (js/notifications.js), damit sie im "Mehr" >
// "Benachrichtigungen"-Tab sichtbar ist - auch wenn sie ankam, während kein
// Tab offen war (der Store läuft über IndexedDB, nicht über die Seite).
try {
self.importScripts('./js/notifications.js');
} catch (err) {
// Sollte importScripts fehlschlagen, bleibt die Push-Zustellung selbst
// trotzdem funktionsfähig - nur der Verlauf fehlt dann.
}
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 (err) {
data = { title: 'ÖPNV Navi', body: event.data ? event.data.text() : '' };
}
const title = data.title || 'ÖPNV Navi';
const options = {
body: data.body || '',
tag: data.tag || 'oepnv-navi',
icon: '/icons/icon-192.png',
badge: '/icons/badge-72.png',
data: { url: data.url || '/' },
};
event.waitUntil(
Promise.all([
self.registration.showNotification(title, options),
(typeof NotificationStore !== 'undefined'
? NotificationStore.add({ title, body: options.body, url: options.data.url }).catch(() => {})
: Promise.resolve()),
self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => {
clientList.forEach((client) => client.postMessage({ type: 'oepnv-navi:notification-added' }));
}),
]),
);
});
// Klick auf die Benachrichtigung: vorhandenen App-Tab in den Vordergrund
// holen, statt immer einen neuen zu öffnen.
self.addEventListener('notificationclick', (event) => {
event.notification.close();
const targetUrl = (event.notification.data && event.notification.data.url) || '/';
event.waitUntil(
self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => {
for (const client of clientList) {
if ('focus' in client) return client.focus();
}
if (self.clients.openWindow) return self.clients.openWindow(targetUrl);
return undefined;
}),
);
});