const SAVE_THROTTLE_MS = 2000; const TOKEN_KEY = '__storage_token__'; const BASE_URL = '/storage/api'; const pendingSaves = new Map>(); // ─── Auth ──────────────────────────────────────────────────── let token: string | null = localStorage.getItem(TOKEN_KEY); export const isLoggedIn = () => token !== null; export const login = async (username: string, password: string): Promise => { const res = await fetch(`${BASE_URL}/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }), }); const json = await res.json(); if (!res.ok) throw new Error(json.error ?? 'Login failed'); token = json.token; localStorage.setItem(TOKEN_KEY, json.token); }; export const logout = () => { token = null; localStorage.removeItem(TOKEN_KEY); }; // ─── Storage ───────────────────────────────────────────────── export const loadObject = async (key: string, defaultObject: T): Promise => { let localObject: Partial = {}; try { const json = localStorage.getItem(key); if (json) localObject = JSON.parse(json); } catch { } let remoteObject: Partial = {}; if (token) { try { const res = await fetch(`${BASE_URL}/storage/${key}`, { headers: { 'Authorization': `Bearer ${token}` }, }); if (res.ok) remoteObject = await res.json(); } catch { } } return { ...defaultObject, ...localObject, ...remoteObject }; } export const saveObject = (key: string, obj: T) => { const saveData = JSON.stringify(obj); try { localStorage.setItem(key, saveData); } catch { } if (token) { const existing = pendingSaves.get(key); if (existing) clearTimeout(existing); pendingSaves.set(key, setTimeout(() => { pendingSaves.delete(key); saveRemote(key, saveData); }, SAVE_THROTTLE_MS)); } }; const saveRemote = async (key: string, saveData: string) => { try { await fetch(`${BASE_URL}/storage/${key}`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, body: saveData, }); } catch { } }; // ─── Compression utils ─────────────────────────────────────── export const compressBlob = async (blob: Blob | string): Promise => { if (typeof blob === 'string') blob = new Blob([blob]); const cs = new CompressionStream("gzip"); return await new Response(blob.stream().pipeThrough(cs)).blob(); } export const decompressBlob = async (blob: Blob): Promise => { const ds = new DecompressionStream("gzip"); return await new Response(blob.stream().pipeThrough(ds)).blob(); }