import type { PairedDevice } from "../shared/sync-types"; import { isRouteCapableMeshPeer, MESH_WAITER_RETRY_DELAY_MS, nextMeshWaiterRetryDelay, selectDialTargets, weDialMeshPeer, } from "../shared/mesh-peer-role"; import { openRelaySyncResponderSession, type SyncPeerIdentity, } from "../shared/sync-transport"; import { rendezvousSecretFromHex, waitingRendezvousIds, } from "../shared/rendezvous"; import { runSyncResponder } from "./sync-coordinator"; import { createSyncVaultAdapter } from "./sync-vault-adapter"; import { createLockedSyncVaultAdapter, getLockedSyncIdentity, } from "./locked-sync-spool"; import { getLocalPairingIdentity } from "./device-identity"; import { findPairedDeviceById, listPairedDevices, markLastSyncedAt, } from "./paired-devices"; import { getSyncSettings, relayInForce } from "./sync-settings"; import { isUnlocked, isDecoyMode, getRootKeyHex } from "./vault"; import { getPairSecret } from "./pair-secrets"; import { recordInboundSyncCompletion } from "./sync-trigger"; const RECONCILE_INTERVAL_MS = 60_000; interface MeshIdentity { identity: Omit; privateKey: Uint8Array; publicKey: Uint8Array; signingPrivateKey?: CryptoKey; } interface WaiterHandle { abort: AbortController; } interface MeshListenerState { installed: boolean; reconcileQueued: boolean; timer: ReturnType | null; waiters: Map; activeSessions: Set<{ close(): Promise }>; } function state(): MeshListenerState { const global = globalThis as typeof globalThis & { __peachMeshListenerState?: MeshListenerState; }; if (!global.__peachMeshListenerState) { global.__peachMeshListenerState = { installed: false, reconcileQueued: false, timer: null, waiters: new Map(), activeSessions: new Set(), }; } return global.__peachMeshListenerState; } export async function selectProductionDialTargets( devices: PairedDevice[], ): Promise { const local = await getMeshIdentity(); if (!local) return devices; try { return selectDialTargets( devices, local.identity.longTermPublicKeyHex, ); } finally { local.privateKey.fill(0); local.publicKey.fill(0); } } export function setupMeshRelayListener(): void { const listener = state(); if (listener.installed) return; listener.installed = true; chrome.storage?.onChanged?.addListener(() => requestMeshRelayReconcile()); requestMeshRelayReconcile(); } export function abortMeshRelaySessions(): void { const listener = state(); abortAll(listener); for (const session of listener.activeSessions) void session.close().catch(() => undefined); listener.activeSessions.clear(); } export function requestMeshRelayReconcile(): void { const listener = state(); if (listener.reconcileQueued) return; listener.reconcileQueued = true; queueMicrotask(() => { listener.reconcileQueued = false; void reconcile().catch(() => undefined); }); } async function reconcile(): Promise { const listener = state(); if (isDecoyMode()) { abortAll(listener); return; } if (listener.timer !== null) clearTimeout(listener.timer); listener.timer = setTimeout(requestMeshRelayReconcile, RECONCILE_INTERVAL_MS); const local = await getMeshIdentity(); if (!local) { abortAll(listener); return; } const desired = new Map(); try { const settings = await getSyncSettings(); if (isDecoyMode()) { abortAll(listener); return; } const fallbackRelay = relayInForce(settings); const nowSeconds = Date.now() / 1000; for (const peer of await listPairedDevices()) { if (isDecoyMode()) { abortAll(listener); return; } if ( !isRouteCapableMeshPeer(peer) || weDialMeshPeer( local.identity.longTermPublicKeyHex, peer.longTermPublicKeyHex!, ) ) { continue; } const relayUrl = peer.relayUrl ?? fallbackRelay; // PB-26-213: the registry carries no secrets; a peer without a // resolvable secret is skipped until the next unlock reconciles it. const secretHex = await getPairSecret(peer.deviceId, isUnlocked() ? getRootKeyHex() : null); if (!secretHex) continue; const secret = rendezvousSecretFromHex(secretHex); for (const rendezvousId of waitingRendezvousIds(secret, nowSeconds)) { desired.set(`${peer.deviceId}|${relayUrl}|${rendezvousId}`, { peer, relayUrl, rendezvousId, }); } } } finally { local.privateKey.fill(0); local.publicKey.fill(0); } for (const [key, handle] of listener.waiters) { if (desired.has(key)) continue; handle.abort.abort(); listener.waiters.delete(key); } for (const [key, target] of desired) { if (listener.waiters.has(key)) continue; const abort = new AbortController(); listener.waiters.set(key, { abort }); void holdWaiter(target, abort.signal).finally(() => { if (listener.waiters.get(key)?.abort === abort) { listener.waiters.delete(key); } }); } } async function holdWaiter( target: { peer: PairedDevice; relayUrl: string; rendezvousId: string }, signal: AbortSignal, ): Promise { let retryDelayMs = MESH_WAITER_RETRY_DELAY_MS; while (!signal.aborted && !isDecoyMode()) { const current = await findPairedDeviceById(target.peer.deviceId); if (isDecoyMode()) return; if (!current || !isRouteCapableMeshPeer(current)) return; let session: Awaited> | null = null; let local: MeshIdentity | null = null; let succeeded = false; try { local = await getMeshIdentity(); if (!local) return; const identity: SyncPeerIdentity = { ...local.identity, longTermPrivateKey: local.privateKey, }; session = await openRelaySyncResponderSession( target.relayUrl, identity, current.longTermPublicKeyHex!, target.rendezvousId, { signal }, ); state().activeSessions.add(session); if (isDecoyMode()) { state().activeSessions.delete(session); await session.close().catch(() => undefined); return; } const adapter = isUnlocked() ? createSyncVaultAdapter({ deviceName: current.deviceName }) : await createLockedSyncVaultAdapter(); if (!adapter || isDecoyMode()) return; const outcome = await runSyncResponder(session, adapter); if (isDecoyMode()) return; if (outcome.status === "completed") { if (isDecoyMode()) return; await markLastSyncedAt(current.deviceId, Date.now()); recordInboundSyncCompletion(outcome.entriesReceived, outcome.entriesSent); succeeded = true; } if (!isDecoyMode()) requestMeshRelayReconcile(); } catch { } finally { if (session) state().activeSessions.delete(session); local?.privateKey.fill(0); local?.publicKey.fill(0); await session?.close().catch(() => undefined); } await waitForRetry(signal, retryDelayMs); retryDelayMs = nextMeshWaiterRetryDelay(retryDelayMs, succeeded); } } async function getMeshIdentity(): Promise<{ identity: Omit; privateKey: Uint8Array; publicKey: Uint8Array; signingPrivateKey?: CryptoKey; } | null> { if (isUnlocked()) return getLocalPairingIdentity(); const cached = await getLockedSyncIdentity(); if (!cached) return null; return { identity: { deviceId: cached.deviceId, deviceName: cached.deviceName, deviceType: cached.deviceType, longTermPublicKeyHex: cached.longTermPublicKeyHex, }, privateKey: cached.longTermPrivateKey, publicKey: hexToBytes(cached.longTermPublicKeyHex), }; } function hexToBytes(hex: string): Uint8Array { const out = new Uint8Array(hex.length / 2); for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16); return out; } function waitForRetry(signal: AbortSignal, delayMs: number): Promise { if (signal.aborted) return Promise.resolve(); return new Promise((resolve) => { const onAbort = () => { clearTimeout(timer); resolve(); }; const timer = setTimeout(() => { signal.removeEventListener("abort", onAbort); resolve(); }, delayMs); signal.addEventListener("abort", onAbort, { once: true }); }); } function abortAll(listener: MeshListenerState): void { for (const handle of listener.waiters.values()) handle.abort.abort(); listener.waiters.clear(); for (const session of listener.activeSessions) void session.close().catch(() => undefined); listener.activeSessions.clear(); }