/** * Dormant SYN-001 coordinator helpers. * * This module owns extension-side sync preflight and cancellation rules before * any live background path enables native messaging. Entry payload exchange is * intentionally left to the next phase so the encrypted-entry envelope can be * reviewed on its own. */ import { buildSyncManifest, diffSyncManifests } from "../shared/sync-protocol"; import type { SyncDiff } from "../shared/sync-protocol"; import type { SyncManifestEntry } from "../shared/sync-types"; import { encodeSyncMessage } from "../shared/sync-wire"; import type { VaultEntry } from "../shared/types"; export type SyncPreflightError = "pro_required" | "vault_locked"; export interface SyncPreflightResult { ok: boolean; error?: SyncPreflightError; } export interface SyncSnapshot { entries: VaultEntry[]; manifest: SyncManifestEntry[]; } export interface SyncPlan { localManifest: SyncManifestEntry[]; diff: SyncDiff; } export interface SyncCoordinatorDeps { isProUser: () => Promise; isUnlocked: () => boolean; getEntries: () => VaultEntry[]; sendSyncData: (connectionId: string, data: string) => void; } async function loadDefaultDeps(): Promise { const [license, sync, vault] = await Promise.all([ import("./license"), import("./sync"), import("./vault"), ]); return { isProUser: license.isProUser, isUnlocked: vault.isUnlocked, getEntries: vault.getEntries, sendSyncData: sync.sendSyncData, }; } export async function checkSyncPreflight( deps?: Pick ): Promise { const readyDeps = deps ?? (await loadDefaultDeps()); if (!(await readyDeps.isProUser())) return { ok: false, error: "pro_required" }; if (!readyDeps.isUnlocked()) return { ok: false, error: "vault_locked" }; return { ok: true }; } export async function buildLocalSyncSnapshot( deps?: Pick ): Promise { const readyDeps = deps ?? (await loadDefaultDeps()); const preflight = await checkSyncPreflight(readyDeps); if (!preflight.ok) throw new Error(preflight.error); const entries = readyDeps.getEntries(); return { entries, manifest: await buildSyncManifest(entries), }; } export async function planManifestSync( remoteManifest: SyncManifestEntry[], deps?: Pick ): Promise { const snapshot = await buildLocalSyncSnapshot(deps); return { localManifest: snapshot.manifest, diff: diffSyncManifests(snapshot.manifest, remoteManifest), }; } export async function cancelSyncIfVaultLocked( connectionId: string, deps?: Pick ): Promise { const readyDeps = deps ?? (await loadDefaultDeps()); if (readyDeps.isUnlocked()) return false; readyDeps.sendSyncData( connectionId, encodeSyncMessage({ type: "SYNC_ERROR", code: "vault_locked", message: "Vault is locked; unlock Peach before syncing.", }) ); return true; }