import { DEFAULT_RELAY_URL, normalizeRelayUrl } from "../shared/rendezvous"; import { setRelayUrlForAllPairedDevices } from "./paired-devices"; /** * Sync is exactly three named choices (docs/sync-lan-retirement.md): * * - `public` — Peach's relay. The default, zero setup. * - `private` — a self-hosted peach-relay the user points their devices at. * - `off` — no sync; the vault moves between devices by Codex/CSV export. * * LAN peer-to-peer sync is retired, not deprecated: there is no fourth mode and * no "local network only" opt-out. `public` and `private` differ only in which * relay URL is in force, so the transport below them is identical. */ export type SyncMode = "public" | "private" | "off"; export interface SyncSettings { mode: SyncMode; /** * The relay in force. Always `DEFAULT_RELAY_URL` under `public`; the user's * URL under `private`. Retained (not blanked) under `off` so turning sync * back on does not lose a configured private relay. */ relayUrl: string; } const RELAY_URL_KEY = "peach_relay_url_v1"; const SYNC_MODE_KEY = "peach_sync_mode_v1"; export async function getSyncSettings(): Promise { try { const values = (await chrome.storage.local.get([ RELAY_URL_KEY, SYNC_MODE_KEY, ])) as Record; const storedUrl = typeof values[RELAY_URL_KEY] === "string" ? safeNormalize(values[RELAY_URL_KEY] as string) : DEFAULT_RELAY_URL; const stored = values[SYNC_MODE_KEY]; if (stored === "public" || stored === "private" || stored === "off") { return { mode: stored, relayUrl: relayFor(stored, storedUrl) }; } // A missing mode is a fresh install or an incomplete local store. Infer the // current mode from the configured relay; retired LAN settings are no // longer interpreted. const mode: SyncMode = storedUrl === DEFAULT_RELAY_URL ? "public" : "private"; return { mode, relayUrl: relayFor(mode, storedUrl) }; } catch { return { mode: "public", relayUrl: DEFAULT_RELAY_URL }; } } /** * `private` chosen but no relay entered yet. The stored URL still reads as the * public default, so running sync here would quietly put the user on Peach's * relay under a "Private relay" label — the exact trap the retired `lanOnly` * flag set. Every sync entry point fails closed on this instead. */ export function isPrivateRelayUnconfigured(settings: SyncSettings): boolean { return settings.mode === "private" && settings.relayUrl === DEFAULT_RELAY_URL; } /** * The relay to dial, or a reason not to. Single gate for every sync and pairing * entry point, so a new caller cannot forget one of the two ways sync can be * unusable. */ export function relayInForce(settings: SyncSettings): string { if (settings.mode === "off") throw new Error("sync_disabled"); if (isPrivateRelayUnconfigured(settings)) throw new Error("private_relay_unset"); return settings.relayUrl; } export async function setSyncMode(mode: SyncMode): Promise { const current = await getSyncSettings(); const next: SyncSettings = { mode, relayUrl: relayFor(mode, current.relayUrl) }; // The relay is mesh state: every paired device must agree on it, or a peer // keeps dialling a relay we no longer use. Only propagate one we will // actually use — switching to `private` before a URL is typed must not push // the public relay back out to the mesh. if (mode !== "off" && !isPrivateRelayUnconfigured(next)) { await setRelayUrlForAllPairedDevices(next.relayUrl); } await chrome.storage.local.set({ [SYNC_MODE_KEY]: mode, [RELAY_URL_KEY]: next.relayUrl }); return next; } /** * Point this mesh at a relay by URL. A custom URL implies `private` — offering * one and not switching to it would be a trap. Peach's own relay stays * `public`: adoption hands the source's relay over this same path, so calling * that "private" would mislabel every device that joined over the public relay * and strand it in the unconfigured-private state above. */ export async function setRelayUrl(input: string): Promise { const relayUrl = normalizeRelayUrl(input); const mode: SyncMode = relayUrl === DEFAULT_RELAY_URL ? "public" : "private"; await setRelayUrlForAllPairedDevices(relayUrl); await chrome.storage.local.set({ [RELAY_URL_KEY]: relayUrl, [SYNC_MODE_KEY]: mode }); return { mode, relayUrl }; } function relayFor(mode: SyncMode, configured: string): string { return mode === "public" ? DEFAULT_RELAY_URL : configured; } function safeNormalize(input: string): string { try { return normalizeRelayUrl(input); } catch { return DEFAULT_RELAY_URL; } }