1234567891011121314import type { PairedDevice } from "../shared/sync-types";1516const REGISTRY_KEY = "peach_paired_devices_v3";17const LAST_IP_KEY_PREFIX = "peach_last_known_ip_v3:";18const LAST_SYNC_AT_KEY = "peach_last_sync_at";19202122232425export async function listPairedDevices(): Promise<PairedDevice[]> {26 try {27 const result = await chrome.storage.sync.get(REGISTRY_KEY);28 const devices = (result as Record<string, unknown>)[REGISTRY_KEY];29 if (!Array.isArray(devices)) return [];30 return devices.filter(isPairedDevice);31 } catch {32 return [];33 }34}3536export async function findPairedDeviceById(deviceId: string): Promise<PairedDevice | null> {37 const devices = await listPairedDevices();38 return devices.find((d) => d.deviceId === deviceId) ?? null;39}4041export async function findPairedDeviceByPubkey(pubkeyHex: string): Promise<PairedDevice | null> {42 const devices = await listPairedDevices();43 return devices.find((d) => d.longTermPublicKeyHex === pubkeyHex) ?? null;44}45464748495051export async function upsertPairedDevice(device: PairedDevice): Promise<void> {52 if (!device.deviceId) throw new Error("device.deviceId is required");53 if (!device.longTermPublicKeyHex) throw new Error("device.longTermPublicKeyHex is required");54 if (!device.deviceName) throw new Error("device.deviceName is required");55 if (!device.deviceType) throw new Error("device.deviceType is required");5657 const devices = await listPairedDevices();58 const idx = devices.findIndex((d) => d.deviceId === device.deviceId);59 if (idx >= 0) {60 devices[idx] = { ...devices[idx], ...device };61 } else {62 devices.push(device);63 }64 await chrome.storage.sync.set({ [REGISTRY_KEY]: devices });65}6667export async function removePairedDeviceById(deviceId: string): Promise<void> {68 const devices = await listPairedDevices();69 const filtered = devices.filter((d) => d.deviceId !== deviceId);70 await chrome.storage.sync.set({ [REGISTRY_KEY]: filtered });71 72 const device = devices.find((d) => d.deviceId === deviceId);73 if (device?.longTermPublicKeyHex) {74 await chrome.storage.local.remove(LAST_IP_KEY_PREFIX + device.longTermPublicKeyHex);75 }76}7778export async function clearPairedDevices(): Promise<void> {79 await chrome.storage.sync.remove(REGISTRY_KEY);80}8182export async function markLastSyncedAt(deviceId: string, when: number): Promise<void> {83 const devices = await listPairedDevices();84 const idx = devices.findIndex((d) => d.deviceId === deviceId);85 if (idx < 0) return;86 devices[idx] = { ...devices[idx], lastSyncedAt: when };87 await chrome.storage.sync.set({ [REGISTRY_KEY]: devices });88}8990export async function getLastKnownIp(pubkeyHex: string): Promise<string | null> {91 try {92 const key = LAST_IP_KEY_PREFIX + pubkeyHex;93 const result = await chrome.storage.local.get(key);94 const value = (result as Record<string, unknown>)[key];95 return typeof value === "string" ? value : null;96 } catch {97 return null;98 }99}100101export async function setLastKnownIp(pubkeyHex: string, ip: string): Promise<void> {102 await chrome.storage.local.set({ [LAST_IP_KEY_PREFIX + pubkeyHex]: ip });103}104105export async function clearLastKnownIp(pubkeyHex: string): Promise<void> {106 await chrome.storage.local.remove(LAST_IP_KEY_PREFIX + pubkeyHex);107}108109export async function getLastSyncAt(): Promise<number | null> {110 try {111 const result = await chrome.storage.local.get(LAST_SYNC_AT_KEY);112 const value = (result as Record<string, unknown>)[LAST_SYNC_AT_KEY];113 if (typeof value === "number") return value;114 if (typeof value === "string" && value !== "") {115 const n = Number.parseInt(value, 10);116 return Number.isFinite(n) ? n : null;117 }118 return null;119 } catch {120 return null;121 }122}123124export async function setLastSyncAt(when: number): Promise<void> {125 await chrome.storage.local.set({ [LAST_SYNC_AT_KEY]: when });126}127128function isPairedDevice(x: unknown): x is PairedDevice {129 if (typeof x !== "object" || x === null) return false;130 const r = x as Record<string, unknown>;131 if (typeof r.deviceId !== "string" || !r.deviceId) return false;132 if (typeof r.pairedAt !== "number") return false;133 return true;134}