WebSocket & Push Service (Node.js)
Der smartkris-ws-service ist ein dedizierter Node.js-Microservice (basierend auf Node 20 Alpine). Seine einzige Aufgabe ist die asynchrone Echtzeit-Verteilung von Meldungen an externe Systeme (z. B. digitale Stadt-Stelen, externe Webseiten) sowie die Auslösung von Push-Benachrichtigungen für die mobile App.
Er fungiert als Bindeglied zwischen der ereignisgesteuerten Spring Boot Architektur und der Außenwelt.
1. Infrastruktur & Routing
Der Service läuft als eigenständiger Container.
-
Port & Health: Der interne HTTP-Server lauscht auf Port
3001. Er stellt unter/healtheinen einfachen Liveness-Check (200 OK) bereit. -
Traefik-Routing: Im Deployment (siehe
docker-compose.traefik.yml.j2) fängt Traefik alle Anfragen ab, die den Pfad-Präfix/wsbesitzen (z. B.https://api.smartcity.de/ws), und routet diese exklusiv an diesen Service. -
Netzwerk: Der Service hat über das interne
shared-networkZugriff auf das Spring Boot Backend und RabbitMQ.
2. RabbitMQ Consumer (lib/rabbit.js)
Der Service verbindet sich beim Start mit dem RabbitMQ-Broker und erstellt einen Channel.
-
Exchange: Er lauscht auf den
fanout-Exchangecrisis-updates-exchange, in den das Spring Boot Backend alle Statusänderungen publiziert. -
Queue: Der Service erzeugt eine temporäre, exklusive Queue und bindet sie an den Exchange.
-
Resilienz: Bei Verbindungsabbrüchen versucht der Service automatisch alle 5 Sekunden einen Reconnect (
setTimeout). Nachrichten werden im Modus{ noAck: true }konsumiert (Fire-and-Forget).
3. WebSocket Server (lib/websocket-server.js)
Der WebSocket-Server dockt sich an den HTTP-Server an und fängt Upgrade-Requests auf dem Pfad /ws ab.
Verbindungsaufbau & Initial State
Sobald sich ein Client (z. B. eine Stele) verbindet, muss dieser den aktuellen Krisen-Status der Stadt kennen, bevor er auf zukünftige Updates warten kann.
-
Der Service macht einen synchronen REST-Call an das Spring Boot Backend (
BACKEND_API_URL/api/messages?situationStatus=aktiv&sort=createdAt,desc&size=100). -
Für die Authentifizierung am Backend nutzt er den
SMARTKRIS_API_ADMIN_KEY. -
Gruppierung: Aus den zurückgegebenen Meldungen filtert der Service per
Mapexakt die neueste Meldung pro Lage (Situation) heraus. -
Transformation: Die Daten werden transformiert (siehe unten) und als JSON-String an den neu verbundenen Client gesendet:
{ "type": "INITIAL_STATE", "payload": [ ... ] }
Broadcast von Live-Events
Wenn der RabbitMQ-Consumer ein Event vom Backend empfängt (CREATE, UPDATE, DELETE), schleift der WebSocket-Server dieses durch. Bei CREATE und UPDATE wird die Payload zunächst transformiert. Anschließend wird die Nachricht an alle aktuell verbundenen (readyState === 1) WebSocket-Clients gebroadcastet.
Datentransformation (transformMessageAreasForWebSocket)
Um die Bandbreite für externe Clients gering zu halten und das Parsing auf dummen IoT-Geräten zu vereinfachen, manipuliert der Service die JSON-Struktur, bevor sie gesendet wird:
Das Array von Area-Objekten ([{id: 1, name: "Burg", …}]) wird in der Message und in der verschachtelten Situation zu einem simplen Array von Strings abstrahiert (["Burg"]). Fehlt der Name, wird der Fallback "Unbekannter Bereich" gesetzt.
4. Parse Server Push Notifications (lib/push.js)
Wenn eine neue oder aktualisierte Meldung den Kanal App (messagePayload.channels.includes('App')) enthält, kümmert sich der Service um den Versand von nativen Push-Benachrichtigungen (iOS/Android).
Dafür kommuniziert er über REST mit einem externen Parse Server (PARSE_URL). Dieser Prozess verläuft in zwei Schritten:
Schritt 1: PushNotification Objekt erstellen
Der Service baut ein maßgeschneidertes Payload-Objekt für Parse zusammen und sendet es via POST /classes/PushNotification:
* Titel: Setzt sich zusammen aus dem Eskalationslevel und dem Lagen-Titel (z. B. "Warnung: Hochwasser an der Wupper (3)").
* Body: Der eigentliche Content der Meldung.
* Deep Links: Erzeugt einen App-spezifischen Deep-Link (Präfix konfiguriert via APP_DEEPLINK_PREFIX, standardmäßig smartkris://). Die genaue URL lautet: smartkris:///messages/detail?object={messageId}.
* Zielgruppe: Das Tag pushTags: ['alle'] sorgt dafür, dass alle registrierten Geräte erreicht werden.
Schritt 2: Push-Job triggern
Da Parse das Objekt nun gespeichert hat, muss der Versand aktiv angestoßen werden. Dies geschieht durch einen zweiten synchronen Request via POST /jobs/Push Notification. Das Resultat wird zur Diagnose auf der Konsole geloggt. Fehlt eine der Umgebungsvariablen (Parse App ID, Master Key, URL), bricht der Vorgang mit einer Warnung ab, ohne den WebSocket-Broadcast zu blockieren.