diff options
Diffstat (limited to 'cli/rt')
-rw-r--r-- | cli/rt/11_workers.js | 15 | ||||
-rw-r--r-- | cli/rt/27_websocket.js | 305 | ||||
-rw-r--r-- | cli/rt/99_main.js | 6 |
3 files changed, 310 insertions, 16 deletions
diff --git a/cli/rt/11_workers.js b/cli/rt/11_workers.js index 8ae0d5ad5..9a23e3dd8 100644 --- a/cli/rt/11_workers.js +++ b/cli/rt/11_workers.js @@ -39,20 +39,6 @@ const encoder = new TextEncoder(); const decoder = new TextDecoder(); - class MessageEvent extends Event { - constructor(type, eventInitDict) { - super(type, { - bubbles: eventInitDict?.bubbles ?? false, - cancelable: eventInitDict?.cancelable ?? false, - composed: eventInitDict?.composed ?? false, - }); - - this.data = eventInitDict?.data ?? null; - this.origin = eventInitDict?.origin ?? ""; - this.lastEventId = eventInitDict?.lastEventId ?? ""; - } - } - function encodeMessage(data) { const dataJson = JSON.stringify(data); return encoder.encode(dataJson); @@ -226,6 +212,5 @@ window.__bootstrap.worker = { Worker, - MessageEvent, }; })(this); diff --git a/cli/rt/27_websocket.js b/cli/rt/27_websocket.js new file mode 100644 index 000000000..0b113ebca --- /dev/null +++ b/cli/rt/27_websocket.js @@ -0,0 +1,305 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +((window) => { + const { sendAsync } = window.__bootstrap.dispatchJson; + const { close } = window.__bootstrap.resources; + const { requiredArguments } = window.__bootstrap.webUtil; + const CONNECTING = 0; + const OPEN = 1; + const CLOSING = 2; + const CLOSED = 3; + + class WebSocket extends EventTarget { + #readyState = CONNECTING; + + constructor(url, protocols = []) { + super(); + requiredArguments("WebSocket", arguments.length, 1); + + const wsURL = new URL(url); + + if (wsURL.protocol !== "ws:" && wsURL.protocol !== "wss:") { + throw new DOMException( + "Only ws & wss schemes are allowed in a WebSocket URL.", + "SyntaxError", + ); + } + + if (wsURL.hash !== "" || wsURL.href.endsWith("#")) { + throw new DOMException( + "Fragments are not allowed in a WebSocket URL.", + "SyntaxError", + ); + } + + this.#url = wsURL.href; + + if (protocols && typeof protocols === "string") { + protocols = [protocols]; + } + + if ( + protocols.some((x) => protocols.indexOf(x) !== protocols.lastIndexOf(x)) + ) { + throw new DOMException( + "Can't supply multiple times the same protocol.", + "SyntaxError", + ); + } + + sendAsync("op_ws_create", { + url: wsURL.href, + protocols: protocols.join("; "), + }).then((create) => { + if (create.success) { + this.#rid = create.rid; + this.#extensions = create.extensions; + this.#protocol = create.protocol; + + if (this.#readyState === CLOSING) { + sendAsync("op_ws_close", { + rid: this.#rid, + }).then(() => { + this.#readyState = CLOSED; + + const errEvent = new Event("error"); + errEvent.target = this; + this.onerror?.(errEvent); + this.dispatchEvent(errEvent); + + const event = new CloseEvent("close"); + event.target = this; + this.onclose?.(event); + this.dispatchEvent(event); + close(this.#rid); + }); + + const event = new Event("error"); + event.target = this; + this.onerror?.(event); + this.dispatchEvent(event); + } else { + this.#readyState = OPEN; + const event = new Event("open"); + event.target = this; + this.onopen?.(event); + this.dispatchEvent(event); + + this.#eventLoop(); + } + } else { + this.#readyState = CLOSED; + + const errEvent = new Event("error"); + errEvent.target = this; + this.onerror?.(errEvent); + this.dispatchEvent(errEvent); + + const closeEvent = new CloseEvent("close"); + closeEvent.target = this; + this.onclose?.(closeEvent); + this.dispatchEvent(closeEvent); + } + }); + } + + 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; + } + + onopen = () => {}; + onerror = () => {}; + onclose = () => {}; + onmessage = () => {}; + + send(data) { + requiredArguments("WebSocket.send", arguments.length, 1); + + if (this.#readyState != OPEN) { + throw Error("readyState not OPEN"); + } + + const sendTypedArray = (ta) => { + this.#bufferedAmount += ta.size; + sendAsync("op_ws_send", { + rid: this.#rid, + }, ta).then(() => { + this.#bufferedAmount -= ta.size; + }); + }; + + if (data instanceof Blob) { + 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 + ) { + sendTypedArray(data); + } else if (data instanceof ArrayBuffer) { + sendTypedArray(new DataView(data)); + } else { + const string = String(data); + const encoder = new TextEncoder(); + const d = encoder.encode(string); + this.#bufferedAmount += d.size; + sendAsync("op_ws_send", { + rid: this.#rid, + text: string, + }).then(() => { + this.#bufferedAmount -= d.size; + }); + } + } + + close(code, reason) { + if (code && (code !== 1000 && !(3000 <= code > 5000))) { + throw new DOMException( + "The close code must be either 1000 or in the range of 3000 to 4999.", + "NotSupportedError", + ); + } + + const encoder = new TextEncoder(); + if (reason && encoder.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; + + sendAsync("op_ws_close", { + rid: this.#rid, + code, + reason, + }).then(() => { + this.#readyState = CLOSED; + const event = new CloseEvent("close", { + wasClean: true, + code, + reason, + }); + event.target = this; + this.onclose?.(event); + this.dispatchEvent(event); + close(this.#rid); + }); + } + } + + async #eventLoop() { + if (this.#readyState === OPEN) { + const message = await sendAsync("op_ws_next_event", { rid: this.#rid }); + if (message.type === "string" || message.type === "binary") { + let data; + + if (message.type === "string") { + data = message.data; + } else { + if (this.binaryType === "blob") { + data = new Blob([new Uint8Array(message.data)]); + } else { + data = new Uint8Array(message.data).buffer; + } + } + + const event = new MessageEvent("message", { + data, + origin: this.#url, + }); + event.target = this; + this.onmessage?.(event); + this.dispatchEvent(event); + + this.#eventLoop(); + } else if (message.type === "close") { + this.#readyState = CLOSED; + const event = new CloseEvent("close", { + wasClean: true, + code: message.code, + reason: message.reason, + }); + event.target = this; + this.onclose?.(event); + this.dispatchEvent(event); + } else if (message.type === "error") { + const event = new Event("error"); + event.target = this; + this.onerror?.(event); + this.dispatchEvent(event); + } + } + } + } + + Object.defineProperties(WebSocket, { + CONNECTING: { + value: 0, + }, + OPEN: { + value: 1, + }, + CLOSING: { + value: 2, + }, + CLOSED: { + value: 3, + }, + }); + + window.__bootstrap.webSocket = { + WebSocket, + }; +})(this); diff --git a/cli/rt/99_main.js b/cli/rt/99_main.js index c6d6d1395..ede47980c 100644 --- a/cli/rt/99_main.js +++ b/cli/rt/99_main.js @@ -30,6 +30,7 @@ delete Object.prototype.__proto__; const progressEvent = window.__bootstrap.progressEvent; const fileReader = window.__bootstrap.fileReader; const formData = window.__bootstrap.formData; + const webSocket = window.__bootstrap.webSocket; const request = window.__bootstrap.request; const fetch = window.__bootstrap.fetch; const denoNs = window.__bootstrap.denoNs; @@ -80,7 +81,7 @@ delete Object.prototype.__proto__; let isClosing = false; async function workerMessageRecvCallback(data) { - const msgEvent = new worker.MessageEvent("message", { + const msgEvent = new MessageEvent("message", { cancelable: false, data, }); @@ -232,10 +233,13 @@ delete Object.prototype.__proto__; CustomEvent: util.nonEnumerable(CustomEvent), DOMException: util.nonEnumerable(DOMException), ErrorEvent: util.nonEnumerable(ErrorEvent), + CloseEvent: util.nonEnumerable(CloseEvent), + MessageEvent: util.nonEnumerable(MessageEvent), Event: util.nonEnumerable(Event), EventTarget: util.nonEnumerable(EventTarget), Headers: util.nonEnumerable(headers.Headers), FormData: util.nonEnumerable(formData.FormData), + WebSocket: util.nonEnumerable(webSocket.WebSocket), ReadableStream: util.nonEnumerable(streams.ReadableStream), Request: util.nonEnumerable(request.Request), Response: util.nonEnumerable(fetch.Response), |