12345678910111213import {14 pairWithCode,15 type CodePairingDeps,16 type CodePairingCallbacks,17} from "./code-pairing";18import { openPairingSocket } from "../shared/pairing-dial";19import {20 clearDeviceRevocation,21 upsertPairedDevice,22 setLastKnownIp,23} from "./paired-devices";24import { buildLocalIdentity } from "./device-identity";25import { computeVaultId, deriveSyncIdentityKeypair } from "../shared/sync-identity";26import { isUnlocked, getRootKeyHex } from "./vault";27import type { LocalIdentity } from "../shared/pairing-handshake";28import { getRelaySettings } from "./relay-settings";2930function hexToBytesLocal(hex: string): Uint8Array {31 const out = new Uint8Array(hex.length / 2);32 for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);33 return out;34}353637function push(type: string, payload: unknown): void {38 try {39 const maybe = chrome.runtime.sendMessage({ type, payload }) as unknown;40 if (maybe && typeof (maybe as Promise<unknown>).catch === "function") {41 (maybe as Promise<unknown>).catch(() => undefined);42 }43 } catch {44 45 }46}474849export function productionCodePairingDeps(): CodePairingDeps {50 return {51 openSocket: (ip, port) => openPairingSocket(ip, port),52 async getRelayUrl() {53 return (await getRelaySettings()).relayUrl;54 },55 async getLocalIdentity(): Promise<{56 identity: LocalIdentity;57 privateKey: Uint8Array;58 publicKey: Uint8Array;59 }> {60 if (!isUnlocked()) throw new Error("vault_locked");61 const masterKeyHex = getRootKeyHex();62 if (!masterKeyHex) throw new Error("vault_locked");63 64 65 const masterKey = hexToBytesLocal(masterKeyHex);66 try {67 const base = await buildLocalIdentity(masterKey);68 const vaultId = await computeVaultId(masterKey);69 const keys = deriveSyncIdentityKeypair(masterKey, base.deviceId);70 return {71 identity: { ...base, vaultId },72 privateKey: keys.secretKey,73 publicKey: keys.publicKey,74 };75 } finally {76 masterKey.fill(0);77 }78 },79 80 81 82 persistDevice: async (d) => {83 await clearDeviceRevocation(d.deviceId);84 await upsertPairedDevice(d);85 },86 cacheLastKnownIp: (pubkeyHex, ip) => setLastKnownIp(pubkeyHex, ip),87 };88}8990919293let pendingConfirm: ((confirmed: boolean) => void) | null = null;94let inFlight = false;9596function clearPairingState(): void {97 pendingConfirm = null;98 inFlight = false;99}100101export function isCodePairingInFlight(): boolean {102 return inFlight;103}104105106107108109110export function startCodePairing(111 code: string,112 deps: CodePairingDeps = productionCodePairingDeps(),113): void {114 inFlight = true;115 const cb: CodePairingCallbacks = {116 onSasReady: (sas) => push("CODE_PAIR_SAS", { sas }),117 onComplete: (device) => {118 push("CODE_PAIR_RESULT", { status: "complete", device });119 clearPairingState();120 },121 onError: (error) => {122 push("CODE_PAIR_RESULT", { status: "error", error });123 clearPairingState();124 },125 };126 127 const confirm = (_sas: string): Promise<boolean> =>128 new Promise<boolean>((resolve) => { pendingConfirm = resolve; });129130 void pairWithCode(code, cb, deps, confirm).finally(() => {131 132 133 if (inFlight) clearPairingState();134 });135}136137138export function resolveCodePairingConfirm(confirmed: boolean): void {139 const resolve = pendingConfirm;140 pendingConfirm = null;141 if (resolve) resolve(confirmed);142}