feat(socket): improve WebSocket connection fallback mechanism
This commit is contained in:
@@ -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 _) {
|
||||||
|
|||||||
Reference in New Issue
Block a user