Skip to content
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

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion dio/lib/browser.dart
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export 'src/adapters/browser_adapter.dart' show BrowserHttpClientAdapter;
export 'src/dio/dio_for_browser.dart' show DioForBrowser;
2 changes: 1 addition & 1 deletion dio/lib/src/adapter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'dart:typed_data';
import 'package:meta/meta.dart';

import 'adapters/io_adapter.dart'
if (dart.library.html) 'adapters/browser_adapter.dart' as adapter;
if (dart.library.js_util) 'adapters/browser_adapter.dart' as adapter;
import 'headers.dart';
import 'options.dart';
import 'redirect_record.dart';
Expand Down
310 changes: 1 addition & 309 deletions dio/lib/src/adapters/browser_adapter.dart
Original file line number Diff line number Diff line change
@@ -1,313 +1,5 @@
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 {
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();
}
}
2 changes: 1 addition & 1 deletion dio/lib/src/compute/compute.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

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' as _c;

/// Signature for the callback passed to [compute].
///
Expand Down
2 changes: 1 addition & 1 deletion dio/lib/src/dio.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'dart:async';
import 'adapter.dart';
import 'cancel_token.dart';
import 'dio/dio_for_native.dart'
if (dart.library.html) 'dio/dio_for_browser.dart';
if (dart.library.js_util) 'dio/dio_for_browser.dart';
import 'dio_mixin.dart';
import 'headers.dart';
import 'options.dart';
Expand Down
3 changes: 2 additions & 1 deletion dio/lib/src/dio/dio_for_browser.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import '../adapters/browser_adapter.dart';
import 'package:dio_browser_adapter/dio_browser_adapter.dart';

import '../cancel_token.dart';
import '../dio.dart';
import '../dio_mixin.dart';
Expand Down
2 changes: 1 addition & 1 deletion dio/lib/src/dio_mixin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import 'headers.dart';
import 'interceptors/imply_content_type.dart';
import 'options.dart';
import 'progress_stream/io_progress_stream.dart'
if (dart.library.html) 'progress_stream/browser_progress_stream.dart';
if (dart.library.js_util) 'progress_stream/browser_progress_stream.dart';
import 'response.dart';
import 'response/response_stream_handler.dart';
import 'transformer.dart';
Expand Down
2 changes: 1 addition & 1 deletion dio/lib/src/multipart_file.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'dart:convert';
import 'package:http_parser/http_parser.dart';

import 'multipart_file/io_multipart_file.dart'
if (dart.library.html) 'multipart_file/browser_multipart_file.dart';
if (dart.library.js_util) 'multipart_file/browser_multipart_file.dart';
import 'utils.dart';

/// A file to be uploaded as part of a [MultipartRequest]. This doesn't need to
Expand Down
4 changes: 3 additions & 1 deletion dio/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ dependencies:
http_parser: ^4.0.0
meta: ^1.5.0
path: ^1.8.0

# TODO: Modify the version of the package after it is published.
dio_browser_adapter:

dev_dependencies:
lints: any
test: ^1.5.1
Expand Down
Loading
Loading