sync-storage.ts

5.6 KB · raw

1import type { TrustedNetwork, PairedDevice, IncomingPairingRequest } from '../shared/sync-types';23const TRUSTED_NETWORKS_KEY = 'peach_trusted_networks';4const PAIRED_DEVICES_KEY = 'peach_paired_devices';5const INCOMING_PAIR_KEY = 'peach_incoming_pair';67// ---------------------------------------------------------------------------8// Trusted Networks9// ---------------------------------------------------------------------------1011export async function getTrustedNetworks(): Promise<TrustedNetwork[]> {12  try {13    const result = await chrome.storage.local.get(TRUSTED_NETWORKS_KEY);14    return (result as { peach_trusted_networks?: TrustedNetwork[] })[TRUSTED_NETWORKS_KEY] ?? [];15  } catch {16    return [];17  }18}1920export async function addTrustedNetwork(network: TrustedNetwork): Promise<void> {21  try {22    const networks = await getTrustedNetworks();23    networks.push(network);24    await chrome.storage.local.set({ [TRUSTED_NETWORKS_KEY]: networks });25  } catch (err) {26    throw new Error(`Failed to add trusted network: ${err}`);27  }28}2930export async function removeTrustedNetwork(addedAt: number): Promise<void> {31  try {32    const networks = await getTrustedNetworks();33    const filtered = networks.filter((n) => n.addedAt !== addedAt);34    await chrome.storage.local.set({ [TRUSTED_NETWORKS_KEY]: filtered });35  } catch (err) {36    throw new Error(`Failed to remove trusted network: ${err}`);37  }38}3940export async function isCurrentNetworkTrusted(): Promise<boolean> {41  try {42    const networks = await getTrustedNetworks();43    if (networks.length === 0) return false;4445    // eslint-disable-next-line @typescript-eslint/no-explicit-any46    const systemNetwork = (chrome as any).system?.network;47    const interfaces: Array<{ name: string; address: string; prefixLength?: number }> =48      systemNetwork ? await systemNetwork.getNetworkInterfaces() : [];4950    const hasTailscaleIp = interfaces.some((iface) => isTailscaleAddress(iface.address));5152    for (const network of networks) {53      if (network.isTailscale && hasTailscaleIp) return true;54    }5556    return false;57  } catch {58    return false;59  }60}6162function isTailscaleAddress(address: string): boolean {63  const parts = address.split('.').map(Number);64  if (parts.length !== 4 || parts.some(isNaN)) return false;65  const [a, b] = parts;66  return a === 100 && b >= 64 && b <= 127;67}6869// ---------------------------------------------------------------------------70// Paired Devices71// ---------------------------------------------------------------------------7273export async function getPairedDevices(): Promise<PairedDevice[]> {74  try {75    const result = await chrome.storage.local.get(PAIRED_DEVICES_KEY);76    return (result as { peach_paired_devices?: PairedDevice[] })[PAIRED_DEVICES_KEY] ?? [];77  } catch {78    return [];79  }80}8182export async function addPairedDevice(device: PairedDevice): Promise<void> {83  try {84    const devices = await getPairedDevices();85    const exists = devices.some((d) => d.deviceId === device.deviceId);86    if (exists) throw new Error(`Device ${device.deviceId} is already paired`);87    devices.push(device);88    await chrome.storage.local.set({ [PAIRED_DEVICES_KEY]: devices });89  } catch (err) {90    throw new Error(`Failed to add paired device: ${err}`);91  }92}9394export async function removePairedDevice(deviceId: string): Promise<void> {95  try {96    const devices = await getPairedDevices();97    const filtered = devices.filter((d) => d.deviceId !== deviceId);98    await chrome.storage.local.set({ [PAIRED_DEVICES_KEY]: filtered });99  } catch (err) {100    throw new Error(`Failed to remove paired device: ${err}`);101  }102}103104export async function updatePairedDevice(deviceId: string, updates: Partial<PairedDevice>): Promise<void> {105  try {106    const devices = await getPairedDevices();107    const index = devices.findIndex((d) => d.deviceId === deviceId);108    if (index === -1) throw new Error(`Device ${deviceId} not found`);109    devices[index] = { ...devices[index], ...updates, deviceId };110    await chrome.storage.local.set({ [PAIRED_DEVICES_KEY]: devices });111  } catch (err) {112    throw new Error(`Failed to update paired device: ${err}`);113  }114}115116export async function getPairedDevice(deviceId: string): Promise<PairedDevice | undefined> {117  try {118    const devices = await getPairedDevices();119    return devices.find((d) => d.deviceId === deviceId);120  } catch {121    return undefined;122  }123}124125// ---------------------------------------------------------------------------126// Incoming Pairing Request127//128// Stored in session storage so the request disappears when the browser closes129// and never lands on disk. The 60-second expiry is enforced by the consumer130// (`getIncomingPairingRequest` returns null past expiry and clears storage).131// ---------------------------------------------------------------------------132133export async function setIncomingPairingRequest(134  request: IncomingPairingRequest135): Promise<void> {136  await chrome.storage.session.set({ [INCOMING_PAIR_KEY]: request });137}138139export async function getIncomingPairingRequest(): Promise<IncomingPairingRequest | null> {140  try {141    const result = await chrome.storage.session.get(INCOMING_PAIR_KEY);142    const request = (result as { peach_incoming_pair?: IncomingPairingRequest })[INCOMING_PAIR_KEY];143    if (!request) return null;144    if (Date.now() >= request.expiresAt) {145      await clearIncomingPairingRequest();146      return null;147    }148    return request;149  } catch {150    return null;151  }152}153154export async function clearIncomingPairingRequest(): Promise<void> {155  try {156    await chrome.storage.session.remove(INCOMING_PAIR_KEY);157  } catch {158    // session storage may be unavailable in some contexts; safe to ignore.159  }160}