Skip to content

Commit 4f4cdee

Browse files
⚡️ Split web_adapter (#2223)
Breaks the web business into a separate package to prepare for further changes regarding the WASM builds. ### Additional context and info (if any) Here we put a v1 for the package. Once it works, we will migrate to the js_interop and `package:web` implementation and publish v2. --------- Co-authored-by: hhh <[email protected]>
1 parent b87a3b0 commit 4f4cdee

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+1248
-455
lines changed

.github/ISSUE_TEMPLATE/bug.yml

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ body:
2323
- cookie_manager
2424
- http2_adapter
2525
- native_dio_adapter
26+
- web_adapter
2627
validations:
2728
required: true
2829
- type: input

.github/workflows/tests.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ jobs:
3939
cache: true
4040
flutter-version: ${{ matrix.sdk == 'min' && '3.3.0' || '' }}
4141
channel: ${{ matrix.sdk == 'min' && '' || matrix.channel }}
42-
- run: dart pub get
42+
- name: Prepare dependencies for the project management
43+
run: dart pub get
4344
- uses: bluefireteam/melos-action@v3
4445
with:
4546
run-bootstrap: false

.idea/modules.xml

+2-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README-ZH.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,12 @@ Language: [English](README.md) | 简体中文
3535
[![Pub](https://img.shields.io/pub/v/dio_http2_adapter.svg?label=dev&include_prereleases)](https://pub.flutter-io.cn/packages/dio_http2_adapter)
3636
- native_dio_adapter: [链接](plugins/native_dio_adapter)
3737
[![Pub](https://img.shields.io/pub/v/native_dio_adapter.svg?label=dev&include_prereleases)](https://pub.dev/packages/native_dio_adapter)
38+
- web_adapter: [链接](plugins/web_adapter)
39+
[![Pub](https://img.shields.io/pub/v/dio_web_adapter.svg?label=dev&include_prereleases)](https://pub.dev/packages/dio_web_adapter)
3840

3941
### 示例
4042

41-
- example: [链接](example)
43+
- example: [链接](example_dart)
4244
- example_flutter_app: [链接](example_flutter_app)
4345

4446
## 版权 & 协议

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,12 @@ To know about our compatibility policy, see the [Compatibility Policy][] doc.
3636
[![Pub](https://img.shields.io/pub/v/dio_http2_adapter.svg?label=dev&include_prereleases)](https://pub.dev/packages/dio_http2_adapter)
3737
- native_dio_adapter: [link](plugins/native_dio_adapter)
3838
[![Pub](https://img.shields.io/pub/v/native_dio_adapter.svg?label=dev&include_prereleases)](https://pub.dev/packages/native_dio_adapter)
39+
- web_adapter: [link](plugins/web_adapter)
40+
[![Pub](https://img.shields.io/pub/v/dio_web_adapter.svg?label=dev&include_prereleases)](https://pub.dev/packages/dio_web_adapter)
3941

4042
### Examples
4143

42-
- example: [link](example)
44+
- example: [link](example_dart)
4345
- example_flutter_app: [link](example_flutter_app)
4446

4547
## Copyright & License

dio/lib/src/adapter.dart

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'dart:typed_data';
44
import 'package:meta/meta.dart';
55

66
import 'adapters/io_adapter.dart'
7+
if (dart.library.js_interop) 'adapters/browser_adapter.dart'
78
if (dart.library.html) 'adapters/browser_adapter.dart' as adapter;
89
import 'headers.dart';
910
import 'options.dart';
+2-313
Original file line numberDiff line numberDiff line change
@@ -1,313 +1,2 @@
1-
import 'dart:async';
2-
import 'dart:convert';
3-
import 'dart:html';
4-
import 'dart:typed_data';
5-
6-
import 'package:meta/meta.dart';
7-
8-
import '../adapter.dart';
9-
import '../dio_exception.dart';
10-
import '../headers.dart';
11-
import '../options.dart';
12-
import '../utils.dart';
13-
14-
HttpClientAdapter createAdapter() => BrowserHttpClientAdapter();
15-
16-
/// The default [HttpClientAdapter] for Web platforms.
17-
class BrowserHttpClientAdapter implements HttpClientAdapter {
18-
BrowserHttpClientAdapter({this.withCredentials = false});
19-
20-
/// These are aborted if the client is closed.
21-
@visibleForTesting
22-
final xhrs = <HttpRequest>{};
23-
24-
/// Whether to send credentials such as cookies or authorization headers for
25-
/// cross-site requests.
26-
///
27-
/// Defaults to `false`.
28-
///
29-
/// You can also override this value using `Options.extra['withCredentials']`
30-
/// for each request.
31-
bool withCredentials;
32-
33-
@override
34-
Future<ResponseBody> fetch(
35-
RequestOptions options,
36-
Stream<Uint8List>? requestStream,
37-
Future<void>? cancelFuture,
38-
) async {
39-
final xhr = HttpRequest();
40-
xhrs.add(xhr);
41-
xhr
42-
..open(options.method, '${options.uri}')
43-
..responseType = 'arraybuffer';
44-
45-
final withCredentialsOption = options.extra['withCredentials'];
46-
if (withCredentialsOption != null) {
47-
xhr.withCredentials = withCredentialsOption == true;
48-
} else {
49-
xhr.withCredentials = withCredentials;
50-
}
51-
52-
options.headers.remove(Headers.contentLengthHeader);
53-
options.headers.forEach((key, v) {
54-
if (v is Iterable) {
55-
xhr.setRequestHeader(key, v.join(', '));
56-
} else {
57-
xhr.setRequestHeader(key, v.toString());
58-
}
59-
});
60-
61-
final sendTimeout = options.sendTimeout ?? Duration.zero;
62-
final connectTimeout = options.connectTimeout ?? Duration.zero;
63-
final receiveTimeout = options.receiveTimeout ?? Duration.zero;
64-
final xhrTimeout = (connectTimeout + receiveTimeout).inMilliseconds;
65-
xhr.timeout = xhrTimeout;
66-
67-
final completer = Completer<ResponseBody>();
68-
69-
xhr.onLoad.first.then((_) {
70-
final Uint8List body = (xhr.response as ByteBuffer).asUint8List();
71-
completer.complete(
72-
ResponseBody.fromBytes(
73-
body,
74-
xhr.status!,
75-
headers: xhr.responseHeaders.map((k, v) => MapEntry(k, v.split(','))),
76-
statusMessage: xhr.statusText,
77-
isRedirect: xhr.status == 302 ||
78-
xhr.status == 301 ||
79-
options.uri.toString() != xhr.responseUrl,
80-
),
81-
);
82-
});
83-
84-
Timer? connectTimeoutTimer;
85-
if (connectTimeout > Duration.zero) {
86-
connectTimeoutTimer = Timer(
87-
connectTimeout,
88-
() {
89-
connectTimeoutTimer = null;
90-
if (completer.isCompleted) {
91-
// connectTimeout is triggered after the fetch has been completed.
92-
return;
93-
}
94-
xhr.abort();
95-
completer.completeError(
96-
DioException.connectionTimeout(
97-
requestOptions: options,
98-
timeout: connectTimeout,
99-
),
100-
StackTrace.current,
101-
);
102-
},
103-
);
104-
}
105-
106-
// This code is structured to call `xhr.upload.onProgress.listen` only when
107-
// absolutely necessary, because registering an xhr upload listener prevents
108-
// the request from being classified as a "simple request" by the CORS spec.
109-
// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests
110-
// Upload progress events only get triggered if the request body exists,
111-
// so we can check it beforehand.
112-
if (requestStream != null) {
113-
if (connectTimeoutTimer != null) {
114-
xhr.upload.onProgress.listen((event) {
115-
connectTimeoutTimer?.cancel();
116-
connectTimeoutTimer = null;
117-
});
118-
}
119-
120-
if (sendTimeout > Duration.zero) {
121-
final uploadStopwatch = Stopwatch();
122-
xhr.upload.onProgress.listen((event) {
123-
if (!uploadStopwatch.isRunning) {
124-
uploadStopwatch.start();
125-
}
126-
final duration = uploadStopwatch.elapsed;
127-
if (duration > sendTimeout) {
128-
uploadStopwatch.stop();
129-
completer.completeError(
130-
DioException.sendTimeout(
131-
timeout: sendTimeout,
132-
requestOptions: options,
133-
),
134-
StackTrace.current,
135-
);
136-
xhr.abort();
137-
}
138-
});
139-
}
140-
141-
final onSendProgress = options.onSendProgress;
142-
if (onSendProgress != null) {
143-
xhr.upload.onProgress.listen((event) {
144-
if (event.loaded != null && event.total != null) {
145-
onSendProgress(event.loaded!, event.total!);
146-
}
147-
});
148-
}
149-
} else {
150-
if (sendTimeout > Duration.zero) {
151-
debugLog(
152-
'sendTimeout cannot be used without a request body to send',
153-
StackTrace.current,
154-
);
155-
}
156-
if (options.onSendProgress != null) {
157-
debugLog(
158-
'onSendProgress cannot be used without a request body to send',
159-
StackTrace.current,
160-
);
161-
}
162-
}
163-
164-
final receiveStopwatch = Stopwatch();
165-
Timer? receiveTimer;
166-
167-
void stopWatchReceiveTimeout() {
168-
receiveTimer?.cancel();
169-
receiveTimer = null;
170-
receiveStopwatch.stop();
171-
}
172-
173-
void watchReceiveTimeout() {
174-
if (receiveTimeout <= Duration.zero) {
175-
return;
176-
}
177-
receiveStopwatch.reset();
178-
if (!receiveStopwatch.isRunning) {
179-
receiveStopwatch.start();
180-
}
181-
receiveTimer?.cancel();
182-
receiveTimer = Timer(receiveTimeout, () {
183-
if (!completer.isCompleted) {
184-
xhr.abort();
185-
completer.completeError(
186-
DioException.receiveTimeout(
187-
timeout: receiveTimeout,
188-
requestOptions: options,
189-
),
190-
StackTrace.current,
191-
);
192-
}
193-
stopWatchReceiveTimeout();
194-
});
195-
}
196-
197-
xhr.onProgress.listen(
198-
(ProgressEvent event) {
199-
if (connectTimeoutTimer != null) {
200-
connectTimeoutTimer!.cancel();
201-
connectTimeoutTimer = null;
202-
}
203-
watchReceiveTimeout();
204-
if (options.onReceiveProgress != null &&
205-
event.loaded != null &&
206-
event.total != null) {
207-
options.onReceiveProgress!(event.loaded!, event.total!);
208-
}
209-
},
210-
onDone: () => stopWatchReceiveTimeout(),
211-
);
212-
213-
xhr.onError.first.then((_) {
214-
connectTimeoutTimer?.cancel();
215-
// Unfortunately, the underlying XMLHttpRequest API doesn't expose any
216-
// specific information about the error itself.
217-
// See also: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequestEventTarget/onerror
218-
completer.completeError(
219-
DioException.connectionError(
220-
requestOptions: options,
221-
reason: 'The XMLHttpRequest onError callback was called. '
222-
'This typically indicates an error on the network layer.',
223-
),
224-
StackTrace.current,
225-
);
226-
});
227-
228-
xhr.onTimeout.first.then((_) {
229-
final isConnectTimeout = connectTimeoutTimer != null;
230-
if (connectTimeoutTimer != null) {
231-
connectTimeoutTimer?.cancel();
232-
}
233-
if (!completer.isCompleted) {
234-
if (isConnectTimeout) {
235-
completer.completeError(
236-
DioException.connectionTimeout(
237-
timeout: connectTimeout,
238-
requestOptions: options,
239-
),
240-
);
241-
} else {
242-
completer.completeError(
243-
DioException.receiveTimeout(
244-
timeout: Duration(milliseconds: xhrTimeout),
245-
requestOptions: options,
246-
),
247-
StackTrace.current,
248-
);
249-
}
250-
}
251-
});
252-
253-
cancelFuture?.then((_) {
254-
if (xhr.readyState < HttpRequest.DONE &&
255-
xhr.readyState > HttpRequest.UNSENT) {
256-
connectTimeoutTimer?.cancel();
257-
try {
258-
xhr.abort();
259-
} catch (_) {}
260-
if (!completer.isCompleted) {
261-
completer.completeError(
262-
DioException.requestCancelled(
263-
requestOptions: options,
264-
reason: 'The XMLHttpRequest was aborted.',
265-
),
266-
);
267-
}
268-
}
269-
});
270-
271-
if (requestStream != null) {
272-
if (options.method == 'GET') {
273-
debugLog(
274-
'GET request with a body data are not support on the '
275-
'web platform. Use POST/PUT instead.',
276-
StackTrace.current,
277-
);
278-
}
279-
final completer = Completer<Uint8List>();
280-
final sink = ByteConversionSink.withCallback(
281-
(bytes) => completer.complete(
282-
bytes is Uint8List ? bytes : Uint8List.fromList(bytes),
283-
),
284-
);
285-
requestStream.listen(
286-
sink.add,
287-
onError: (Object e, StackTrace s) => completer.completeError(e, s),
288-
onDone: sink.close,
289-
cancelOnError: true,
290-
);
291-
final bytes = await completer.future;
292-
xhr.send(bytes);
293-
} else {
294-
xhr.send();
295-
}
296-
return completer.future.whenComplete(() {
297-
xhrs.remove(xhr);
298-
});
299-
}
300-
301-
/// Closes the client.
302-
///
303-
/// This terminates all active requests.
304-
@override
305-
void close({bool force = false}) {
306-
if (force) {
307-
for (final xhr in xhrs) {
308-
xhr.abort();
309-
}
310-
}
311-
xhrs.clear();
312-
}
313-
}
1+
export 'package:dio_web_adapter/dio_web_adapter.dart'
2+
show createAdapter, BrowserHttpClientAdapter;

dio/lib/src/compute/compute.dart

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525

2626
import 'dart:async';
2727

28-
import 'compute_io.dart' if (dart.library.html) 'compute_web.dart' as _c;
28+
import 'compute_io.dart'
29+
if (dart.library.js_interop) 'compute_web.dart'
30+
if (dart.library.html) 'compute_web.dart' as _c;
2931

3032
/// Signature for the callback passed to [compute].
3133
///

0 commit comments

Comments
 (0)