1234567891011121314import {15 pairWithDesktopHelper,16 type DesktopPairingDeps,17 type DesktopPairingSocket,18 type PairWithDesktopCallbacks,19} from "./desktop-pairing";20import { upsertPairedDevice, setLastKnownIp } from "./paired-devices";21import { buildLocalIdentity } from "./chirp-identity";22import { computeVaultId } from "../shared/sync-identity";23import { isUnlocked, getRootKeyHex } from "./vault";24import { probeSubnetForPort, wsPortOpen } from "./desktop-pairing-probe";25import { discoverPeachSync } from "./desktop-pairing-mdns";26import { getLocalSubnet } from "../shared/local-network";27import type { LocalIdentity } from "../shared/chirp-handshake";2829function hexToBytesLocal(hex: string): Uint8Array {30 const out = new Uint8Array(hex.length / 2);31 for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);32 return out;33}343536function push(type: string, payload: unknown): void {37 try {38 const maybe = chrome.runtime.sendMessage({ type, payload }) as unknown;39 if (maybe && typeof (maybe as Promise<unknown>).catch === "function") {40 (maybe as Promise<unknown>).catch(() => undefined);41 }42 } catch {43 44 }45}464748export function openBinaryWebSocket(ip: string, port: number): Promise<DesktopPairingSocket> {49 return new Promise((resolve, reject) => {50 let ws: WebSocket;51 try {52 ws = new WebSocket(`ws://${ip}:${port}/`);53 } catch (err) {54 reject(err instanceof Error ? err : new Error("websocket open failed"));55 return;56 }57 ws.binaryType = "arraybuffer";5859 const inbox: Uint8Array[] = [];60 const readers: Array<{ resolve: (b: Uint8Array) => void; reject: (e: Error) => void }> = [];61 let opened = false;62 let closed = false;63 64 65 let sawCloseBeforeFrame = false;6667 ws.onopen = () => {68 opened = true;69 resolve(sock);70 };71 ws.onmessage = (ev: MessageEvent) => {72 const bytes = new Uint8Array(ev.data as ArrayBuffer);73 const r = readers.shift();74 if (r) { sawCloseBeforeFrame = false; r.resolve(bytes); }75 else inbox.push(bytes);76 };77 ws.onerror = () => {78 if (!opened) reject(new Error("websocket connection failed"));79 };80 ws.onclose = () => {81 closed = true;82 if (readers.length) {83 sawCloseBeforeFrame = true;84 while (readers.length) readers.shift()!.reject(new Error("socket closed"));85 }86 if (!opened) reject(new Error("socket closed before open"));87 };8889 const sock: DesktopPairingSocket = {90 send(bytes: Uint8Array) { ws.send(bytes as Uint8Array<ArrayBuffer>); },91 receive(): Promise<Uint8Array> {92 const queued = inbox.shift();93 if (queued) { sawCloseBeforeFrame = false; return Promise.resolve(queued); }94 if (closed) { sawCloseBeforeFrame = true; return Promise.reject(new Error("socket closed")); }95 return new Promise<Uint8Array>((res, rej) => readers.push({ resolve: res, reject: rej }));96 },97 close() { try { ws.close(); } catch { } },98 closedBeforeFrame() { return sawCloseBeforeFrame; },99 };100 });101}102103104export function productionDesktopPairingDeps(): DesktopPairingDeps {105 return {106 async findHost(port) {107 108 109 110 if (await wsPortOpen("127.0.0.1", port, 800)) return "127.0.0.1";111 112 const discovered = await discoverPeachSync();113 if (discovered && (await wsPortOpen(discovered.ip, port, 800))) return discovered.ip;114 115 const subnet = await getLocalSubnet();116 if (!subnet) return null;117 return probeSubnetForPort(subnet, port);118 },119 openSocket: (ip, port) => openBinaryWebSocket(ip, port),120 async getLocalIdentity(): Promise<LocalIdentity> {121 if (!isUnlocked()) throw new Error("vault_locked");122 const masterKeyHex = getRootKeyHex();123 if (!masterKeyHex) throw new Error("vault_locked");124 125 126 const masterKey = hexToBytesLocal(masterKeyHex);127 try {128 const base = await buildLocalIdentity(masterKey);129 const vaultId = await computeVaultId(masterKey);130 return { ...base, vaultId };131 } finally {132 masterKey.fill(0);133 }134 },135 persistDevice: (d) => upsertPairedDevice(d),136 cacheLastKnownIp: (pubkeyHex, ip) => setLastKnownIp(pubkeyHex, ip),137 };138}139140141142143let pendingConfirm: ((confirmed: boolean) => void) | null = null;144let inFlight = false;145146function clearPairingState(): void {147 pendingConfirm = null;148 inFlight = false;149}150151export function isDesktopPairingInFlight(): boolean {152 return inFlight;153}154155156157158159160export function startDesktopPairing(161 code: string,162 deps: DesktopPairingDeps = productionDesktopPairingDeps(),163): void {164 inFlight = true;165 const cb: PairWithDesktopCallbacks = {166 onSasReady: (sas) => push("DESKTOP_PAIR_SAS", { sas }),167 onComplete: (device) => {168 push("DESKTOP_PAIR_RESULT", { status: "complete", device });169 clearPairingState();170 },171 onError: (error) => {172 push("DESKTOP_PAIR_RESULT", { status: "error", error });173 clearPairingState();174 },175 };176 177 const confirm = (_sas: string): Promise<boolean> =>178 new Promise<boolean>((resolve) => { pendingConfirm = resolve; });179180 void pairWithDesktopHelper(code, cb, deps, confirm).finally(() => {181 182 183 if (inFlight) clearPairingState();184 });185}186187188export function resolveDesktopPairingConfirm(confirmed: boolean): void {189 const resolve = pendingConfirm;190 pendingConfirm = null;191 if (resolve) resolve(confirmed);192}