// The WebSocket seam every relay-facing Peach flow shares. // // Production wires the real `WebSocket`; tests inject a fake. One binary // message per frame. Nothing here touches chrome or the vault, because vault // adoption runs before a vault exists at all. /** * Minimal binary WebSocket seam. One binary message per frame. */ export interface PairingSocket { send(bytes: Uint8Array): void; /** Resolves with the next inbound binary frame, or rejects on close. */ receive(): Promise; close(): void; /** True if the socket closed before the next frame arrived. */ closedBeforeFrame(): boolean; } /** Relay pairing uses this binary socket seam over `wss://`. */ export function openRelayPairingSocket(url: string): Promise { return new Promise((resolve, reject) => { let ws: WebSocket; try { ws = new WebSocket(url); } catch (err) { reject(err instanceof Error ? err : new Error("websocket open failed")); return; } ws.binaryType = "arraybuffer"; const inbox: Uint8Array[] = []; const readers: Array<{ resolve: (b: Uint8Array) => void; reject: (e: Error) => void }> = []; let opened = false; let closed = false; // True iff the socket closed with no frame delivered for the pending read — // the wrong/expired-code signal (the listener sends no error frame). let sawCloseBeforeFrame = false; ws.onopen = () => { opened = true; resolve(sock); }; ws.onmessage = (ev: MessageEvent) => { const bytes = new Uint8Array(ev.data as ArrayBuffer); const r = readers.shift(); if (r) { sawCloseBeforeFrame = false; r.resolve(bytes); } else inbox.push(bytes); }; ws.onerror = () => { if (!opened) reject(new Error("websocket connection failed")); }; ws.onclose = () => { closed = true; if (readers.length) { sawCloseBeforeFrame = true; while (readers.length) readers.shift()!.reject(new Error("socket closed")); } if (!opened) reject(new Error("socket closed before open")); }; const sock: PairingSocket = { send(bytes: Uint8Array) { ws.send(bytes as Uint8Array); }, receive(): Promise { const queued = inbox.shift(); if (queued) { sawCloseBeforeFrame = false; return Promise.resolve(queued); } if (closed) { sawCloseBeforeFrame = true; return Promise.reject(new Error("socket closed")); } return new Promise((res, rej) => readers.push({ resolve: res, reject: rej })); }, close() { try { ws.close(); } catch { /* ignore */ } }, closedBeforeFrame() { return sawCloseBeforeFrame; }, }; }); }