/** * WebSocket broadcast manager for process events. * Handles debounced output events, backpressure control, and client subscriptions. */ import type { IncomingMessage } from "node:http"; import { WebSocketServer } from "ws"; import type { CardExpandDefaults, SessionSnapshot, ProcessEvent } from "./types.js"; import { type AuthService } from "./auth.js"; import type { PtyTerminalSnapshot } from "./pty-terminal-state.js"; export type { ProcessEvent } from "./types.js"; export interface WsSessionPort { getSession(id: string): SessionSnapshot | null; getTerminalState?(id: string): PtyTerminalSnapshot | null; sendPtyInput?(id: string, input: string, shortcutKey?: string, userInput?: boolean): void; resizePty?(id: string, cols: number, rows: number): void; pausePtyOutput?(id: string): void; resumePtyOutput?(id: string): void; } export declare class WsBroadcastManager { private wss; private clients; private outputDebounceCache; private heartbeatTimer?; private disposed; private port?; private readonly ptyPauseRefs; private getCardDefaults; private useHttps; private authService?; private authenticateRequest?; constructor(wss: WebSocketServer, getCardDefaults?: () => CardExpandDefaults, useHttps?: boolean, authService?: Pick, authenticateRequest?: (req: IncomingMessage) => boolean); /** Immediately disconnect all authenticated clients after global revocation. */ disconnectAll(): void; /** Stop timers, discard deferred output, and terminate every client. */ dispose(): void; /** Set up connection handling. Should be called once during server startup. */ setup(portOrGetSession: WsSessionPort | ((id: string) => SessionSnapshot | null)): void; /** * 心跳 tick:对每个 client 执行 stale 判定 + 主动 ping。 * - 超过 HEARTBEAT_STALE_MS 没消息 → 视为半开 / 死连接,直接 terminate()。 * terminate() 不发 Close 帧,立刻断开 socket;前端 onclose 触发后会按 * 重连退避梯度自动重连。 * - 否则:应用层 send `{type:"ping", t}`(给前端拿来更新 lastWsMessageAt * 和测 RTT),同时 ws.ping() 发协议层 ping(浏览器/CDN 友好,保 NAT)。 */ private runHeartbeatTick; /** Emit a process event to all subscribed WebSocket clients. */ emitEvent(event: ProcessEvent): void; /** Flush any pending debounced output for a session (e.g., before session close). */ flushOutput(sessionId: string): void; /** * Send an init/resync snapshot to a single client. Bumps the per-session * sequence counter so the client can detect gaps between the init payload * and the first incremental update. */ private sendInit; /** * 按客户端偏好窗口化一段完整 messages:opted-in 的块级窗口(iOS)会附带 * leadingBlockOffset/leadingBlockTotal;否则 turn 级窗口(字段与改动前一致)。 */ private windowForClient; private broadcast; /** * Resume accepting business messages after the queue crosses the low-water * mark, then enqueue the resync notices owed for output dropped while * paused. Notices are themselves bounded by the same queue capacity; any * remainder stays in the set and is appended by a later drain cycle. */ private resumeAndQueueResyncNotices; private discardClient; private acquirePtyPause; private releasePtyPause; private releaseAllPtyPauses; private sendPtyError; private processWsQueue; }