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:
@@ -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!,
|
||||||
|
|||||||
@@ -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];
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user