import type { IncomingMessage } from 'node:http'; import type { Duplex } from 'node:stream'; /** * WebSocket upgrade relay for runtimes whose HTTP server cannot hand the raw * upgraded socket back to JavaScript. * * Bun's `node:http` server is one of them: the socket passed to the `'upgrade'` * event never delivers client bytes (`'data'` does not fire) and its * `socket.write()` never reaches the peer, so `http-proxy`'s WebSocket pass * drops the `101` response and then the whole frame stream * (oven-sh/bun#9882, oven-sh/bun#18945, oven-sh/bun#28396). The internal * services keep working because they use `ws`, which Bun implements natively. * * The relay therefore terminates the client side with that same native * WebSocket support and speaks the upstream hop itself over a raw TCP/Unix * socket. The upstream side stays byte-accurate — including non-101 rejections * and Unix socket targets — while `WebSocketFrames` bridges messages to frames. */ export interface NativeUpgradeTarget { url?: string; socketPath?: string; } /** Logging surface of the relay (satisfied by `getLoggerFor`). */ export interface RelayLogger { debug(message: string): void; warn(message: string): void; error(message: string): void; } /** Subset of the `ws` module surface used by the relay. */ export interface RelayWebSocket { send(data: Buffer, options?: { binary?: boolean; }): void; close(code?: number, reason?: string): void; on(event: 'message', listener: (data: Buffer | ArrayBuffer | Buffer[] | string, isBinary: boolean) => void): void; on(event: 'close', listener: (code: number, reason: Buffer) => void): void; on(event: 'error', listener: (error: Error) => void): void; } export interface RelayWebSocketServer { handleUpgrade(request: IncomingMessage, socket: Duplex, head: Buffer, callback: (client: RelayWebSocket) => void): void; } export interface RelayWebSocketModule { WebSocketServer: new (options: Record) => RelayWebSocketServer; } /** Loads the runtime's `ws` implementation; `undefined` when it is unavailable. */ export type WebSocketModuleLoader = () => Promise; export interface BunNativeUpgradeRelayOptions { logger: RelayLogger; /** Relays the upgrade the classic way when native WebSocket support is unavailable. */ fallback(req: IncomingMessage, socket: Duplex, head: Buffer, target: NativeUpgradeTarget): void; /** Test seam: replaces the runtime `ws` import. */ loadWebSocketModule?: WebSocketModuleLoader; handshakeTimeoutMs?: number; closeGraceMs?: number; maxHandshakeBytes?: number; } export interface RelayConnectionDependencies { logger: RelayLogger; onClosed(connection: NativeUpgradeConnection): void; handshakeTimeoutMs: number; closeGraceMs: number; maxHandshakeBytes: number; } export declare class BunNativeUpgradeRelay { private readonly options; private readonly connections; private readonly loadWebSocketModule; private readonly handshakeTimeoutMs; private readonly closeGraceMs; private readonly maxHandshakeBytes; private modulePromise?; private moduleReported; constructor(options: BunNativeUpgradeRelayOptions); handle(req: IncomingMessage, socket: Duplex, head: Buffer, target: NativeUpgradeTarget): void; /** Terminates every relayed connection (gateway shutdown/restart). */ close(): void; private start; private webSocketModule; } export declare class NativeUpgradeConnection { private readonly module; private readonly deps; private upstream?; private clientSocket?; private client?; private parser?; private protocol; private handshakeBuffer; private handshakeTimer?; private handshakeListener?; private closeTimer?; private errorTimer?; private state; constructor(module: RelayWebSocketModule, deps: RelayConnectionDependencies); start(req: IncomingMessage, socket: Duplex, head: Buffer, target: NativeUpgradeTarget): void; /** Closes the relayed connection as part of gateway shutdown. */ shutdown(): void; private connectUpstream; private onUpstreamHandshakeData; /** * Relays a non-101 upstream answer verbatim so clients observe the real * status (401 for an expired ticket, 404 for an unknown channel, ...). */ private relayRejection; private closeRejectedClient; private createFrameParser; private upgradeClient; private forwardClientMessage; private onClientClose; /** Waits briefly for the upstream close handshake before dropping the socket. */ private scheduleUpstreamClose; private fail; private destroy; private clearHandshakeTimer; private finish; }