type HostMessage = | { type: "ADVERTISE"; port: number; x25519Pubkey: string; ed25519Pubkey: string; name: string; pairing: boolean } | { type: "STOP_ADVERTISING" } | { type: "DISCOVER" } | { type: "CONNECT"; host: string; port: number } | { type: "DISCONNECT" } | { type: "HANDSHAKE_START"; isInitiator: boolean; remoteStaticKey: string } | { type: "HANDSHAKE_DATA"; data: string } | { type: "SYNC_SEND"; message: string } | { type: "SYNC_RECEIVE" } | { type: "STATUS"; isTrustedNetwork: boolean; isTailscale: boolean; ssid?: string }; type HostResponse = | { type: "DISCOVERED"; host: string; port: number; name: string; pairing: boolean; x25519Pubkey: string } | { type: "CONNECTED" } | { type: "DISCONNECTED" } | { type: "HANDSHAKE_RESULT"; success: boolean; data?: string } | { type: "SYNC_MESSAGE"; message: string } | { type: "STATUS"; isTrustedNetwork: boolean; isTailscale: boolean; ssid?: string } | { type: "ERROR"; message: string }; export interface PeerInfo { host: string; port: number; name: string; pairing: boolean; x25519Pubkey: string; } let syncPort: chrome.runtime.Port | null = null; const peerDiscoveredHandlers: Array<(peer: PeerInfo) => void> = []; const connectedHandlers: Array<() => void> = []; const disconnectedHandlers: Array<() => void> = []; const handshakeResultHandlers: Array<(success: boolean, data?: string) => void> = []; const syncMessageHandlers: Array<(message: string) => void> = []; const errorHandlers: Array<(error: string) => void> = []; let pendingDiscover: { resolve: (r: HostResponse) => void; reject: (e: Error) => void } | null = null; let pendingConnect: { resolve: () => void; reject: (e: Error) => void } | null = null; let pendingStatus: { resolve: (r: HostResponse) => void; reject: (e: Error) => void } | null = null; function handleHostMessage(response: HostResponse): void { switch (response.type) { case "DISCOVERED": { const peer: PeerInfo = { host: response.host, port: response.port, name: response.name, pairing: response.pairing, x25519Pubkey: response.x25519Pubkey, }; if (pendingDiscover) { const { resolve } = pendingDiscover; pendingDiscover = null; resolve(response); } for (const handler of peerDiscoveredHandlers) handler(peer); break; } case "CONNECTED": { if (pendingConnect) { const { resolve } = pendingConnect; pendingConnect = null; resolve(); } for (const handler of connectedHandlers) handler(); break; } case "DISCONNECTED": { if (pendingConnect) { const { reject } = pendingConnect; pendingConnect = null; reject(new Error("Peer disconnected during connect")); } for (const handler of disconnectedHandlers) handler(); break; } case "HANDSHAKE_RESULT": { for (const handler of handshakeResultHandlers) handler(response.success, response.data); break; } case "SYNC_MESSAGE": { for (const handler of syncMessageHandlers) handler(response.message); break; } case "STATUS": { if (pendingStatus) { const { resolve } = pendingStatus; pendingStatus = null; resolve(response); } break; } case "ERROR": { if (pendingDiscover) { const { reject } = pendingDiscover; pendingDiscover = null; reject(new Error(response.message)); } if (pendingConnect) { const { reject } = pendingConnect; pendingConnect = null; reject(new Error(response.message)); } if (pendingStatus) { const { reject } = pendingStatus; pendingStatus = null; reject(new Error(response.message)); } for (const handler of errorHandlers) handler(response.message); break; } } } function sendToHost(msg: HostMessage): void { syncPort?.postMessage(msg); } export function connectSyncHost(): void { syncPort = chrome.runtime.connectNative("com.peachpasswords.sync"); syncPort.onMessage.addListener(handleHostMessage); syncPort.onDisconnect.addListener(() => { syncPort = null; if (pendingConnect) { const { reject } = pendingConnect; pendingConnect = null; reject(new Error("Host disconnected")); } if (pendingDiscover) { const { reject } = pendingDiscover; pendingDiscover = null; reject(new Error("Host disconnected")); } if (pendingStatus) { const { reject } = pendingStatus; pendingStatus = null; reject(new Error("Host disconnected")); } for (const handler of disconnectedHandlers) handler(); }); } export function disconnectSyncHost(): void { syncPort?.disconnect(); syncPort = null; } export function isHostConnected(): boolean { return syncPort !== null; } export function advertise( port: number, x25519Pubkey: string, ed25519Pubkey: string, name: string, pairing: boolean, ): void { sendToHost({ type: "ADVERTISE", port, x25519Pubkey, ed25519Pubkey, name, pairing }); } export function stopAdvertising(): void { sendToHost({ type: "STOP_ADVERTISING" }); } export function discoverPeers(): Promise { return new Promise((resolve, reject) => { pendingDiscover = { resolve, reject }; sendToHost({ type: "DISCOVER" }); }); } export function connectToPeer(host: string, port: number): Promise { return new Promise((resolve, reject) => { pendingConnect = { resolve, reject }; sendToHost({ type: "CONNECT", host, port }); }); } export function disconnectFromPeer(): void { sendToHost({ type: "DISCONNECT" }); } export function sendHandshakeStart(isInitiator: boolean, remoteStaticKey: string): void { sendToHost({ type: "HANDSHAKE_START", isInitiator, remoteStaticKey }); } export function sendHandshakeData(data: string): void { sendToHost({ type: "HANDSHAKE_DATA", data }); } export function sendSyncMessage(message: string): void { sendToHost({ type: "SYNC_SEND", message }); } export function requestSyncMessage(): void { sendToHost({ type: "SYNC_RECEIVE" }); } export function getNetworkStatus(): Promise { return new Promise((resolve, reject) => { pendingStatus = { resolve, reject }; sendToHost({ type: "STATUS", isTrustedNetwork: false, isTailscale: false }); }); } export function onPeerDiscovered(handler: (peer: PeerInfo) => void): void { peerDiscoveredHandlers.push(handler); } export function onConnected(handler: () => void): void { connectedHandlers.push(handler); } export function onDisconnected(handler: () => void): void { disconnectedHandlers.push(handler); } export function onHandshakeResult(handler: (success: boolean, data?: string) => void): void { handshakeResultHandlers.push(handler); } export function onSyncMessage(handler: (message: string) => void): void { syncMessageHandlers.push(handler); } export function onError(handler: (error: string) => void): void { errorHandlers.push(handler); }