deno.land / x / deno@v1.28.2 / ext / websocket / 01_websocket.js
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license."use strict";
/// <reference path="../../core/internal.d.ts" />
((window) => { const core = window.Deno.core; const ops = core.ops; const { URL } = window.__bootstrap.url; const webidl = window.__bootstrap.webidl; const { HTTP_TOKEN_CODE_POINT_RE } = window.__bootstrap.infra; const { DOMException } = window.__bootstrap.domException; const { Event, ErrorEvent, CloseEvent, MessageEvent, defineEventHandler } = window.__bootstrap.event; const { EventTarget } = window.__bootstrap.eventTarget; const { Blob, BlobPrototype } = globalThis.__bootstrap.file; const { ArrayBufferPrototype, ArrayBufferIsView, ArrayPrototypeJoin, ArrayPrototypeMap, ArrayPrototypeSome, ErrorPrototypeToString, ObjectDefineProperties, ObjectPrototypeIsPrototypeOf, PromisePrototypeThen, RegExpPrototypeTest, Set, StringPrototypeEndsWith, StringPrototypeToLowerCase, Symbol, SymbolIterator, PromisePrototypeCatch, queueMicrotask, SymbolFor, Uint8Array, } = window.__bootstrap.primordials;
webidl.converters["sequence<DOMString> or DOMString"] = (V, opts) => { // Union for (sequence<DOMString> or DOMString) if (webidl.type(V) === "Object" && V !== null) { if (V[SymbolIterator] !== 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 (ObjectPrototypeIsPrototypeOf(BlobPrototype, V)) { return webidl.converters["Blob"](V, opts); } if (typeof V === "object") { // TODO(littledivy): use primordial for SharedArrayBuffer if ( ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V) || ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, V) ) { return webidl.converters["ArrayBuffer"](V, opts); } if (ArrayBufferIsView(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;
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]]"); const _eventLoop = Symbol("[[eventLoop]]");
const _server = Symbol("[[server]]"); const _idleTimeoutDuration = Symbol("[[idleTimeout]]"); const _idleTimeoutTimeout = Symbol("[[idleTimeoutTimeout]]"); const _serverHandleIdleTimeout = Symbol("[[serverHandleIdleTimeout]]"); class WebSocket extends EventTarget { [_rid];
[_readyState] = CONNECTING; get readyState() { webidl.assertBranded(this, WebSocketPrototype); return this[_readyState]; }
get CONNECTING() { webidl.assertBranded(this, WebSocketPrototype); return CONNECTING; } get OPEN() { webidl.assertBranded(this, WebSocketPrototype); return OPEN; } get CLOSING() { webidl.assertBranded(this, WebSocketPrototype); return CLOSING; } get CLOSED() { webidl.assertBranded(this, WebSocketPrototype); return CLOSED; }
[_extensions] = ""; get extensions() { webidl.assertBranded(this, WebSocketPrototype); return this[_extensions]; }
[_protocol] = ""; get protocol() { webidl.assertBranded(this, WebSocketPrototype); return this[_protocol]; }
[_url] = ""; get url() { webidl.assertBranded(this, WebSocketPrototype); return this[_url]; }
[_binaryType] = "blob"; get binaryType() { webidl.assertBranded(this, WebSocketPrototype); return this[_binaryType]; } set binaryType(value) { webidl.assertBranded(this, WebSocketPrototype); 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, WebSocketPrototype); return this[_bufferedAmount]; }
constructor(url, protocols = []) { super(); 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( "Only ws & wss schemes are allowed in a WebSocket URL.", "SyntaxError", ); }
if (wsURL.hash !== "" || StringPrototypeEndsWith(wsURL.href, "#")) { throw new DOMException( "Fragments are not allowed in a WebSocket URL.", "SyntaxError", ); }
this[_url] = wsURL.href;
ops.op_ws_check_permission_and_cancel_handle( "WebSocket.abort()", this[_url], false, );
if (typeof protocols === "string") { protocols = [protocols]; }
if ( protocols.length !== new Set( ArrayPrototypeMap(protocols, (p) => StringPrototypeToLowerCase(p)), ).size ) { throw new DOMException( "Can't supply multiple times the same protocol.", "SyntaxError", ); }
if ( ArrayPrototypeSome( protocols, (protocol) => !RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, protocol), ) ) { throw new DOMException( "Invalid protocol value.", "SyntaxError", ); }
PromisePrototypeThen( core.opAsync( "op_ws_create", "new WebSocket()", wsURL.href, ArrayPrototypeJoin(protocols, ", "), ), (create) => { this[_rid] = create.rid; this[_extensions] = create.extensions; this[_protocol] = create.protocol;
if (this[_readyState] === CLOSING) { PromisePrototypeThen( core.opAsync("op_ws_close", this[_rid]), () => { this[_readyState] = CLOSED;
const errEvent = new ErrorEvent("error"); this.dispatchEvent(errEvent);
const event = new CloseEvent("close"); this.dispatchEvent(event); core.tryClose(this[_rid]); }, ); } else { this[_readyState] = OPEN; const event = new Event("open"); this.dispatchEvent(event);
this[_eventLoop](); } }, (err) => { this[_readyState] = CLOSED;
const errorEv = new ErrorEvent( "error", { error: err, message: ErrorPrototypeToString(err) }, ); this.dispatchEvent(errorEv);
const closeEv = new CloseEvent("close"); this.dispatchEvent(closeEv); }, ); }
send(data) { webidl.assertBranded(this, WebSocketPrototype); 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 new DOMException("readyState not OPEN", "InvalidStateError"); }
if (typeof data === "string") { // try to send in one go! const d = core.byteLength(data); const sent = ops.op_ws_try_send_string(this[_rid], data); this[_bufferedAmount] += d; if (!sent) { PromisePrototypeThen( core.opAsync("op_ws_send_string", this[_rid], data), () => { this[_bufferedAmount] -= d; }, ); } else { // Spec expects data to be start flushing on next tick but oh well... // we already sent it so we can just decrement the bufferedAmount // on the next tick. queueMicrotask(() => { this[_bufferedAmount] -= d; }); } return; }
const sendTypedArray = (ta) => { // try to send in one go! const sent = ops.op_ws_try_send_binary(this[_rid], ta); this[_bufferedAmount] += ta.byteLength; if (!sent) { PromisePrototypeThen( core.opAsync("op_ws_send_binary", this[_rid], ta), () => { this[_bufferedAmount] -= ta.byteLength; }, ); } else { // Spec expects data to be start flushing on next tick but oh well... // we already sent it so we can just decrement the bufferedAmount // on the next tick. queueMicrotask(() => { this[_bufferedAmount] -= ta.byteLength; }); } };
if (ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, data)) { sendTypedArray(new Uint8Array(data)); } else if (ArrayBufferIsView(data)) { sendTypedArray(data); } else if (ObjectPrototypeIsPrototypeOf(BlobPrototype, data)) { PromisePrototypeThen( data.slice().arrayBuffer(), (ab) => sendTypedArray(new Uint8Array(ab)), ); } }
close(code = undefined, reason = undefined) { webidl.assertBranded(this, WebSocketPrototype); 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 (!this[_server]) { 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.", "InvalidAccessError", ); } }
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;
PromisePrototypeCatch( core.opAsync("op_ws_close", this[_rid], code, reason), (err) => { this[_readyState] = CLOSED;
const errorEv = new ErrorEvent("error", { error: err, message: ErrorPrototypeToString(err), }); this.dispatchEvent(errorEv);
const closeEv = new CloseEvent("close"); this.dispatchEvent(closeEv); core.tryClose(this[_rid]); }, ); } }
async [_eventLoop]() { while (this[_readyState] !== CLOSED) { const { kind, value } = await core.opAsync( "op_ws_next_event", this[_rid], );
switch (kind) { case "string": { this[_serverHandleIdleTimeout](); const event = new MessageEvent("message", { data: value, origin: this[_url], }); this.dispatchEvent(event); break; } case "binary": { this[_serverHandleIdleTimeout](); let data;
if (this.binaryType === "blob") { data = new Blob([value]); } else { data = value.buffer; }
const event = new MessageEvent("message", { data, origin: this[_url], }); this.dispatchEvent(event); break; } case "ping": { core.opAsync("op_ws_send", this[_rid], { kind: "pong", }); break; } case "pong": { this[_serverHandleIdleTimeout](); break; } case "closed": case "close": { const prevState = this[_readyState]; this[_readyState] = CLOSED; clearTimeout(this[_idleTimeoutTimeout]);
if (prevState === OPEN) { try { await core.opAsync( "op_ws_close", this[_rid], value.code, value.reason, ); } catch { // ignore failures } }
const event = new CloseEvent("close", { wasClean: true, code: value.code, reason: value.reason, }); this.dispatchEvent(event); core.tryClose(this[_rid]); break; } case "error": { this[_readyState] = CLOSED;
const errorEv = new ErrorEvent("error", { message: value, }); this.dispatchEvent(errorEv);
const closeEv = new CloseEvent("close"); this.dispatchEvent(closeEv); core.tryClose(this[_rid]); break; } } } }
[_serverHandleIdleTimeout]() { if (this[_idleTimeoutDuration]) { clearTimeout(this[_idleTimeoutTimeout]); this[_idleTimeoutTimeout] = setTimeout(async () => { if (this[_readyState] === OPEN) { await core.opAsync("op_ws_send", this[_rid], { kind: "ping", }); this[_idleTimeoutTimeout] = setTimeout(async () => { if (this[_readyState] === OPEN) { this[_readyState] = CLOSING; const reason = "No response from ping frame."; await core.opAsync("op_ws_close", this[_rid], 1001, reason); this[_readyState] = CLOSED;
const errEvent = new ErrorEvent("error", { message: reason, }); this.dispatchEvent(errEvent);
const event = new CloseEvent("close", { wasClean: false, code: 1001, reason, }); this.dispatchEvent(event); core.tryClose(this[_rid]); } else { clearTimeout(this[_idleTimeoutTimeout]); } }, (this[_idleTimeoutDuration] / 2) * 1000); } else { clearTimeout(this[_idleTimeoutTimeout]); } }, (this[_idleTimeoutDuration] / 2) * 1000); } }
[SymbolFor("Deno.customInspect")](inspect) { return `${this.constructor.name} ${ inspect({ url: this.url, readyState: this.readyState, extensions: this.extensions, protocol: this.protocol, binaryType: this.binaryType, bufferedAmount: this.bufferedAmount, }) }`; } }
ObjectDefineProperties(WebSocket, { CONNECTING: { value: 0, }, OPEN: { value: 1, }, CLOSING: { value: 2, }, CLOSED: { value: 3, }, });
defineEventHandler(WebSocket.prototype, "message"); defineEventHandler(WebSocket.prototype, "error"); defineEventHandler(WebSocket.prototype, "close"); defineEventHandler(WebSocket.prototype, "open");
webidl.configurePrototype(WebSocket); const WebSocketPrototype = WebSocket.prototype;
window.__bootstrap.webSocket = { WebSocket, _rid, _readyState, _eventLoop, _protocol, _server, _idleTimeoutDuration, _idleTimeoutTimeout, _serverHandleIdleTimeout, };})(this);
Version Info