-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Split the web support to package dio_browser_adapter #2218
Changes from all commits
213cbe0
7e1d82d
e67fe9e
7d200fb
3dc622c
93adfe4
a544384
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ import 'dart:typed_data'; | |
import 'package:meta/meta.dart'; | ||
|
||
import 'adapters/io_adapter.dart' | ||
if (dart.library.js_util) 'adapters/browser_adapter.dart' | ||
if (dart.library.html) 'adapters/browser_adapter.dart' as adapter; | ||
huanghui1998hhh marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line should be removed. ALL references to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually removing these might require upping the min Dart version to 3.3 which isn't ideal There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Will this cause WASM to degrade or something related? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure. All I know is the migration docs say to remove them. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this is an import condition it shouldn't cause any issues, but the js_util ones definitely need changed to js_interop as js_util is one of the deprecated web packages There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the imports are resolved at compile time, so it should be fine. But definitely should switch to the new |
||
import 'headers.dart'; | ||
import 'options.dart'; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,313 +1,12 @@ | ||
import 'dart:async'; | ||
import 'dart:convert'; | ||
import 'dart:html'; | ||
import 'dart:typed_data'; | ||
|
||
import 'package:meta/meta.dart'; | ||
import 'package:dio_browser_adapter/dio_browser_adapter.dart'; | ||
|
||
import '../adapter.dart'; | ||
import '../dio_exception.dart'; | ||
import '../headers.dart'; | ||
import '../options.dart'; | ||
import '../utils.dart'; | ||
|
||
HttpClientAdapter createAdapter() => BrowserHttpClientAdapter(); | ||
|
||
/// The default [HttpClientAdapter] for Web platforms. | ||
class BrowserHttpClientAdapter implements HttpClientAdapter { | ||
class BrowserHttpClientAdapter with BrowserHttpClientAdapterMixin { | ||
BrowserHttpClientAdapter({this.withCredentials = false}); | ||
|
||
/// These are aborted if the client is closed. | ||
@visibleForTesting | ||
final xhrs = <HttpRequest>{}; | ||
|
||
/// Whether to send credentials such as cookies or authorization headers for | ||
/// cross-site requests. | ||
/// | ||
/// Defaults to `false`. | ||
/// | ||
/// You can also override this value using `Options.extra['withCredentials']` | ||
/// for each request. | ||
bool withCredentials; | ||
|
||
@override | ||
Future<ResponseBody> fetch( | ||
RequestOptions options, | ||
Stream<Uint8List>? requestStream, | ||
Future<void>? cancelFuture, | ||
) async { | ||
final xhr = HttpRequest(); | ||
xhrs.add(xhr); | ||
xhr | ||
..open(options.method, '${options.uri}') | ||
..responseType = 'arraybuffer'; | ||
|
||
final withCredentialsOption = options.extra['withCredentials']; | ||
if (withCredentialsOption != null) { | ||
xhr.withCredentials = withCredentialsOption == true; | ||
} else { | ||
xhr.withCredentials = withCredentials; | ||
} | ||
|
||
options.headers.remove(Headers.contentLengthHeader); | ||
options.headers.forEach((key, v) { | ||
if (v is Iterable) { | ||
xhr.setRequestHeader(key, v.join(', ')); | ||
} else { | ||
xhr.setRequestHeader(key, v.toString()); | ||
} | ||
}); | ||
|
||
final sendTimeout = options.sendTimeout ?? Duration.zero; | ||
final connectTimeout = options.connectTimeout ?? Duration.zero; | ||
final receiveTimeout = options.receiveTimeout ?? Duration.zero; | ||
final xhrTimeout = (connectTimeout + receiveTimeout).inMilliseconds; | ||
xhr.timeout = xhrTimeout; | ||
|
||
final completer = Completer<ResponseBody>(); | ||
|
||
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, | ||
), | ||
); | ||
}); | ||
|
||
Timer? connectTimeoutTimer; | ||
if (connectTimeout > Duration.zero) { | ||
connectTimeoutTimer = Timer( | ||
connectTimeout, | ||
() { | ||
connectTimeoutTimer = null; | ||
if (completer.isCompleted) { | ||
// connectTimeout is triggered after the fetch has been completed. | ||
return; | ||
} | ||
xhr.abort(); | ||
completer.completeError( | ||
DioException.connectionTimeout( | ||
requestOptions: options, | ||
timeout: connectTimeout, | ||
), | ||
StackTrace.current, | ||
); | ||
}, | ||
); | ||
} | ||
|
||
// This code is structured to call `xhr.upload.onProgress.listen` only when | ||
// absolutely necessary, because registering an xhr upload listener prevents | ||
// the request from being classified as a "simple request" by the CORS spec. | ||
// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests | ||
// Upload progress events only get triggered if the request body exists, | ||
// so we can check it beforehand. | ||
if (requestStream != null) { | ||
if (connectTimeoutTimer != null) { | ||
xhr.upload.onProgress.listen((event) { | ||
connectTimeoutTimer?.cancel(); | ||
connectTimeoutTimer = null; | ||
}); | ||
} | ||
|
||
if (sendTimeout > Duration.zero) { | ||
final uploadStopwatch = Stopwatch(); | ||
xhr.upload.onProgress.listen((event) { | ||
if (!uploadStopwatch.isRunning) { | ||
uploadStopwatch.start(); | ||
} | ||
final duration = uploadStopwatch.elapsed; | ||
if (duration > sendTimeout) { | ||
uploadStopwatch.stop(); | ||
completer.completeError( | ||
DioException.sendTimeout( | ||
timeout: sendTimeout, | ||
requestOptions: options, | ||
), | ||
StackTrace.current, | ||
); | ||
xhr.abort(); | ||
} | ||
}); | ||
} | ||
|
||
final onSendProgress = options.onSendProgress; | ||
if (onSendProgress != null) { | ||
xhr.upload.onProgress.listen((event) { | ||
if (event.loaded != null && event.total != null) { | ||
onSendProgress(event.loaded!, event.total!); | ||
} | ||
}); | ||
} | ||
} else { | ||
if (sendTimeout > Duration.zero) { | ||
debugLog( | ||
'sendTimeout cannot be used without a request body to send', | ||
StackTrace.current, | ||
); | ||
} | ||
if (options.onSendProgress != null) { | ||
debugLog( | ||
'onSendProgress cannot be used without a request body to send', | ||
StackTrace.current, | ||
); | ||
} | ||
} | ||
|
||
final receiveStopwatch = Stopwatch(); | ||
Timer? receiveTimer; | ||
|
||
void stopWatchReceiveTimeout() { | ||
receiveTimer?.cancel(); | ||
receiveTimer = null; | ||
receiveStopwatch.stop(); | ||
} | ||
|
||
void watchReceiveTimeout() { | ||
if (receiveTimeout <= Duration.zero) { | ||
return; | ||
} | ||
receiveStopwatch.reset(); | ||
if (!receiveStopwatch.isRunning) { | ||
receiveStopwatch.start(); | ||
} | ||
receiveTimer?.cancel(); | ||
receiveTimer = Timer(receiveTimeout, () { | ||
if (!completer.isCompleted) { | ||
xhr.abort(); | ||
completer.completeError( | ||
DioException.receiveTimeout( | ||
timeout: receiveTimeout, | ||
requestOptions: options, | ||
), | ||
StackTrace.current, | ||
); | ||
} | ||
stopWatchReceiveTimeout(); | ||
}); | ||
} | ||
|
||
xhr.onProgress.listen( | ||
(ProgressEvent event) { | ||
if (connectTimeoutTimer != null) { | ||
connectTimeoutTimer!.cancel(); | ||
connectTimeoutTimer = null; | ||
} | ||
watchReceiveTimeout(); | ||
if (options.onReceiveProgress != null && | ||
event.loaded != null && | ||
event.total != null) { | ||
options.onReceiveProgress!(event.loaded!, event.total!); | ||
} | ||
}, | ||
onDone: () => stopWatchReceiveTimeout(), | ||
); | ||
|
||
xhr.onError.first.then((_) { | ||
connectTimeoutTimer?.cancel(); | ||
// Unfortunately, the underlying XMLHttpRequest API doesn't expose any | ||
// specific information about the error itself. | ||
// See also: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequestEventTarget/onerror | ||
completer.completeError( | ||
DioException.connectionError( | ||
requestOptions: options, | ||
reason: 'The XMLHttpRequest onError callback was called. ' | ||
'This typically indicates an error on the network layer.', | ||
), | ||
StackTrace.current, | ||
); | ||
}); | ||
|
||
xhr.onTimeout.first.then((_) { | ||
final isConnectTimeout = connectTimeoutTimer != null; | ||
if (connectTimeoutTimer != null) { | ||
connectTimeoutTimer?.cancel(); | ||
} | ||
if (!completer.isCompleted) { | ||
if (isConnectTimeout) { | ||
completer.completeError( | ||
DioException.connectionTimeout( | ||
timeout: connectTimeout, | ||
requestOptions: options, | ||
), | ||
); | ||
} else { | ||
completer.completeError( | ||
DioException.receiveTimeout( | ||
timeout: Duration(milliseconds: xhrTimeout), | ||
requestOptions: options, | ||
), | ||
StackTrace.current, | ||
); | ||
} | ||
} | ||
}); | ||
|
||
cancelFuture?.then((_) { | ||
if (xhr.readyState < HttpRequest.DONE && | ||
xhr.readyState > HttpRequest.UNSENT) { | ||
connectTimeoutTimer?.cancel(); | ||
try { | ||
xhr.abort(); | ||
} catch (_) {} | ||
if (!completer.isCompleted) { | ||
completer.completeError( | ||
DioException.requestCancelled( | ||
requestOptions: options, | ||
reason: 'The XMLHttpRequest was aborted.', | ||
), | ||
); | ||
} | ||
} | ||
}); | ||
|
||
if (requestStream != null) { | ||
if (options.method == 'GET') { | ||
debugLog( | ||
'GET request with a body data are not support on the ' | ||
'web platform. Use POST/PUT instead.', | ||
StackTrace.current, | ||
); | ||
} | ||
final completer = Completer<Uint8List>(); | ||
final sink = ByteConversionSink.withCallback( | ||
(bytes) => completer.complete( | ||
bytes is Uint8List ? bytes : Uint8List.fromList(bytes), | ||
), | ||
); | ||
requestStream.listen( | ||
sink.add, | ||
onError: (Object e, StackTrace s) => completer.completeError(e, s), | ||
onDone: sink.close, | ||
cancelOnError: true, | ||
); | ||
final bytes = await completer.future; | ||
xhr.send(bytes); | ||
} else { | ||
xhr.send(); | ||
} | ||
return completer.future.whenComplete(() { | ||
xhrs.remove(xhr); | ||
}); | ||
} | ||
|
||
/// Closes the client. | ||
/// | ||
/// This terminates all active requests. | ||
@override | ||
void close({bool force = false}) { | ||
if (force) { | ||
for (final xhr in xhrs) { | ||
xhr.abort(); | ||
} | ||
} | ||
xhrs.clear(); | ||
} | ||
bool withCredentials; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,7 +25,9 @@ | |
|
||
import 'dart:async'; | ||
|
||
import 'compute_io.dart' if (dart.library.html) 'compute_web.dart' as _c; | ||
import 'compute_io.dart' | ||
if (dart.library.js_util) 'compute_web.dart' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here |
||
if (dart.library.html) 'compute_web.dart' as _c; | ||
|
||
/// Signature for the callback passed to [compute]. | ||
/// | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ import 'dart:async'; | |
import 'adapter.dart'; | ||
import 'cancel_token.dart'; | ||
import 'dio/dio_for_native.dart' | ||
if (dart.library.js_util) 'dio/dio_for_browser.dart' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here |
||
if (dart.library.html) 'dio/dio_for_browser.dart'; | ||
import 'dio_mixin.dart'; | ||
import 'headers.dart'; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ import 'headers.dart'; | |
import 'interceptors/imply_content_type.dart'; | ||
import 'options.dart'; | ||
import 'progress_stream/io_progress_stream.dart' | ||
if (dart.library.js_util) 'progress_stream/browser_progress_stream.dart' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here |
||
if (dart.library.html) 'progress_stream/browser_progress_stream.dart'; | ||
import 'response.dart'; | ||
import 'response/response_stream_handler.dart'; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ import 'dart:convert'; | |
import 'package:http_parser/http_parser.dart'; | ||
|
||
import 'multipart_file/io_multipart_file.dart' | ||
if (dart.library.js_util) 'multipart_file/browser_multipart_file.dart' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same |
||
if (dart.library.html) 'multipart_file/browser_multipart_file.dart'; | ||
import 'utils.dart'; | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be
dart.library.js_interop
as mentioned here: https://dart.dev/interop/js-interop/package-web#conditional-imports