/** * WorkerTcpTransport — TcpTransport implementation for Web Workers. * Proxies TCP operations to the main thread via postMessage, where the * main thread relays them to msgapi.tcp (the C# native bridge). * * Same interface as BridgeTcpTransport, but works in a Worker context * where window/msgapi aren't available. */ /** Function to send messages to the main thread. Set during worker init. */ let postToMain: (msg: any) => void = () => { throw new Error("Worker TCP transport not initialized"); }; let reqCounter = 0; const pendingRequests = new Map void; reject: (e: Error) => void }>(); const instances = new Map(); /** Call once from worker-entry to wire up the postMessage channel. */ export function initWorkerTcp(post: (msg: any) => void): void { postToMain = post; } /** Route incoming TCP messages from the main thread to the right transport instance. */ export function handleTcpMessage(msg: any): void { if (msg.type === "tcp-response") { const pending = pendingRequests.get(msg.reqId); if (pending) { pendingRequests.delete(msg.reqId); if (msg.error) pending.reject(new Error(msg.error)); else pending.resolve(msg.result); } } else if (msg.type === "tcp-data") { instances.get(msg.streamId)?.dataHandler?.(msg.data); } else if (msg.type === "tcp-close") { const t = instances.get(msg.streamId); if (t) { t._connected = false; instances.delete(msg.streamId); t.closeHandler?.(msg.hadError); } } else if (msg.type === "tcp-error") { const t = instances.get(msg.streamId); if (t) { t.errorHandler?.(new Error(msg.message)); } } } function tcpRequest(op: string, params: any): Promise { const reqId = ++reqCounter; return new Promise((resolve, reject) => { pendingRequests.set(reqId, { resolve, reject }); postToMain({ type: "tcp", op, reqId, ...params }); // 30s timeout setTimeout(() => { if (pendingRequests.has(reqId)) { pendingRequests.delete(reqId); reject(new Error(`TCP ${op} timeout`)); } }, 30000); }); } export class WorkerTcpTransport { streamId: number | null = null; dataHandler: ((data: string) => void) | null = null; closeHandler: ((hadError: boolean) => void) | null = null; errorHandler: ((err: Error) => void) | null = null; _connected = false; get connected(): boolean { return this._connected; } async connect(host: string, port: number, tls: boolean, _servername?: string): Promise { const streamId = await tcpRequest("connect", { host, port, tls }); this.streamId = Number(streamId); this._connected = true; instances.set(this.streamId, this); } async upgradeTLS(servername?: string): Promise { if (this.streamId == null) throw new Error("Not connected"); await tcpRequest("upgradeTLS", { streamId: this.streamId, servername: servername || "" }); } async write(data: string | Uint8Array): Promise { if (this.streamId == null) throw new Error("Not connected"); const s = typeof data === "string" ? data : new TextDecoder().decode(data); await tcpRequest("write", { streamId: this.streamId, data: s }); } onData(handler: (data: string) => void): void { this.dataHandler = handler; } onClose(handler: (hadError: boolean) => void): void { this.closeHandler = handler; } onError(handler: (err: Error) => void): void { this.errorHandler = handler; } close(): void { if (this.streamId != null) { postToMain({ type: "tcp", op: "close", streamId: this.streamId }); instances.delete(this.streamId); this.streamId = null; this._connected = false; } } }