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;