feat(transport): Improve socket transport mode selection and localization
This commit is contained in:
@@ -7,6 +7,7 @@ import 'package:flutter/foundation.dart';
|
||||
// import 'package:http_parser/http_parser.dart';
|
||||
// Removed legacy websocket/socket.io imports
|
||||
import 'package:uuid/uuid.dart';
|
||||
import '../models/backend_config.dart';
|
||||
import '../models/server_config.dart';
|
||||
import '../models/user.dart';
|
||||
import '../models/model.dart';
|
||||
@@ -253,6 +254,44 @@ class ApiService {
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<BackendConfig?> getBackendConfig() async {
|
||||
try {
|
||||
final response = await _dio.get('/api/config');
|
||||
final data = response.data;
|
||||
Map<String, dynamic>? jsonMap;
|
||||
if (data is Map<String, dynamic>) {
|
||||
jsonMap = data;
|
||||
} else if (data is String && data.isNotEmpty) {
|
||||
final decoded = json.decode(data);
|
||||
if (decoded is Map<String, dynamic>) {
|
||||
jsonMap = decoded;
|
||||
}
|
||||
}
|
||||
if (jsonMap == null) {
|
||||
return null;
|
||||
}
|
||||
return BackendConfig.fromJson(jsonMap);
|
||||
} on DioException catch (e, stackTrace) {
|
||||
_traceApi('Backend config request failed: $e');
|
||||
DebugLogger.error(
|
||||
'backend-config-error',
|
||||
scope: 'api/config',
|
||||
error: e,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
rethrow;
|
||||
} catch (e, stackTrace) {
|
||||
_traceApi('Backend config decode error: $e');
|
||||
DebugLogger.error(
|
||||
'backend-config-decode',
|
||||
scope: 'api/config',
|
||||
error: e,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// Authentication
|
||||
Future<Map<String, dynamic>> login(String username, String password) async {
|
||||
try {
|
||||
|
||||
@@ -26,7 +26,7 @@ class SettingsService {
|
||||
static const String _voiceAutoSendKey = PreferenceKeys.voiceAutoSendFinal;
|
||||
// Realtime transport preference
|
||||
static const String _socketTransportModeKey =
|
||||
PreferenceKeys.socketTransportMode; // 'auto' or 'ws'
|
||||
PreferenceKeys.socketTransportMode; // 'polling' or 'ws'
|
||||
// Quick pill visibility selections (max 2)
|
||||
static const String _quickPillsKey = PreferenceKeys
|
||||
.quickPills; // StringList of identifiers e.g. ['web','image','tools']
|
||||
@@ -256,15 +256,27 @@ class SettingsService {
|
||||
return _preferencesBox().put(_voiceAutoSendKey, value);
|
||||
}
|
||||
|
||||
/// Transport mode: 'auto' (polling+websocket) or 'ws' (websocket only)
|
||||
/// Transport mode: 'polling' (HTTP polling + WebSocket upgrade) or 'ws'
|
||||
static Future<String> getSocketTransportMode() {
|
||||
final value = _preferencesBox().get(_socketTransportModeKey) as String?;
|
||||
return Future.value(value ?? 'ws');
|
||||
final raw = _preferencesBox().get(_socketTransportModeKey) as String?;
|
||||
if (raw == null) {
|
||||
return Future.value('ws');
|
||||
}
|
||||
if (raw == 'auto') {
|
||||
return Future.value('polling');
|
||||
}
|
||||
if (raw != 'polling' && raw != 'ws') {
|
||||
return Future.value('ws');
|
||||
}
|
||||
return Future.value(raw);
|
||||
}
|
||||
|
||||
static Future<void> setSocketTransportMode(String mode) {
|
||||
if (mode != 'auto' && mode != 'ws') {
|
||||
mode = 'auto';
|
||||
if (mode == 'auto') {
|
||||
mode = 'polling';
|
||||
}
|
||||
if (mode != 'polling' && mode != 'ws') {
|
||||
mode = 'polling';
|
||||
}
|
||||
return _preferencesBox().put(_socketTransportModeKey, mode);
|
||||
}
|
||||
@@ -344,7 +356,7 @@ class AppSettings {
|
||||
final String? voiceLocaleId;
|
||||
final bool voiceHoldToTalk;
|
||||
final bool voiceAutoSendFinal;
|
||||
final String socketTransportMode; // 'auto' or 'ws'
|
||||
final String socketTransportMode; // 'polling' or 'ws'
|
||||
final List<String> quickPills; // e.g., ['web','image']
|
||||
final bool sendOnEnter;
|
||||
final String? ttsVoice;
|
||||
@@ -566,8 +578,17 @@ class AppSettingsNotifier extends _$AppSettingsNotifier {
|
||||
}
|
||||
|
||||
Future<void> setSocketTransportMode(String mode) async {
|
||||
state = state.copyWith(socketTransportMode: mode);
|
||||
await SettingsService.setSocketTransportMode(mode);
|
||||
var sanitized = mode;
|
||||
if (sanitized == 'auto') {
|
||||
sanitized = 'polling';
|
||||
}
|
||||
if (sanitized != 'polling' && sanitized != 'ws') {
|
||||
sanitized = 'polling';
|
||||
}
|
||||
if (state.socketTransportMode != sanitized) {
|
||||
state = state.copyWith(socketTransportMode: sanitized);
|
||||
}
|
||||
await SettingsService.setSocketTransportMode(sanitized);
|
||||
}
|
||||
|
||||
Future<void> setQuickPills(List<String> pills) async {
|
||||
|
||||
@@ -22,6 +22,7 @@ typedef SocketChannelEventHandler =
|
||||
class SocketService with WidgetsBindingObserver {
|
||||
final ServerConfig serverConfig;
|
||||
final bool websocketOnly;
|
||||
final bool allowWebsocketUpgrade;
|
||||
io.Socket? _socket;
|
||||
String? _authToken;
|
||||
bool _isAppForeground = true;
|
||||
@@ -34,6 +35,7 @@ class SocketService with WidgetsBindingObserver {
|
||||
required this.serverConfig,
|
||||
String? authToken,
|
||||
this.websocketOnly = false,
|
||||
this.allowWebsocketUpgrade = true,
|
||||
}) : _authToken = authToken {
|
||||
final binding = WidgetsBinding.instance;
|
||||
final lifecycle = binding.lifecycleState;
|
||||
@@ -72,11 +74,18 @@ class SocketService with WidgetsBindingObserver {
|
||||
} catch (_) {}
|
||||
final path = '/ws/socket.io';
|
||||
|
||||
final usePollingOnly = !websocketOnly && !allowWebsocketUpgrade;
|
||||
final transports = websocketOnly
|
||||
? const ['websocket']
|
||||
: usePollingOnly
|
||||
? const ['polling']
|
||||
: const ['polling', 'websocket'];
|
||||
|
||||
final builder = io.OptionBuilder()
|
||||
// Transport selection - WebSocket only, no polling fallback
|
||||
.setTransports(['websocket'])
|
||||
.setRememberUpgrade(false)
|
||||
.setUpgrade(false)
|
||||
// Transport selection switches between WebSocket-only and polling fallback
|
||||
.setTransports(transports)
|
||||
.setRememberUpgrade(!websocketOnly && allowWebsocketUpgrade)
|
||||
.setUpgrade(!websocketOnly && allowWebsocketUpgrade)
|
||||
// Tune reconnect/backoff and timeouts
|
||||
.setReconnectionAttempts(0) // 0/Infinity semantics: unlimited attempts
|
||||
.setReconnectionDelay(1000)
|
||||
|
||||
Reference in New Issue
Block a user