UNPKG

@ayonli/jsext

Version:

A JavaScript extension package for building strong and modern applications.

141 lines (125 loc) 5.16 kB
import { createCloseEvent, createErrorEvent } from "../event.ts"; 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 }; const _handler = Symbol.for("handler"); const _clients = Symbol.for("clients"); declare var WebSocketPair: { new(): [WebSocket, WebSocket & { accept: () => void; }]; }; export class WebSocketServer { protected idleTimeout: number; protected perMessageDeflate: boolean; protected [_handler]: WebSocketHandler | undefined; protected [_clients]: Map<Request, WebSocketConnection> = new Map(); constructor(handler?: WebSocketHandler | undefined); constructor(options: ServerOptions, handler: WebSocketHandler); constructor(...args: any[]) { if (args.length === 2) { this.idleTimeout = args[0]?.idleTimeout || 30; this.perMessageDeflate = args[0]?.perMessageDeflate ?? false; this[_handler] = args[1]; } else { this.idleTimeout = 30; this.perMessageDeflate = false; this[_handler] = args[0]; } } upgrade(request: Request): { socket: WebSocketConnection; response: Response; }; upgrade(request: IncomingMessage): { socket: WebSocketConnection; }; upgrade(request: Request | IncomingMessage): { socket: WebSocketConnection; response: Response; } { if ("socket" in request) { throw new TypeError("Expected a Request instance"); } const upgradeHeader = request.headers.get("Upgrade"); if (!upgradeHeader || upgradeHeader !== "websocket") { throw new TypeError("Expected Upgrade: websocket"); } else if (typeof WebSocketPair !== "function") { throw new Error("WebSocket is not supported in this environment"); } const handler = this[_handler]; const clients = this[_clients]; const [client, server] = Object.values(new WebSocketPair()) as [WebSocket, WebSocket & { accept: () => void; }]; const socket = new WebSocketConnection(new Promise<WebSocketLike>(resolve => { server.accept(); server.addEventListener("message", ev => { if (typeof ev.data === "string") { socket.dispatchEvent(new MessageEvent("message", { data: ev.data, })); } else if (ev.data instanceof ArrayBuffer) { socket.dispatchEvent(new MessageEvent("message", { data: new Uint8Array(ev.data), })); } else { (ev.data as Blob).arrayBuffer().then(buffer => { socket.dispatchEvent(new MessageEvent("message", { data: new Uint8Array(buffer), })); }).catch(() => { }); } }); server.addEventListener("close", ev => { if (!ev.wasClean) { socket.dispatchEvent(createErrorEvent("error", { error: new Error(`WebSocket connection closed: ${ev.reason} (${ev.code})`), })); } clients.delete(request); socket.dispatchEvent(createCloseEvent("close", { code: ev.code, reason: ev.reason, wasClean: ev.wasClean, })); }); if (server.readyState === 1) { resolve(server); } else { server.addEventListener("open", () => { resolve(server); }); } })); socket.ready.then(() => { clients.set(request, socket); handler?.call(this, socket); socket.dispatchEvent(new Event("open")); }); return { socket, response: new Response(null, { status: 101, statusText: "Switching Protocols", headers: new Headers({ "Upgrade": "websocket", "Connection": "Upgrade", }), // @ts-ignore webSocket: client, }), }; } bunBind(server: BunServer): void { void server; } 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; } { return { idleTimeout: this.idleTimeout, perMessageDeflate: this.perMessageDeflate, message: () => void 0, open: () => void 0, error: () => void 0, close: () => void 0, }; } }