Skip to content

Commit

Permalink
♻️ Move all source Dart files to *_impl.dart to avoid naming collision
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexV525 committed Feb 25, 2025
1 parent 890305a commit 4b06b75
Show file tree
Hide file tree
Showing 12 changed files with 423 additions and 398 deletions.
5 changes: 5 additions & 0 deletions plugins/web_adapter/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

*None.*

## 1.1.1

- Move all source Dart files to `*_impl.dart` to avoid naming collision.
This is a workaround of https://github.com/dart-lang/sdk/issues/56498.

## 1.1.0

- Support `FileAccessMode` in `Dio.download` and `Dio.downloadUri` to change download file opening mode.
Expand Down
8 changes: 4 additions & 4 deletions plugins/web_adapter/lib/dio_web_adapter.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
library dio_web_adapter;

export 'src/adapter.dart';
export 'src/compute.dart';
export 'src/adapter_impl.dart';
export 'src/compute_impl.dart';
export 'src/dio_impl.dart';
export 'src/multipart_file.dart';
export 'src/progress_stream.dart';
export 'src/multipart_file_impl.dart';
export 'src/progress_stream_impl.dart';
301 changes: 5 additions & 296 deletions plugins/web_adapter/lib/src/adapter.dart
Original file line number Diff line number Diff line change
@@ -1,296 +1,5 @@
import 'dart:async';
import 'dart:convert';
import 'dart:html';
import 'dart:typed_data';

import 'package:dio/dio.dart';
import 'package:dio/src/utils.dart';
import 'package:meta/meta.dart';

BrowserHttpClientAdapter 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!);
}
});
}
}

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') {
warningLog(
'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();
}
}
@Deprecated(
'Import adapter_impl.dart instead. '
'See https://github.com/dart-lang/sdk/issues/56498',
)
export 'adapter_impl.dart';
Loading

0 comments on commit 4b06b75

Please sign in to comment.