12345678910111213import {14 pairWithCode,15 type CodePairingDeps,16 type CodePairingCallbacks,17} from "./code-pairing";18import { openPairingSocket } from "../shared/pairing-dial";19import { upsertPairedDevice, setLastKnownIp } from "./paired-devices";20import { buildLocalIdentity } from "./device-identity";21import { computeVaultId, deriveSyncIdentityKeypair } from "../shared/sync-identity";22import { isUnlocked, getRootKeyHex } from "./vault";23import type { LocalIdentity } from "../shared/pairing-handshake";24import { getRelaySettings } from "./relay-settings";2526function hexToBytesLocal(hex: string): Uint8Array {27 const out = new Uint8Array(hex.length / 2);28 for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);29 return out;30}313233function push(type: string, payload: unknown): void {34 try {35 const maybe = chrome.runtime.sendMessage({ type, payload }) as unknown;36 if (maybe && typeof (maybe as Promise<unknown>).catch === "function") {37 (maybe as Promise<unknown>).catch(() => undefined);38 }39 } catch {40 41 }42}434445export function productionCodePairingDeps(): CodePairingDeps {46 return {47 openSocket: (ip, port) => openPairingSocket(ip, port),48 async getRelayUrl() {49 return (await getRelaySettings()).relayUrl;50 },51 async getLocalIdentity(): Promise<{52 identity: LocalIdentity;53 privateKey: Uint8Array;54 publicKey: Uint8Array;55 }> {56 if (!isUnlocked()) throw new Error("vault_locked");57 const masterKeyHex = getRootKeyHex();58 if (!masterKeyHex) throw new Error("vault_locked");59 60 61 const masterKey = hexToBytesLocal(masterKeyHex);62 try {63 const base = await buildLocalIdentity(masterKey);64 const vaultId = await computeVaultId(masterKey);65 const keys = deriveSyncIdentityKeypair(masterKey, base.deviceId);66 return {67 identity: { ...base, vaultId },68 privateKey: keys.secretKey,69 publicKey: keys.publicKey,70 };71 } finally {72 masterKey.fill(0);73 }74 },75 persistDevice: (d) => upsertPairedDevice(d),76 cacheLastKnownIp: (pubkeyHex, ip) => setLastKnownIp(pubkeyHex, ip),77 };78}7980818283let pendingConfirm: ((confirmed: boolean) => void) | null = null;84let inFlight = false;8586function clearPairingState(): void {87 pendingConfirm = null;88 inFlight = false;89}9091export function isCodePairingInFlight(): boolean {92 return inFlight;93}949596979899100export function startCodePairing(101 code: string,102 deps: CodePairingDeps = productionCodePairingDeps(),103): void {104 inFlight = true;105 const cb: CodePairingCallbacks = {106 onSasReady: (sas) => push("CODE_PAIR_SAS", { sas }),107 onComplete: (device) => {108 push("CODE_PAIR_RESULT", { status: "complete", device });109 clearPairingState();110 },111 onError: (error) => {112 push("CODE_PAIR_RESULT", { status: "error", error });113 clearPairingState();114 },115 };116 117 const confirm = (_sas: string): Promise<boolean> =>118 new Promise<boolean>((resolve) => { pendingConfirm = resolve; });119120 void pairWithCode(code, cb, deps, confirm).finally(() => {121 122 123 if (inFlight) clearPairingState();124 });125}126127128export function resolveCodePairingConfirm(confirmed: boolean): void {129 const resolve = pendingConfirm;130 pendingConfirm = null;131 if (resolve) resolve(confirmed);132}