1234567891011121314151617181920212223import type { PairedDevice } from "../shared/sync-types";2425const REGISTRY_KEY = "peach_paired_devices_v3";26const LAST_IP_KEY_PREFIX = "peach_last_known_ip_v3:";27const LAST_SYNC_AT_KEY = "peach_last_sync_at";28const LAST_SYNC_RESULT_KEY = "peach_last_sync_result";2930export async function listPairedDevices(): Promise<PairedDevice[]> {31 try {32 const result = await chrome.storage.local.get(REGISTRY_KEY);33 const devices = (result as Record<string, unknown>)[REGISTRY_KEY];34 if (Array.isArray(devices)) return devices.filter(isPairedDevice);35 return [];36 } catch {37 return [];38 }39}4041export async function findPairedDeviceById(deviceId: string): Promise<PairedDevice | null> {42 const devices = await listPairedDevices();43 return devices.find((d) => d.deviceId === deviceId) ?? null;44}4546export async function findPairedDeviceByPubkey(pubkeyHex: string): Promise<PairedDevice | null> {47 const devices = await listPairedDevices();48 return devices.find((d) => d.longTermPublicKeyHex === pubkeyHex) ?? null;49}50515253545556export async function upsertPairedDevice(device: PairedDevice): Promise<void> {57 if (!device.deviceId) throw new Error("device.deviceId is required");58 if (!device.longTermPublicKeyHex) throw new Error("device.longTermPublicKeyHex is required");59 if (!device.deviceName) throw new Error("device.deviceName is required");60 if (!device.deviceType) throw new Error("device.deviceType is required");6162 const devices = await listPairedDevices();63 const idx = devices.findIndex((d) => d.deviceId === device.deviceId);64 if (idx >= 0) {65 devices[idx] = { ...devices[idx], ...device };66 } else {67 devices.push(device);68 }69 await chrome.storage.local.set({ [REGISTRY_KEY]: devices });70}71727374757677787980818283export async function removePairedDeviceById(deviceId: string): Promise<void> {84 const devices = await listPairedDevices();85 const filtered = devices.filter((d) => d.deviceId !== deviceId);86 87 88 89 await chrome.storage.local.set({ [REGISTRY_KEY]: filtered });90 91 const device = devices.find((d) => d.deviceId === deviceId);92 if (device?.longTermPublicKeyHex) {93 await chrome.storage.local.remove(LAST_IP_KEY_PREFIX + device.longTermPublicKeyHex);94 }95}9697export async function clearPairedDevices(): Promise<void> {98 await chrome.storage.local.remove(REGISTRY_KEY);99}100101export async function markLastSyncedAt(deviceId: string, when: number): Promise<void> {102 const devices = await listPairedDevices();103 const idx = devices.findIndex((d) => d.deviceId === deviceId);104 if (idx < 0) return;105 devices[idx] = { ...devices[idx], lastSyncedAt: when };106 await chrome.storage.local.set({ [REGISTRY_KEY]: devices });107}108109export async function getLastKnownIp(pubkeyHex: string): Promise<string | null> {110 try {111 const key = LAST_IP_KEY_PREFIX + pubkeyHex;112 const result = await chrome.storage.local.get(key);113 const value = (result as Record<string, unknown>)[key];114 return typeof value === "string" ? value : null;115 } catch {116 return null;117 }118}119120export async function setLastKnownIp(pubkeyHex: string, ip: string): Promise<void> {121 await chrome.storage.local.set({ [LAST_IP_KEY_PREFIX + pubkeyHex]: ip });122}123124export async function clearLastKnownIp(pubkeyHex: string): Promise<void> {125 await chrome.storage.local.remove(LAST_IP_KEY_PREFIX + pubkeyHex);126}127128export async function getLastSyncAt(): Promise<number | null> {129 try {130 const result = await chrome.storage.local.get(LAST_SYNC_AT_KEY);131 const value = (result as Record<string, unknown>)[LAST_SYNC_AT_KEY];132 if (typeof value === "number") return value;133 if (typeof value === "string" && value !== "") {134 const n = Number.parseInt(value, 10);135 return Number.isFinite(n) ? n : null;136 }137 return null;138 } catch {139 return null;140 }141}142143export async function setLastSyncAt(when: number): Promise<void> {144 await chrome.storage.local.set({ [LAST_SYNC_AT_KEY]: when });145}146147export interface SyncResult {148 entriesReceived: number;149 entriesSent: number;150}151152export async function getLastSyncResult(): Promise<SyncResult | null> {153 try {154 const result = await chrome.storage.local.get(LAST_SYNC_RESULT_KEY);155 const value = (result as Record<string, unknown>)[LAST_SYNC_RESULT_KEY];156 if (157 value &&158 typeof value === "object" &&159 typeof (value as Record<string, unknown>).entriesReceived === "number" &&160 typeof (value as Record<string, unknown>).entriesSent === "number"161 ) {162 return value as SyncResult;163 }164 return null;165 } catch {166 return null;167 }168}169170export async function setLastSyncResult(result: SyncResult): Promise<void> {171 await chrome.storage.local.set({ [LAST_SYNC_RESULT_KEY]: result });172}173174function isPairedDevice(x: unknown): x is PairedDevice {175 if (typeof x !== "object" || x === null) return false;176 const r = x as Record<string, unknown>;177 if (typeof r.deviceId !== "string" || !r.deviceId) return false;178 if (typeof r.pairedAt !== "number") return false;179 return true;180}