12345678910export interface PairingSocket {11 send(bytes: Uint8Array): void;12 13 receive(): Promise<Uint8Array>;14 close(): void;15 16 closedBeforeFrame(): boolean;17}181920212223export const MAX_PAIRING_FRAME_BYTES = 1024 * 1024;24export const MAX_PAIRING_INBOX_BYTES = 12 * 1024 * 1024;25export const MAX_PAIRING_INBOX_MESSAGES = 256;26export const PAIRING_RESOURCE_LIMIT = "PAIRING_RESOURCE_LIMIT";272829export function openRelayPairingSocket(url: string): Promise<PairingSocket> {30 return new Promise((resolve, reject) => {31 let ws: WebSocket;32 try {33 ws = new WebSocket(url);34 } catch (err) {35 reject(err instanceof Error ? err : new Error("websocket open failed"));36 return;37 }38 ws.binaryType = "arraybuffer";3940 const inbox: Uint8Array[] = [];41 const readers: Array<{ resolve: (b: Uint8Array) => void; reject: (e: Error) => void }> = [];42 let opened = false;43 let closed = false;44 45 46 let sawCloseBeforeFrame = false;47 48 49 let queuedBytes = 0;50 let limitError: Error | null = null;5152 function failLimit(): void {53 if (limitError) return;54 limitError = new Error(PAIRING_RESOURCE_LIMIT);55 inbox.length = 0;56 queuedBytes = 0;57 while (readers.length) readers.shift()!.reject(limitError);58 try { ws.close(1009); } catch { }59 }6061 ws.onopen = () => {62 opened = true;63 resolve(sock);64 };65 ws.onmessage = (ev: MessageEvent) => {66 if (limitError) return;67 68 if (!(ev.data instanceof ArrayBuffer) || ev.data.byteLength > MAX_PAIRING_FRAME_BYTES) {69 failLimit();70 return;71 }72 if (73 inbox.length + 1 > MAX_PAIRING_INBOX_MESSAGES ||74 queuedBytes + ev.data.byteLength > MAX_PAIRING_INBOX_BYTES75 ) {76 failLimit();77 return;78 }79 const bytes = new Uint8Array(ev.data);80 const r = readers.shift();81 if (r) { sawCloseBeforeFrame = false; r.resolve(bytes); }82 else { inbox.push(bytes); queuedBytes += bytes.byteLength; }83 };84 ws.onerror = () => {85 if (!opened) reject(new Error("websocket connection failed"));86 };87 ws.onclose = () => {88 if (limitError) return;89 closed = true;90 if (readers.length) {91 sawCloseBeforeFrame = true;92 while (readers.length) readers.shift()!.reject(new Error("socket closed"));93 }94 if (!opened) reject(new Error("socket closed before open"));95 };9697 const sock: PairingSocket = {98 send(bytes: Uint8Array) {99 if (bytes.byteLength > MAX_PAIRING_FRAME_BYTES) throw new Error(PAIRING_RESOURCE_LIMIT);100 ws.send(bytes as Uint8Array<ArrayBuffer>);101 },102 receive(): Promise<Uint8Array> {103 if (limitError) return Promise.reject(limitError);104 const queued = inbox.shift();105 if (queued) { queuedBytes -= queued.byteLength; sawCloseBeforeFrame = false; return Promise.resolve(queued); }106 if (closed) { sawCloseBeforeFrame = true; return Promise.reject(new Error("socket closed")); }107 return new Promise<Uint8Array>((res, rej) => readers.push({ resolve: res, reject: rej }));108 },109 close() { try { ws.close(); } catch { } },110 closedBeforeFrame() { return sawCloseBeforeFrame; },111 };112 });113}