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

chore: update WPT #23111

Merged
merged 18 commits into from
Apr 8, 2024
20 changes: 20 additions & 0 deletions cli/tsc/dts/lib.deno.unstable.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2227,6 +2227,26 @@ declare var WebSocketStream: {
new (url: string, options?: WebSocketStreamOptions): WebSocketStream;
};

/** **UNSTABLE**: New API, yet to be vetted.
*
* @tags allow-net
* @category Web Sockets
*/
declare interface WebSocketError extends DOMException {
readonly closeCode: number;
readonly reason: string;
}

/** **UNSTABLE**: New API, yet to be vetted.
*
* @tags allow-net
* @category Web Sockets
*/
declare var WebSocketError: {
readonly prototype: WebSocketError;
new (message?: string, init?: WebSocketCloseInfo): WebSocketError;
};

// Adapted from `tc39/proposal-temporal`: https://github.com/tc39/proposal-temporal/blob/main/polyfill/index.d.ts

/**
Expand Down
180 changes: 130 additions & 50 deletions ext/websocket/02_websocketstream.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ const {
ArrayPrototypeJoin,
ArrayPrototypeMap,
DateNow,
Error,
ObjectPrototypeIsPrototypeOf,
PromisePrototypeCatch,
PromisePrototypeThen,
Expand Down Expand Up @@ -68,8 +67,12 @@ webidl.converters.WebSocketCloseInfo = webidl.createDictionaryConverter(
"WebSocketCloseInfo",
[
{
key: "code",
converter: webidl.converters["unsigned short"],
key: "closeCode",
converter: (V, prefix, context, opts) =>
webidl.converters["unsigned short"](V, prefix, context, {
...opts,
enforceRange: true,
}),
},
{
key: "reason",
Expand Down Expand Up @@ -189,20 +192,14 @@ class WebSocketStream {
}
})(),
() => {
const err = new DOMException(
"Closed while connecting",
"NetworkError",
);
const err = new WebSocketError("Closed while connecting");
this[_opened].reject(err);
this[_closed].reject(err);
},
);
},
() => {
const err = new DOMException(
"Closed while connecting",
"NetworkError",
);
const err = new WebSocketError("Closed while connecting");
this[_opened].reject(err);
this[_closed].reject(err);
},
Expand All @@ -225,17 +222,26 @@ class WebSocketStream {
);
}
},
close: async (reason) => {
try {
this.close(reason?.code !== undefined ? reason : {});
} catch (_) {
this.close();
}
close: async () => {
this.close();
await this.closed;
},
abort: async (reason) => {
let closeCode = null;
let reasonString = "";

if (
ObjectPrototypeIsPrototypeOf(WebSocketErrorPrototype, reason)
) {
closeCode = reason.closeCode;
reasonString = reason.reason;
}

try {
this.close(reason?.code !== undefined ? reason : {});
this.close({
closeCode,
reason: reasonString,
});
} catch (_) {
this.close();
}
Expand All @@ -261,23 +267,23 @@ class WebSocketStream {
}
case 3: {
/* error */
const err = new Error(op_ws_get_error(this[_rid]));
const err = new WebSocketError(op_ws_get_error(this[_rid]));
this[_closed].reject(err);
controller.error(err);
core.tryClose(this[_rid]);
break;
}
case 1005: {
/* closed */
this[_closed].resolve({ code: 1005, reason: "" });
this[_closed].resolve({ closeCode: 1005, reason: "" });
core.tryClose(this[_rid]);
break;
}
default: {
/* close */
const reason = op_ws_get_error(this[_rid]);
this[_closed].resolve({
code: kind,
closeCode: kind,
reason,
});
core.tryClose(this[_rid]);
Expand All @@ -297,7 +303,7 @@ class WebSocketStream {
}

const error = op_ws_get_error(this[_rid]);
this[_closed].reject(new Error(error));
this[_closed].reject(new WebSocketError(error));
core.tryClose(this[_rid]);
}
};
Expand Down Expand Up @@ -327,8 +333,21 @@ class WebSocketStream {
},
pull,
cancel: async (reason) => {
let closeCode = null;
let reasonString = "";

if (
ObjectPrototypeIsPrototypeOf(WebSocketErrorPrototype, reason)
) {
closeCode = reason.closeCode;
reasonString = reason.reason;
}
littledivy marked this conversation as resolved.
Show resolved Hide resolved

try {
this.close(reason?.code !== undefined ? reason : {});
this.close({
closeCode,
reason: reasonString,
});
} catch (_) {
this.close();
}
Expand All @@ -350,6 +369,7 @@ class WebSocketStream {
err = options.signal.reason;
} else {
core.tryClose(cancelRid);
err = new WebSocketError(err.message);
}
this[_opened].reject(err);
this[_closed].reject(err);
Expand Down Expand Up @@ -380,45 +400,25 @@ class WebSocketStream {
"Argument 1",
);

if (
closeInfo.code &&
!(closeInfo.code === 1000 ||
(3000 <= closeInfo.code && closeInfo.code < 5000))
) {
throw new DOMException(
"The close code must be either 1000 or in the range of 3000 to 4999.",
"InvalidAccessError",
);
}
validateCloseCodeAndReason(closeInfo);

const encoder = new TextEncoder();
if (
closeInfo.reason &&
TypedArrayPrototypeGetByteLength(encoder.encode(closeInfo.reason)) > 123
) {
throw new DOMException(
"The close reason may not be longer than 123 bytes.",
"SyntaxError",
);
}

let code = closeInfo.code;
if (closeInfo.reason && code === undefined) {
code = 1000;
if (closeInfo.reason && closeInfo.closeCode === null) {
closeInfo.closeCode = 1000;
}

if (this[_opened].state === "pending") {
this[_earlyClose] = true;
} else if (this[_closed].state === "pending") {
PromisePrototypeThen(
op_ws_close(this[_rid], code, closeInfo.reason),
op_ws_close(this[_rid], closeInfo.closeCode, closeInfo.reason),
() => {
setTimeout(() => {
this[_closeSent].resolve(DateNow());
}, 0);
},
(err) => {
this[_rid] && core.tryClose(this[_rid]);
err = new WebSocketError(err.message);
this[_closed].reject(err);
},
);
Expand All @@ -440,7 +440,87 @@ class WebSocketStream {
);
}
}

const WebSocketStreamPrototype = WebSocketStream.prototype;

export { WebSocketStream };
function validateCloseCodeAndReason(closeInfo) {
if (!closeInfo.closeCode) {
closeInfo.closeCode = null;
}

if (
closeInfo.closeCode &&
!(closeInfo.closeCode === 1000 ||
(3000 <= closeInfo.closeCode && closeInfo.closeCode < 5000))
) {
throw new DOMException(
"The close code must be either 1000 or in the range of 3000 to 4999.",
"InvalidAccessError",
);
}

const encoder = new TextEncoder();
if (
closeInfo.reason &&
TypedArrayPrototypeGetByteLength(encoder.encode(closeInfo.reason)) > 123
) {
throw new DOMException(
"The close reason may not be longer than 123 bytes.",
"SyntaxError",
);
}
}

class WebSocketError extends DOMException {
#closeCode;
#reason;

constructor(message = "", init = {}) {
super(message, "WebSocketError");
this[webidl.brand] = webidl.brand;

init = webidl.converters["WebSocketCloseInfo"](
init,
"Failed to construct 'WebSocketError'",
"Argument 2",
);

validateCloseCodeAndReason(init);

if (init.reason && init.closeCode === null) {
init.closeCode = 1000;
}

this.#closeCode = init.closeCode;
this.#reason = init.reason;
}

get closeCode() {
webidl.assertBranded(this, WebSocketErrorPrototype);
return this.#closeCode;
}

get reason() {
webidl.assertBranded(this, WebSocketErrorPrototype);
return this.#reason;
}

[SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
return inspect(
createFilteredInspectProxy({
object: this,
evaluate: ObjectPrototypeIsPrototypeOf(WebSocketErrorPrototype, this),
keys: [
"message",
"name",
"closeCode",
"reason",
],
}),
inspectOptions,
);
}
}
webidl.configureInterface(WebSocketError);
const WebSocketErrorPrototype = WebSocketError.prototype;

export { WebSocketError, WebSocketStream };
1 change: 1 addition & 0 deletions runtime/js/98_global_scope_shared.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ unstableForWindowOrWorkerGlobalScope[unstableIds.broadcastChannel] = {
};
unstableForWindowOrWorkerGlobalScope[unstableIds.net] = {
WebSocketStream: core.propNonEnumerable(webSocketStream.WebSocketStream),
WebSocketError: core.propNonEnumerable(webSocketStream.WebSocketError),
};
// deno-fmt-ignore
unstableForWindowOrWorkerGlobalScope[unstableIds.webgpu] = {
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/lsp_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5144,7 +5144,7 @@ fn lsp_jsr_auto_import_completion() {
json!({ "triggerKind": 1 }),
);
assert!(!list.is_incomplete);
assert_eq!(list.items.len(), 262);
assert_eq!(list.items.len(), 263);
let item = list.items.iter().find(|i| i.label == "add").unwrap();
assert_eq!(&item.label, "add");
assert_eq!(
Expand Down Expand Up @@ -5224,7 +5224,7 @@ fn lsp_jsr_auto_import_completion_import_map() {
json!({ "triggerKind": 1 }),
);
assert!(!list.is_incomplete);
assert_eq!(list.items.len(), 262);
assert_eq!(list.items.len(), 263);
let item = list.items.iter().find(|i| i.label == "add").unwrap();
assert_eq!(&item.label, "add");
assert_eq!(json!(&item.label_details), json!({ "description": "add" }));
Expand Down
Loading