1import { noiseNKInitiator } from './noise-js';2import { bytesToBase64, base64ToBytes } from './sync-wire';34export interface SyncTransport {5 send(data: Uint8Array): Promise<void>;6 receive(): Promise<Uint8Array>;7 close(): Promise<void>;8}91011121314export async function createWebSocketSyncTransport(15 url: string,16 remotePublicKeyHex: string,17): Promise<SyncTransport> {18 const remotePublicKey = hexToBytes(remotePublicKeyHex);1920 return new Promise((resolve, reject) => {21 const ws = new WebSocket(url);2223 const messageQueue: string[] = [];24 const waiters: ((frame: string) => void)[] = [];2526 const enqueue = (frame: string) => {27 if (waiters.length > 0) {28 waiters.shift()!(frame);29 } else {30 messageQueue.push(frame);31 }32 };3334 ws.onmessage = (event) => enqueue(event.data as string);35 ws.onerror = () => reject(new Error('WebSocket connection failed'));36 ws.onclose = () => { waiters.forEach(w => w('')); waiters.length = 0; };3738 ws.onopen = async () => {39 try {40 const rawSend = (msg: Uint8Array) => {41 ws.send(bytesToBase64(msg));42 return Promise.resolve();43 };44 const rawReceive = (): Promise<Uint8Array> => new Promise((res) => {45 const handle = (frame: string) => {46 if (frame === '') { res(new Uint8Array(0)); return; }47 res(base64ToBytes(frame));48 };49 if (messageQueue.length > 0) handle(messageQueue.shift()!);50 else waiters.push(handle);51 });5253 54 const session = await noiseNKInitiator(remotePublicKey, rawSend, rawReceive);5556 resolve({57 send: async (plaintext) => {58 ws.send(bytesToBase64(session.encrypt(plaintext)));59 },60 receive: () => new Promise((res) => {61 const handle = (frame: string) => {62 if (frame === '') { res(new Uint8Array(0)); return; }63 res(session.decrypt(base64ToBytes(frame)));64 };65 if (messageQueue.length > 0) handle(messageQueue.shift()!);66 else waiters.push(handle);67 }),68 close: () => {69 ws.close();70 return Promise.resolve();71 },72 });73 } catch (err) {74 ws.close();75 reject(err);76 }77 };78 });79}8081function hexToBytes(hex: string): Uint8Array {82 const bytes = new Uint8Array(hex.length / 2);83 for (let i = 0; i < hex.length; i += 2) {84 bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);85 }86 return bytes;87}