From c429be122d2b6d24f9bc3eb4e28329e98ef63058 Mon Sep 17 00:00:00 2001 From: mbfakourii Date: Sun, 2 Jun 2024 18:02:15 +0330 Subject: [PATCH 01/10] feat: add support web download --- dio/lib/src/dio/dio_for_browser.dart | 54 ++++++++++++++++++---------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/dio/lib/src/dio/dio_for_browser.dart b/dio/lib/src/dio/dio_for_browser.dart index 6b13609a5..f55846f0f 100644 --- a/dio/lib/src/dio/dio_for_browser.dart +++ b/dio/lib/src/dio/dio_for_browser.dart @@ -1,10 +1,8 @@ +import 'dart:async'; +import 'dart:html'; + +import '../../dio.dart'; import '../adapters/browser_adapter.dart'; -import '../cancel_token.dart'; -import '../dio.dart'; -import '../dio_mixin.dart'; -import '../headers.dart'; -import '../options.dart'; -import '../response.dart'; /// Create the [Dio] instance for Web platforms. Dio createDio([BaseOptions? options]) => DioForBrowser(options); @@ -20,18 +18,38 @@ class DioForBrowser with DioMixin implements Dio { @override Future download( - String urlPath, - dynamic savePath, { - ProgressCallback? onReceiveProgress, - Map? queryParameters, - CancelToken? cancelToken, - bool deleteOnError = true, - String lengthHeader = Headers.contentLengthHeader, - Object? data, - Options? options, - }) { - throw UnsupportedError( - 'The download method is not available in the Web environment.', + String urlPath, + dynamic savePath, { + ProgressCallback? onReceiveProgress, + Map? queryParameters, + CancelToken? cancelToken, + bool deleteOnError = true, + String lengthHeader = Headers.contentLengthHeader, + Object? data, + Options? options, + }) async { + options ??= DioMixin.checkOptions('GET', options); + + // Set receiveTimeout to 48 hours because `Duration.zero` not work! + options=options.copyWith(receiveTimeout: const Duration(hours: 48)); + + final Response response = await request( + urlPath, + data: data, + options: options, + queryParameters: queryParameters, + cancelToken: cancelToken, + onReceiveProgress: onReceiveProgress, ); + + final completer = Completer(); + + // Create blob url from byte data + response.data = Url.createObjectUrlFromBlob(Blob([response.data])); + + // Set response in Completer + completer.complete(response); + + return DioMixin.listenCancelForAsyncTask(cancelToken, completer.future); } } From 7147d04f76e57271775cc38fd691b3e5fe4053d6 Mon Sep 17 00:00:00 2001 From: mbfakourii Date: Sun, 2 Jun 2024 18:12:19 +0330 Subject: [PATCH 02/10] fix: code format --- dio/lib/src/dio/dio_for_browser.dart | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/dio/lib/src/dio/dio_for_browser.dart b/dio/lib/src/dio/dio_for_browser.dart index f55846f0f..321a9231c 100644 --- a/dio/lib/src/dio/dio_for_browser.dart +++ b/dio/lib/src/dio/dio_for_browser.dart @@ -18,20 +18,20 @@ class DioForBrowser with DioMixin implements Dio { @override Future download( - String urlPath, - dynamic savePath, { - ProgressCallback? onReceiveProgress, - Map? queryParameters, - CancelToken? cancelToken, - bool deleteOnError = true, - String lengthHeader = Headers.contentLengthHeader, - Object? data, - Options? options, - }) async { + String urlPath, + dynamic savePath, { + ProgressCallback? onReceiveProgress, + Map? queryParameters, + CancelToken? cancelToken, + bool deleteOnError = true, + String lengthHeader = Headers.contentLengthHeader, + Object? data, + Options? options, + }) async { options ??= DioMixin.checkOptions('GET', options); // Set receiveTimeout to 48 hours because `Duration.zero` not work! - options=options.copyWith(receiveTimeout: const Duration(hours: 48)); + options = options.copyWith(receiveTimeout: const Duration(hours: 48)); final Response response = await request( urlPath, From 6e5a96b957c302bc1010edb78b0f64d204dea303 Mon Sep 17 00:00:00 2001 From: mbfakourii Date: Sun, 2 Jun 2024 22:33:57 +0330 Subject: [PATCH 03/10] fix: add `ResponseType.bytes` --- dio/lib/src/dio/dio_for_browser.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dio/lib/src/dio/dio_for_browser.dart b/dio/lib/src/dio/dio_for_browser.dart index 321a9231c..b0f16cd33 100644 --- a/dio/lib/src/dio/dio_for_browser.dart +++ b/dio/lib/src/dio/dio_for_browser.dart @@ -30,6 +30,8 @@ class DioForBrowser with DioMixin implements Dio { }) async { options ??= DioMixin.checkOptions('GET', options); + options = options.copyWith(responseType: ResponseType.bytes); + // Set receiveTimeout to 48 hours because `Duration.zero` not work! options = options.copyWith(receiveTimeout: const Duration(hours: 48)); From 4dec2237d14ecd9b27a5718e720c114ffacd81a9 Mon Sep 17 00:00:00 2001 From: mbfakourii Date: Mon, 3 Jun 2024 15:40:49 +0330 Subject: [PATCH 04/10] fix: change implementation base `XMLHttpRequest` --- dio/lib/src/adapters/browser_adapter.dart | 42 ++++++++++++++++------- dio/lib/src/dio/dio_for_browser.dart | 35 +++++++++---------- dio/lib/src/options.dart | 8 +++++ 3 files changed, 53 insertions(+), 32 deletions(-) diff --git a/dio/lib/src/adapters/browser_adapter.dart b/dio/lib/src/adapters/browser_adapter.dart index b388e42ec..e56637574 100644 --- a/dio/lib/src/adapters/browser_adapter.dart +++ b/dio/lib/src/adapters/browser_adapter.dart @@ -40,7 +40,7 @@ class BrowserHttpClientAdapter implements HttpClientAdapter { xhrs.add(xhr); xhr ..open(options.method, '${options.uri}') - ..responseType = 'arraybuffer'; + ..responseType = options.blobUrl ? 'blob' : 'arraybuffer'; final withCredentialsOption = options.extra['withCredentials']; if (withCredentialsOption != null) { @@ -67,18 +67,34 @@ class BrowserHttpClientAdapter implements HttpClientAdapter { final completer = Completer(); xhr.onLoad.first.then((_) { - final Uint8List body = (xhr.response as ByteBuffer).asUint8List(); - completer.complete( - ResponseBody.fromBytes( - body, - xhr.status!, - headers: xhr.responseHeaders.map((k, v) => MapEntry(k, v.split(','))), - statusMessage: xhr.statusText, - isRedirect: xhr.status == 302 || - xhr.status == 301 || - options.uri.toString() != xhr.responseUrl, - ), - ); + if (options.blobUrl) { + completer.complete( + ResponseBody.fromString( + Url.createObjectUrl(xhr.response), + xhr.status!, + headers: + xhr.responseHeaders.map((k, v) => MapEntry(k, v.split(','))), + statusMessage: xhr.statusText, + isRedirect: xhr.status == 302 || + xhr.status == 301 || + options.uri.toString() != xhr.responseUrl, + ), + ); + } else { + final Uint8List body = (xhr.response as ByteBuffer).asUint8List(); + completer.complete( + ResponseBody.fromBytes( + body, + xhr.status!, + headers: + xhr.responseHeaders.map((k, v) => MapEntry(k, v.split(','))), + statusMessage: xhr.statusText, + isRedirect: xhr.status == 302 || + xhr.status == 301 || + options.uri.toString() != xhr.responseUrl, + ), + ); + } }); Timer? connectTimeoutTimer; diff --git a/dio/lib/src/dio/dio_for_browser.dart b/dio/lib/src/dio/dio_for_browser.dart index b0f16cd33..e97f1e845 100644 --- a/dio/lib/src/dio/dio_for_browser.dart +++ b/dio/lib/src/dio/dio_for_browser.dart @@ -1,8 +1,12 @@ import 'dart:async'; -import 'dart:html'; -import '../../dio.dart'; import '../adapters/browser_adapter.dart'; +import '../cancel_token.dart'; +import '../dio.dart'; +import '../dio_mixin.dart'; +import '../headers.dart'; +import '../options.dart'; +import '../response.dart'; /// Create the [Dio] instance for Web platforms. Dio createDio([BaseOptions? options]) => DioForBrowser(options); @@ -28,27 +32,20 @@ class DioForBrowser with DioMixin implements Dio { Object? data, Options? options, }) async { - options ??= DioMixin.checkOptions('GET', options); - - options = options.copyWith(responseType: ResponseType.bytes); - - // Set receiveTimeout to 48 hours because `Duration.zero` not work! - options = options.copyWith(receiveTimeout: const Duration(hours: 48)); - - final Response response = await request( - urlPath, - data: data, - options: options, - queryParameters: queryParameters, - cancelToken: cancelToken, - onReceiveProgress: onReceiveProgress, + final Response response = await fetch( + RequestOptions( + baseUrl: urlPath, + data: data, + method: 'GET', + queryParameters: queryParameters, + cancelToken: cancelToken, + onReceiveProgress: onReceiveProgress, + blobUrl: true, + ), ); final completer = Completer(); - // Create blob url from byte data - response.data = Url.createObjectUrlFromBlob(Blob([response.data])); - // Set response in Completer completer.complete(response); diff --git a/dio/lib/src/options.dart b/dio/lib/src/options.dart index 10e092148..e30648381 100644 --- a/dio/lib/src/options.dart +++ b/dio/lib/src/options.dart @@ -485,6 +485,7 @@ class RequestOptions extends _RequestConfig with OptionsMixin { this.onReceiveProgress, this.onSendProgress, this.cancelToken, + this.blobUrl = false, super.method, super.sendTimeout, super.receiveTimeout, @@ -526,6 +527,7 @@ class RequestOptions extends _RequestConfig with OptionsMixin { ProgressCallback? onReceiveProgress, ProgressCallback? onSendProgress, CancelToken? cancelToken, + bool? blobUrl, Map? extra, Map? headers, bool? preserveHeaderCase, @@ -558,6 +560,7 @@ class RequestOptions extends _RequestConfig with OptionsMixin { connectTimeout: connectTimeout ?? this.connectTimeout, data: data ?? this.data, path: path ?? this.path, + blobUrl: blobUrl ?? false, baseUrl: baseUrl ?? this.baseUrl, queryParameters: queryParameters ?? Map.from(this.queryParameters), onReceiveProgress: onReceiveProgress ?? this.onReceiveProgress, @@ -630,6 +633,11 @@ class RequestOptions extends _RequestConfig with OptionsMixin { /// {@macro dio.options.ProgressCallback} ProgressCallback? onSendProgress; + + /// Get blob url + /// + /// Only work in web + bool blobUrl; } bool _defaultValidateStatus(int? status) { From 0b1fbe65218df81c058ae2d5e467f06824704d67 Mon Sep 17 00:00:00 2001 From: mbfakourii Date: Tue, 18 Jun 2024 14:40:35 +0330 Subject: [PATCH 05/10] fix: change the implementation based on the latest changes --- dio/lib/src/options.dart | 11 ++--- plugins/web_adapter/lib/src/html/adapter.dart | 44 +++++++++++++------ .../web_adapter/lib/src/html/dio_impl.dart | 23 ++++++++-- 3 files changed, 55 insertions(+), 23 deletions(-) diff --git a/dio/lib/src/options.dart b/dio/lib/src/options.dart index a1e2461a9..ef58ff282 100644 --- a/dio/lib/src/options.dart +++ b/dio/lib/src/options.dart @@ -41,6 +41,10 @@ enum ResponseType { /// Get the original bytes, the [Response.data] will be [List]. bytes, + + /// Get blob url. + /// Only work in web. + blobUrl, } /// {@template dio.options.ListFormat} @@ -492,7 +496,6 @@ class RequestOptions extends _RequestConfig with OptionsMixin { this.onReceiveProgress, this.onSendProgress, this.cancelToken, - this.blobUrl = false, super.method, super.sendTimeout, super.receiveTimeout, @@ -567,7 +570,6 @@ class RequestOptions extends _RequestConfig with OptionsMixin { connectTimeout: connectTimeout ?? this.connectTimeout, data: data ?? this.data, path: path ?? this.path, - blobUrl: blobUrl ?? false, baseUrl: baseUrl ?? this.baseUrl, queryParameters: queryParameters ?? Map.from(this.queryParameters), onReceiveProgress: onReceiveProgress ?? this.onReceiveProgress, @@ -640,11 +642,6 @@ class RequestOptions extends _RequestConfig with OptionsMixin { /// {@macro dio.options.ProgressCallback} ProgressCallback? onSendProgress; - - /// Get blob url - /// - /// Only work in web - bool blobUrl; } bool _defaultValidateStatus(int? status) { diff --git a/plugins/web_adapter/lib/src/html/adapter.dart b/plugins/web_adapter/lib/src/html/adapter.dart index 3351eb21f..0c6188ca1 100644 --- a/plugins/web_adapter/lib/src/html/adapter.dart +++ b/plugins/web_adapter/lib/src/html/adapter.dart @@ -38,7 +38,8 @@ class BrowserHttpClientAdapter implements HttpClientAdapter { xhrs.add(xhr); xhr ..open(options.method, '${options.uri}') - ..responseType = 'arraybuffer'; + ..responseType = + options.responseType == ResponseType.blobUrl ? 'blob' : 'arraybuffer'; final withCredentialsOption = options.extra['withCredentials']; if (withCredentialsOption != null) { @@ -65,18 +66,35 @@ class BrowserHttpClientAdapter implements HttpClientAdapter { final completer = Completer(); xhr.onLoad.first.then((_) { - final Uint8List body = (xhr.response as ByteBuffer).asUint8List(); - completer.complete( - ResponseBody.fromBytes( - body, - xhr.status!, - headers: xhr.responseHeaders.map((k, v) => MapEntry(k, v.split(','))), - statusMessage: xhr.statusText, - isRedirect: xhr.status == 302 || - xhr.status == 301 || - options.uri.toString() != xhr.responseUrl, - ), - ); + if (options.responseType == ResponseType.blobUrl) { + completer.complete( + ResponseBody.fromString( + Url.createObjectUrl(xhr.response), + xhr.status!, + headers: + xhr.responseHeaders.map((k, v) => MapEntry(k, v.split(','))), + statusMessage: xhr.statusText, + isRedirect: xhr.status == 302 || + xhr.status == 301 || + options.uri.toString() != xhr.responseUrl, + ), + ); + } else { + final Uint8List body = (xhr.response as ByteBuffer).asUint8List(); + completer.complete( + ResponseBody.fromBytes( + body, + xhr.status!, + headers: xhr.responseHeaders.map( + (k, v) => MapEntry(k, v.split(',')), + ), + statusMessage: xhr.statusText, + isRedirect: xhr.status == 302 || + xhr.status == 301 || + options.uri.toString() != xhr.responseUrl, + ), + ); + } }); Timer? connectTimeoutTimer; diff --git a/plugins/web_adapter/lib/src/html/dio_impl.dart b/plugins/web_adapter/lib/src/html/dio_impl.dart index f5adc61bd..9a7698561 100644 --- a/plugins/web_adapter/lib/src/html/dio_impl.dart +++ b/plugins/web_adapter/lib/src/html/dio_impl.dart @@ -1,5 +1,7 @@ // export '../js_interop/dio_impl.dart'; +import 'dart:async'; + import 'package:dio/dio.dart'; import 'adapter.dart'; @@ -27,9 +29,24 @@ class DioForBrowser with DioMixin implements Dio { String lengthHeader = Headers.contentLengthHeader, Object? data, Options? options, - }) { - throw UnsupportedError( - 'The download method is not available in the Web environment.', + }) async { + final Response response = await fetch( + RequestOptions( + baseUrl: urlPath, + data: data, + method: 'GET', + responseType: ResponseType.blobUrl, + queryParameters: queryParameters, + cancelToken: cancelToken, + onReceiveProgress: onReceiveProgress, + ), ); + + final completer = Completer(); + + // Set response in Completer + completer.complete(response); + + return DioMixin.listenCancelForAsyncTask(cancelToken, completer.future); } } From 77c63b8b9510ca04137f9250def8de31136a17b6 Mon Sep 17 00:00:00 2001 From: mbfakourii Date: Tue, 18 Jun 2024 14:41:58 +0330 Subject: [PATCH 06/10] fix: change the implementation based on the latest changes --- dio/lib/src/options.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/dio/lib/src/options.dart b/dio/lib/src/options.dart index ef58ff282..a929bc1ec 100644 --- a/dio/lib/src/options.dart +++ b/dio/lib/src/options.dart @@ -537,7 +537,6 @@ class RequestOptions extends _RequestConfig with OptionsMixin { ProgressCallback? onReceiveProgress, ProgressCallback? onSendProgress, CancelToken? cancelToken, - bool? blobUrl, Map? extra, Map? headers, bool? preserveHeaderCase, From 1bbc7f9079ebcad1aa2ee997726f24e6ce474193 Mon Sep 17 00:00:00 2001 From: mbfakourii Date: Mon, 8 Jul 2024 08:19:20 +0330 Subject: [PATCH 07/10] fix: improve description --- dio/lib/src/options.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dio/lib/src/options.dart b/dio/lib/src/options.dart index e26e13e59..2900972a4 100644 --- a/dio/lib/src/options.dart +++ b/dio/lib/src/options.dart @@ -43,7 +43,10 @@ enum ResponseType { bytes, /// Get blob url. + /// This value is needed when you need content in the form of a blob url. /// Only work in web. + /// + /// the [Response.data] will be [String]. blobUrl, } From 9db2305fdd73417a200ddcfb10e812c97de9ba24 Mon Sep 17 00:00:00 2001 From: mbfakourii Date: Mon, 8 Jul 2024 08:53:23 +0330 Subject: [PATCH 08/10] fix: remove listenCancelForAsyncTask --- plugins/web_adapter/lib/src/html/dio_impl.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/web_adapter/lib/src/html/dio_impl.dart b/plugins/web_adapter/lib/src/html/dio_impl.dart index 9a7698561..ed4152263 100644 --- a/plugins/web_adapter/lib/src/html/dio_impl.dart +++ b/plugins/web_adapter/lib/src/html/dio_impl.dart @@ -47,6 +47,6 @@ class DioForBrowser with DioMixin implements Dio { // Set response in Completer completer.complete(response); - return DioMixin.listenCancelForAsyncTask(cancelToken, completer.future); + return completer.future; } } From d0292943c98ed17d4bd2614f271b0433a390b026 Mon Sep 17 00:00:00 2001 From: mbfakourii Date: Wed, 30 Oct 2024 08:22:40 +0330 Subject: [PATCH 09/10] fix: blob with web --- plugins/web_adapter/lib/src/adapter.dart | 9 ++++----- plugins/web_adapter/lib/src/dio_impl.dart | 2 ++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/plugins/web_adapter/lib/src/adapter.dart b/plugins/web_adapter/lib/src/adapter.dart index 4fce9a4ee..a7a9c4b7f 100644 --- a/plugins/web_adapter/lib/src/adapter.dart +++ b/plugins/web_adapter/lib/src/adapter.dart @@ -70,14 +70,13 @@ class BrowserHttpClientAdapter implements HttpClientAdapter { if (options.responseType == ResponseType.blobUrl) { completer.complete( ResponseBody.fromString( - Url.createObjectUrl(xhr.response), - xhr.status!, - headers: - xhr.responseHeaders.map((k, v) => MapEntry(k, v.split(','))), + web.URL.createObjectURL(xhr.response as web.Blob), + xhr.status, + headers: xhr.getResponseHeaders(), statusMessage: xhr.statusText, isRedirect: xhr.status == 302 || xhr.status == 301 || - options.uri.toString() != xhr.responseUrl, + options.uri.toString() != xhr.responseURL, ), ); } else { diff --git a/plugins/web_adapter/lib/src/dio_impl.dart b/plugins/web_adapter/lib/src/dio_impl.dart index 3a641a2e9..378665e6c 100644 --- a/plugins/web_adapter/lib/src/dio_impl.dart +++ b/plugins/web_adapter/lib/src/dio_impl.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:dio/dio.dart'; import 'adapter.dart'; From 9f1f36eaf998e6cd432cf808ca0d5272e370642c Mon Sep 17 00:00:00 2001 From: mbfakourii Date: Wed, 30 Oct 2024 09:25:23 +0330 Subject: [PATCH 10/10] test: add test --- plugins/web_adapter/test/browser_test.dart | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/plugins/web_adapter/test/browser_test.dart b/plugins/web_adapter/test/browser_test.dart index d752fe85e..5daf220cd 100644 --- a/plugins/web_adapter/test/browser_test.dart +++ b/plugins/web_adapter/test/browser_test.dart @@ -18,4 +18,18 @@ void main() { browserAdapter.fetch(opts, testStream, cancelFuture); expect(browserAdapter.xhrs.every((e) => e.withCredentials == true), isTrue); }); + + test('ResponseType in blobUrl', () async { + final browserAdapter = BrowserHttpClientAdapter(withCredentials: true); + final opts = RequestOptions(responseType : ResponseType.blobUrl); + final testStream = Stream.periodic( + const Duration(seconds: 1), + (x) => Uint8List(x), + ); + final cancelFuture = opts.cancelToken?.whenCancel; + + browserAdapter.fetch(opts, testStream, cancelFuture); + expect(browserAdapter.xhrs.every((e) => e.withCredentials == true), isTrue); + expect(opts.responseType, ResponseType.blobUrl); + }); }