diff options
Diffstat (limited to 'extensions/websocket/01_websocket.js')
-rw-r--r-- | extensions/websocket/01_websocket.js | 322 |
1 files changed, 206 insertions, 116 deletions
diff --git a/extensions/websocket/01_websocket.js b/extensions/websocket/01_websocket.js index 3c3e12f83..225c6725b 100644 --- a/extensions/websocket/01_websocket.js +++ b/extensions/websocket/01_websocket.js @@ -3,28 +3,41 @@ ((window) => { const core = window.Deno.core; - - // provided by "deno_web" const { URL } = window.__bootstrap.url; + const webidl = window.__bootstrap.webidl; + const { HTTP_TOKEN_CODE_POINT_RE } = window.__bootstrap.infra; + + webidl.converters["sequence<DOMString> or DOMString"] = (V, opts) => { + // Union for (sequence<DOMString> or DOMString) + if (webidl.type(V) === "Object" && V !== null) { + if (V[Symbol.iterator] !== undefined) { + return webidl.converters["sequence<DOMString>"](V, opts); + } + } + return webidl.converters.DOMString(V, opts); + }; + + webidl.converters["WebSocketSend"] = (V, opts) => { + // Union for (Blob or ArrayBufferView or ArrayBuffer or USVString) + if (V instanceof Blob) { + return webidl.converters["Blob"](V, opts); + } + if (typeof V === "object") { + if (V instanceof ArrayBuffer || V instanceof SharedArrayBuffer) { + return webidl.converters["ArrayBuffer"](V, opts); + } + if (ArrayBuffer.isView(V)) { + return webidl.converters["ArrayBufferView"](V, opts); + } + } + return webidl.converters["USVString"](V, opts); + }; const CONNECTING = 0; const OPEN = 1; const CLOSING = 2; const CLOSED = 3; - function requiredArguments( - name, - length, - required, - ) { - if (length < required) { - const errMsg = `${name} requires at least ${required} argument${ - required === 1 ? "" : "s" - }, but only ${length} present`; - throw new TypeError(errMsg); - } - } - /** * Tries to close the resource (and ignores BadResource errors). * @param {number} rid @@ -74,14 +87,104 @@ }); } + const _readyState = Symbol("[[readyState]]"); + const _url = Symbol("[[url]]"); + const _rid = Symbol("[[rid]]"); + const _extensions = Symbol("[[extensions]]"); + const _protocol = Symbol("[[protocol]]"); + const _binaryType = Symbol("[[binaryType]]"); + const _bufferedAmount = Symbol("[[bufferedAmount]]"); class WebSocket extends EventTarget { - #readyState = CONNECTING; + [_rid]; + + [_readyState] = CONNECTING; + get readyState() { + webidl.assertBranded(this, WebSocket); + return this[_readyState]; + } + + get CONNECTING() { + webidl.assertBranded(this, WebSocket); + return CONNECTING; + } + get OPEN() { + webidl.assertBranded(this, WebSocket); + return OPEN; + } + get CLOSING() { + webidl.assertBranded(this, WebSocket); + return CLOSING; + } + get CLOSED() { + webidl.assertBranded(this, WebSocket); + return CLOSED; + } + + [_extensions] = ""; + get extensions() { + webidl.assertBranded(this, WebSocket); + return this[_extensions]; + } + + [_protocol] = ""; + get protocol() { + webidl.assertBranded(this, WebSocket); + return this[_protocol]; + } + + [_url] = ""; + get url() { + webidl.assertBranded(this, WebSocket); + return this[_url]; + } + + [_binaryType] = "blob"; + get binaryType() { + webidl.assertBranded(this, WebSocket); + return this[_binaryType]; + } + set binaryType(value) { + webidl.assertBranded(this, WebSocket); + value = webidl.converters.DOMString(value, { + prefix: "Failed to set 'binaryType' on 'WebSocket'", + }); + if (value === "blob" || value === "arraybuffer") { + this[_binaryType] = value; + } + } + + [_bufferedAmount] = 0; + get bufferedAmount() { + webidl.assertBranded(this, WebSocket); + return this[_bufferedAmount]; + } constructor(url, protocols = []) { super(); - requiredArguments("WebSocket", arguments.length, 1); - - const wsURL = new URL(url); + this[webidl.brand] = webidl.brand; + const prefix = "Failed to construct 'WebSocket'"; + webidl.requiredArguments(arguments.length, 1, { + prefix, + }); + url = webidl.converters.USVString(url, { + prefix, + context: "Argument 1", + }); + protocols = webidl.converters["sequence<DOMString> or DOMString"]( + protocols, + { + prefix, + context: "Argument 2", + }, + ); + + let wsURL; + + try { + wsURL = new URL(url); + } catch (e) { + throw new DOMException(e.message, "SyntaxError"); + } if (wsURL.protocol !== "ws:" && wsURL.protocol !== "wss:") { throw new DOMException( @@ -97,16 +200,16 @@ ); } - this.#url = wsURL.href; + this[_url] = wsURL.href; - core.opSync("op_ws_check_permission", this.#url); + core.opSync("op_ws_check_permission", this[_url]); - if (protocols && typeof protocols === "string") { + if (typeof protocols === "string") { protocols = [protocols]; } if ( - protocols.some((x) => protocols.indexOf(x) !== protocols.lastIndexOf(x)) + protocols.length !== new Set(protocols.map((p) => p.toLowerCase())).size ) { throw new DOMException( "Can't supply multiple times the same protocol.", @@ -114,19 +217,28 @@ ); } + if ( + protocols.some((protocol) => !HTTP_TOKEN_CODE_POINT_RE.test(protocol)) + ) { + throw new DOMException( + "Invalid protocol value.", + "SyntaxError", + ); + } + core.opAsync("op_ws_create", { url: wsURL.href, protocols: protocols.join(", "), }).then((create) => { - this.#rid = create.rid; - this.#extensions = create.extensions; - this.#protocol = create.protocol; + this[_rid] = create.rid; + this[_extensions] = create.extensions; + this[_protocol] = create.protocol; - if (this.#readyState === CLOSING) { + if (this[_readyState] === CLOSING) { core.opAsync("op_ws_close", { - rid: this.#rid, + rid: this[_rid], }).then(() => { - this.#readyState = CLOSED; + this[_readyState] = CLOSED; const errEvent = new ErrorEvent("error"); errEvent.target = this; @@ -135,10 +247,10 @@ const event = new CloseEvent("close"); event.target = this; this.dispatchEvent(event); - tryClose(this.#rid); + tryClose(this[_rid]); }); } else { - this.#readyState = OPEN; + this[_readyState] = OPEN; const event = new Event("open"); event.target = this; this.dispatchEvent(event); @@ -146,7 +258,7 @@ this.#eventLoop(); } }).catch((err) => { - this.#readyState = CLOSED; + this[_readyState] = CLOSED; const errorEv = new ErrorEvent( "error", @@ -161,67 +273,29 @@ }); } - get CONNECTING() { - return CONNECTING; - } - get OPEN() { - return OPEN; - } - get CLOSING() { - return CLOSING; - } - get CLOSED() { - return CLOSED; - } - - get readyState() { - return this.#readyState; - } - - #extensions = ""; - #protocol = ""; - #url = ""; - #rid; - - get extensions() { - return this.#extensions; - } - get protocol() { - return this.#protocol; - } - - #binaryType = "blob"; - get binaryType() { - return this.#binaryType; - } - set binaryType(value) { - if (value === "blob" || value === "arraybuffer") { - this.#binaryType = value; - } - } - #bufferedAmount = 0; - get bufferedAmount() { - return this.#bufferedAmount; - } - - get url() { - return this.#url; - } - send(data) { - requiredArguments("WebSocket.send", arguments.length, 1); + webidl.assertBranded(this, WebSocket); + const prefix = "Failed to execute 'send' on 'WebSocket'"; + + webidl.requiredArguments(arguments.length, 1, { + prefix, + }); + data = webidl.converters.WebSocketSend(data, { + prefix, + context: "Argument 1", + }); - if (this.#readyState != OPEN) { - throw Error("readyState not OPEN"); + if (this[_readyState] !== OPEN) { + throw new DOMException("readyState not OPEN", "InvalidStateError"); } const sendTypedArray = (ta) => { - this.#bufferedAmount += ta.size; + this[_bufferedAmount] += ta.byteLength; core.opAsync("op_ws_send", { - rid: this.#rid, + rid: this[_rid], kind: "binary", }, ta).then(() => { - this.#bufferedAmount -= ta.size; + this[_bufferedAmount] -= ta.byteLength; }); }; @@ -229,80 +303,94 @@ data.slice().arrayBuffer().then((ab) => sendTypedArray(new DataView(ab)) ); - } else if ( - data instanceof Int8Array || data instanceof Int16Array || - data instanceof Int32Array || data instanceof Uint8Array || - data instanceof Uint16Array || data instanceof Uint32Array || - data instanceof Uint8ClampedArray || data instanceof Float32Array || - data instanceof Float64Array || data instanceof DataView - ) { + } else if (ArrayBuffer.isView(data)) { sendTypedArray(data); } else if (data instanceof ArrayBuffer) { sendTypedArray(new DataView(data)); } else { const string = String(data); const d = core.encode(string); - this.#bufferedAmount += d.size; + this[_bufferedAmount] += d.byteLength; core.opAsync("op_ws_send", { - rid: this.#rid, + rid: this[_rid], kind: "text", text: string, }).then(() => { - this.#bufferedAmount -= d.size; + this[_bufferedAmount] -= d.byteLength; }); } } - close(code, reason) { - if (code && !(code === 1000 || (3000 <= code && code < 5000))) { + close(code = undefined, reason = undefined) { + webidl.assertBranded(this, WebSocket); + const prefix = "Failed to execute 'close' on 'WebSocket'"; + + if (code !== undefined) { + code = webidl.converters["unsigned short"](code, { + prefix, + clamp: true, + context: "Argument 1", + }); + } + + if (reason !== undefined) { + reason = webidl.converters.USVString(reason, { + prefix, + context: "Argument 2", + }); + } + + if ( + code !== undefined && !(code === 1000 || (3000 <= code && code < 5000)) + ) { throw new DOMException( "The close code must be either 1000 or in the range of 3000 to 4999.", - "NotSupportedError", + "InvalidAccessError", ); } - if (reason && core.encode(reason).byteLength > 123) { + if (reason !== undefined && core.encode(reason).byteLength > 123) { throw new DOMException( "The close reason may not be longer than 123 bytes.", "SyntaxError", ); } - if (this.#readyState === CONNECTING) { - this.#readyState = CLOSING; - } else if (this.#readyState === OPEN) { - this.#readyState = CLOSING; + if (this[_readyState] === CONNECTING) { + this[_readyState] = CLOSING; + } else if (this[_readyState] === OPEN) { + this[_readyState] = CLOSING; core.opAsync("op_ws_close", { - rid: this.#rid, + rid: this[_rid], code, reason, }).then(() => { - this.#readyState = CLOSED; + this[_readyState] = CLOSED; const event = new CloseEvent("close", { wasClean: true, - code, + code: code ?? 1005, reason, }); event.target = this; this.dispatchEvent(event); - tryClose(this.#rid); + tryClose(this[_rid]); }); } } async #eventLoop() { - while (this.#readyState === OPEN) { + while (this[_readyState] === OPEN) { const { kind, value } = await core.opAsync( "op_ws_next_event", - this.#rid, + this[_rid], ); switch (kind) { case "string": { const event = new MessageEvent("message", { data: value, - origin: this.#url, + origin: this[_url], }); event.target = this; this.dispatchEvent(event); @@ -319,7 +407,7 @@ const event = new MessageEvent("message", { data, - origin: this.#url, + origin: this[_url], }); event.target = this; this.dispatchEvent(event); @@ -327,13 +415,13 @@ } case "ping": { core.opAsync("op_ws_send", { - rid: this.#rid, + rid: this[_rid], kind: "pong", }); break; } case "close": { - this.#readyState = CLOSED; + this[_readyState] = CLOSED; const event = new CloseEvent("close", { wasClean: true, @@ -342,11 +430,11 @@ }); event.target = this; this.dispatchEvent(event); - tryClose(this.#rid); + tryClose(this[_rid]); break; } case "error": { - this.#readyState = CLOSED; + this[_readyState] = CLOSED; const errorEv = new ErrorEvent("error", { message: value, @@ -357,7 +445,7 @@ const closeEv = new CloseEvent("close"); closeEv.target = this; this.dispatchEvent(closeEv); - tryClose(this.#rid); + tryClose(this[_rid]); break; } } @@ -385,5 +473,7 @@ defineEventHandler(WebSocket.prototype, "close"); defineEventHandler(WebSocket.prototype, "open"); + webidl.configurePrototype(WebSocket); + window.__bootstrap.webSocket = { WebSocket }; })(this); |