From 6a5bd37e7ef938fca6c19a9df7c3a3196968d6dd Mon Sep 17 00:00:00 2001 From: cogwheel0 <172976095+cogwheel0@users.noreply.github.com> Date: Wed, 3 Dec 2025 14:07:50 +0530 Subject: [PATCH] feat(socket): improve WebSocket connection fallback mechanism --- lib/core/services/socket_service.dart | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/lib/core/services/socket_service.dart b/lib/core/services/socket_service.dart index ffce55a..756fb36 100644 --- a/lib/core/services/socket_service.dart +++ b/lib/core/services/socket_service.dart @@ -28,6 +28,7 @@ class SocketService with WidgetsBindingObserver { bool _isConnecting = false; bool _isAppForeground = true; Timer? _heartbeatTimer; + bool _forcePollingFallback = false; /// Heartbeat interval matching OpenWebUI's 30-second interval. static const Duration _heartbeatInterval = Duration(seconds: 30); @@ -102,8 +103,10 @@ class SocketService with WidgetsBindingObserver { } catch (_) {} final path = '/ws/socket.io'; - final usePollingOnly = !websocketOnly && !allowWebsocketUpgrade; - final transports = websocketOnly + final usePollingFallback = _forcePollingFallback; + final effectiveWebsocketOnly = websocketOnly && !usePollingFallback; + final usePollingOnly = !effectiveWebsocketOnly && !allowWebsocketUpgrade; + final transports = effectiveWebsocketOnly ? const ['websocket'] : usePollingOnly ? const ['polling'] @@ -112,8 +115,8 @@ class SocketService with WidgetsBindingObserver { final builder = io.OptionBuilder() // Transport selection switches between WebSocket-only and polling fallback .setTransports(transports) - .setRememberUpgrade(!websocketOnly && allowWebsocketUpgrade) - .setUpgrade(!websocketOnly && allowWebsocketUpgrade) + .setRememberUpgrade(!effectiveWebsocketOnly && allowWebsocketUpgrade) + .setUpgrade(!effectiveWebsocketOnly && allowWebsocketUpgrade) // Tune reconnect/backoff and timeouts // Note: In socket_io_client, pass a very large number for "unlimited" attempts. // Using double.maxFinite.toInt() ensures unlimited reconnection attempts. @@ -430,6 +433,18 @@ class SocketService with WidgetsBindingObserver { error: err, data: {'serverUrl': serverConfig.url}, ); + + // If WebSocket-only handshake fails, retry once with polling+websocket + // transports to avoid endless spinners (issue #172). + if (websocketOnly && !_forcePollingFallback) { + _forcePollingFallback = true; + DebugLogger.warning( + 'WebSocket connect failed; retrying with polling fallback', + scope: 'socket', + data: {'reason': err?.toString()}, + ); + unawaited(connect(force: true)); + } } void _handleReconnectFailed(dynamic _) {