TF-3323 Make sure that web socket connection can be reconnected in some case

This commit is contained in:
dab246
2025-01-07 12:08:00 +07:00
committed by Dat H. Pham
parent 047df54627
commit 1689f709b5
2 changed files with 60 additions and 18 deletions
+1
View File
@@ -379,6 +379,7 @@ abstract class BaseController extends GetxController
void injectWebSocket(Session? session, AccountId? accountId) { void injectWebSocket(Session? session, AccountId? accountId) {
try { try {
log('$runtimeType::injectWebSocket:');
requireCapability( requireCapability(
session!, session!,
accountId!, accountId!,
@@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart'; import 'package:core/presentation/state/success.dart';
@@ -11,6 +12,7 @@ import 'package:flutter/material.dart';
import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:jmap_dart_client/jmap/push/state_change.dart'; import 'package:jmap_dart_client/jmap/push/state_change.dart';
import 'package:model/extensions/account_id_extensions.dart';
import 'package:tmail_ui_user/features/push_notification/data/model/web_socket_echo_request.dart'; import 'package:tmail_ui_user/features/push_notification/data/model/web_socket_echo_request.dart';
import 'package:tmail_ui_user/features/push_notification/data/model/web_socket_push_enable_request.dart'; import 'package:tmail_ui_user/features/push_notification/data/model/web_socket_push_enable_request.dart';
import 'package:tmail_ui_user/features/push_notification/domain/state/web_socket_push_state.dart'; import 'package:tmail_ui_user/features/push_notification/domain/state/web_socket_push_state.dart';
@@ -34,6 +36,7 @@ class WebSocketController extends PushBaseController {
ConnectWebSocketInteractor? _connectWebSocketInteractor; ConnectWebSocketInteractor? _connectWebSocketInteractor;
int _retryRemained = 3; int _retryRemained = 3;
bool _isConnecting = false;
WebSocketChannel? _webSocketChannel; WebSocketChannel? _webSocketChannel;
Timer? _webSocketPingTimer; Timer? _webSocketPingTimer;
StreamSubscription? _webSocketSubscription; StreamSubscription? _webSocketSubscription;
@@ -43,7 +46,6 @@ class WebSocketController extends PushBaseController {
@override @override
void handleFailureViewState(Failure failure) { void handleFailureViewState(Failure failure) {
logError('WebSocketController::handleFailureViewState():Failure $failure'); logError('WebSocketController::handleFailureViewState():Failure $failure');
_cleanUpWebSocketResources();
if (failure is WebSocketConnectionFailed) { if (failure is WebSocketConnectionFailed) {
_handleWebSocketConnectionRetry(); _handleWebSocketConnectionRetry();
} }
@@ -65,12 +67,11 @@ class WebSocketController extends PushBaseController {
@override @override
void initialize({AccountId? accountId, Session? session}) { void initialize({AccountId? accountId, Session? session}) {
log('WebSocketController::initialize:AccountId = ${accountId?.asString}');
super.initialize(accountId: accountId, session: session); super.initialize(accountId: accountId, session: session);
_connectWebSocket(accountId, session); _connectWebSocket();
if (PlatformInfo.isMobile) { _listenToAppLifeCycle();
_listenToAppLifeCycle(accountId, session);
}
} }
@override @override
@@ -81,30 +82,66 @@ class WebSocketController extends PushBaseController {
super.onClose(); super.onClose();
} }
void _listenToAppLifeCycle(AccountId? accountId, Session? session) { void _listenToAppLifeCycle() {
_appLifecycleListener ??= AppLifecycleListener( _appLifecycleListener ??= AppLifecycleListener(
onStateChange: (appLifecycleState) { onStateChange: _handleAppLifecycleStateChange,
switch (appLifecycleState) {
case AppLifecycleState.resumed:
_connectWebSocket(accountId, session);
break;
default:
_cleanUpWebSocketResources();
}
},
); );
} }
void _connectWebSocket(AccountId? accountId, Session? session) { void _handleAppLifecycleStateChange(AppLifecycleState appLifecycleState) async {
switch (appLifecycleState) {
case AppLifecycleState.resumed:
if (PlatformInfo.isMobile) {
_connectWebSocket();
} else if (PlatformInfo.isWeb) {
final isConnected = await _isWebSocketConnected();
log('WebSocketController::_handleAppLifecycleStateChange:isWebSocketConnected = $isConnected');
if (!isConnected) {
_connectWebSocket();
}
}
break;
default:
if (PlatformInfo.isMobile) {
_cleanUpWebSocketResources();
}
break;
}
}
Future<bool> _isWebSocketConnected() async {
try {
if (_webSocketChannel == null) return false;
await _webSocketChannel!.ready;
log('WebSocketController::_isWebSocketConnected:webSocketChannel ready');
return true;
} on SocketException catch (e) {
logError('WebSocketController::_isWebSocketConnected:SocketException = $e');
} on WebSocketChannelException catch (e) {
logError('WebSocketController::_isWebSocketConnected:WebSocketChannelException = $e');
} catch (e) {
logError('WebSocketController::_isWebSocketConnected:Exception = $e');
}
return false;
}
void _connectWebSocket() {
_connectWebSocketInteractor = getBinding<ConnectWebSocketInteractor>(); _connectWebSocketInteractor = getBinding<ConnectWebSocketInteractor>();
if (_connectWebSocketInteractor == null || accountId == null || session == null) { if (_connectWebSocketInteractor == null || accountId == null || session == null) {
logError('WebSocketController::_connectWebSocket: Skipping');
return; return;
} }
if (_isConnecting) return;
consumeState(_connectWebSocketInteractor!.execute(session, accountId)); _isConnecting = true;
log('WebSocketController::_connectWebSocket: Connecting');
consumeState(_connectWebSocketInteractor!.execute(session!, accountId!));
} }
void _cleanUpWebSocketResources() { void _cleanUpWebSocketResources() {
log('WebSocketController::_cleanUpWebSocketResources:');
_isConnecting = false;
_webSocketSubscription?.cancel(); _webSocketSubscription?.cancel();
_webSocketChannel = null; _webSocketChannel = null;
_webSocketPingTimer?.cancel(); _webSocketPingTimer?.cancel();
@@ -125,10 +162,11 @@ class WebSocketController extends PushBaseController {
} }
void _handleWebSocketConnectionRetry() { void _handleWebSocketConnectionRetry() {
log('WebSocketController::_handleWebSocketConnectionRetry:_retryRemained = $_retryRemained');
_cleanUpWebSocketResources(); _cleanUpWebSocketResources();
if (_retryRemained > 0) { if (_retryRemained > 0) {
_retryRemained--; _retryRemained--;
_connectWebSocket(accountId, session); _connectWebSocket();
} }
} }
@@ -139,7 +177,9 @@ class WebSocketController extends PushBaseController {
} }
void _pingWebSocket() { void _pingWebSocket() {
log('WebSocketController::_pingWebSocket:');
_webSocketPingTimer = Timer.periodic(const Duration(seconds: 10), (_) { _webSocketPingTimer = Timer.periodic(const Duration(seconds: 10), (_) {
log('WebSocketController::_pingWebSocket: Processing');
_webSocketChannel?.sink.add(jsonEncode(WebSocketEchoRequest().toJson())); _webSocketChannel?.sink.add(jsonEncode(WebSocketEchoRequest().toJson()));
}); });
} }
@@ -183,6 +223,7 @@ class WebSocketController extends PushBaseController {
void _handleStateChange(StateChange? stateChange) { void _handleStateChange(StateChange? stateChange) {
try { try {
log('WebSocketController::_handleStateChange:stateChange = $stateChange');
if (stateChange == null || accountId == null || session == null) return; if (stateChange == null || accountId == null || session == null) return;
final mapTypeState = stateChange.getMapTypeState(accountId!); final mapTypeState = stateChange.getMapTypeState(accountId!);