TF-2177 Fix download attachment not working on web

Signed-off-by: dab246 <tdvu@linagora.com>
(cherry picked from commit 09cbfe5245c206670aa2191c151fd8f2ea48df3a)
This commit is contained in:
dab246
2023-10-23 23:19:12 +07:00
committed by Dat H. Pham
parent 73a8919f18
commit 424ab675d5
17 changed files with 134 additions and 113 deletions
@@ -55,9 +55,11 @@ class DownloadAttachmentForWebSuccess extends UIState {
class DownloadAttachmentForWebFailure extends FeatureFailure { class DownloadAttachmentForWebFailure extends FeatureFailure {
final DownloadTaskId taskId; final DownloadTaskId? taskId;
DownloadAttachmentForWebFailure(this.taskId, dynamic exception) : super(exception: exception); DownloadAttachmentForWebFailure({
this.taskId, dynamic exception
}) : super(exception: exception);
@override @override
List<Object?> get props => [taskId, ...super.props]; List<Object?> get props => [taskId, ...super.props];
@@ -45,39 +45,38 @@ class DownloadAttachmentForWebInteractor {
if (currentAccount.authenticationType == AuthenticationType.oidc) { if (currentAccount.authenticationType == AuthenticationType.oidc) {
final tokenOidc = await _authenticationOIDCRepository.getStoredTokenOIDC(currentAccount.id); final tokenOidc = await _authenticationOIDCRepository.getStoredTokenOIDC(currentAccount.id);
accountRequest = AccountRequest( accountRequest = AccountRequest.withOidc(token: tokenOidc.toToken());
token: tokenOidc.toToken(),
authenticationType: AuthenticationType.oidc);
} else { } else {
final authenticationInfoCache = await credentialRepository.getAuthenticationInfoStored(); final authenticationInfoCache = await credentialRepository.getAuthenticationInfoStored();
if (authenticationInfoCache != null) { accountRequest = AccountRequest.withBasic(
accountRequest = AccountRequest( userName: UserName(authenticationInfoCache.username),
userName: UserName(authenticationInfoCache.username), password: Password(authenticationInfoCache.password),
password: Password(authenticationInfoCache.password), );
authenticationType: AuthenticationType.basic);
}
} }
if (accountRequest != null) { final bytesDownloaded = await emailRepository.downloadAttachmentForWeb(
final bytesDownloaded = await emailRepository.downloadAttachmentForWeb( taskId,
taskId, attachment,
attachment, accountId,
accountId, baseDownloadUrl,
baseDownloadUrl, accountRequest,
accountRequest, onReceiveController
onReceiveController); );
yield Right<Failure, Success>(DownloadAttachmentForWebSuccess( yield Right<Failure, Success>(
taskId, DownloadAttachmentForWebSuccess(
attachment,
bytesDownloaded));
} else {
yield Left<Failure, Success>(DownloadAttachmentForWebFailure(taskId, null));
}
} catch (exception) {
yield Left<Failure, Success>(DownloadAttachmentForWebFailure(
taskId, taskId,
exception)); attachment,
bytesDownloaded
)
);
} catch (exception) {
yield Left<Failure, Success>(
DownloadAttachmentForWebFailure(
taskId: taskId,
exception: exception
)
);
} }
} }
} }
@@ -46,32 +46,25 @@ class DownloadAttachmentsInteractor {
if (currentAccount.authenticationType == AuthenticationType.oidc) { if (currentAccount.authenticationType == AuthenticationType.oidc) {
final tokenOidc = await _authenticationOIDCRepository.getStoredTokenOIDC(currentAccount.id); final tokenOidc = await _authenticationOIDCRepository.getStoredTokenOIDC(currentAccount.id);
accountRequest = AccountRequest( accountRequest = AccountRequest.withOidc(token: tokenOidc.toToken());
token: tokenOidc.toToken(),
authenticationType: AuthenticationType.oidc);
} else { } else {
final authenticationInfoCache = await credentialRepository.getAuthenticationInfoStored(); final authenticationInfoCache = await credentialRepository.getAuthenticationInfoStored();
if (authenticationInfoCache != null) { accountRequest = AccountRequest.withBasic(
accountRequest = AccountRequest( userName: UserName(authenticationInfoCache.username),
userName: UserName(authenticationInfoCache.username), password: Password(authenticationInfoCache.password),
password: Password(authenticationInfoCache.password), );
authenticationType: AuthenticationType.basic);
}
} }
if (accountRequest != null) { final taskIds = await emailRepository.downloadAttachments(
final taskIds = await emailRepository.downloadAttachments( attachments,
attachments, accountId,
accountId, baseDownloadUrl,
baseDownloadUrl, accountRequest
accountRequest); );
yield Right<Failure, Success>(DownloadAttachmentsSuccess(taskIds)); yield Right<Failure, Success>(DownloadAttachmentsSuccess(taskIds));
} else {
yield Left<Failure, Success>(DownloadAttachmentsFailure(null));
}
} catch (exception) { } catch (exception) {
log('DownloadAttachmentsInteractor::execute(): $exception'); logError('DownloadAttachmentsInteractor::execute(): $exception');
if (exception is DownloadAttachmentHasTokenExpiredException && if (exception is DownloadAttachmentHasTokenExpiredException &&
exception.refreshToken.isNotEmpty) { exception.refreshToken.isNotEmpty) {
yield* _retryDownloadAttachments( yield* _retryDownloadAttachments(
@@ -101,9 +94,10 @@ class DownloadAttachmentsInteractor {
oidcConfig.scopes, oidcConfig.scopes,
refreshToken); refreshToken);
await _accountRepository.deleteCurrentAccount(accountCurrent.id);
await Future.wait([ await Future.wait([
_authenticationOIDCRepository.persistTokenOIDC(newTokenOIDC), _authenticationOIDCRepository.persistTokenOIDC(newTokenOIDC),
_accountRepository.deleteCurrentAccount(accountCurrent.id),
_accountRepository.setCurrentAccount(PersonalAccount( _accountRepository.setCurrentAccount(PersonalAccount(
newTokenOIDC.tokenIdHash, newTokenOIDC.tokenIdHash,
AuthenticationType.oidc, AuthenticationType.oidc,
@@ -117,9 +111,7 @@ class DownloadAttachmentsInteractor {
newToken: newTokenOIDC.toToken(), newToken: newTokenOIDC.toToken(),
newConfig: oidcConfig); newConfig: oidcConfig);
final accountRequest = AccountRequest( final accountRequest = AccountRequest.withOidc(token: newTokenOIDC.toToken());
token: newTokenOIDC.toToken(),
authenticationType: AuthenticationType.oidc);
final taskIds = await emailRepository.downloadAttachments( final taskIds = await emailRepository.downloadAttachments(
attachments, attachments,
@@ -38,31 +38,24 @@ class ExportAttachmentInteractor {
if (currentAccount.authenticationType == AuthenticationType.oidc) { if (currentAccount.authenticationType == AuthenticationType.oidc) {
final tokenOidc = await _authenticationOIDCRepository.getStoredTokenOIDC(currentAccount.id); final tokenOidc = await _authenticationOIDCRepository.getStoredTokenOIDC(currentAccount.id);
accountRequest = AccountRequest( accountRequest = AccountRequest.withOidc(token: tokenOidc.toToken());
token: tokenOidc.toToken(),
authenticationType: AuthenticationType.oidc);
} else { } else {
final authenticationInfoCache = await credentialRepository.getAuthenticationInfoStored(); final authenticationInfoCache = await credentialRepository.getAuthenticationInfoStored();
if (authenticationInfoCache != null) { accountRequest = AccountRequest.withBasic(
accountRequest = AccountRequest( userName: UserName(authenticationInfoCache.username),
userName: UserName(authenticationInfoCache.username), password: Password(authenticationInfoCache.password),
password: Password(authenticationInfoCache.password), );
authenticationType: AuthenticationType.basic);
}
} }
if (accountRequest != null) { final downloadedResponse = await emailRepository.exportAttachment(
final downloadedResponse = await emailRepository.exportAttachment( attachment,
attachment, accountId,
accountId, baseDownloadUrl,
baseDownloadUrl, accountRequest,
accountRequest, cancelToken
cancelToken); );
yield Right<Failure, Success>(ExportAttachmentSuccess(downloadedResponse)); yield Right<Failure, Success>(ExportAttachmentSuccess(downloadedResponse));
} else {
yield Left<Failure, Success>(ExportAttachmentFailure(null));
}
} catch (exception) { } catch (exception) {
log('ExportAttachmentInteractor::execute(): exception: $exception'); log('ExportAttachmentInteractor::execute(): exception: $exception');
yield Left<Failure, Success>(ExportAttachmentFailure(exception)); yield Left<Failure, Success>(ExportAttachmentFailure(exception));
@@ -74,6 +74,7 @@ import 'package:tmail_ui_user/features/manage_account/domain/usecases/create_new
import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_identities_interactor.dart'; import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_identities_interactor.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/extensions/datetime_extension.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/extensions/datetime_extension.dart';
import 'package:tmail_ui_user/features/rules_filter_creator/presentation/model/rules_filter_creator_arguments.dart'; import 'package:tmail_ui_user/features/rules_filter_creator/presentation/model/rules_filter_creator_arguments.dart';
import 'package:tmail_ui_user/features/session/data/exceptions/session_exceptions.dart';
import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart'; import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart';
import 'package:tmail_ui_user/features/thread/presentation/model/delete_action_type.dart'; import 'package:tmail_ui_user/features/thread/presentation/model/delete_action_type.dart';
import 'package:tmail_ui_user/main/error/capability_validator.dart'; import 'package:tmail_ui_user/main/error/capability_validator.dart';
@@ -698,8 +699,9 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
void _downloadAttachmentForWebAction(BuildContext context, Attachment attachment) async { void _downloadAttachmentForWebAction(BuildContext context, Attachment attachment) async {
final accountId = mailboxDashBoardController.accountId.value; final accountId = mailboxDashBoardController.accountId.value;
if (accountId != null && mailboxDashBoardController.sessionCurrent != null) { final session = mailboxDashBoardController.sessionCurrent;
final baseDownloadUrl = mailboxDashBoardController.sessionCurrent!.getDownloadUrl(jmapUrl: _dynamicUrlInterceptors.jmapUrl); if (accountId != null && session != null) {
final baseDownloadUrl = session.getDownloadUrl(jmapUrl: _dynamicUrlInterceptors.jmapUrl);
final generateTaskId = DownloadTaskId(_uuid.v4()); final generateTaskId = DownloadTaskId(_uuid.v4());
consumeState(_downloadAttachmentForWebInteractor.execute( consumeState(_downloadAttachmentForWebInteractor.execute(
generateTaskId, generateTaskId,
@@ -707,6 +709,10 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
accountId, accountId,
baseDownloadUrl, baseDownloadUrl,
_downloadProgressStateController)); _downloadProgressStateController));
} else {
consumeState(Stream.value(
Left(DownloadAttachmentForWebFailure(exception: NotFoundSessionException()))
));
} }
} }
@@ -721,7 +727,9 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
void _downloadAttachmentForWebFailureAction(DownloadAttachmentForWebFailure failure) { void _downloadAttachmentForWebFailureAction(DownloadAttachmentForWebFailure failure) {
log('SingleEmailController::_downloadAttachmentForWebFailureAction(): $failure'); log('SingleEmailController::_downloadAttachmentForWebFailureAction(): $failure');
mailboxDashBoardController.deleteDownloadTask(failure.taskId); if (failure.taskId != null) {
mailboxDashBoardController.deleteDownloadTask(failure.taskId!);
}
if (currentOverlayContext != null && currentContext != null) { if (currentOverlayContext != null && currentContext != null) {
_appToast.showToastErrorMessage( _appToast.showToastErrorMessage(
@@ -13,21 +13,21 @@ class HiveAccountDatasourceImpl extends AccountDatasource {
@override @override
Future<PersonalAccount> getCurrentAccount() { Future<PersonalAccount> getCurrentAccount() {
return Future.sync(() async { return Future.sync(() async {
return await _accountCacheManager.getSelectedAccount(); return await _accountCacheManager.getCurrentAccount();
}).catchError(_exceptionThrower.throwException); }).catchError(_exceptionThrower.throwException);
} }
@override @override
Future<void> setCurrentAccount(PersonalAccount newCurrentAccount) { Future<void> setCurrentAccount(PersonalAccount newCurrentAccount) {
return Future.sync(() async { return Future.sync(() async {
return await _accountCacheManager.setSelectedAccount(newCurrentAccount); return await _accountCacheManager.setCurrentAccount(newCurrentAccount);
}).catchError(_exceptionThrower.throwException); }).catchError(_exceptionThrower.throwException);
} }
@override @override
Future<void> deleteCurrentAccount(String accountId) { Future<void> deleteCurrentAccount(String accountId) {
return Future.sync(() async { return Future.sync(() async {
return await _accountCacheManager.deleteSelectedAccount(accountId); return await _accountCacheManager.deleteCurrentAccount(accountId);
}).catchError(_exceptionThrower.throwException); }).catchError(_exceptionThrower.throwException);
} }
} }
@@ -1,3 +1,4 @@
import 'package:collection/collection.dart';
import 'package:core/utils/app_logger.dart'; import 'package:core/utils/app_logger.dart';
import 'package:model/account/personal_account.dart'; import 'package:model/account/personal_account.dart';
import 'package:tmail_ui_user/features/caching/clients/account_cache_client.dart'; import 'package:tmail_ui_user/features/caching/clients/account_cache_client.dart';
@@ -10,24 +11,29 @@ class AccountCacheManager {
AccountCacheManager(this._accountCacheClient); AccountCacheManager(this._accountCacheClient);
Future<PersonalAccount> getSelectedAccount() async { Future<PersonalAccount> getCurrentAccount() async {
try { final allAccounts = await _accountCacheClient.getAll();
final allAccounts = await _accountCacheClient.getAll(); log('AccountCacheManager::getCurrentAccount::allAccounts(): $allAccounts');
return allAccounts.firstWhere((account) => account.isSelected) final accountCache = allAccounts.firstWhereOrNull((account) => account.isSelected);
.toAccount(); log('AccountCacheManager::getCurrentAccount::accountCache(): $accountCache');
} catch (e) { if (accountCache != null) {
logError('AccountCacheManager::getSelectedAccount(): $e'); return accountCache.toAccount();
} else {
throw NotFoundAuthenticatedAccountException(); throw NotFoundAuthenticatedAccountException();
} }
} }
Future<void> setSelectedAccount(PersonalAccount account) { Future<void> setCurrentAccount(PersonalAccount account) async {
log('AccountCacheManager::setSelectedAccount(): $_accountCacheClient'); log('AccountCacheManager::setCurrentAccount(): $account');
final accountCacheExist = await _accountCacheClient.isExistTable();
if (accountCacheExist) {
await _accountCacheClient.clearAllData();
}
return _accountCacheClient.insertItem(account.id, account.toCache()); return _accountCacheClient.insertItem(account.id, account.toCache());
} }
Future<void> deleteSelectedAccount(String accountId) { Future<void> deleteCurrentAccount(String hashId) {
log('AccountCacheManager::deleteSelectedAccount(): $accountId'); log('AccountCacheManager::deleteCurrentAccount(): $hashId');
return _accountCacheClient.deleteItem(accountId); return _accountCacheClient.deleteItem(hashId);
} }
} }
@@ -1,5 +1,6 @@
import 'package:tmail_ui_user/features/caching/clients/authentication_info_cache_client.dart'; import 'package:tmail_ui_user/features/caching/clients/authentication_info_cache_client.dart';
import 'package:tmail_ui_user/features/login/data/model/authentication_info_cache.dart'; import 'package:tmail_ui_user/features/login/data/model/authentication_info_cache.dart';
import 'package:tmail_ui_user/features/login/domain/exceptions/authentication_exception.dart';
class AuthenticationInfoCacheManager { class AuthenticationInfoCacheManager {
final AuthenticationInfoCacheClient _authenticationInfoCacheClient; final AuthenticationInfoCacheClient _authenticationInfoCacheClient;
@@ -12,8 +13,13 @@ class AuthenticationInfoCacheManager {
authenticationInfoCache); authenticationInfoCache);
} }
Future<AuthenticationInfoCache?> getAuthenticationInfoStored() { Future<AuthenticationInfoCache> getAuthenticationInfoStored() async {
return _authenticationInfoCacheClient.getItem(AuthenticationInfoCache.keyCacheValue); final authenticationInfoCache = await _authenticationInfoCacheClient.getItem(AuthenticationInfoCache.keyCacheValue);
if (authenticationInfoCache != null) {
return authenticationInfoCache;
} else {
throw NotFoundAuthenticationInfoCache();
}
} }
Future<void> removeAuthenticationInfo() { Future<void> removeAuthenticationInfo() {
@@ -56,8 +56,7 @@ class AuthorizationInterceptors extends QueuedInterceptorsWrapper {
@override @override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) { void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
log('AuthorizationInterceptors::onRequest():DATA: ${options.data}'); log('AuthorizationInterceptors::onRequest():data: ${options.data} | header: ${options.headers}');
log('AuthorizationInterceptors::onRequest():TOKEN_HASHCODE_CURRENT: ${_token?.token.hashCode}');
switch(_authenticationType) { switch(_authenticationType) {
case AuthenticationType.basic: case AuthenticationType.basic:
if (_authorization != null) { if (_authorization != null) {
@@ -78,8 +77,6 @@ class AuthorizationInterceptors extends QueuedInterceptorsWrapper {
@override @override
void onError(DioError err, ErrorInterceptorHandler handler) async { void onError(DioError err, ErrorInterceptorHandler handler) async {
logError('AuthorizationInterceptors::onError(): $err'); logError('AuthorizationInterceptors::onError(): $err');
logError('AuthorizationInterceptors::onError():TOKEN_HASHCODE_CURRENT: ${_token?.token.hashCode}');
final requestOptions = err.requestOptions; final requestOptions = err.requestOptions;
final extraInRequest = requestOptions.extra; final extraInRequest = requestOptions.extra;
var retries = extraInRequest[RETRY_KEY] ?? 0; var retries = extraInRequest[RETRY_KEY] ?? 0;
@@ -94,20 +91,20 @@ class AuthorizationInterceptors extends QueuedInterceptorsWrapper {
_token!.refreshToken _token!.refreshToken
); );
final accountCurrent = await _accountCacheManager.getSelectedAccount(); final currentAccount = await _accountCacheManager.getCurrentAccount();
await _accountCacheManager.deleteSelectedAccount(_token!.tokenIdHash); await _accountCacheManager.deleteCurrentAccount(currentAccount.id);
await Future.wait([ await Future.wait([
_tokenOidcCacheManager.persistOneTokenOidc(newToken), _tokenOidcCacheManager.persistOneTokenOidc(newToken),
_accountCacheManager.setSelectedAccount( _accountCacheManager.setCurrentAccount(
PersonalAccount( PersonalAccount(
newToken.tokenIdHash, newToken.tokenIdHash,
AuthenticationType.oidc, AuthenticationType.oidc,
isSelected: true, isSelected: true,
accountId: accountCurrent.accountId, accountId: currentAccount.accountId,
apiUrl: accountCurrent.apiUrl, apiUrl: currentAccount.apiUrl,
userName: accountCurrent.userName userName: currentAccount.userName
) )
) )
]); ]);
@@ -1,4 +1,3 @@
import 'package:core/utils/app_logger.dart';
import 'package:model/account/personal_account.dart'; import 'package:model/account/personal_account.dart';
import 'package:tmail_ui_user/features/login/data/datasource/account_datasource.dart'; import 'package:tmail_ui_user/features/login/data/datasource/account_datasource.dart';
import 'package:tmail_ui_user/features/login/domain/repository/account_repository.dart'; import 'package:tmail_ui_user/features/login/domain/repository/account_repository.dart';
@@ -16,12 +15,11 @@ class AccountRepositoryImpl extends AccountRepository {
@override @override
Future<void> setCurrentAccount(PersonalAccount newCurrentAccount) { Future<void> setCurrentAccount(PersonalAccount newCurrentAccount) {
log('AccountRepositoryImpl::setCurrentAccount(): $newCurrentAccount');
return _accountDatasource.setCurrentAccount(newCurrentAccount); return _accountDatasource.setCurrentAccount(newCurrentAccount);
} }
@override @override
Future<void> deleteCurrentAccount(String accountId) { Future<void> deleteCurrentAccount(String hashId) {
return _accountDatasource.deleteCurrentAccount(accountId); return _accountDatasource.deleteCurrentAccount(hashId);
} }
} }
@@ -36,7 +36,7 @@ class CredentialRepositoryImpl extends CredentialRepository {
} }
@override @override
Future<AuthenticationInfoCache?> getAuthenticationInfoStored() { Future<AuthenticationInfoCache> getAuthenticationInfoStored() {
return _authenticationInfoCacheManager.getAuthenticationInfoStored(); return _authenticationInfoCacheManager.getAuthenticationInfoStored();
} }
@@ -52,3 +52,5 @@ class CanNotFoundUserName implements Exception {}
class CanNotFoundPassword implements Exception {} class CanNotFoundPassword implements Exception {}
class CanNotAuthenticationInfoOnWeb implements Exception {} class CanNotAuthenticationInfoOnWeb implements Exception {}
class NotFoundAuthenticationInfoCache implements Exception {}
@@ -6,5 +6,5 @@ abstract class AccountRepository {
Future<void> setCurrentAccount(PersonalAccount newCurrentAccount); Future<void> setCurrentAccount(PersonalAccount newCurrentAccount);
Future<void> deleteCurrentAccount(String accountId); Future<void> deleteCurrentAccount(String hashId);
} }
@@ -9,7 +9,7 @@ abstract class CredentialRepository {
Future<void> storeAuthenticationInfo(AuthenticationInfoCache authenticationInfoCache); Future<void> storeAuthenticationInfo(AuthenticationInfoCache authenticationInfoCache);
Future<AuthenticationInfoCache?> getAuthenticationInfoStored(); Future<AuthenticationInfoCache> getAuthenticationInfoStored();
Future<void> removeAuthenticationInfo(); Future<void> removeAuthenticationInfo();
} }
@@ -33,7 +33,7 @@ class HiveSessionDataSourceImpl extends SessionDataSource {
if (sessionHiveObj != null) { if (sessionHiveObj != null) {
return sessionHiveObj.toSession(); return sessionHiveObj.toSession();
} else { } else {
throw NotFoundSessionHiveObject(); throw NotFoundSessionException();
} }
}).catchError(_exceptionThrower.throwException); }).catchError(_exceptionThrower.throwException);
} }
@@ -1,2 +1,2 @@
class NotFoundSessionHiveObject implements Exception {} class NotFoundSessionException implements Exception {}
+18
View File
@@ -19,6 +19,24 @@ class AccountRequest with EquatableMixin {
this.authenticationType = AuthenticationType.none, this.authenticationType = AuthenticationType.none,
}); });
factory AccountRequest.withOidc({required Token token}) {
return AccountRequest(
token: token,
authenticationType: AuthenticationType.oidc
);
}
factory AccountRequest.withBasic({
required UserName userName,
required Password password
}) {
return AccountRequest(
userName: userName,
password: password,
authenticationType: AuthenticationType.basic
);
}
String get basicAuth => 'Basic ${base64Encode(utf8.encode('${userName?.value}:${password?.value}'))}'; String get basicAuth => 'Basic ${base64Encode(utf8.encode('${userName?.value}:${password?.value}'))}';
String get bearerToken => 'Bearer ${token?.token}'; String get bearerToken => 'Bearer ${token?.token}';