Skip to content

Commit

Permalink
feat: add support for AbortController. (#606)
Browse files Browse the repository at this point in the history
  • Loading branch information
andycall authored May 19, 2024
2 parents d5de253 + 8cbbbb7 commit 46297da
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 9 deletions.
53 changes: 53 additions & 0 deletions bridge/polyfill/src/abort-signal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const SECRET = {};

// @ts-ignore
export class _AbortSignal extends EventTarget {
public _aborted: boolean;

get aborted() {
return this._aborted;
}

constructor(secret: any) {
super();
if (secret !== SECRET) {
throw new TypeError("Illegal constructor.");
}
this._aborted = false;
}

private _onabort: any;
get onabort() {
return this._aborted;
}
set onabort(callback: any) {
const existing = this._onabort;
if (existing) {
this.removeEventListener("abort", existing);
}
this._onabort = callback;
this.addEventListener("abort", callback);
}

}

export class _AbortController {
public _signal: _AbortSignal;
constructor() {
this._signal = new _AbortSignal(SECRET);
}

get signal() {
return this._signal;
}

abort() {
const signal = this.signal;
if (!signal.aborted) {
signal._aborted = true;
signal.dispatchEvent(new Event("abort"));
}
}
}


33 changes: 24 additions & 9 deletions bridge/polyfill/src/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Copyright (C) 2022-present The WebF authors. All rights reserved.
*/

import { webf } from './webf';
import {webf} from './webf';

function normalizeName(name: any) {
if (typeof name !== 'string') {
Expand Down Expand Up @@ -205,6 +205,13 @@ export class Request extends Body {
}
this.method = normalizeMethod(init.method || this.method || 'GET');
this.mode = init.mode || this.mode || null;
this.signal = init.signal || this.signal || (function () {
if ('AbortController' in window) {
let ctrl = new AbortController();
return ctrl.signal;
}
return undefined;
}());

if ((this.method === 'GET' || this.method === 'HEAD') && body) {
throw new TypeError('Body not allowed for GET or HEAD requests')
Expand All @@ -223,7 +230,7 @@ export class Request extends Body {
// readonly redirect: RequestRedirect; // not supported
// readonly referrer: string; // not supported
// readonly referrerPolicy: ReferrerPolicy;
// readonly signal: AbortSignal; // not supported
readonly signal: AbortSignal; // not supported

readonly url: string;
readonly method: string;
Expand Down Expand Up @@ -294,18 +301,22 @@ export class Response extends Body {

export function fetch(input: Request | string, init?: RequestInit) {
return new Promise((resolve, reject) => {
let url = typeof input === 'string' ? input : input.url;
init = init || {method: 'GET'};
let headers = init.headers || new Headers();
let request = new Request(input, init);

if (request.signal && request.signal.aborted) {
return reject(new DOMException('Aborted', 'AbortError'))
}
let headers = request.headers || new Headers();

if (!(headers instanceof Headers)) {
headers = new Headers(headers);
function abortRequest() {
webf.invokeModule('Fetch', 'abortRequest');
}

webf.invokeModule('Fetch', url, ({
webf.invokeModule('Fetch', request.url, ({
...init,
headers: (headers as Headers).map
}), (e, data) => {
request.signal.removeEventListener('abort', abortRequest);
if (e) return reject(e);
let [err, statusCode, body] = data;
// network error didn't have statusCode
Expand All @@ -318,10 +329,14 @@ export function fetch(input: Request | string, init?: RequestInit) {
status: statusCode
});

res.url = url;
res.url = request.url;

return resolve(res);
});

if (request.signal) {
request.signal.addEventListener('abort', abortRequest)
}
}
);
}
3 changes: 3 additions & 0 deletions bridge/polyfill/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { URL } from './url';
import { webf } from './webf';
import { WebSocket } from './websocket'
import { ResizeObserver } from './resize-observer';
import { _AbortController, _AbortSignal } from './abort-signal';

defineGlobalProperty('console', console);
defineGlobalProperty('Request', Request);
Expand All @@ -42,6 +43,8 @@ defineGlobalProperty('URL', URL);
defineGlobalProperty('webf', webf);
defineGlobalProperty('WebSocket', WebSocket);
defineGlobalProperty('ResizeObserver', ResizeObserver);
defineGlobalProperty('AbortSignal', _AbortSignal);
defineGlobalProperty('AbortController', _AbortController);

function defineGlobalProperty(key: string, value: any, isEnumerable: boolean = true) {
Object.defineProperty(globalThis, key, {
Expand Down
4 changes: 4 additions & 0 deletions integration_tests/scripts/mock_http_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ app.get('/set_cookie', (req, res) => {
res.end();
});

app.get('/unresponse', (req, res) => {

});

app.get('/verify_cookie', (req, res) => {
const query = req.query;
const value = req.cookies[query.id];
Expand Down
20 changes: 20 additions & 0 deletions integration_tests/specs/fetch/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,3 +461,23 @@ describe('Response', function() {
});
});
});

describe('AbortController', () => {
it('should abort request with abortController', (done) => {
const abortController = new AbortController();
const signal = abortController.signal;

fetch(`http://localhost:${location.port}/unresponse`, {
signal
}).then(response => response.text()).then(() => {
done.fail();
}).catch(err => {
expect(err.message.trim()).toBe('HttpException: Request has been aborted');
done();
});

setTimeout(() => {
abortController.abort();
}, 200);
});
});
13 changes: 13 additions & 0 deletions webf/lib/src/module/fetch.dart
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,20 @@ class FetchModule extends BaseModule {
});
}

HttpClientRequest? _currentRequest;

void _abortRequest() {
_currentRequest?.abort();
_currentRequest = null;
}

@override
String invoke(String method, params, InvokeModuleCallback callback) {
if (method == 'abortRequest') {
_abortRequest();
return '';
}

Uri uri = _resolveUri(method);
Map<String, dynamic> options = params;

Expand All @@ -109,6 +121,7 @@ class FetchModule extends BaseModule {

getRequest(uri, options['method'], options['headers'], options['body']).then((HttpClientRequest request) {
if (_disposed) return Future.value(null);
_currentRequest = request;
return request.close();
}).then((HttpClientResponse? res) {
if (res == null) {
Expand Down

0 comments on commit 46297da

Please sign in to comment.