From ea159d4ed770680424448481a75fd5309f7c9ea1 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Fri, 2 Oct 2026 01:08:37 +0200 Subject: [PATCH] =?UTF-8?q?fix(app):=20Zombie-Verbindung=20=E2=80=94=20App?= =?UTF-8?q?=20zeigt=20'getrennt',=20empfaengt=20aber=20weiter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Symptom (live beobachtet): die App steht auf 'getrennt' und nichts, was Stefan schreibt oder spricht, erreicht den Server — ARIAs Antwort und TTS kommen aber trotzdem in der App an. Ursache: Beim Verwerfen einer Verbindung wurden nur onclose und onerror entfernt, NICHT onmessage. Die aufgegebene Verbindung lieferte deshalb weiter Nachrichten an die App-Listener (daher die ankommende Antwort), waehrend this.ws auf die neue/tote zeigte — send() lehnte ab und der Zustand blieb 'disconnected'. Betraf beide Abriss-Pfade (Neu-Connect und TLS-Fallback). Fix: detachSocket() klemmt eine Verbindung vollstaendig ab (onopen/onmessage/ onclose/onerror + close) und wird in beiden Pfaden genutzt. Zusaetzlich ignoriert der onmessage-Handler Nachrichten, die nicht von der AKTUELLEN Verbindung stammen — sonst taeuscht ein Zombie weiter Betrieb vor. tsc: 0 Fehler. Co-Authored-By: Claude Opus 4.8 --- android/src/services/rvs.ts | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/android/src/services/rvs.ts b/android/src/services/rvs.ts index dcb6d39..8889585 100644 --- a/android/src/services/rvs.ts +++ b/android/src/services/rvs.ts @@ -104,11 +104,7 @@ class RVSConnection { // Verbindungen + doppelte Events. if (this.ws) { this.log('info', 'Bestehende WS-Verbindung wird geschlossen vor Neu-Connect'); - try { - this.ws.onclose = null; // verhindert dass scheduleReconnect doppelt feuert - this.ws.onerror = null; - this.ws.close(); - } catch (_) {} + this.detachSocket(this.ws); this.ws = null; } @@ -215,7 +211,12 @@ class RVSConnection { this.startHeartbeat(); }; + const socket = this.ws; // genau DIESE Verbindung (nicht 'this.ws' spaeter) this.ws.onmessage = (event: WebSocketMessageEvent) => { + // Nur die AKTUELLE Verbindung darf Nachrichten einspeisen. Sonst + // koennte eine alte, laengst aufgegebene Verbindung weiter Daten + // liefern und vortaeuschen, es sei alles in Ordnung. + if (socket !== this.ws) return; try { const message: RVSMessage = JSON.parse(event.data as string); this.notifyMessageListeners(message); @@ -258,8 +259,7 @@ class RVSConnection { this.log('warn', 'TLS fehlgeschlagen — Fallback auf ws:// (ohne TLS)'); this.clearTimers(); if (this.ws) { - this.ws.onclose = null; // onclose-Handler entfernen um Doppel-Reconnect zu verhindern - try { this.ws.close(); } catch (_) {} + this.detachSocket(this.ws); // inkl. onmessage — sonst bleibt ein Zombie, der weiter empfaengt } this.ws = null; this.shouldReconnect = true; @@ -302,6 +302,25 @@ class RVSConnection { }, HEARTBEAT_INTERVAL_MS); } + /** + * Klemmt eine Verbindung VOLLSTAENDIG ab, bevor sie verworfen wird. + * + * Wichtig: frueher wurden nur onclose/onerror entfernt — onmessage blieb + * haengen. Eine aufgegebene (Zombie-)Verbindung lieferte dadurch weiter + * Nachrichten an die App, waehrend gesendet wurde ueber die neue/tote. + * Symptom: die App zeigt "getrennt", ARIAs Sprachausgabe kommt aber trotzdem + * an — und nichts, was man schreibt oder spricht, erreicht den Server. + */ + private detachSocket(ws: WebSocket): void { + try { + ws.onopen = null; + ws.onmessage = null; // <- das fehlte + ws.onclose = null; // verhindert doppelten Reconnect + ws.onerror = null; + ws.close(); + } catch (_) {} + } + private clearTimers(): void { if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer);