@ayonli/jsext
Version:
A JavaScript extension package for building strong and modern applications.
210 lines (209 loc) • 7.01 kB
TypeScript
/**
* 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>;
}