TF-2280 Fix mark as read mailbox when token expired on mobile

Signed-off-by: dab246 <tdvu@linagora.com>
(cherry picked from commit caab5a896875168f2d98c249207eb3eb537c58ec)
This commit is contained in:
dab246
2023-10-27 20:46:17 +07:00
committed by Dat H. Pham
parent e956250e04
commit ad5dff4ac8
17 changed files with 237 additions and 82 deletions
@@ -4,7 +4,6 @@ import 'dart:typed_data';
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/platform_info.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:path_provider/path_provider.dart' as path_provider;
import 'package:tmail_ui_user/features/caching/caching_manager.dart';
@@ -30,6 +29,8 @@ import 'package:tmail_ui_user/features/push_notification/data/model/fcm_subscrip
import 'package:tmail_ui_user/features/session/data/model/session_hive_obj.dart';
import 'package:tmail_ui_user/features/thread/data/model/email_address_hive_cache.dart';
import 'package:tmail_ui_user/features/thread/data/model/email_cache.dart';
import 'package:tmail_ui_user/main/bindings/network/binding_tag.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
class HiveCacheConfig {
@@ -59,7 +60,11 @@ class HiveCacheConfig {
}
static Future<void> initializeEncryptionKey() async {
final encryptionKeyCacheManager = Get.find<EncryptionKeyCacheManager>();
final encryptionKeyCacheManager = getBinding<EncryptionKeyCacheManager>() ?? getBinding<EncryptionKeyCacheManager>(tag: BindingTag.isolateTag);
if (encryptionKeyCacheManager == null) {
log('HiveCacheConfig::_initializeEncryptionKey(): encryptionKeyCacheManager not found');
return;
}
final encryptionKeyCache = await encryptionKeyCacheManager.getEncryptionKeyStored();
if (encryptionKeyCache == null) {
final secureKey = Hive.generateSecureKey();
@@ -70,7 +75,11 @@ class HiveCacheConfig {
}
static Future<Uint8List?> getEncryptionKey() async {
final encryptionKeyCacheManager = Get.find<EncryptionKeyCacheManager>();
final encryptionKeyCacheManager = getBinding<EncryptionKeyCacheManager>() ?? getBinding<EncryptionKeyCacheManager>(tag: BindingTag.isolateTag);
if (encryptionKeyCacheManager == null) {
log('HiveCacheConfig::getEncryptionKey(): encryptionKeyCacheManager not found');
return null;
}
var encryptionKeyCache = await encryptionKeyCacheManager.getEncryptionKeyStored();
if (encryptionKeyCache != null) {
@@ -30,5 +30,5 @@ abstract class AuthenticationClientBase {
Future<bool> logoutOidc(TokenId tokenId, OIDCConfiguration config, OIDCDiscoveryResponse oidcRescovery);
factory AuthenticationClientBase() => getAuthenticationClientImplementation();
factory AuthenticationClientBase({String? tag}) => getAuthenticationClientImplementation(tag: tag);
}
@@ -98,5 +98,5 @@ class AuthenticationClientMobile implements AuthenticationClientBase {
}
}
AuthenticationClientBase getAuthenticationClientImplementation() =>
AuthenticationClientMobile(Get.find<FlutterAppAuth>());
AuthenticationClientBase getAuthenticationClientImplementation({String? tag}) =>
AuthenticationClientMobile(Get.find<FlutterAppAuth>(tag: tag));
@@ -108,5 +108,5 @@ class AuthenticationClientWeb implements AuthenticationClientBase {
}
}
AuthenticationClientBase getAuthenticationClientImplementation() =>
AuthenticationClientWeb(Get.find<AppAuthWebPlugin>());
AuthenticationClientBase getAuthenticationClientImplementation({String? tag}) =>
AuthenticationClientWeb(Get.find<AppAuthWebPlugin>(tag: tag));
@@ -80,79 +80,90 @@ class AuthorizationInterceptors extends QueuedInterceptorsWrapper {
@override
void onError(DioError err, ErrorInterceptorHandler handler) async {
logError('AuthorizationInterceptors::onError(): $err');
try {
final requestOptions = err.requestOptions;
final extraInRequest = requestOptions.extra;
var retries = extraInRequest[RETRY_KEY] ?? 0;
final requestOptions = err.requestOptions;
final extraInRequest = requestOptions.extra;
var retries = extraInRequest[RETRY_KEY] ?? 0;
if (_validateToRefreshToken(err)) {
log('AuthorizationInterceptors::onError:>> _validateToRefreshToken');
final newToken = await _authenticationClient.refreshingTokensOIDC(
_configOIDC!.clientId,
_configOIDC!.redirectUrl,
_configOIDC!.discoveryUrl,
_configOIDC!.scopes,
_token!.refreshToken
);
if (_validateToRefreshToken(err)) {
log('AuthorizationInterceptors::onError:>> _validateToRefreshToken');
final newToken = await _authenticationClient.refreshingTokensOIDC(
_configOIDC!.clientId,
_configOIDC!.redirectUrl,
_configOIDC!.discoveryUrl,
_configOIDC!.scopes,
_token!.refreshToken
);
final currentAccount = await _accountCacheManager.getCurrentAccount();
final currentAccount = await _accountCacheManager.getCurrentAccount();
await _accountCacheManager.deleteCurrentAccount(currentAccount.id);
await _accountCacheManager.deleteCurrentAccount(currentAccount.id);
await Future.wait([
_tokenOidcCacheManager.persistOneTokenOidc(newToken),
_accountCacheManager.setCurrentAccount(
PersonalAccount(
newToken.tokenIdHash,
AuthenticationType.oidc,
isSelected: true,
accountId: currentAccount.accountId,
apiUrl: currentAccount.apiUrl,
userName: currentAccount.userName
await Future.wait([
_tokenOidcCacheManager.persistOneTokenOidc(newToken),
_accountCacheManager.setCurrentAccount(
PersonalAccount(
newToken.tokenIdHash,
AuthenticationType.oidc,
isSelected: true,
accountId: currentAccount.accountId,
apiUrl: currentAccount.apiUrl,
userName: currentAccount.userName
)
)
)
]);
_updateNewToken(newToken.toToken());
]);
_updateNewToken(newToken.toToken());
if (extraInRequest.containsKey(FileUploader.uploadAttachmentExtraKey)) {
final uploadExtra = extraInRequest[FileUploader.uploadAttachmentExtraKey];
if (extraInRequest.containsKey(FileUploader.uploadAttachmentExtraKey)) {
final uploadExtra = extraInRequest[FileUploader.uploadAttachmentExtraKey];
requestOptions.headers[HttpHeaders.authorizationHeader] = _getTokenAsBearerHeader(newToken.token);
requestOptions.headers[HttpHeaders.contentTypeHeader] = uploadExtra[FileUploader.typeExtraKey];
requestOptions.headers[HttpHeaders.contentLengthHeader] = uploadExtra[FileUploader.sizeExtraKey];
requestOptions.headers[HttpHeaders.authorizationHeader] = _getTokenAsBearerHeader(newToken.token);
requestOptions.headers[HttpHeaders.contentTypeHeader] = uploadExtra[FileUploader.typeExtraKey];
requestOptions.headers[HttpHeaders.contentLengthHeader] = uploadExtra[FileUploader.sizeExtraKey];
final newOptions = Options(
method: requestOptions.method,
headers: requestOptions.headers,
);
final newOptions = Options(
method: requestOptions.method,
headers: requestOptions.headers,
);
final response = await _dio.request(
requestOptions.path,
data: uploadExtra[FileUploader.platformExtraKey] == 'web'
? BodyBytesStream.fromBytes(uploadExtra[FileUploader.bytesExtraKey])
: File(uploadExtra[FileUploader.filePathExtraKey]).openRead(),
queryParameters: requestOptions.queryParameters,
options: newOptions,
);
final response = await _dio.request(
requestOptions.path,
data: _getDataUploadRequest(uploadExtra),
queryParameters: requestOptions.queryParameters,
options: newOptions,
);
return handler.resolve(response);
} else {
requestOptions.headers[HttpHeaders.authorizationHeader] = _getTokenAsBearerHeader(newToken.token);
return handler.resolve(response);
} else {
requestOptions.headers[HttpHeaders.authorizationHeader] = _getTokenAsBearerHeader(newToken.token);
final response = await _dio.fetch(requestOptions);
return handler.resolve(response);
}
} else if (_validateToRetry(err, retries)) {
log('AuthorizationInterceptors::onError:>> _validateToRetry | retries: $retries');
retries++;
requestOptions.headers[HttpHeaders.authorizationHeader] = _getTokenAsBearerHeader(_token!.token);
requestOptions.extra = {RETRY_KEY: retries};
final response = await _dio.fetch(requestOptions);
return handler.resolve(response);
} else {
super.onError(err, handler);
}
} else if (_validateToRetry(err, retries)) {
log('AuthorizationInterceptors::onError:>> _validateToRetry | retries: $retries');
retries++;
} catch (e) {
logError('AuthorizationInterceptors::onError:Exception: $e');
super.onError(err.copyWith(error: e), handler);
}
}
requestOptions.headers[HttpHeaders.authorizationHeader] = _getTokenAsBearerHeader(_token!.token);
requestOptions.extra = {RETRY_KEY: retries};
final response = await _dio.fetch(requestOptions);
return handler.resolve(response);
Stream<List<int>>? _getDataUploadRequest(dynamic mapUploadExtra) {
final currentPlatform = mapUploadExtra[FileUploader.platformExtraKey];
if (currentPlatform == 'web') {
return BodyBytesStream.fromBytes(mapUploadExtra[FileUploader.bytesExtraKey]);
} else {
super.onError(err, handler);
return File(mapUploadExtra[FileUploader.filePathExtraKey]).openRead();
}
}
@@ -1,5 +1,6 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/services.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/mail/mailbox/mailbox.dart';
@@ -13,13 +14,16 @@ class MailboxMarkAsReadArguments with EquatableMixin {
final MailboxId mailboxId;
final ThreadAPI threadAPI;
final EmailAPI emailAPI;
final RootIsolateToken isolateToken;
MailboxMarkAsReadArguments(
this.session,
this.threadAPI,
this.emailAPI,
this.accountId,
this.mailboxId);
this.mailboxId,
this.isolateToken,
);
@override
List<Object?> get props => [
@@ -27,6 +31,7 @@ class MailboxMarkAsReadArguments with EquatableMixin {
accountId,
threadAPI,
emailAPI,
mailboxId
mailboxId,
isolateToken,
];
}
@@ -5,6 +5,7 @@ import 'package:core/presentation/state/success.dart';
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/platform_info.dart';
import 'package:dartz/dartz.dart';
import 'package:flutter/services.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/properties/properties.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart';
@@ -19,11 +20,13 @@ import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'package:model/email/email_property.dart';
import 'package:model/email/read_actions.dart';
import 'package:tmail_ui_user/features/caching/config/hive_cache_config.dart';
import 'package:tmail_ui_user/features/email/data/network/email_api.dart';
import 'package:tmail_ui_user/features/mailbox/data/model/mailbox_mark_as_read_arguments.dart';
import 'package:tmail_ui_user/features/mailbox/domain/state/mark_as_mailbox_read_state.dart';
import 'package:tmail_ui_user/features/thread/data/network/thread_api.dart';
import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart';
import 'package:tmail_ui_user/main/exceptions/isolate_exception.dart';
import 'package:worker_manager/worker_manager.dart';
class MailboxIsolateWorker {
@@ -49,13 +52,20 @@ class MailboxIsolateWorker {
totalEmailUnread,
onProgressController);
} else {
final rootIsolateToken = RootIsolateToken.instance;
if (rootIsolateToken == null) {
throw CanNotGetRootIsolateToken();
}
final result = await _isolateExecutor.execute(
arg1: MailboxMarkAsReadArguments(
session,
_threadApi,
_emailApi,
accountId,
mailboxId),
mailboxId,
rootIsolateToken
),
fun1: _handleMarkAsMailboxReadAction,
notification: (value) {
if (value is List<Email>) {
@@ -74,6 +84,10 @@ class MailboxIsolateWorker {
MailboxMarkAsReadArguments args,
TypeSendPort sendPort
) async {
final rootIsolateToken = args.isolateToken;
BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
await HiveCacheConfig().setUp();
List<Email> emailListCompleted = List.empty(growable: true);
try {
bool mailboxHasEmails = true;
@@ -61,9 +61,21 @@ class MarkAsMailboxReadHasSomeEmailFailure extends UIActionState {
];
}
class MarkAsMailboxReadAllFailure extends FeatureFailure {}
class MarkAsMailboxReadAllFailure extends FeatureFailure {
final String mailboxDisplayName;
MarkAsMailboxReadAllFailure({required this.mailboxDisplayName});
@override
List<Object?> get props => [mailboxDisplayName];
}
class MarkAsMailboxReadFailure extends FeatureFailure {
MarkAsMailboxReadFailure(dynamic exception) : super(exception: exception);
final String mailboxDisplayName;
MarkAsMailboxReadFailure({
required this.mailboxDisplayName,
dynamic exception
}) : super(exception: exception);
}
@@ -54,10 +54,13 @@ class MarkAsMailboxReadInteractor {
currentEmailState: currentEmailState,
currentMailboxState: currentMailboxState));
} else {
yield Left(MarkAsMailboxReadAllFailure());
yield Left(MarkAsMailboxReadAllFailure(mailboxDisplayName: mailboxDisplayName));
}
} catch (e) {
yield Left(MarkAsMailboxReadFailure(e));
yield Left(MarkAsMailboxReadFailure(
mailboxDisplayName: mailboxDisplayName,
exception: e
));
}
}
}
@@ -364,8 +364,9 @@ class MailboxDashBoardController extends ReloadableController {
_handleUpdateEmailAsDraftsFailure(failure);
} else if (failure is RemoveEmailDraftsFailure) {
clearState();
} else if (failure is MarkAsMailboxReadAllFailure ||
failure is MarkAsMailboxReadFailure) {
} else if (failure is MarkAsMailboxReadAllFailure) {
_markAsReadMailboxAllFailure(failure);
} else if (failure is MarkAsMailboxReadFailure) {
_markAsReadMailboxFailure(failure);
} else if (failure is GetEmailByIdFailure) {
_handleGetEmailDetailedFailed(failure);
@@ -1368,8 +1369,27 @@ class MailboxDashBoardController extends ReloadableController {
}
}
void _markAsReadMailboxFailure(Failure failure) {
void _markAsReadMailboxFailure(MarkAsMailboxReadFailure failure) {
viewStateMarkAsReadMailbox.value = Right(UIState.idle);
if (currentOverlayContext != null && currentContext != null) {
_appToast.showToastErrorMessage(
currentOverlayContext!,
AppLocalizations.of(currentContext!).toastMessageMarkAsReadFolderFailureWithReason(
failure.mailboxDisplayName,
failure.exception.toString()
)
);
}
}
void _markAsReadMailboxAllFailure(MarkAsMailboxReadAllFailure failure) {
viewStateMarkAsReadMailbox.value = Right(UIState.idle);
if (currentOverlayContext != null && currentContext != null) {
_appToast.showToastErrorMessage(
currentOverlayContext!,
AppLocalizations.of(currentContext!).toastMessageMarkAsReadFolderAllFailure(failure.mailboxDisplayName)
);
}
}
void goToComposer(ComposerArguments arguments) async {