diff --git a/android/App.tsx b/android/App.tsx index 1f4cbcd..4bdc927 100644 --- a/android/App.tsx +++ b/android/App.tsx @@ -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(); }; }, []); diff --git a/android/src/services/rvs.ts b/android/src/services/rvs.ts index 2e50650..dcb6d39 100644 --- a/android/src/services/rvs.ts +++ b/android/src/services/rvs.ts @@ -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(); }