TF-2245 Fix upload attachment when token expired on web

Signed-off-by: dab246 <tdvu@linagora.com>
(cherry picked from commit 0b2ab3c6ff4213bccc064822e640cc40592a39e8)
This commit is contained in:
dab246
2023-10-24 11:22:38 +07:00
committed by Dat H. Pham
parent ebd9663a5c
commit 14047c9ccc
9 changed files with 89 additions and 41 deletions
@@ -40,15 +40,18 @@ class DownloadClient {
String fileExtension, String fileExtension,
String fileName, String fileName,
{ {
Uint8List? bytesData, String? filePath,
double? maxWidth, double? maxWidth,
bool? compress, bool? compress,
} }
) async { ) async {
try { try {
if (bytesData == null) { Uint8List? bytesData;
if (filePath == null || filePath.isEmpty) {
log('DownloadClient::downloadImageAsBase64(): bytesData is NULL'); log('DownloadClient::downloadImageAsBase64(): bytesData is NULL');
bytesData = await _dioClient.get(url, options: Options(responseType: ResponseType.bytes)); bytesData = await _dioClient.get(url, options: Options(responseType: ResponseType.bytes));
} else {
bytesData = await File(filePath).readAsBytes();
} }
if (bytesData == null) { if (bytesData == null) {
@@ -27,7 +27,7 @@ class ComposerDataSourceImpl extends ComposerDataSource {
cid, cid,
fileInfo.fileExtension, fileInfo.fileExtension,
fileInfo.fileName, fileInfo.fileName,
bytesData: fileInfo.bytes, filePath: fileInfo.filePath,
maxWidth: maxWidth, maxWidth: maxWidth,
compress: compress); compress: compress);
}).catchError(_exceptionThrower.throwException); }).catchError(_exceptionThrower.throwException);
@@ -1750,17 +1750,17 @@ class ComposerController extends BaseController {
Future<InlineImage?> _selectFromFile() async { Future<InlineImage?> _selectFromFile() async {
final filePickerResult = await FilePicker.platform.pickFiles( final filePickerResult = await FilePicker.platform.pickFiles(
type: FileType.image, type: FileType.image,
withData: PlatformInfo.isMobile, withData: PlatformInfo.isWeb
withReadStream: PlatformInfo.isWeb); );
final platformFile = filePickerResult?.files.single; if (filePickerResult?.files.isNotEmpty == true) {
if (platformFile != null) { PlatformFile platformFile = filePickerResult!.files.first;
final fileSelected = FileInfo( final fileSelected = FileInfo(
platformFile.name, platformFile.name,
PlatformInfo.isWeb ? '' : platformFile.path ?? '', PlatformInfo.isWeb ? '' : platformFile.path ?? '',
platformFile.size, platformFile.size,
bytes: platformFile.bytes, bytes: PlatformInfo.isWeb ? platformFile.bytes : null,
readStream: platformFile.readStream); );
return InlineImage(ImageSource.local, fileInfo: fileSelected); return InlineImage(ImageSource.local, fileInfo: fileSelected);
} }
@@ -2,7 +2,6 @@ import 'dart:convert' as convert;
import 'dart:typed_data' as type_data; import 'dart:typed_data' as type_data;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:get/get_connect/http/src/request/request.dart';
import 'package:html_editor_enhanced/utils/file_upload_model.dart'; import 'package:html_editor_enhanced/utils/file_upload_model.dart';
import 'package:model/upload/file_info.dart'; import 'package:model/upload/file_info.dart';
@@ -26,9 +25,9 @@ extension FileUploadExtension on FileUpload {
Future<FileInfo?> toFileInfo() async { Future<FileInfo?> toFileInfo() async {
if (base64Data != null) { if (base64Data != null) {
final bytesStream = await compute(convertBytesToStream, base64Data!); final bytes = await compute(convertBase64ToBytes, base64Data!);
return FileInfo.fromStream( return FileInfo.fromBytes(
stream: bytesStream, bytes: bytes,
name: name, name: name,
size: size size: size
); );
@@ -37,9 +36,8 @@ extension FileUploadExtension on FileUpload {
} }
} }
static Stream<List<int>> convertBytesToStream(String base64) { static Uint8List convertBase64ToBytes(String base64) {
type_data.Uint8List decodeBytes = convert.base64Decode(base64); type_data.Uint8List decodeBytes = convert.base64Decode(base64);
final bytesStream = BodyBytesStream.fromBytes(decodeBytes); return decodeBytes;
return bytesStream;
} }
} }
@@ -1,8 +1,10 @@
import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:core/utils/app_logger.dart'; import 'package:core/utils/app_logger.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:get/get_connect/http/src/request/request.dart';
import 'package:model/account/personal_account.dart'; import 'package:model/account/personal_account.dart';
import 'package:model/account/authentication_type.dart'; import 'package:model/account/authentication_type.dart';
import 'package:model/oidc/oidc_configuration.dart'; import 'package:model/oidc/oidc_configuration.dart';
@@ -12,6 +14,7 @@ import 'package:tmail_ui_user/features/login/domain/extensions/oidc_configuratio
import 'package:tmail_ui_user/features/login/data/local/account_cache_manager.dart'; import 'package:tmail_ui_user/features/login/data/local/account_cache_manager.dart';
import 'package:tmail_ui_user/features/login/data/local/token_oidc_cache_manager.dart'; import 'package:tmail_ui_user/features/login/data/local/token_oidc_cache_manager.dart';
import 'package:tmail_ui_user/features/login/data/network/authentication_client/authentication_client_base.dart'; import 'package:tmail_ui_user/features/login/data/network/authentication_client/authentication_client_base.dart';
import 'package:tmail_ui_user/features/upload/data/network/file_uploader.dart';
class AuthorizationInterceptors extends QueuedInterceptorsWrapper { class AuthorizationInterceptors extends QueuedInterceptorsWrapper {
@@ -77,6 +80,7 @@ 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');
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;
@@ -110,16 +114,36 @@ class AuthorizationInterceptors extends QueuedInterceptorsWrapper {
]); ]);
_updateNewToken(newToken.toToken()); _updateNewToken(newToken.toToken());
final requestOptions = err.requestOptions; if (extraInRequest.containsKey(FileUploader.uploadAttachmentExtraKey)) {
requestOptions.headers[HttpHeaders.authorizationHeader] = _getTokenAsBearerHeader(newToken.token); final uploadExtra = extraInRequest[FileUploader.uploadAttachmentExtraKey];
final response = await _dio.fetch(requestOptions); requestOptions.headers[HttpHeaders.authorizationHeader] = _getTokenAsBearerHeader(newToken.token);
return handler.resolve(response); requestOptions.headers[HttpHeaders.contentTypeHeader] = uploadExtra[FileUploader.typeExtraKey];
requestOptions.headers[HttpHeaders.contentLengthHeader] = uploadExtra[FileUploader.sizeExtraKey];
final newOptions = Options(
method: requestOptions.method,
headers: requestOptions.headers,
);
final response = await _dio.request(
requestOptions.path,
data: BodyBytesStream.fromBytes(uploadExtra[FileUploader.bytesExtraKey]),
queryParameters: requestOptions.queryParameters,
options: newOptions,
);
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)) { } else if (_validateToRetry(err, retries)) {
log('AuthorizationInterceptors::onError:>> _validateToRetry | retries: $retries'); log('AuthorizationInterceptors::onError:>> _validateToRetry | retries: $retries');
retries++; retries++;
final requestOptions = err.requestOptions;
requestOptions.headers[HttpHeaders.authorizationHeader] = _getTokenAsBearerHeader(_token!.token); requestOptions.headers[HttpHeaders.authorizationHeader] = _getTokenAsBearerHeader(_token!.token);
requestOptions.extra = {RETRY_KEY: retries}; requestOptions.extra = {RETRY_KEY: retries};
@@ -10,6 +10,7 @@ import 'package:core/utils/app_logger.dart';
import 'package:core/utils/platform_info.dart'; import 'package:core/utils/platform_info.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:get/get_connect/http/src/request/request.dart';
import 'package:model/email/attachment.dart'; import 'package:model/email/attachment.dart';
import 'package:model/upload/file_info.dart'; import 'package:model/upload/file_info.dart';
import 'package:model/upload/upload_response.dart'; import 'package:model/upload/upload_response.dart';
@@ -22,6 +23,12 @@ import 'package:worker_manager/worker_manager.dart' as worker;
class FileUploader { class FileUploader {
static const String uploadAttachmentExtraKey = 'upload-attachment';
static const String platformExtraKey = 'platform';
static const String bytesExtraKey = 'bytes';
static const String typeExtraKey = 'type';
static const String sizeExtraKey = 'size';
final DioClient _dioClient; final DioClient _dioClient;
final worker.Executor _isolateExecutor; final worker.Executor _isolateExecutor;
@@ -104,12 +111,23 @@ class FileUploader {
final headerParam = _dioClient.getHeaders(); final headerParam = _dioClient.getHeaders();
headerParam[HttpHeaders.contentTypeHeader] = fileInfo.mimeType; headerParam[HttpHeaders.contentTypeHeader] = fileInfo.mimeType;
headerParam[HttpHeaders.contentLengthHeader] = fileInfo.fileSize; headerParam[HttpHeaders.contentLengthHeader] = fileInfo.fileSize;
final data = fileInfo.readStream;
final mapExtra = <String, dynamic>{
uploadAttachmentExtraKey: {
platformExtraKey: 'web',
bytesExtraKey: fileInfo.bytes,
typeExtraKey: fileInfo.mimeType,
sizeExtraKey: fileInfo.fileSize,
}
};
final resultJson = await _dioClient.post( final resultJson = await _dioClient.post(
Uri.decodeFull(uploadUri.toString()), Uri.decodeFull(uploadUri.toString()),
options: Options(headers: headerParam), options: Options(
data: data, headers: headerParam,
extra: mapExtra
),
data: BodyBytesStream.fromBytes(fileInfo.bytes!),
cancelToken: cancelToken, cancelToken: cancelToken,
onSendProgress: (count, total) { onSendProgress: (count, total) {
log('FileUploader::_handleUploadAttachmentActionOnWeb():onSendProgress: [${uploadId.id}] = $count'); log('FileUploader::_handleUploadAttachmentActionOnWeb():onSendProgress: [${uploadId.id}] = $count');
@@ -1,9 +1,10 @@
import 'package:core/core.dart'; import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:core/utils/platform_info.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart'; import 'package:model/upload/file_info.dart';
import 'package:model/model.dart';
import 'package:tmail_ui_user/features/upload/domain/state/local_file_picker_state.dart'; import 'package:tmail_ui_user/features/upload/domain/state/local_file_picker_state.dart';
class LocalFilePickerInteractor { class LocalFilePickerInteractor {
@@ -12,14 +13,20 @@ class LocalFilePickerInteractor {
Stream<Either<Failure, Success>> execute({FileType fileType = FileType.any}) async* { Stream<Either<Failure, Success>> execute({FileType fileType = FileType.any}) async* {
try { try {
final filesResult = await FilePicker.platform.pickFiles(type: fileType, allowMultiple: true, withReadStream: true); final filesResult = await FilePicker.platform.pickFiles(
type: fileType,
allowMultiple: true,
withData: PlatformInfo.isWeb
);
if (filesResult != null && filesResult.files.isNotEmpty) { if (filesResult != null && filesResult.files.isNotEmpty) {
final fileInfoResults = filesResult.files final fileInfoResults = filesResult.files
.map((platformFile) => FileInfo( .map((platformFile) => FileInfo(
platformFile.name, platformFile.name,
kIsWeb ? '' : platformFile.path ?? '', PlatformInfo.isWeb ? '' : platformFile.path ?? '',
platformFile.size, platformFile.size,
readStream: platformFile.readStream)).toList(); bytes: PlatformInfo.isWeb ? platformFile.bytes : null
))
.toList();
yield Right<Failure, Success>(LocalFilePickerSuccess(fileInfoResults)); yield Right<Failure, Success>(LocalFilePickerSuccess(fileInfoResults));
} else { } else {
yield Left<Failure, Success>(LocalFilePickerCancel()); yield Left<Failure, Success>(LocalFilePickerCancel());
@@ -265,7 +265,6 @@ class UploadController extends BaseController {
} }
void _handleUploadAttachmentsSuccess(SuccessAttachmentUploadState success) { void _handleUploadAttachmentsSuccess(SuccessAttachmentUploadState success) {
log('UploadController::_handleUploadAttachmentsSuccess(): $success');
if (currentContext != null && currentOverlayContext != null && _uploadingStateFiles.allSuccess) { if (currentContext != null && currentOverlayContext != null && _uploadingStateFiles.allSuccess) {
_appToast.showToastSuccessMessage( _appToast.showToastSuccessMessage(
currentOverlayContext!, currentOverlayContext!,
+5 -6
View File
@@ -7,21 +7,20 @@ class FileInfo with EquatableMixin {
final String fileName; final String fileName;
final String filePath; final String filePath;
final int fileSize; final int fileSize;
final Stream<List<int>>? readStream;
final Uint8List? bytes; final Uint8List? bytes;
FileInfo(this.fileName, this.filePath, this.fileSize, {this.readStream, this.bytes}); FileInfo(this.fileName, this.filePath, this.fileSize, {this.bytes});
factory FileInfo.empty() { factory FileInfo.empty() {
return FileInfo('', '', 0); return FileInfo('', '', 0);
} }
factory FileInfo.fromStream({ factory FileInfo.fromBytes({
required Stream<List<int>> stream, required Uint8List bytes,
String? name, String? name,
int? size int? size
}) { }) {
return FileInfo(name ?? '', '', size ?? 0, readStream: stream); return FileInfo(name ?? '', '', size ?? 0, bytes: bytes);
} }
String get fileExtension => fileName.split('.').last; String get fileExtension => fileName.split('.').last;
@@ -29,5 +28,5 @@ class FileInfo with EquatableMixin {
String get mimeType => lookupMimeType(kIsWeb ? fileName : filePath) ?? 'application/octet-stream'; String get mimeType => lookupMimeType(kIsWeb ? fileName : filePath) ?? 'application/octet-stream';
@override @override
List<Object?> get props => [fileName, filePath, fileSize, readStream, bytes]; List<Object?> get props => [fileName, filePath, fileSize, bytes];
} }