UNPKG

@ayonli/jsext

Version:

A JavaScript extension package for building strong and modern applications.

210 lines (209 loc) 7.01 kB
/** * This module provides a unified WebSocket server interface for Node.js, Deno, * Bun and Cloudflare Workers. This module is based on the `EventTarget` * interface and conforms the web standard. * * **IMPORTANT**: The {@link WebSocketConnection} interface is an abstraction of * the WebSocket on the server side, it's design is not consistent with the * {@link WebSocket} API in the browser. For example, when receiving binary data, * the `data` property is always a `Uint8Array` object, which is different from * the `Blob` object (or `ArrayBuffer`) in the browser. In the future, we may * provide a more consistent API, be aware of this when using this module. * @module * @experimental */ /// <reference types="node" /> import type { BunServer } from "./http/server.ts"; import { ServerOptions, WebSocketConnection, WebSocketHandler, WebSocketLike } from "./ws/base.ts"; import type { IncomingMessage } from "node:http"; export type { ServerOptions, WebSocketConnection, WebSocketHandler, WebSocketLike }; declare const _handler: unique symbol; declare const _clients: unique symbol; declare const _httpServer: unique symbol; declare const _wsServer: unique symbol; declare const _connTasks: unique symbol; /** * A unified WebSocket server interface for Node.js, Deno, Bun and Cloudflare * Workers. * * There are two ways to handle WebSocket connections: * * 1. By passing a listener function to the constructor, handle all connections * in a central place. * 2. By using the `socket` object returned from the `upgrade` method to handle * each connection in a more flexible way. However, at this stage, the * connection may not be ready yet, and we need to listen the `open` event * or wait for the `ready` promise (deprecated) to resolve before we can * start sending messages. * * The `socket` object is an async iterable object, which can be used in the * `for await...of` loop to read messages with backpressure support. * * @example * ```ts * // centralized connection handler * import { WebSocketServer } from "@ayonli/jsext/ws"; * * const wsServer = new WebSocketServer(socket => { * console.log("WebSocket connection established."); * * socket.addEventListener("message", (event) => { * socket.send("received: " + event.data); * }); * * socket.addEventListener("error", (event) => { * console.error("WebSocket connection error:", event.error); * }); * * socket.addEventListener("close", (event) => { * console.log(`WebSocket connection closed, reason: ${event.reason}, code: ${event.code}`); * }); * }); * * // Node.js * import * as http from "node:http"; * const httpServer = http.createServer(req => { * wsServer.upgrade(req); * }); * httpServer.listen(3000); * * // Node.js (withWeb) * import { withWeb } from "@ayonli/jsext/http"; * const httpServer2 = http.createServer(withWeb(req => { * const { response } = wsServer.upgrade(req); * return response; * })); * httpServer2.listen(3001); * * // Bun * const bunServer = Bun.serve({ * fetch(req) { * const { response } = wsServer.upgrade(req); * return response; * }, * websocket: wsServer.bunListener, * }); * wsServer.bunBind(bunServer); * * // Deno * Deno.serve(req => { * const { response } = wsServer.upgrade(req); * return response; * }); * * // Cloudflare Workers * export default { * fetch(req) { * const { response } = wsServer.upgrade(req); * return response; * }, * }; * ``` * * @example * ```ts * // per-request connection handler (Deno example) * import { WebSocketServer } from "@ayonli/jsext/ws"; * * const wsServer = new WebSocketServer(); * * Deno.serve(req => { * const { socket, response } = wsServer.upgrade(req); * * socket.addEventListener("open", () => { * console.log("WebSocket connection established."); * }); * * socket.addEventListener("message", (event) => { * socket.send("received: " + event.data); * }); * * socket.addEventListener("error", (event) => { * console.error("WebSocket connection error:", event.error); * }); * * socket.addEventListener("close", (event) => { * console.log(`WebSocket connection closed, reason: ${event.reason}, code: ${event.code}`); * }); * * // The response should be returned immediately, otherwise the web socket * // will not be ready. * return response; * }); * ``` * * @example * ```ts * // async iterable * const wsServer = new WebSocketServer(async socket => { * console.log("WebSocket connection established."); * * try { * for await (const message of socket) { * socket.send("received: " + message); * } * } catch (error) { * console.error("WebSocket connection error:", error); * } * * console.log("WebSocket connection closed"); * }); * * Deno.serve(req => { * const { response } = wsServer.upgrade(req); * return response; * }); * ``` */ export declare class WebSocketServer { protected idleTimeout: number; protected perMessageDeflate: boolean; protected [_handler]: WebSocketHandler | undefined; protected [_clients]: Map<Request | IncomingMessage, WebSocketConnection>; private [_httpServer]; private [_wsServer]; private [_connTasks]; constructor(handler?: WebSocketHandler | undefined); constructor(options: ServerOptions, handler: WebSocketHandler); /** * Upgrades the request to a WebSocket connection in Deno, Bun and Cloudflare * Workers. * * This function can also be used in Node.js if the HTTP request listener is * created using the `withWeb` function from `@ayonli/jsext/http` module. * * NOTE: This function fails if the request is not a WebSocket upgrade request. */ upgrade(request: Request): { socket: WebSocketConnection; response: Response; }; /** * Upgrades the request to a WebSocket connection in Node.js. * * NOTE: This function fails if the request is not a WebSocket upgrade request. */ upgrade(request: IncomingMessage): { socket: WebSocketConnection; }; /** * Used in Bun, to bind the WebSocket server to the Bun server instance. */ bunBind(server: BunServer): void; /** * A WebSocket listener for `Bun.serve()`. */ get bunListener(): { idleTimeout: number; perMessageDeflate: boolean; message: (ws: any, message: string | ArrayBuffer | Uint8Array) => void; open: (ws: any) => void; error: (ws: any, error: Error) => void; close: (ws: any, code: number, reason: string) => void; }; /** * An iterator that yields all connected WebSocket clients, can be used to * broadcast messages to all clients. */ get clients(): IterableIterator<WebSocketConnection>; }