@@ -17,17 +17,123 @@ export const AVAILABLE_SETTINGS_KEYS: SettingsKey[] = Object.keys(
1717 DEFAULT_SETTINGS_VALUES
1818) as SettingsKey [ ] ;
1919
20+ // Generic async key-value store interface to abstract storage backends
21+ interface KeyValueStore {
22+ get < T = unknown > ( key : string ) : Promise < T | null > ;
23+ set < T = unknown > ( key : string , value : T ) : Promise < void > ;
24+ save ( ) : Promise < void > ;
25+ has ( key : string ) : Promise < boolean > ;
26+ delete ( key : string ) : Promise < void > ;
27+ clear ( ) : Promise < void > ;
28+ length ( ) : Promise < number > ;
29+ }
30+
31+ // localStorage-based implementation as a browser fallback
32+ class LocalStorageStore implements KeyValueStore {
33+ private readonly keyPrefix : string ;
34+ private readonly isBrowser : boolean ;
35+
36+ constructor ( keyPrefix : string = "settings:" ) {
37+ this . keyPrefix = keyPrefix ;
38+ this . isBrowser = typeof window !== "undefined" && typeof window . localStorage !== "undefined" ;
39+ }
40+
41+ private toFullKey ( key : string ) : string {
42+ return `${ this . keyPrefix } ${ key } ` ;
43+ }
44+
45+ async get < T = unknown > ( key : string ) : Promise < T | null > {
46+ if ( ! this . isBrowser ) return null ;
47+ try {
48+ const raw = window . localStorage . getItem ( this . toFullKey ( key ) ) ;
49+ if ( raw === null ) return null ;
50+ return JSON . parse ( raw ) as T ;
51+ } catch ( _err ) {
52+ return null ;
53+ }
54+ }
55+
56+ async set < T = unknown > ( key : string , value : T ) : Promise < void > {
57+ if ( ! this . isBrowser ) return ;
58+ try {
59+ window . localStorage . setItem ( this . toFullKey ( key ) , JSON . stringify ( value ) ) ;
60+ } catch ( _err ) {
61+ // ignore quota or serialization errors
62+ }
63+ }
64+
65+ async save ( ) : Promise < void > {
66+ // No-op for localStorage
67+ return ;
68+ }
69+
70+ async has ( key : string ) : Promise < boolean > {
71+ if ( ! this . isBrowser ) return false ;
72+ return window . localStorage . getItem ( this . toFullKey ( key ) ) !== null ;
73+ }
74+
75+ async delete ( key : string ) : Promise < void > {
76+ if ( ! this . isBrowser ) return ;
77+ try {
78+ window . localStorage . removeItem ( this . toFullKey ( key ) ) ;
79+ } catch ( _err ) {
80+ // ignore
81+ }
82+ }
83+
84+ async clear ( ) : Promise < void > {
85+ if ( ! this . isBrowser ) return ;
86+ try {
87+ const keysToRemove : string [ ] = [ ] ;
88+ for ( let i = 0 ; i < window . localStorage . length ; i ++ ) {
89+ const k = window . localStorage . key ( i ) ;
90+ if ( k && k . startsWith ( this . keyPrefix ) ) {
91+ keysToRemove . push ( k ) ;
92+ }
93+ }
94+ keysToRemove . forEach ( k => window . localStorage . removeItem ( k ) ) ;
95+ } catch ( _err ) {
96+ // ignore
97+ }
98+ }
99+
100+ async length ( ) : Promise < number > {
101+ if ( ! this . isBrowser ) return 0 ;
102+ try {
103+ let count = 0 ;
104+ for ( let i = 0 ; i < window . localStorage . length ; i ++ ) {
105+ const k = window . localStorage . key ( i ) ;
106+ if ( k && k . startsWith ( this . keyPrefix ) ) count ++ ;
107+ }
108+ return count ;
109+ } catch ( _err ) {
110+ return 0 ;
111+ }
112+ }
113+ }
114+
20115/**
21116 * Application settings store
22117 */
23118class SettingsStore {
24- private store : LazyStore ;
119+ private store : KeyValueStore ;
25120
26121 constructor ( ) {
27- this . store = new LazyStore ( "settings.json" ) ;
122+ this . store = this . createInitialStore ( ) ;
28123 this . initialize ( ) ;
29124 }
30125
126+ private createInitialStore ( ) : KeyValueStore {
127+ try {
128+ if ( typeof window !== "undefined" && ( window as any ) . __TAURI__ ) {
129+ return new LazyStore ( "settings.json" ) as unknown as KeyValueStore ;
130+ }
131+ } catch ( _err ) {
132+ // ignore and fallback to localStorage
133+ }
134+ return new LocalStorageStore ( "settings:" ) ;
135+ }
136+
31137 private async initialize ( ) : Promise < void > {
32138 try {
33139 const isEmpty = ! ( await this . store . length ( ) ) ;
@@ -39,11 +145,23 @@ class SettingsStore {
39145 }
40146 }
41147
42- if ( import . meta. env . DEV ) {
148+ if ( import . meta. env . DEV && typeof window !== "undefined" ) {
43149 ( window as any ) . settingsStore = this ;
44150 }
45151 } catch ( error ) {
46152 console . error ( "Failed to initialize settings store:" , error ) ;
153+ // Fallback to localStorage store if tauri store fails at runtime
154+ if ( ! ( this . store instanceof LocalStorageStore ) ) {
155+ this . store = new LocalStorageStore ( "settings:" ) ;
156+ try {
157+ const isEmpty = ! ( await this . store . length ( ) ) ;
158+ if ( isEmpty ) {
159+ await this . setDefaults ( ) ;
160+ }
161+ } catch ( fallbackError ) {
162+ console . error ( "Failed to initialize fallback localStorage store:" , fallbackError ) ;
163+ }
164+ }
47165 }
48166 }
49167
0 commit comments