@types/bun: Reimplement WebSocket type in default environment (#19017)

This commit is contained in:
Alistair Smith
2025-04-14 19:10:52 -07:00
committed by GitHub
parent 27a580a4d5
commit 6b53301375
7 changed files with 553 additions and 46 deletions

View File

@@ -27,10 +27,8 @@
}, },
"packages/bun-types": { "packages/bun-types": {
"name": "bun-types", "name": "bun-types",
"version": "1.2.5",
"dependencies": { "dependencies": {
"@types/node": "*", "@types/node": "*",
"@types/ws": "*",
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^1.5.3", "@biomejs/biome": "^1.5.3",
@@ -166,8 +164,6 @@
"@types/semver": ["@types/semver@7.5.8", "", {}, "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ=="], "@types/semver": ["@types/semver@7.5.8", "", {}, "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ=="],
"@types/ws": ["@types/ws@8.5.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-4+q7P5h3SpJxaBft0Dzpbr6lmMaqh0Jr2tbhJZ/luAwvD7ohSCniYkwz/pLxuT2h0EOa6QADgJj1Ko+TzRfZ+w=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@7.16.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "7.16.1", "@typescript-eslint/type-utils": "7.16.1", "@typescript-eslint/utils": "7.16.1", "@typescript-eslint/visitor-keys": "7.16.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^1.3.0" }, "peerDependencies": { "@typescript-eslint/parser": "^7.0.0", "eslint": "^8.56.0" } }, "sha512-SxdPak/5bO0EnGktV05+Hq8oatjAYVY3Zh2bye9pGZy6+jwyR3LG3YKkV4YatlsgqXP28BTeVm9pqwJM96vf2A=="], "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@7.16.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "7.16.1", "@typescript-eslint/type-utils": "7.16.1", "@typescript-eslint/utils": "7.16.1", "@typescript-eslint/visitor-keys": "7.16.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^1.3.0" }, "peerDependencies": { "@typescript-eslint/parser": "^7.0.0", "eslint": "^8.56.0" } }, "sha512-SxdPak/5bO0EnGktV05+Hq8oatjAYVY3Zh2bye9pGZy6+jwyR3LG3YKkV4YatlsgqXP28BTeVm9pqwJM96vf2A=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@7.16.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "7.16.1", "@typescript-eslint/types": "7.16.1", "@typescript-eslint/typescript-estree": "7.16.1", "@typescript-eslint/visitor-keys": "7.16.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-u+1Qx86jfGQ5i4JjK33/FnawZRpsLxRnKzGE6EABZ40KxVT/vWsiZFEBBHjFOljmmV3MBYOHEKi0Jm9hbAOClA=="], "@typescript-eslint/parser": ["@typescript-eslint/parser@7.16.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "7.16.1", "@typescript-eslint/types": "7.16.1", "@typescript-eslint/typescript-estree": "7.16.1", "@typescript-eslint/visitor-keys": "7.16.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-u+1Qx86jfGQ5i4JjK33/FnawZRpsLxRnKzGE6EABZ40KxVT/vWsiZFEBBHjFOljmmV3MBYOHEKi0Jm9hbAOClA=="],
@@ -916,8 +912,6 @@
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"@types/ws/@types/node": ["@types/node@20.12.14", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg=="],
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
@@ -1008,8 +1002,6 @@
"@definitelytyped/utils/which/isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="], "@definitelytyped/utils/which/isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="],
"@types/ws/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
"are-we-there-yet/readable-stream/isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], "are-we-there-yet/readable-stream/isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],

View File

@@ -162,11 +162,6 @@ declare module "bun" {
open: Event; open: Event;
} }
interface EventListenerOptions {
/** Not directly used by Node.js. Added for API completeness. Default: `false`. */
capture?: boolean;
}
interface AddEventListenerOptions extends EventListenerOptions { interface AddEventListenerOptions extends EventListenerOptions {
/** When `true`, the listener is automatically removed when it is first invoked. Default: `false`. */ /** When `true`, the listener is automatically removed when it is first invoked. Default: `false`. */
once?: boolean; once?: boolean;
@@ -4264,6 +4259,211 @@ declare module "bun" {
compact?: boolean; compact?: boolean;
} }
type WebSocketOptionsProtocolsOrProtocol =
| {
/**
* Protocols to use for the WebSocket connection
*/
protocols?: string | string[];
}
| {
/**
* Protocol to use for the WebSocket connection
*/
protocol?: string;
};
type WebSocketOptionsTLS = {
/**
* Options for the TLS connection
*/
tls?: {
/**
* Whether to reject the connection if the certificate is not valid
*
* @default true
*/
rejectUnauthorized?: boolean;
};
};
type WebSocketOptionsHeaders = {
/**
* Headers to send to the server
*/
headers?: import("node:http").OutgoingHttpHeaders;
};
/**
* Constructor options for the `Bun.WebSocket` client
*/
type WebSocketOptions = WebSocketOptionsProtocolsOrProtocol & WebSocketOptionsTLS & WebSocketOptionsHeaders;
interface WebSocketEventMap {
close: CloseEvent;
error: Event;
message: MessageEvent;
open: Event;
}
/**
* A WebSocket client implementation
*
* @example
* ```ts
* const ws = new WebSocket("ws://localhost:8080", {
* headers: {
* "x-custom-header": "hello",
* },
* });
*
* ws.addEventListener("open", () => {
* console.log("Connected to server");
* });
*
* ws.addEventListener("message", (event) => {
* console.log("Received message:", event.data);
* });
*
* ws.send("Hello, server!");
* ws.terminate();
* ```
*/
interface WebSocket extends EventTarget {
/**
* The URL of the WebSocket connection
*/
readonly url: string;
/**
* Legacy URL property (same as url)
* @deprecated Use url instead
*/
readonly URL: string;
/**
* The current state of the connection
*/
readonly readyState:
| typeof WebSocket.CONNECTING
| typeof WebSocket.OPEN
| typeof WebSocket.CLOSING
| typeof WebSocket.CLOSED;
/**
* The number of bytes of data that have been queued using send() but not yet transmitted to the network
*/
readonly bufferedAmount: number;
/**
* The protocol selected by the server
*/
readonly protocol: string;
/**
* The extensions selected by the server
*/
readonly extensions: string;
/**
* The type of binary data being received.
*/
binaryType: "arraybuffer" | "nodebuffer";
/**
* Event handler for open event
*/
onopen: ((this: WebSocket, ev: Event) => any) | null;
/**
* Event handler for message event
*/
onmessage: ((this: WebSocket, ev: MessageEvent) => any) | null;
/**
* Event handler for error event
*/
onerror: ((this: WebSocket, ev: Event) => any) | null;
/**
* Event handler for close event
*/
onclose: ((this: WebSocket, ev: CloseEvent) => any) | null;
/**
* Transmits data to the server
* @param data The data to send to the server
*/
send(data: string | ArrayBufferLike | ArrayBufferView): void;
/**
* Closes the WebSocket connection
* @param code A numeric value indicating the status code
* @param reason A human-readable string explaining why the connection is closing
*/
close(code?: number, reason?: string): void;
/**
* Sends a ping frame to the server
* @param data Optional data to include in the ping frame
*/
ping(data?: string | ArrayBufferLike | ArrayBufferView): void;
/**
* Sends a pong frame to the server
* @param data Optional data to include in the pong frame
*/
pong(data?: string | ArrayBufferLike | ArrayBufferView): void;
/**
* Immediately terminates the connection
*/
terminate(): void;
/**
* Registers an event handler of a specific event type on the WebSocket.
* @param type A case-sensitive string representing the event type to listen for
* @param listener The function to be called when the event occurs
* @param options An options object that specifies characteristics about the event listener
*/
addEventListener<K extends keyof WebSocketEventMap>(
type: K,
listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any,
options?: boolean | AddEventListenerOptions,
): void;
addEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions,
): void;
/**
* Removes an event listener previously registered with addEventListener()
* @param type A case-sensitive string representing the event type to remove
* @param listener The function to remove from the event target
* @param options An options object that specifies characteristics about the event listener
*/
removeEventListener<K extends keyof WebSocketEventMap>(
type: K,
listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any,
options?: boolean | EventListenerOptions,
): void;
removeEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
options?: boolean | EventListenerOptions,
): void;
/** @deprecated Use instance property instead */
readonly CONNECTING: 0;
/** @deprecated Use instance property instead */
readonly OPEN: 1;
/** @deprecated Use instance property instead */
readonly CLOSING: 2;
/** @deprecated Use instance property instead */
readonly CLOSED: 3;
}
/** /**
* Pretty-print an object the same as {@link console.log} to a `string` * Pretty-print an object the same as {@link console.log} to a `string`
* *

View File

@@ -11,7 +11,7 @@ declare module "bun" {
type NodeCryptoWebcryptoSubtleCrypto = import("crypto").webcrypto.SubtleCrypto; type NodeCryptoWebcryptoSubtleCrypto = import("crypto").webcrypto.SubtleCrypto;
type NodeCryptoWebcryptoCryptoKey = import("crypto").webcrypto.CryptoKey; type NodeCryptoWebcryptoCryptoKey = import("crypto").webcrypto.CryptoKey;
type LibEmptyOrWSWebSocket = LibDomIsLoaded extends true ? {} : import("ws").WebSocket; type LibEmptyOrBunWebSocket = LibDomIsLoaded extends true ? {} : Bun.WebSocket;
type LibEmptyOrNodeUtilTextEncoder = LibDomIsLoaded extends true ? {} : import("node:util").TextEncoder; type LibEmptyOrNodeUtilTextEncoder = LibDomIsLoaded extends true ? {} : import("node:util").TextEncoder;
@@ -63,15 +63,71 @@ declare var Worker: Bun.__internal.UseLibDomIfAvailable<
} }
>; >;
interface WebSocket extends Bun.__internal.LibEmptyOrWSWebSocket {} /**
* A WebSocket client implementation.
*/
interface WebSocket extends Bun.__internal.LibEmptyOrBunWebSocket {}
/** /**
* A WebSocket client implementation * A WebSocket client implementation
*
* If `DOM` is included in tsconfig `lib`, this falls back to the default DOM global `WebSocket`.
* Otherwise (when outside of a browser environment), this will be the `WebSocket`
* implementation from the `ws` package, which Bun implements.
*/ */
declare var WebSocket: Bun.__internal.UseLibDomIfAvailable<"WebSocket", typeof import("ws").WebSocket>; declare var WebSocket: Bun.__internal.UseLibDomIfAvailable<
"WebSocket",
{
prototype: WebSocket;
/**
* Creates a new WebSocket instance with the given URL and options.
*
* @param url The URL to connect to.
* @param options The options to use for the connection.
*
* @example
* ```ts
* const ws = new WebSocket("wss://dev.local", {
* protocols: ["proto1", "proto2"],
* headers: {
* "Cookie": "session=123456",
* },
* });
* ```
*/
new (url: string | URL, options?: Bun.WebSocketOptions): WebSocket;
/**
* Creates a new WebSocket instance with the given URL and protocols.
*
* @param url The URL to connect to.
* @param protocols The protocols to use for the connection.
*
* @example
* ```ts
* const ws = new WebSocket("wss://dev.local");
* const ws = new WebSocket("wss://dev.local", ["proto1", "proto2"]);
* ```
*/
new (url: string | URL, protocols?: string | string[]): WebSocket;
/**
* The connection is not yet open
*/
readonly CONNECTING: 0;
/**
* The connection is open and ready to communicate
*/
readonly OPEN: 1;
/**
* The connection is in the process of closing
*/
readonly CLOSING: 2;
/**
* The connection is closed or couldn't be opened
*/
readonly CLOSED: 3;
}
>;
interface Crypto { interface Crypto {
readonly subtle: SubtleCrypto; readonly subtle: SubtleCrypto;

View File

@@ -15,8 +15,7 @@
], ],
"homepage": "https://bun.sh", "homepage": "https://bun.sh",
"dependencies": { "dependencies": {
"@types/node": "*", "@types/node": "*"
"@types/ws": "*"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^1.5.3", "@biomejs/biome": "^1.5.3",

View File

@@ -122,7 +122,22 @@ describe("@types/bun integration test", () => {
"error TS2339: Property 'write' does not exist on type 'ReadableByteStreamController'.", "error TS2339: Property 'write' does not exist on type 'ReadableByteStreamController'.",
"websocket.ts", "websocket.ts",
"error TS2353: Object literal may only specify known properties, and 'headers' does not exist in type 'string[]'.", `error TS2353: Object literal may only specify known properties, and 'protocols' does not exist in type 'string[]'.`,
`error TS2353: Object literal may only specify known properties, and 'protocol' does not exist in type 'string[]'.`,
`error TS2353: Object literal may only specify known properties, and 'protocol' does not exist in type 'string[]'.`,
`error TS2353: Object literal may only specify known properties, and 'headers' does not exist in type 'string[]'.`,
`error TS2353: Object literal may only specify known properties, and 'protocols' does not exist in type 'string[]'.`,
`error TS2554: Expected 2 arguments, but got 0.`,
`error TS2551: Property 'URL' does not exist on type 'WebSocket'. Did you mean 'url'?`,
`error TS2339: Property 'ping' does not exist on type 'WebSocket'.`,
`error TS2339: Property 'ping' does not exist on type 'WebSocket'.`,
`error TS2339: Property 'ping' does not exist on type 'WebSocket'.`,
`error TS2339: Property 'ping' does not exist on type 'WebSocket'.`,
`error TS2339: Property 'pong' does not exist on type 'WebSocket'.`,
`error TS2339: Property 'pong' does not exist on type 'WebSocket'.`,
`error TS2339: Property 'pong' does not exist on type 'WebSocket'.`,
`error TS2339: Property 'pong' does not exist on type 'WebSocket'.`,
`error TS2339: Property 'terminate' does not exist on type 'WebSocket'.`,
"worker.ts", "worker.ts",
"error TS2339: Property 'ref' does not exist on type 'Worker'.", "error TS2339: Property 'ref' does not exist on type 'Worker'.",

View File

@@ -1,15 +1,271 @@
export class TestWebSocketClient { import { expectType } from "./utilities";
#ws: WebSocket;
constructor() { // WebSocket constructor tests
this.#ws = new WebSocket("wss://dev.local", { {
headers: { // Constructor with string URL only
cookie: "test=test", new WebSocket("wss://dev.local");
},
});
}
close() { // Constructor with string URL and protocols array
if (this.#ws != null) this.#ws.close(); new WebSocket("wss://dev.local", ["proto1", "proto2"]);
}
// Constructor with string URL and single protocol string
new WebSocket("wss://dev.local", "proto1");
// Constructor with URL object only
new WebSocket(new URL("wss://dev.local"));
// Constructor with URL object and protocols array
new WebSocket(new URL("wss://dev.local"), ["proto1", "proto2"]);
// Constructor with URL object and single protocol string
new WebSocket(new URL("wss://dev.local"), "proto1");
// Constructor with string URL and options object with protocols
new WebSocket("wss://dev.local", {
protocols: ["proto1", "proto2"],
});
// Constructor with string URL and options object with protocol
new WebSocket("wss://dev.local", {
protocol: "proto1",
});
// Constructor with URL object and options with TLS settings
new WebSocket(new URL("wss://dev.local"), {
protocol: "proto1",
tls: {
rejectUnauthorized: false,
},
});
// Constructor with headers
new WebSocket("wss://dev.local", {
headers: {
"Cookie": "session=123456",
"User-Agent": "BunWebSocketTest",
},
});
// Constructor with full options object
new WebSocket("wss://dev.local", {
protocols: ["proto1", "proto2"],
headers: {
"Cookie": "session=123456",
},
tls: {
rejectUnauthorized: true,
},
});
}
// Assignability test
{
function toAny<T>(value: T): any {
return value;
}
const AnySocket = toAny(WebSocket);
const ws: WebSocket = new AnySocket("wss://dev.local");
ws.close();
ws.addEventListener("open", e => expectType(e).is<Event>());
ws.addEventListener("message", e => expectType(e).is<MessageEvent>());
ws.addEventListener("message", (e: MessageEvent<string>) => expectType(e).is<MessageEvent<string>>());
ws.addEventListener("message", (e: MessageEvent<string>) => expectType(e.data).is<string>());
}
// WebSocket static properties test
{
expectType(WebSocket.CONNECTING).is<0>();
expectType(WebSocket.OPEN).is<1>();
expectType(WebSocket.CLOSING).is<2>();
expectType(WebSocket.CLOSED).is<3>();
const instance: WebSocket = null as never;
expectType(instance.CONNECTING).is<0>();
expectType(instance.OPEN).is<1>();
expectType(instance.CLOSING).is<2>();
expectType(instance.CLOSED).is<3>();
}
// WebSocket event handlers test
{
const ws = new WebSocket("wss://dev.local");
// Using event handler properties
ws.onopen = (event: Event) => {
expectType(event).is<Event>();
};
ws.onmessage = (event: MessageEvent<string>) => {
expectType(event.data).is<string>();
};
ws.onerror = (event: Event) => {
expectType(event).is<Event>();
};
ws.onclose = (event: CloseEvent) => {
expectType(event).is<CloseEvent>();
expectType(event.code).is<number>();
expectType(event.reason).is<string>();
expectType(event.wasClean).is<boolean>();
};
// Using event handler properties without typing the agument
ws.onopen = event => {
expectType(event).is<Event>();
};
ws.onmessage = event => {
expectType(event.data).is<any>();
if (typeof event.data === "string") {
expectType(event.data).is<string>();
} else if (event.data instanceof ArrayBuffer) {
expectType(event.data).is<ArrayBuffer>();
}
};
ws.onerror = event => {
expectType(event).is<Event>();
};
ws.onclose = event => {
expectType(event).is<CloseEvent>();
expectType(event.code).is<number>();
expectType(event.reason).is<string>();
expectType(event.wasClean).is<boolean>();
};
}
// WebSocket addEventListener test
{
const ws = new WebSocket("wss://dev.local");
// Event handler functions
const handleOpen = (event: Event) => {
expectType(event).is<Event>();
};
const handleMessage = (event: MessageEvent<string>) => {
expectType(event.data).is<string>();
};
const handleError = (event: Event) => {
expectType(event).is<Event>();
};
const handleClose = (event: CloseEvent) => {
expectType(event).is<CloseEvent>();
expectType(event.code).is<number>();
expectType(event.reason).is<string>();
expectType(event.wasClean).is<boolean>();
};
// Add event listeners
ws.addEventListener("open", handleOpen);
ws.addEventListener("message", handleMessage);
ws.addEventListener("error", handleError);
ws.addEventListener("close", handleClose);
// Remove event listeners
ws.removeEventListener("open", handleOpen);
ws.removeEventListener("message", handleMessage);
ws.removeEventListener("error", handleError);
ws.removeEventListener("close", handleClose);
}
// WebSocket property access test
{
const ws = new WebSocket("wss://dev.local");
// Read various properties
expectType(ws.readyState).is<0 | 2 | 1 | 3>();
expectType(ws.bufferedAmount).is<number>();
expectType(ws.url).is<string>();
expectType(ws.protocol).is<string>();
expectType(ws.extensions).is<string>();
// Legacy URL property (deprecated but exists)
expectType(ws.URL).is<string>();
// Set binary type
ws.binaryType = "arraybuffer";
ws.binaryType = "blob";
}
// WebSocket send method test
{
const ws = new WebSocket("wss://dev.local");
// Send string data
ws.send("Hello, server!");
// Send ArrayBuffer
const buffer = new ArrayBuffer(10);
ws.send(buffer);
// Send ArrayBufferView (Uint8Array)
const uint8Array = new Uint8Array(buffer);
ws.send(uint8Array);
// --------------------------------------- //
// `.send(blob)` is not supported yet
// --------------------------------------- //
// // Send Blob
// const blob = new Blob(["Hello, server!"]);
// ws.send(blob);
// --------------------------------------- //
}
// WebSocket close method test
{
const ws = new WebSocket("wss://dev.local");
// Close without parameters
ws.close();
// Close with code
ws.close(1000);
// Close with code and reason
ws.close(1001, "Going away");
}
// Bun-specific WebSocket extensions test
{
const ws = new WebSocket("wss://dev.local");
// Send ping frame with no data
ws.ping();
// Send ping frame with string data
ws.ping("ping data");
// Send ping frame with ArrayBuffer
const pingBuffer = new ArrayBuffer(4);
ws.ping(pingBuffer);
// Send ping frame with ArrayBufferView
const pingView = new Uint8Array(pingBuffer);
ws.ping(pingView);
// Send pong frame with no data
ws.pong();
// Send pong frame with string data
ws.pong("pong data");
// Send pong frame with ArrayBuffer
const pongBuffer = new ArrayBuffer(4);
ws.pong(pongBuffer);
// Send pong frame with ArrayBufferView
const pongView = new Uint8Array(pongBuffer);
ws.pong(pongView);
// Terminate the connection immediately
ws.terminate();
} }

View File

@@ -1,11 +0,0 @@
import { WebSocket, WebSocketServer } from "ws";
const ws = new WebSocket("ws://www.host.com/path");
ws.send("asdf");
const wss = new WebSocketServer({
port: 8080,
perMessageDeflate: false,
});
wss;