@@ -109,19 +109,72 @@ const createDB = (): Promise<IDBPDatabase<DBSchema>> => {
109109 } ) ;
110110} ;
111111
112- const initDB = async ( ) : Promise < IDBPDatabase < DBSchema > > => {
113- try {
114- return await createDB ( ) ;
115- } catch ( error ) {
116- if ( ( error as Error ) . name === 'VersionError' ) {
117- console . warn ( 'IndexedDB version mismatch. Clearing database and retrying...' ) ;
112+ // Database instance management using a closure to avoid mutations
113+ const createDBManager = ( ) => {
114+ const cache = new Map < string , Promise < IDBPDatabase < DBSchema > > > ( ) ;
115+
116+ const handleDatabaseError = async ( error : unknown ) : Promise < IDBPDatabase < DBSchema > > => {
117+ const errorMessage = ( error as Error ) . message || '' ;
118+
119+ // Check if it's an object store not found error
120+ if ( errorMessage . includes ( 'object stores was not found' ) ||
121+ errorMessage . includes ( 'object store' ) ||
122+ ( error as Error ) . name === 'NotFoundError' ) {
123+ console . warn ( 'IndexedDB object stores not found. Recreating database...' ) ;
118124 await deleteDatabase ( ) ;
119- return await createDB ( ) ;
125+ cache . clear ( ) ;
126+ return await initDB ( ) ;
120127 }
128+
121129 throw error ;
122- }
130+ } ;
131+
132+ const initDB = async ( ) : Promise < IDBPDatabase < DBSchema > > => {
133+ const cacheKey = 'db' ;
134+ const cached = cache . get ( cacheKey ) ;
135+ if ( cached ) {
136+ return cached ;
137+ }
138+
139+ const dbPromise = ( async ( ) => {
140+ try {
141+ return await createDB ( ) ;
142+ } catch ( error ) {
143+ // Remove from cache on error so next call will retry
144+ cache . delete ( cacheKey ) ;
145+
146+ if ( ( error as Error ) . name === 'VersionError' ) {
147+ console . warn ( 'IndexedDB version mismatch. Clearing database and retrying...' ) ;
148+ await deleteDatabase ( ) ;
149+ return await createDB ( ) ;
150+ }
151+ throw error ;
152+ }
153+ } ) ( ) ;
154+
155+ cache . set ( cacheKey , dbPromise ) ;
156+
157+ try {
158+ return await dbPromise ;
159+ } catch ( error ) {
160+ // Remove from cache if promise rejects
161+ cache . delete ( cacheKey ) ;
162+ throw error ;
163+ }
164+ } ;
165+
166+ const resetCache = ( ) => {
167+ cache . clear ( ) ;
168+ } ;
169+
170+ return { initDB, handleDatabaseError, resetCache } ;
123171} ;
124172
173+ const dbManager = createDBManager ( ) ;
174+ const initDB = dbManager . initDB ;
175+ const handleDatabaseError = dbManager . handleDatabaseError ;
176+ const resetDBInstance = dbManager . resetCache ;
177+
125178export const getShielderIndexedDB = ( chainId : number , privateKey : Address ) => {
126179 const chainKey = chainId . toString ( ) ;
127180 const hashedKey = sha256 ( privateKey ) ;
@@ -135,7 +188,14 @@ export const getShielderIndexedDB = (chainId: number, privateKey: Address) => {
135188 return addrData ?. [ chainKey ] ?. [ itemKey ] ?? null ;
136189 } catch ( error ) {
137190 console . error ( 'Failed to get item from IndexedDB:' , error ) ;
138- return null ;
191+ try {
192+ const db = await handleDatabaseError ( error ) ;
193+ const addrData = await db . get ( STORE_CLIENTS , hashedKey ) ;
194+ return addrData ?. [ chainKey ] ?. [ itemKey ] ?? null ;
195+ } catch ( retryError ) {
196+ console . error ( 'Failed to get item from IndexedDB after retry:' , retryError ) ;
197+ return null ;
198+ }
139199 }
140200 } ,
141201 setItem : async ( itemKey : string , value : string ) : Promise < void > => {
@@ -150,6 +210,18 @@ export const getShielderIndexedDB = (chainId: number, privateKey: Address) => {
150210 ) ;
151211 } catch ( error ) {
152212 console . error ( 'Failed to set item in IndexedDB:' , error ) ;
213+ try {
214+ const db = await handleDatabaseError ( error ) ;
215+ const addrData = ( await db . get ( STORE_CLIENTS , hashedKey ) ) ?? { } ;
216+ const chainData = addrData [ chainKey ] ?? { } ;
217+ await db . put (
218+ STORE_CLIENTS ,
219+ { ...addrData , [ chainKey ] : { ...chainData , [ itemKey ] : value } } ,
220+ hashedKey ,
221+ ) ;
222+ } catch ( retryError ) {
223+ console . error ( 'Failed to set item in IndexedDB after retry:' , retryError ) ;
224+ }
153225 }
154226 } ,
155227 } ;
@@ -167,51 +239,101 @@ export const getLocalShielderActivityHistoryIndexedDB = (accountAddress: string)
167239 return raw ? fromActivityHistoryStorageFormat ( raw ) : null ;
168240 } catch ( error ) {
169241 console . error ( 'Failed to get activity history from IndexedDB:' , error ) ;
170- return null ;
242+ try {
243+ const db = await handleDatabaseError ( error ) ;
244+ const allChains = await db . get ( STORE_LOCAL_SHIELDER_ACTIVITY_HISTORY , accountAddress ) ;
245+ const raw = allChains ?. [ chainId . toString ( ) ] ;
246+ return raw ? fromActivityHistoryStorageFormat ( raw ) : null ;
247+ } catch ( retryError ) {
248+ console . error ( 'Failed to get activity history from IndexedDB after retry:' , retryError ) ;
249+ return null ;
250+ }
171251 }
172252 } ,
173253 upsertItem : async ( chainId : number , activity : PartialLocalShielderActivityHistory ) => {
174- const db = await dbp ;
175- const chainKey = chainId . toString ( ) ;
176- const allChains = ( await db . get ( STORE_LOCAL_SHIELDER_ACTIVITY_HISTORY , accountAddress ) ) ?? { } ;
177- const existingRaw = allChains [ chainKey ] ;
178- const existing = existingRaw ? fromActivityHistoryStorageFormat ( existingRaw ) : [ ] ;
254+ try {
255+ const db = await dbp ;
256+ const chainKey = chainId . toString ( ) ;
257+ const allChains = ( await db . get ( STORE_LOCAL_SHIELDER_ACTIVITY_HISTORY , accountAddress ) ) ?? { } ;
258+ const existingRaw = allChains [ chainKey ] ;
259+ const existing = existingRaw ? fromActivityHistoryStorageFormat ( existingRaw ) : [ ] ;
179260
180- const isSame = ( a : PartialLocalShielderActivityHistory , b : PartialLocalShielderActivityHistory ) =>
181- ( isPresent ( a . localId ) && a . localId === b . localId ) ||
182- ( isPresent ( a . txHash ) && a . txHash === b . txHash ) ;
261+ const isSame = ( a : PartialLocalShielderActivityHistory , b : PartialLocalShielderActivityHistory ) =>
262+ ( isPresent ( a . localId ) && a . localId === b . localId ) ||
263+ ( isPresent ( a . txHash ) && a . txHash === b . txHash ) ;
183264
184- const matches = existing . filter ( item => isSame ( item , activity ) ) ;
185- const rest = existing . filter ( item => ! isSame ( item , activity ) ) ;
265+ const matches = existing . filter ( item => isSame ( item , activity ) ) ;
266+ const rest = existing . filter ( item => ! isSame ( item , activity ) ) ;
186267
187- const localOnly = matches . find ( i => isPresent ( i . localId ) && ! isPresent ( i . txHash ) ) ?? { } ;
188- const withTxHash = matches . find ( i => isPresent ( i . txHash ) ) ?? { } ;
268+ const localOnly = matches . find ( i => isPresent ( i . localId ) && ! isPresent ( i . txHash ) ) ?? { } ;
269+ const withTxHash = matches . find ( i => isPresent ( i . txHash ) ) ?? { } ;
189270
190- const merged = {
191- ...localOnly ,
192- ...withTxHash ,
193- ...activity ,
194- } ;
271+ const merged = {
272+ ...localOnly ,
273+ ...withTxHash ,
274+ ...activity ,
275+ } ;
195276
196- const updated : LocalShielderActivityHistoryArray = [ ...rest , merged ] ;
277+ const updated : LocalShielderActivityHistoryArray = [ ...rest , merged ] ;
197278
198- await db . put (
199- STORE_LOCAL_SHIELDER_ACTIVITY_HISTORY ,
200- { ...allChains , [ chainKey ] : toActivityHistoryStorageFormat ( updated ) } ,
201- accountAddress ,
202- ) ;
279+ await db . put (
280+ STORE_LOCAL_SHIELDER_ACTIVITY_HISTORY ,
281+ { ...allChains , [ chainKey ] : toActivityHistoryStorageFormat ( updated ) } ,
282+ accountAddress ,
283+ ) ;
203284
204- return merged ;
285+ return merged ;
286+ } catch ( error ) {
287+ console . error ( 'Failed to upsert activity history in IndexedDB:' , error ) ;
288+ try {
289+ const db = await handleDatabaseError ( error ) ;
290+ const chainKey = chainId . toString ( ) ;
291+ const allChains = ( await db . get ( STORE_LOCAL_SHIELDER_ACTIVITY_HISTORY , accountAddress ) ) ?? { } ;
292+ const existingRaw = allChains [ chainKey ] ;
293+ const existing = existingRaw ? fromActivityHistoryStorageFormat ( existingRaw ) : [ ] ;
294+
295+ const isSame = ( a : PartialLocalShielderActivityHistory , b : PartialLocalShielderActivityHistory ) =>
296+ ( isPresent ( a . localId ) && a . localId === b . localId ) ||
297+ ( isPresent ( a . txHash ) && a . txHash === b . txHash ) ;
298+
299+ const matches = existing . filter ( item => isSame ( item , activity ) ) ;
300+ const rest = existing . filter ( item => ! isSame ( item , activity ) ) ;
301+
302+ const localOnly = matches . find ( i => isPresent ( i . localId ) && ! isPresent ( i . txHash ) ) ?? { } ;
303+ const withTxHash = matches . find ( i => isPresent ( i . txHash ) ) ?? { } ;
304+
305+ const merged = {
306+ ...localOnly ,
307+ ...withTxHash ,
308+ ...activity ,
309+ } ;
310+
311+ const updated : LocalShielderActivityHistoryArray = [ ...rest , merged ] ;
312+
313+ await db . put (
314+ STORE_LOCAL_SHIELDER_ACTIVITY_HISTORY ,
315+ { ...allChains , [ chainKey ] : toActivityHistoryStorageFormat ( updated ) } ,
316+ accountAddress ,
317+ ) ;
318+
319+ return merged ;
320+ } catch ( retryError ) {
321+ console . error ( 'Failed to upsert activity history in IndexedDB after retry:' , retryError ) ;
322+ throw retryError ;
323+ }
324+ }
205325 } ,
206326 } ;
207327} ;
208328
209329export const clearShielderIndexedDB = async ( ) : Promise < void > => {
210330 try {
211- const db = await openDB ( DB_NAME , DB_VERSION ) ;
331+ const db = await initDB ( ) ;
212332 await Promise . all ( [ db . clear ( STORE_CLIENTS ) , db . clear ( STORE_LOCAL_SHIELDER_ACTIVITY_HISTORY ) ] ) ;
333+ resetDBInstance ( ) ; // Reset instance after clearing to ensure fresh state
213334 } catch ( error ) {
214335 console . error ( 'Failed to clear IndexedDB:' , error ) ;
336+ resetDBInstance ( ) ; // Reset instance on error as well
215337 throw error ;
216338 }
217339} ;
0 commit comments