One-click backups for AT Protocol
1// localStorage-polyfill.ts 2import { Store } from "@tauri-apps/plugin-store"; 3 4interface StorageData { 5 [key: string]: string; 6} 7 8class LocalStoragePolyfill implements Storage { 9 private store: Store; 10 private cache: Map<string, string>; 11 private initialized: boolean = false; 12 private initPromise: Promise<void> | null = null; 13 14 constructor(store: Store) { 15 this.store = store; 16 this.cache = new Map(); 17 } 18 19 async init(): Promise<void> { 20 if (this.initialized) return; 21 22 if (this.initPromise) { 23 return this.initPromise; 24 } 25 26 this.initPromise = this._initialize(); 27 return this.initPromise; 28 } 29 30 private async _initialize(): Promise<void> { 31 try { 32 const data = (await this.store.get("data")) as StorageData | null; 33 if (data && typeof data === "object") { 34 Object.entries(data).forEach(([key, value]) => { 35 this.cache.set(key, value); 36 }); 37 } 38 } catch (error) { 39 console.error("Failed to load localStorage data:", error); 40 } 41 42 this.initialized = true; 43 } 44 45 private async persist(): Promise<void> { 46 try { 47 const data: StorageData = Object.fromEntries(this.cache); 48 await this.store.set("data", data); 49 await this.store.save(); 50 } catch (error) { 51 console.error("Failed to persist localStorage data:", error); 52 } 53 } 54 55 getItem(key: string): string | null { 56 return this.cache.get(key) || null; 57 } 58 59 setItem(key: string, value: string): void { 60 this.cache.set(key, String(value)); 61 // Persist asynchronously to avoid blocking 62 this.persist().catch(console.error); 63 } 64 65 removeItem(key: string): void { 66 this.cache.delete(key); 67 this.persist().catch(console.error); 68 } 69 70 clear(): void { 71 this.cache.clear(); 72 this.persist().catch(console.error); 73 } 74 75 get length(): number { 76 return this.cache.size; 77 } 78 79 key(index: number): string | null { 80 return Array.from(this.cache.keys())[index] || null; 81 } 82 83 // Required for Storage interface compatibility 84 [name: string]: any; 85} 86 87// Initialize and replace global localStorage 88let tauriLocalStorage: LocalStoragePolyfill | undefined; 89 90export const initializeLocalStorage = async (): Promise<void> => { 91 if (typeof window !== "undefined") { 92 const store = await Store.load("localStorage.json"); 93 tauriLocalStorage = new LocalStoragePolyfill(store); 94 await tauriLocalStorage.init(); 95 96 // Try to replace localStorage using Object.defineProperty 97 try { 98 Object.defineProperty(window, "localStorage", { 99 value: tauriLocalStorage, 100 writable: true, 101 configurable: true, 102 }); 103 } catch (error) { 104 console.warn("Could not replace global localStorage:", error); 105 // Fallback: store reference for manual usage 106 (window as any).__tauriLocalStorage = tauriLocalStorage; 107 } 108 } 109}; 110 111export const getTauriLocalStorage = (): LocalStoragePolyfill | undefined => { 112 return tauriLocalStorage; 113}; 114 115// Helper function to get localStorage (either polyfill or native) 116export const getLocalStorage = (): Storage => { 117 if (typeof window !== "undefined") { 118 return window.localStorage || (window as any).__tauriLocalStorage; 119 } 120 throw new Error("localStorage not available"); 121}; 122 123export default tauriLocalStorage;