mirror of
https://github.com/denoland/deno.git
synced 2025-09-25 03:42:30 +00:00
feat(unstable): WebSocket headers field (#30321)
This changes the second argument in the WebSocket constructor to be able to take an object, which can contain a headers field with which the headers for the connection can be set. --------- Co-authored-by: Luca Casonato <hello@lcas.dev>
This commit is contained in:
parent
b88c621f22
commit
c217928649
5 changed files with 148 additions and 23 deletions
30
cli/tsc/dts/lib.deno_websocket.d.ts
vendored
30
cli/tsc/dts/lib.deno_websocket.d.ts
vendored
|
@ -267,6 +267,13 @@ interface WebSocket extends EventTarget {
|
||||||
* // Using URL object instead of string
|
* // Using URL object instead of string
|
||||||
* const url = new URL("ws://localhost:8080/path");
|
* const url = new URL("ws://localhost:8080/path");
|
||||||
* const wsWithUrl = new WebSocket(url);
|
* const wsWithUrl = new WebSocket(url);
|
||||||
|
*
|
||||||
|
* // WebSocket with headers
|
||||||
|
* const wsWithProtocols = new WebSocket("ws://localhost:8080", {
|
||||||
|
* headers: {
|
||||||
|
* "Authorization": "Bearer foo",
|
||||||
|
* },
|
||||||
|
* });
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket
|
||||||
|
@ -274,13 +281,34 @@ interface WebSocket extends EventTarget {
|
||||||
*/
|
*/
|
||||||
declare var WebSocket: {
|
declare var WebSocket: {
|
||||||
readonly prototype: WebSocket;
|
readonly prototype: WebSocket;
|
||||||
new (url: string | URL, protocols?: string | string[]): WebSocket;
|
new (
|
||||||
|
url: string | URL,
|
||||||
|
protocolsOrOptions?: string | string[] | WebSocketOptions,
|
||||||
|
): WebSocket;
|
||||||
readonly CLOSED: number;
|
readonly CLOSED: number;
|
||||||
readonly CLOSING: number;
|
readonly CLOSING: number;
|
||||||
readonly CONNECTING: number;
|
readonly CONNECTING: number;
|
||||||
readonly OPEN: number;
|
readonly OPEN: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for a WebSocket instance.
|
||||||
|
* This feature is non-standard.
|
||||||
|
*
|
||||||
|
* @category WebSockets
|
||||||
|
*/
|
||||||
|
interface WebSocketOptions {
|
||||||
|
/**
|
||||||
|
* The sub-protocol(s) that the client would like to use, in order of preference.
|
||||||
|
*/
|
||||||
|
protocols?: string | string[];
|
||||||
|
/**
|
||||||
|
* A Headers object, an object literal, or an array of two-item arrays to set handshake's headers.
|
||||||
|
* This feature is non-standard.
|
||||||
|
*/
|
||||||
|
headers?: HeadersInit;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies the type of binary data being received over a `WebSocket` connection.
|
* Specifies the type of binary data being received over a `WebSocket` connection.
|
||||||
*
|
*
|
||||||
|
|
|
@ -6,8 +6,6 @@ import { primordials } from "ext:core/mod.js";
|
||||||
import { op_utf8_to_byte_string } from "ext:core/ops";
|
import { op_utf8_to_byte_string } from "ext:core/ops";
|
||||||
const {
|
const {
|
||||||
ArrayPrototypeFind,
|
ArrayPrototypeFind,
|
||||||
ArrayPrototypeSlice,
|
|
||||||
ArrayPrototypeSplice,
|
|
||||||
Number,
|
Number,
|
||||||
NumberIsFinite,
|
NumberIsFinite,
|
||||||
NumberIsNaN,
|
NumberIsNaN,
|
||||||
|
@ -200,16 +198,7 @@ class EventSource extends EventTarget {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.#headers) {
|
if (this.#headers) {
|
||||||
const headerList = headerListFromHeaders(initialHeaders);
|
fillHeaders(initialHeaders, this.#headers);
|
||||||
const headers = this.#headers ?? ArrayPrototypeSlice(
|
|
||||||
headerList,
|
|
||||||
0,
|
|
||||||
headerList.length,
|
|
||||||
);
|
|
||||||
if (headerList.length !== 0) {
|
|
||||||
ArrayPrototypeSplice(headerList, 0, headerList.length);
|
|
||||||
}
|
|
||||||
fillHeaders(initialHeaders, headers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const req = newInnerRequest(
|
const req = newInnerRequest(
|
||||||
|
|
|
@ -23,6 +23,7 @@ import {
|
||||||
} from "ext:core/ops";
|
} from "ext:core/ops";
|
||||||
const {
|
const {
|
||||||
ArrayBufferIsView,
|
ArrayBufferIsView,
|
||||||
|
ArrayIsArray,
|
||||||
ArrayPrototypeJoin,
|
ArrayPrototypeJoin,
|
||||||
ArrayPrototypeMap,
|
ArrayPrototypeMap,
|
||||||
ArrayPrototypePush,
|
ArrayPrototypePush,
|
||||||
|
@ -64,18 +65,41 @@ import {
|
||||||
} from "ext:deno_web/02_event.js";
|
} from "ext:deno_web/02_event.js";
|
||||||
import { Blob, BlobPrototype } from "ext:deno_web/09_file.js";
|
import { Blob, BlobPrototype } from "ext:deno_web/09_file.js";
|
||||||
import { getLocationHref } from "ext:deno_web/12_location.js";
|
import { getLocationHref } from "ext:deno_web/12_location.js";
|
||||||
|
import {
|
||||||
|
fillHeaders,
|
||||||
|
headerListFromHeaders,
|
||||||
|
headersFromHeaderList,
|
||||||
|
} from "ext:deno_fetch/20_headers.js";
|
||||||
|
|
||||||
webidl.converters["sequence<DOMString> or DOMString"] = (
|
webidl.converters["WebSocketInit"] = webidl.createDictionaryConverter(
|
||||||
|
"WebSocketInit",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
key: "headers",
|
||||||
|
converter: webidl.converters["HeadersInit"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "protocols",
|
||||||
|
converter: webidl.converters["sequence<DOMString>"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
webidl.converters["WebSocketInit or sequence<DOMString> or DOMString"] = (
|
||||||
V,
|
V,
|
||||||
prefix,
|
prefix,
|
||||||
context,
|
context,
|
||||||
opts,
|
opts,
|
||||||
) => {
|
) => {
|
||||||
// Union for (sequence<DOMString> or DOMString)
|
// Union for (WebSocketInit or sequence<DOMString> or DOMString)
|
||||||
|
if (V === null || V === undefined) {
|
||||||
|
return webidl.converters["WebSocketInit"](V, prefix, context, opts);
|
||||||
|
}
|
||||||
if (webidl.type(V) === "Object" && V !== null) {
|
if (webidl.type(V) === "Object" && V !== null) {
|
||||||
if (V[SymbolIterator] !== undefined) {
|
if (V[SymbolIterator] !== undefined) {
|
||||||
return webidl.converters["sequence<DOMString>"](V, prefix, context, opts);
|
return webidl.converters["sequence<DOMString>"](V, prefix, context, opts);
|
||||||
}
|
}
|
||||||
|
return webidl.converters["WebSocketInit"](V, prefix, context, opts);
|
||||||
}
|
}
|
||||||
return webidl.converters.DOMString(V, prefix, context, opts);
|
return webidl.converters.DOMString(V, prefix, context, opts);
|
||||||
};
|
};
|
||||||
|
@ -124,7 +148,7 @@ const _idleTimeoutTimeout = Symbol("[[idleTimeoutTimeout]]");
|
||||||
const _serverHandleIdleTimeout = Symbol("[[serverHandleIdleTimeout]]");
|
const _serverHandleIdleTimeout = Symbol("[[serverHandleIdleTimeout]]");
|
||||||
|
|
||||||
class WebSocket extends EventTarget {
|
class WebSocket extends EventTarget {
|
||||||
constructor(url, protocols = []) {
|
constructor(url, initOrProtocols) {
|
||||||
super();
|
super();
|
||||||
this[webidl.brand] = webidl.brand;
|
this[webidl.brand] = webidl.brand;
|
||||||
this[_rid] = undefined;
|
this[_rid] = undefined;
|
||||||
|
@ -142,11 +166,12 @@ class WebSocket extends EventTarget {
|
||||||
const prefix = "Failed to construct 'WebSocket'";
|
const prefix = "Failed to construct 'WebSocket'";
|
||||||
webidl.requiredArguments(arguments.length, 1, prefix);
|
webidl.requiredArguments(arguments.length, 1, prefix);
|
||||||
url = webidl.converters.USVString(url, prefix, "Argument 1");
|
url = webidl.converters.USVString(url, prefix, "Argument 1");
|
||||||
protocols = webidl.converters["sequence<DOMString> or DOMString"](
|
initOrProtocols = webidl.converters
|
||||||
protocols,
|
["WebSocketInit or sequence<DOMString> or DOMString"](
|
||||||
prefix,
|
initOrProtocols,
|
||||||
"Argument 2",
|
prefix,
|
||||||
);
|
"Argument 2",
|
||||||
|
);
|
||||||
|
|
||||||
let wsURL;
|
let wsURL;
|
||||||
|
|
||||||
|
@ -179,8 +204,20 @@ class WebSocket extends EventTarget {
|
||||||
this[_url] = wsURL.href;
|
this[_url] = wsURL.href;
|
||||||
this[_role] = CLIENT;
|
this[_role] = CLIENT;
|
||||||
|
|
||||||
if (typeof protocols === "string") {
|
let protocols;
|
||||||
protocols = [protocols];
|
let headers = null;
|
||||||
|
|
||||||
|
if (typeof initOrProtocols === "string") {
|
||||||
|
protocols = [initOrProtocols];
|
||||||
|
} else if (ArrayIsArray(initOrProtocols)) {
|
||||||
|
protocols = initOrProtocols;
|
||||||
|
} else {
|
||||||
|
protocols = initOrProtocols.protocols || [];
|
||||||
|
|
||||||
|
if (initOrProtocols.headers !== undefined) {
|
||||||
|
headers = headersFromHeaderList([], "request");
|
||||||
|
fillHeaders(headers, initOrProtocols.headers);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -224,6 +261,7 @@ class WebSocket extends EventTarget {
|
||||||
wsURL.href,
|
wsURL.href,
|
||||||
ArrayPrototypeJoin(protocols, ", "),
|
ArrayPrototypeJoin(protocols, ", "),
|
||||||
cancelRid,
|
cancelRid,
|
||||||
|
headers ? headerListFromHeaders(headers) : null,
|
||||||
),
|
),
|
||||||
(create) => {
|
(create) => {
|
||||||
this[_rid] = create.rid;
|
this[_rid] = create.rid;
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
// Copyright 2018-2025 the Deno authors. MIT license.
|
// Copyright 2018-2025 the Deno authors. MIT license.
|
||||||
|
|
||||||
|
// deno-lint-ignore-file no-console
|
||||||
|
|
||||||
import {
|
import {
|
||||||
assert,
|
assert,
|
||||||
assertEquals,
|
assertEquals,
|
||||||
|
@ -867,3 +870,68 @@ Deno.test("websocket close ongoing handshake", async () => {
|
||||||
assert(gotError2);
|
assert(gotError2);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function createOnErrorCb(ac: AbortController): (err: unknown) => Response {
|
||||||
|
return (err) => {
|
||||||
|
console.error(err);
|
||||||
|
ac.abort();
|
||||||
|
return new Response("Internal server error", { status: 500 });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function onListen(
|
||||||
|
resolve: (value: void | PromiseLike<void>) => void,
|
||||||
|
): ({ hostname, port }: { hostname: string; port: number }) => void {
|
||||||
|
return () => {
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Deno.test("WebSocket headers", async () => {
|
||||||
|
const ac = new AbortController();
|
||||||
|
const listeningDeferred = Promise.withResolvers<void>();
|
||||||
|
const doneDeferred = Promise.withResolvers<void>();
|
||||||
|
await using server = Deno.serve({
|
||||||
|
handler: (request) => {
|
||||||
|
assertEquals(request.headers.get("Authorization"), "Bearer foo");
|
||||||
|
const {
|
||||||
|
response,
|
||||||
|
socket,
|
||||||
|
} = Deno.upgradeWebSocket(request);
|
||||||
|
socket.onerror = (e) => {
|
||||||
|
console.error(e);
|
||||||
|
fail();
|
||||||
|
};
|
||||||
|
socket.onmessage = (m) => {
|
||||||
|
socket.send(m.data);
|
||||||
|
socket.close(1001);
|
||||||
|
};
|
||||||
|
socket.onclose = () => doneDeferred.resolve();
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
port: servePort,
|
||||||
|
signal: ac.signal,
|
||||||
|
onListen: onListen(listeningDeferred.resolve),
|
||||||
|
onError: createOnErrorCb(ac),
|
||||||
|
});
|
||||||
|
|
||||||
|
await listeningDeferred.promise;
|
||||||
|
const def = Promise.withResolvers<void>();
|
||||||
|
const ws = new WebSocket(`ws://localhost:${servePort}`, {
|
||||||
|
headers: {
|
||||||
|
"Authorization": "Bearer foo",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
ws.onmessage = (m) => assertEquals(m.data, "foo");
|
||||||
|
ws.onerror = (e) => {
|
||||||
|
console.error(e);
|
||||||
|
fail();
|
||||||
|
};
|
||||||
|
ws.onclose = () => def.resolve();
|
||||||
|
ws.onopen = () => ws.send("foo");
|
||||||
|
|
||||||
|
await def.promise;
|
||||||
|
await doneDeferred.promise;
|
||||||
|
ac.abort();
|
||||||
|
await server.finished;
|
||||||
|
});
|
||||||
|
|
|
@ -14554,6 +14554,7 @@
|
||||||
"eventhandlers.any.worker.html?wpt_flags=h2": true,
|
"eventhandlers.any.worker.html?wpt_flags=h2": true,
|
||||||
"eventhandlers.any.worker.html?wss": true,
|
"eventhandlers.any.worker.html?wss": true,
|
||||||
"idlharness.any.html": [
|
"idlharness.any.html": [
|
||||||
|
"WebSocket interface object length",
|
||||||
"WebSocket interface: constant CONNECTING on interface object",
|
"WebSocket interface: constant CONNECTING on interface object",
|
||||||
"WebSocket interface: constant CONNECTING on interface prototype object",
|
"WebSocket interface: constant CONNECTING on interface prototype object",
|
||||||
"WebSocket interface: constant OPEN on interface object",
|
"WebSocket interface: constant OPEN on interface object",
|
||||||
|
@ -14572,6 +14573,7 @@
|
||||||
"Stringification of new CloseEvent(\"close\")"
|
"Stringification of new CloseEvent(\"close\")"
|
||||||
],
|
],
|
||||||
"idlharness.any.worker.html": [
|
"idlharness.any.worker.html": [
|
||||||
|
"WebSocket interface object length",
|
||||||
"WebSocket interface: constant CONNECTING on interface object",
|
"WebSocket interface: constant CONNECTING on interface object",
|
||||||
"WebSocket interface: constant CONNECTING on interface prototype object",
|
"WebSocket interface: constant CONNECTING on interface prototype object",
|
||||||
"WebSocket interface: constant OPEN on interface object",
|
"WebSocket interface: constant OPEN on interface object",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue