SPB Git

spb/zyquo-cloud-web Public MIT

Zyquo Cloud Web — every cloud model, one beautiful chat, entirely in your browser.

TypeScript 81.9% CSS 8.9% JavaScript 7.5% Shell 1.1% HTML 0.6%
2.9 KB · 108 lines typescript
Raw Blame History
1/*2 *  keys.ts3 *  Zyquo Cloud Web4 *5 *  Author: Simon-Pierre Boucher6 *  Mail: contact@spboucher.ai7 *8 *  Provider API key storage — plain localStorage under zyquo.cloud.web.keys,9 *  namespaced JSON map. This module (and the rest of storage/) is the ONLY10 *  place that touches localStorage. Keys never leave the device except inside11 *  a provider request the user initiated. Optional passphrase lock (WebCrypto12 *  AES-GCM) lives in vault.ts and wraps this map at rest.13 */1415import type { Provider } from '../types'1617const KEYS_KEY = 'zyquo.cloud.web.keys'1819export type KeyMap = Partial<Record<Provider, string>>2021/** In-memory overlay used while the passphrase lock is active (Phase 3). */22let unlockedOverlay: KeyMap | null = null2324export function setUnlockedOverlay(map: KeyMap | null): void {25  unlockedOverlay = map26}2728export function isOverlayActive(): boolean {29  return unlockedOverlay !== null30}3132export function readKeyMap(): KeyMap {33  if (unlockedOverlay) return { ...unlockedOverlay }34  try {35    const raw = localStorage.getItem(KEYS_KEY)36    if (!raw) return {}37    const parsed: unknown = JSON.parse(raw)38    if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {39      return parsed as KeyMap40    }41    return {}42  } catch {43    return {}44  }45}4647function writeKeyMap(map: KeyMap): void {48  if (unlockedOverlay) {49    unlockedOverlay = { ...map }50    return51  }52  localStorage.setItem(KEYS_KEY, JSON.stringify(map))53}5455export function getKey(provider: Provider): string | undefined {56  const value = readKeyMap()[provider]57  return value && value !== '' ? value : undefined58}5960export function setKey(provider: Provider, key: string): void {61  const map = readKeyMap()62  map[provider] = key63  writeKeyMap(map)64}6566export function removeKey(provider: Provider): void {67  const map = readKeyMap()68  delete map[provider]69  writeKeyMap(map)70}7172export function hasKey(provider: Provider): boolean {73  return getKey(provider) !== undefined74}7576export function configuredProviders(): Provider[] {77  return Object.entries(readKeyMap())78    .filter(([, v]) => typeof v === 'string' && v !== '')79    .map(([k]) => k as Provider)80}8182/** Masked display: last 4 characters only (e.g. "••••••••Q3ew"). */83export function maskedKey(key: string): string {84  if (key.length <= 4) return '••••'85  return `${'•'.repeat(8)}${key.slice(-4)}`86}8788/** Replaces the whole map (import / passphrase unlock). */89export function replaceKeyMap(map: KeyMap): void {90  writeKeyMap(map)91}9293export function clearAllKeys(): void {94  if (unlockedOverlay) {95    unlockedOverlay = {}96  }97  localStorage.removeItem(KEYS_KEY)98}99100/** Raw ciphertext/plaintext blob accessors for the passphrase vault (Phase 3). */101export function rawKeysRecord(): string | null {102  return localStorage.getItem(KEYS_KEY)103}104105export function setRawKeysRecord(value: string): void {106  localStorage.setItem(KEYS_KEY, value)107}108