diff options
Diffstat (limited to 'ext/websocket/02_websocketstream.js')
-rw-r--r-- | ext/websocket/02_websocketstream.js | 180 |
1 files changed, 130 insertions, 50 deletions
diff --git a/ext/websocket/02_websocketstream.js b/ext/websocket/02_websocketstream.js index 4eafac88b..01aef45d3 100644 --- a/ext/websocket/02_websocketstream.js +++ b/ext/websocket/02_websocketstream.js @@ -18,7 +18,6 @@ const { ArrayPrototypeJoin, ArrayPrototypeMap, DateNow, - Error, ObjectPrototypeIsPrototypeOf, PromisePrototypeCatch, PromisePrototypeThen, @@ -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", @@ -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); }, @@ -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(); } @@ -261,7 +267,7 @@ 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]); @@ -269,7 +275,7 @@ class WebSocketStream { } case 1005: { /* closed */ - this[_closed].resolve({ code: 1005, reason: "" }); + this[_closed].resolve({ closeCode: 1005, reason: "" }); core.tryClose(this[_rid]); break; } @@ -277,7 +283,7 @@ class WebSocketStream { /* close */ const reason = op_ws_get_error(this[_rid]); this[_closed].resolve({ - code: kind, + closeCode: kind, reason, }); core.tryClose(this[_rid]); @@ -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]); } }; @@ -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; + } + try { - this.close(reason?.code !== undefined ? reason : {}); + this.close({ + closeCode, + reason: reasonString, + }); } catch (_) { this.close(); } @@ -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); @@ -380,38 +400,17 @@ 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()); @@ -419,6 +418,7 @@ class WebSocketStream { }, (err) => { this[_rid] && core.tryClose(this[_rid]); + err = new WebSocketError(err.message); this[_closed].reject(err); }, ); @@ -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 }; |