fix(app): App-Reconnect nach Hintergrund — Sticky-Fallback, Zombie-WS, AppState-Hook
Stefan musste seit der HTTPS-Umstellung nach jedem Hintergrund-Rueckkehr manuell auf "Verbinden" tippen, meist 3x bis es ging. Gleiche Bug-Klasse wie auf der Bridge davor (Sticky-Fallback), plus zwei App-spezifische Symptome. Drei Ursachen: 1. usingTLSFallback klebt: einmal nach onerror auf true gesetzt, blieb es bei allen folgenden Reconnects → App versuchte ws://...:443 gegen den TLS-only Caddy → HTTP 400 → endlos. Reset war NUR im manuellen connect(), nicht in onclose oder scheduleReconnect. Fix: in onclose `usingTLSFallback = false` damit der naechste Reconnect wieder primary (wss://) probiert. 2. Zombie-WebSocket: Android kann den TCP-Socket im Background still killen, der JS-State zeigt aber noch readyState === OPEN. Stefans manueller "Verbinden"-Klick rief connect() → "Bereits verbunden" No-Op statt sich neu aufzubauen. Fix: connect(force=true) optional, bestehendes WS-Objekt wird hart geschlossen (mit onclose=null gegen Doppel-Reconnect) bevor neuer Aufbau startet. 3. Keine aktive Reconnect-Sequence bei Foreground-Resume: App war abhaengig von onclose-Events die bei Zombie-WS nicht zwingend feuern. Fix: AppState-Listener in App.tsx, bei background → active automatischer rvs.connect(true). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+19
-1
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import { PermissionsAndroid, Platform, StatusBar, StyleSheet } from 'react-native';
|
||||
import { AppState, AppStateStatus, PermissionsAndroid, Platform, StatusBar, StyleSheet } from 'react-native';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { NavigationContainer, DefaultTheme } from '@react-navigation/native';
|
||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||
@@ -107,8 +107,26 @@ const App: React.FC = () => {
|
||||
console.warn('[App] GPS-Tracking restore fehlgeschlagen:', err?.message || err);
|
||||
});
|
||||
|
||||
// AppState-Listener: nach Hintergrund-Rueckkehr aktiv die WS-
|
||||
// Verbindung neu aufbauen. Hintergrund: Android kann den TCP-Socket
|
||||
// im Background killen, JS-State zeigt aber noch OPEN → Stefan musste
|
||||
// manuell in Settings auf "Verbinden" tippen, oft mehrfach. Mit dem
|
||||
// force-Reconnect bei "active" greift das automatisch.
|
||||
let lastAppState: AppStateStatus = AppState.currentState;
|
||||
const appStateSub = AppState.addEventListener('change', (next) => {
|
||||
const wasBg = lastAppState !== 'active';
|
||||
lastAppState = next;
|
||||
if (next === 'active' && wasBg) {
|
||||
console.log('[App] Foreground-Resume — force-reconnect zum RVS');
|
||||
try { rvs.connect(true); } catch (e: any) {
|
||||
console.warn('[App] force-reconnect fehlgeschlagen:', e?.message || e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Beim Beenden: Verbindung sauber trennen
|
||||
return () => {
|
||||
appStateSub.remove();
|
||||
rvs.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -83,21 +83,39 @@ class RVSConnection {
|
||||
|
||||
// --- Verbindung ---
|
||||
|
||||
/** Verbindung zum RVS aufbauen */
|
||||
connect(): void {
|
||||
/** Verbindung zum RVS aufbauen. force=true: bestehende Connection hart
|
||||
* schliessen + neu verbinden (auch wenn JS denkt readyState=OPEN — kann
|
||||
* nach Hintergrund-Pause ein Zombie-WS sein wo TCP tot ist aber JS-State
|
||||
* noch OPEN zeigt; in dem Fall war "Bereits verbunden" ein No-Op und
|
||||
* Stefan musste manuell zigmal klicken). */
|
||||
connect(force: boolean = false): void {
|
||||
if (!this.config) {
|
||||
this.log('warn', 'Keine Verbindungskonfiguration vorhanden');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.ws?.readyState === WebSocket.OPEN) {
|
||||
if (!force && this.ws?.readyState === WebSocket.OPEN) {
|
||||
this.log('info', 'Bereits verbunden');
|
||||
return;
|
||||
}
|
||||
|
||||
// Wenn ein WS-Objekt da ist (Zombie oder lebend), sauber abreissen
|
||||
// bevor wir einen neuen aufbauen — sonst gibt's zwei parallele
|
||||
// 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.ws = null;
|
||||
}
|
||||
|
||||
this.shouldReconnect = true;
|
||||
this.reconnectDelay = INITIAL_RECONNECT_DELAY_MS;
|
||||
this.usingTLSFallback = false;
|
||||
this.clearTimers();
|
||||
this.log('info', `Verbindungsaufbau zu ${this.config.host}:${this.config.port} (TLS: ${this.config.useTLS ? 'ja' : 'nein'})`);
|
||||
this.establishConnection();
|
||||
}
|
||||
@@ -212,6 +230,16 @@ class RVSConnection {
|
||||
this.ws = null;
|
||||
this.setState('disconnected');
|
||||
|
||||
// Sticky-Fallback-Reset: beim naechsten Reconnect wieder primary
|
||||
// (wss://) versuchen statt fuer immer auf ws:// zu kleben. War
|
||||
// der Hauptgrund warum die App nach Hintergrund-Rueckkehr nicht
|
||||
// mehr verband — TLS-Handshake-Timeout in einem Reconnect → Fallback
|
||||
// auf ws:// → Caddy refused → endlos im Fallback haengen.
|
||||
if (this.usingTLSFallback) {
|
||||
this.log('info', 'Reset TLS-Fallback fuer naechsten Reconnect (zurueck zu wss://)');
|
||||
this.usingTLSFallback = false;
|
||||
}
|
||||
|
||||
if (this.shouldReconnect) {
|
||||
this.scheduleReconnect();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user