feat(socket): improve WebSocket connection fallback mechanism

This commit is contained in:
cogwheel0
2025-12-03 14:07:50 +05:30
parent 71939cef5b
commit 6a5bd37e7e

View File

@@ -28,6 +28,7 @@ class SocketService with WidgetsBindingObserver {
bool _isConnecting = false; bool _isConnecting = false;
bool _isAppForeground = true; bool _isAppForeground = true;
Timer? _heartbeatTimer; Timer? _heartbeatTimer;
bool _forcePollingFallback = false;
/// Heartbeat interval matching OpenWebUI's 30-second interval. /// Heartbeat interval matching OpenWebUI's 30-second interval.
static const Duration _heartbeatInterval = Duration(seconds: 30); static const Duration _heartbeatInterval = Duration(seconds: 30);
@@ -102,8 +103,10 @@ class SocketService with WidgetsBindingObserver {
} catch (_) {} } catch (_) {}
final path = '/ws/socket.io'; final path = '/ws/socket.io';
final usePollingOnly = !websocketOnly && !allowWebsocketUpgrade; final usePollingFallback = _forcePollingFallback;
final transports = websocketOnly final effectiveWebsocketOnly = websocketOnly && !usePollingFallback;
final usePollingOnly = !effectiveWebsocketOnly && !allowWebsocketUpgrade;
final transports = effectiveWebsocketOnly
? const ['websocket'] ? const ['websocket']
: usePollingOnly : usePollingOnly
? const ['polling'] ? const ['polling']
@@ -112,8 +115,8 @@ class SocketService with WidgetsBindingObserver {
final builder = io.OptionBuilder() final builder = io.OptionBuilder()
// Transport selection switches between WebSocket-only and polling fallback // Transport selection switches between WebSocket-only and polling fallback
.setTransports(transports) .setTransports(transports)
.setRememberUpgrade(!websocketOnly && allowWebsocketUpgrade) .setRememberUpgrade(!effectiveWebsocketOnly && allowWebsocketUpgrade)
.setUpgrade(!websocketOnly && allowWebsocketUpgrade) .setUpgrade(!effectiveWebsocketOnly && allowWebsocketUpgrade)
// Tune reconnect/backoff and timeouts // Tune reconnect/backoff and timeouts
// Note: In socket_io_client, pass a very large number for "unlimited" attempts. // Note: In socket_io_client, pass a very large number for "unlimited" attempts.
// Using double.maxFinite.toInt() ensures unlimited reconnection attempts. // Using double.maxFinite.toInt() ensures unlimited reconnection attempts.
@@ -430,6 +433,18 @@ class SocketService with WidgetsBindingObserver {
error: err, error: err,
data: {'serverUrl': serverConfig.url}, 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 _) { void _handleReconnectFailed(dynamic _) {