import type { TrustedNetwork, PairedDevice, IncomingPairingRequest } from '../shared/sync-types'; const TRUSTED_NETWORKS_KEY = 'peach_trusted_networks'; const PAIRED_DEVICES_KEY = 'peach_paired_devices'; const INCOMING_PAIR_KEY = 'peach_incoming_pair'; // --------------------------------------------------------------------------- // Trusted Networks // --------------------------------------------------------------------------- export async function getTrustedNetworks(): Promise { try { const result = await chrome.storage.local.get(TRUSTED_NETWORKS_KEY); return (result as { peach_trusted_networks?: TrustedNetwork[] })[TRUSTED_NETWORKS_KEY] ?? []; } catch { return []; } } export async function addTrustedNetwork(network: TrustedNetwork): Promise { try { const networks = await getTrustedNetworks(); networks.push(network); await chrome.storage.local.set({ [TRUSTED_NETWORKS_KEY]: networks }); } catch (err) { throw new Error(`Failed to add trusted network: ${err}`); } } export async function removeTrustedNetwork(addedAt: number): Promise { try { const networks = await getTrustedNetworks(); const filtered = networks.filter((n) => n.addedAt !== addedAt); await chrome.storage.local.set({ [TRUSTED_NETWORKS_KEY]: filtered }); } catch (err) { throw new Error(`Failed to remove trusted network: ${err}`); } } export async function isCurrentNetworkTrusted(): Promise { try { const networks = await getTrustedNetworks(); if (networks.length === 0) return false; // eslint-disable-next-line @typescript-eslint/no-explicit-any const systemNetwork = (chrome as any).system?.network; const interfaces: Array<{ name: string; address: string; prefixLength?: number }> = systemNetwork ? await systemNetwork.getNetworkInterfaces() : []; const hasTailscaleIp = interfaces.some((iface) => isTailscaleAddress(iface.address)); for (const network of networks) { if (network.isTailscale && hasTailscaleIp) return true; } return false; } catch { return false; } } function isTailscaleAddress(address: string): boolean { const parts = address.split('.').map(Number); if (parts.length !== 4 || parts.some(isNaN)) return false; const [a, b] = parts; return a === 100 && b >= 64 && b <= 127; } // --------------------------------------------------------------------------- // Paired Devices // --------------------------------------------------------------------------- export async function getPairedDevices(): Promise { try { const result = await chrome.storage.local.get(PAIRED_DEVICES_KEY); return (result as { peach_paired_devices?: PairedDevice[] })[PAIRED_DEVICES_KEY] ?? []; } catch { return []; } } export async function addPairedDevice(device: PairedDevice): Promise { try { const devices = await getPairedDevices(); const exists = devices.some((d) => d.deviceId === device.deviceId); if (exists) throw new Error(`Device ${device.deviceId} is already paired`); devices.push(device); await chrome.storage.local.set({ [PAIRED_DEVICES_KEY]: devices }); } catch (err) { throw new Error(`Failed to add paired device: ${err}`); } } export async function removePairedDevice(deviceId: string): Promise { try { const devices = await getPairedDevices(); const filtered = devices.filter((d) => d.deviceId !== deviceId); await chrome.storage.local.set({ [PAIRED_DEVICES_KEY]: filtered }); } catch (err) { throw new Error(`Failed to remove paired device: ${err}`); } } export async function updatePairedDevice(deviceId: string, updates: Partial): Promise { try { const devices = await getPairedDevices(); const index = devices.findIndex((d) => d.deviceId === deviceId); if (index === -1) throw new Error(`Device ${deviceId} not found`); devices[index] = { ...devices[index], ...updates, deviceId }; await chrome.storage.local.set({ [PAIRED_DEVICES_KEY]: devices }); } catch (err) { throw new Error(`Failed to update paired device: ${err}`); } } export async function getPairedDevice(deviceId: string): Promise { try { const devices = await getPairedDevices(); return devices.find((d) => d.deviceId === deviceId); } catch { return undefined; } } // --------------------------------------------------------------------------- // Incoming Pairing Request // // Stored in session storage so the request disappears when the browser closes // and never lands on disk. The 60-second expiry is enforced by the consumer // (`getIncomingPairingRequest` returns null past expiry and clears storage). // --------------------------------------------------------------------------- export async function setIncomingPairingRequest( request: IncomingPairingRequest ): Promise { await chrome.storage.session.set({ [INCOMING_PAIR_KEY]: request }); } export async function getIncomingPairingRequest(): Promise { try { const result = await chrome.storage.session.get(INCOMING_PAIR_KEY); const request = (result as { peach_incoming_pair?: IncomingPairingRequest })[INCOMING_PAIR_KEY]; if (!request) return null; if (Date.now() >= request.expiresAt) { await clearIncomingPairingRequest(); return null; } return request; } catch { return null; } } export async function clearIncomingPairingRequest(): Promise { try { await chrome.storage.session.remove(INCOMING_PAIR_KEY); } catch { // session storage may be unavailable in some contexts; safe to ignore. } }