diff --git a/lib/features/base/base_controller.dart b/lib/features/base/base_controller.dart index 79ca8cefe..fabf996a9 100644 --- a/lib/features/base/base_controller.dart +++ b/lib/features/base/base_controller.dart @@ -156,7 +156,8 @@ abstract class BaseController extends GetxController bool validateUrgentException(dynamic exception) { return exception is NoNetworkError || exception is BadCredentialsException - || exception is ConnectionError; + || exception is ConnectionError + || exception is RefreshTokenFailedException; } void handleErrorViewState(Object error, StackTrace stackTrace) {} @@ -177,6 +178,8 @@ abstract class BaseController extends GetxController _handleConnectionErrorException(); } else if (exception is BadCredentialsException) { handleBadCredentialsException(); + } else if (exception is RefreshTokenFailedException) { + handleRefreshTokenFailedException(); } } @@ -188,6 +191,8 @@ abstract class BaseController extends GetxController _handleConnectionErrorException(); } else if (exception is BadCredentialsException) { handleBadCredentialsException(); + } else if (exception is RefreshTokenFailedException) { + handleRefreshTokenFailedException(); } } @@ -242,6 +247,15 @@ abstract class BaseController extends GetxController clearDataAndGoToLoginPage(); } + void handleRefreshTokenFailedException() { + log('$runtimeType::handleRefreshTokenFailedException:'); + if (twakeAppManager.hasComposer) { + _performSaveAndReconnection(); + } else { + _performReconnection(); + } + } + void onDataFailureViewState(Failure failure) { log('$runtimeType::onDataFailureViewState:failure = ${failure.runtimeType}'); if (failure is FeatureFailure) { diff --git a/lib/features/login/data/network/interceptors/authorization_interceptors.dart b/lib/features/login/data/network/interceptors/authorization_interceptors.dart index 28c6ee913..03fc2e102 100644 --- a/lib/features/login/data/network/interceptors/authorization_interceptors.dart +++ b/lib/features/login/data/network/interceptors/authorization_interceptors.dart @@ -17,6 +17,7 @@ import 'package:tmail_ui_user/features/login/data/network/authentication_client/ import 'package:tmail_ui_user/features/login/domain/exceptions/oauth_authorization_error.dart'; import 'package:tmail_ui_user/features/login/domain/extensions/oidc_configuration_extensions.dart'; import 'package:tmail_ui_user/features/upload/data/network/file_uploader.dart'; +import 'package:tmail_ui_user/main/exceptions/remote_exception.dart'; import 'package:tmail_ui_user/main/utils/ios_sharing_manager.dart'; class AuthorizationInterceptors extends QueuedInterceptorsWrapper { @@ -93,24 +94,47 @@ class AuthorizationInterceptors extends QueuedInterceptorsWrapper { responseStatusCode: err.response?.statusCode, tokenOIDC: _token )) { - log('AuthorizationInterceptors::onError: Perform get New Token'); - final newTokenOidc = PlatformInfo.isIOS - ? await _getNewTokenForIOSPlatform() - : await _getNewTokenForOtherPlatform(); + try { + log('AuthorizationInterceptors::onError: Perform get New Token'); + final newTokenOidc = PlatformInfo.isIOS + ? await _getNewTokenForIOSPlatform() + : await _getNewTokenForOtherPlatform(); + + if (newTokenOidc.token == _token?.token) { + log('AuthorizationInterceptors::onError: Token duplicated'); + return super.onError(err, handler); + } + _updateNewToken(newTokenOidc); + + final personalAccount = await _updateCurrentAccount(tokenOIDC: newTokenOidc); + + if (PlatformInfo.isIOS) { + await _iosSharingManager.saveKeyChainSharingSession(personalAccount); + } + + isRetryRequest = true; + } on DioException catch (refreshError, st) { + if (refreshError.response?.statusCode == 400) { + logError( + 'AuthorizationInterceptors: Refresh Token Failed 400', + exception: refreshError, + stackTrace: st, + ); + + clear(); + + final sessionExpiredError = DioException( + requestOptions: err.requestOptions, + error: RefreshTokenFailedException(), + type: DioExceptionType.badResponse, + response: refreshError.response, + ); + + return handler.reject(sessionExpiredError); + } - if (newTokenOidc.token == _token?.token) { - log('AuthorizationInterceptors::onError: Token duplicated'); return super.onError(err, handler); } - _updateNewToken(newTokenOidc); - - final personalAccount = await _updateCurrentAccount(tokenOIDC: newTokenOidc); - - if (PlatformInfo.isIOS) { - await _iosSharingManager.saveKeyChainSharingSession(personalAccount); - } - - isRetryRequest = true; } else if (validateToRetryTheRequestWithNewToken( authHeader: requestOptions.headers[HttpHeaders.authorizationHeader], tokenOIDC: _token diff --git a/lib/main/exceptions/remote_exception.dart b/lib/main/exceptions/remote_exception.dart index abd77dd09..e4da7cf65 100644 --- a/lib/main/exceptions/remote_exception.dart +++ b/lib/main/exceptions/remote_exception.dart @@ -62,4 +62,18 @@ class CannotCalculateChangesMethodResponseException extends MethodLevelErrors { class NoNetworkError extends RemoteException { const NoNetworkError() : super(message: RemoteException.noNetworkError); +} + +class RefreshTokenFailedException extends RemoteException { + final int? statusCode; + + RefreshTokenFailedException({ + super.message = + 'Refresh Token failed with 400 Bad Request. The session is invalid/revoked.', + this.statusCode = 400, + }); + + @override + String toString() => + "RefreshTokenFailedException(status: $statusCode): $message"; } \ No newline at end of file diff --git a/lib/main/exceptions/remote_exception_thrower.dart b/lib/main/exceptions/remote_exception_thrower.dart index 57de91129..f5d9e46c9 100644 --- a/lib/main/exceptions/remote_exception_thrower.dart +++ b/lib/main/exceptions/remote_exception_thrower.dart @@ -37,6 +37,10 @@ class RemoteExceptionThrower extends ExceptionThrower { 'RemoteExceptionThrower::throwException():type: ${error.type} | response: ${error.response} | error: ${error.error}', ); + if (error.error is RefreshTokenFailedException) { + throw RefreshTokenFailedException(); + } + final response = error.response; final statusCode = response?.statusCode;