Skip to content

Commit

Permalink
Test fixes WiP
Browse files Browse the repository at this point in the history
  • Loading branch information
Binozo committed Feb 8, 2025
1 parent ba1218f commit dc4efc3
Showing 1 changed file with 118 additions and 40 deletions.
158 changes: 118 additions & 40 deletions plugins/web_adapter/lib/src/adapter.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'dart:async';
import 'dart:js_interop';
import 'dart:js_interop_unsafe';
import 'dart:typed_data';

import 'package:dio/dio.dart';
Expand Down Expand Up @@ -45,20 +44,21 @@ class BrowserHttpClientAdapter implements HttpClientAdapter {
request.credentials = withCredentialsOption ? 'include' : 'same-origin';

options.headers.remove(Headers.contentLengthHeader);
final Map<String, String> requestHeaders = {};
options.headers.forEach((key, v) {
if (v is Iterable) {
request.headers.setProperty(key.toJS, v.join(', ').toJS);
requestHeaders[key] = v.join(', ');
} else {
request.headers.setProperty(key.toJS, v.toString().toJS);
requestHeaders[key] = v.toString();
}
});
request.headers = requestHeaders.jsify()! as web.HeadersInit;

final onSendProgress = options.onSendProgress;
final sendTimeout = options.sendTimeout ?? Duration.zero;
final connectTimeout = options.connectTimeout ?? Duration.zero;
final receiveTimeout = options.receiveTimeout ?? Duration.zero;

final fetchTimeout = connectTimeout + receiveTimeout;
final abortController = web.AbortController();
request.signal = abortController.signal;
_abortables.add(abortController);
Expand All @@ -73,12 +73,14 @@ class BrowserHttpClientAdapter implements HttpClientAdapter {
completer.completeError(
DioException.requestCancelled(
requestOptions: options,
reason: 'The Fetch request was aborted.',
reason: 'cancelled',
stackTrace: StackTrace.current,
),
);
}
});

int totalPayload = -1;
if (requestStream == null) {
if (sendTimeout > Duration.zero) {
warningLog(
Expand All @@ -92,52 +94,74 @@ class BrowserHttpClientAdapter implements HttpClientAdapter {
StackTrace.current,
);
}
} else {
} else if (options.method != 'GET' && options.method != 'HEAD') {
if (options.method == 'GET') {
warningLog(
'GET request with a body data are not support on the '
'web platform. Use POST/PUT instead.',
StackTrace.current,
);
}
request.duplex = 'half';

Stopwatch? uploadStopwatch;
if (sendTimeout > Duration.zero) {
uploadStopwatch = Stopwatch();
}

int sentBytes = 0;
final streamReader = ReadableStream(
ReadableStreamSource.fromStream(
requestStream.map((e) {
if (uploadStopwatch != null) {
if (!uploadStopwatch.isRunning) {
uploadStopwatch.start();
if (_isFirefox()) {
// Firefox has a 8 year old bug preventing us from sending a StreamReader body. Not the actual data gets sent but '[object ReadableStream]'
// https://bugzilla.mozilla.org/show_bug.cgi?id=1387483c
// Here is a demonstration script: https://jsfiddle.net/qLe8gbmu/4/

// For now we read the entire data and send it at once

final bytesBuilder = BytesBuilder();
final List<Uint8List> rawData = await requestStream.toList();
for (int i = 0; i < rawData.length; i++) {
bytesBuilder.add(rawData[i]);
}

totalPayload = bytesBuilder.length;
if (options.onSendProgress != null) {
options.onSendProgress!(0, totalPayload);
}

request.body = bytesBuilder.toBytes().toJS;
} else {
int sentBytes = 0;
final streamReader = ReadableStream(
ReadableStreamSource.fromStream(
requestStream.map((Uint8List e) {
if (uploadStopwatch != null) {
if (!uploadStopwatch.isRunning) {
uploadStopwatch.start();
}

if (uploadStopwatch.elapsed > sendTimeout) {
uploadStopwatch.stop();
completer.completeError(
DioException.sendTimeout(
timeout: sendTimeout,
requestOptions: options,
),
StackTrace.current,
);
abortController.abort();
}
}

if (uploadStopwatch.elapsed > sendTimeout) {
uploadStopwatch.stop();
completer.completeError(
DioException.sendTimeout(
timeout: sendTimeout,
requestOptions: options,
),
StackTrace.current,
);
abortController.abort();
sentBytes += e.lengthInBytes;
if (options.onSendProgress != null) {
options.onSendProgress!(sentBytes, totalPayload);
}
}

sentBytes += e.lengthInBytes;
if (options.onSendProgress != null) {
options.onSendProgress!(sentBytes, -1);
}
return e.toJS;
}),
),
);
return e.toJS;
}),
),
);

request.body = streamReader;
request.body = streamReader;
}
}

// Now send
Expand All @@ -146,11 +170,16 @@ class BrowserHttpClientAdapter implements HttpClientAdapter {

late final web.Response response;
try {
if (fetchTimeout > Duration.zero) {
response = await requestPrototype.timeout(fetchTimeout);
if (connectTimeout > Duration.zero) {
response = await requestPrototype.timeout(connectTimeout);
} else {
response = await requestPrototype;
}
if (_isFirefox() && options.onSendProgress != null) {
if (options.onSendProgress != null) {
options.onSendProgress!(totalPayload, totalPayload);
}
}
} on TimeoutException catch (timeoutException) {
completer.completeError(
DioException.connectionTimeout(
Expand All @@ -162,6 +191,10 @@ class BrowserHttpClientAdapter implements HttpClientAdapter {

return completer.future;
} catch (exception, stackTrace) {
if (completer.isCompleted) {
return completer.future;
}

completer.completeError(
DioException.connectionError(
requestOptions: options,
Expand Down Expand Up @@ -189,6 +222,28 @@ class BrowserHttpClientAdapter implements HttpClientAdapter {
}.toJS,
);

// Request may got cancelled
if (completer.isCompleted) {
return completer.future;
}

// Check CORS
if (response.status == 418) {
if (options.onSendProgress != null ||
sendTimeout > Duration.zero ||
(request.method != 'GET' &&
options.contentType != Headers.textPlainContentType)) {
completer.completeError(
DioException.connectionError(
requestOptions: options,
error: 'CORS preflight request failed',
reason: 'The preflight request responded with status 418',
),
StackTrace.current,
);
}
}

final BytesBuilder receivedBody = BytesBuilder();
final int totalResponseLength = int.tryParse(
response.headers.get(Headers.contentLengthHeader) ?? '-1',
Expand All @@ -214,13 +269,14 @@ class BrowserHttpClientAdapter implements HttpClientAdapter {
int totalRead = 0;
while (true) {
final web.ReadableStreamReadResult chunk = await reader.read().toDart;
if (chunk.done) {
dataStreamController?.close();
break;

if (completer.isCompleted) {
// Request may got cancelled
return completer.future;
}

if (receiveStopwatch != null &&
receiveStopwatch.elapsed > receiveTimeout) {
receiveStopwatch.elapsed >= receiveTimeout) {
receiveStopwatch.stop();
abortController.abort();

Expand All @@ -231,6 +287,12 @@ class BrowserHttpClientAdapter implements HttpClientAdapter {
),
StackTrace.current,
);
return completer.future;
}

if (chunk.done) {
dataStreamController?.close();
break;
}

// https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader/read#examples
Expand All @@ -250,6 +312,10 @@ class BrowserHttpClientAdapter implements HttpClientAdapter {
if (options.responseType == ResponseType.stream) {
readResponse();

if (completer.isCompleted) {
return completer.future;
}

completer.complete(
ResponseBody(
dataStreamController!.stream,
Expand All @@ -262,6 +328,10 @@ class BrowserHttpClientAdapter implements HttpClientAdapter {
} else {
await readResponse();

if (completer.isCompleted) {
return completer.future;
}

completer.complete(
ResponseBody.fromBytes(
receivedBody.toBytes(),
Expand All @@ -275,6 +345,10 @@ class BrowserHttpClientAdapter implements HttpClientAdapter {
} else {
// No response data

if (completer.isCompleted) {
return completer.future;
}

completer.complete(
ResponseBody.fromBytes(
Uint8List(0),
Expand Down Expand Up @@ -305,6 +379,10 @@ class BrowserHttpClientAdapter implements HttpClientAdapter {
}
}

bool _isFirefox() {
return web.window.navigator.userAgent.toLowerCase().contains('firefox');
}

/// Workaround for `Headers` not providing a way to iterate the headers.
/// https://github.com/dart-lang/http/blob/aadf8363a83dd211bb56c36ac301396437b9282b/pkgs/http/lib/src/browser_client.dart#L184
@JS()
Expand Down

0 comments on commit dc4efc3

Please sign in to comment.