12345678910111213141516171819202122import type { PairedDevice } from "../shared/sync-types";2324const REGISTRY_KEY = "peach_paired_devices_v3";25const LAST_IP_KEY_PREFIX = "peach_last_known_ip_v3:";26const LAST_SYNC_AT_KEY = "peach_last_sync_at";27const LAST_SYNC_RESULT_KEY = "peach_last_sync_result";2829export async function listPairedDevices(): Promise<PairedDevice[]> {30 try {31 const result = await chrome.storage.local.get(REGISTRY_KEY);32 const devices = (result as Record<string, unknown>)[REGISTRY_KEY];33 if (Array.isArray(devices)) return devices.filter(isPairedDevice);34 35 36 37 try {38 const syncResult = await chrome.storage.sync.get(REGISTRY_KEY);39 const syncDevices = (syncResult as Record<string, unknown>)[REGISTRY_KEY];40 if (Array.isArray(syncDevices) && syncDevices.length > 0) {41 const valid = syncDevices.filter(isPairedDevice);42 await chrome.storage.local.set({ [REGISTRY_KEY]: valid });43 await chrome.storage.sync.remove(REGISTRY_KEY).catch(() => undefined);44 return valid;45 }46 } catch { }47 return [];48 } catch {49 return [];50 }51}5253export async function findPairedDeviceById(deviceId: string): Promise<PairedDevice | null> {54 const devices = await listPairedDevices();55 return devices.find((d) => d.deviceId === deviceId) ?? null;56}5758export async function findPairedDeviceByPubkey(pubkeyHex: string): Promise<PairedDevice | null> {59 const devices = await listPairedDevices();60 return devices.find((d) => d.longTermPublicKeyHex === pubkeyHex) ?? null;61}62636465666768export async function upsertPairedDevice(device: PairedDevice): Promise<void> {69 if (!device.deviceId) throw new Error("device.deviceId is required");70 if (!device.longTermPublicKeyHex) throw new Error("device.longTermPublicKeyHex is required");71 if (!device.deviceName) throw new Error("device.deviceName is required");72 if (!device.deviceType) throw new Error("device.deviceType is required");7374 const devices = await listPairedDevices();75 const idx = devices.findIndex((d) => d.deviceId === device.deviceId);76 if (idx >= 0) {77 devices[idx] = { ...devices[idx], ...device };78 } else {79 devices.push(device);80 }81 await chrome.storage.local.set({ [REGISTRY_KEY]: devices });82}8384export async function removePairedDeviceById(deviceId: string): Promise<void> {85 const devices = await listPairedDevices();86 const filtered = devices.filter((d) => d.deviceId !== deviceId);87 88 89 90 await chrome.storage.local.set({ [REGISTRY_KEY]: filtered });91 92 const device = devices.find((d) => d.deviceId === deviceId);93 if (device?.longTermPublicKeyHex) {94 await chrome.storage.local.remove(LAST_IP_KEY_PREFIX + device.longTermPublicKeyHex);95 }96}9798export async function clearPairedDevices(): Promise<void> {99 await chrome.storage.local.remove(REGISTRY_KEY);100}101102export async function markLastSyncedAt(deviceId: string, when: number): Promise<void> {103 const devices = await listPairedDevices();104 const idx = devices.findIndex((d) => d.deviceId === deviceId);105 if (idx < 0) return;106 devices[idx] = { ...devices[idx], lastSyncedAt: when };107 await chrome.storage.local.set({ [REGISTRY_KEY]: devices });108}109110export async function getLastKnownIp(pubkeyHex: string): Promise<string | null> {111 try {112 const key = LAST_IP_KEY_PREFIX + pubkeyHex;113 const result = await chrome.storage.local.get(key);114 const value = (result as Record<string, unknown>)[key];115 return typeof value === "string" ? value : null;116 } catch {117 return null;118 }119}120121export async function setLastKnownIp(pubkeyHex: string, ip: string): Promise<void> {122 await chrome.storage.local.set({ [LAST_IP_KEY_PREFIX + pubkeyHex]: ip });123}124125export async function clearLastKnownIp(pubkeyHex: string): Promise<void> {126 await chrome.storage.local.remove(LAST_IP_KEY_PREFIX + pubkeyHex);127}128129export async function getLastSyncAt(): Promise<number | null> {130 try {131 const result = await chrome.storage.local.get(LAST_SYNC_AT_KEY);132 const value = (result as Record<string, unknown>)[LAST_SYNC_AT_KEY];133 if (typeof value === "number") return value;134 if (typeof value === "string" && value !== "") {135 const n = Number.parseInt(value, 10);136 return Number.isFinite(n) ? n : null;137 }138 return null;139 } catch {140 return null;141 }142}143144export async function setLastSyncAt(when: number): Promise<void> {145 await chrome.storage.local.set({ [LAST_SYNC_AT_KEY]: when });146}147148export interface SyncResult {149 entriesReceived: number;150 entriesSent: number;151}152153export async function getLastSyncResult(): Promise<SyncResult | null> {154 try {155 const result = await chrome.storage.local.get(LAST_SYNC_RESULT_KEY);156 const value = (result as Record<string, unknown>)[LAST_SYNC_RESULT_KEY];157 if (158 value &&159 typeof value === "object" &&160 typeof (value as Record<string, unknown>).entriesReceived === "number" &&161 typeof (value as Record<string, unknown>).entriesSent === "number"162 ) {163 return value as SyncResult;164 }165 return null;166 } catch {167 return null;168 }169}170171export async function setLastSyncResult(result: SyncResult): Promise<void> {172 await chrome.storage.local.set({ [LAST_SYNC_RESULT_KEY]: result });173}174175function isPairedDevice(x: unknown): x is PairedDevice {176 if (typeof x !== "object" || x === null) return false;177 const r = x as Record<string, unknown>;178 if (typeof r.deviceId !== "string" || !r.deviceId) return false;179 if (typeof r.pairedAt !== "number") return false;180 return true;181}