11import * as IDB from 'idb-keyval' ;
22import type { UseStore } from 'idb-keyval' ;
3- import { logInfo } from '../../../Logger' ;
3+ import { logAlert , logInfo } from '../../../Logger' ;
44
55// This is a copy of the createStore function from idb-keyval, we need a custom implementation
66// because we need to create the database manually in order to ensure that the store exists before we use it.
77// If the store does not exist, idb-keyval will throw an error
88// source: https://github.com/jakearchibald/idb-keyval/blob/9d19315b4a83897df1e0193dccdc29f78466a0f3/src/index.ts#L12
99function createStore ( dbName : string , storeName : string ) : UseStore {
1010 let dbp : Promise < IDBDatabase > | undefined ;
11+ let closedBy : 'browser' | 'versionchange' | 'verifyStoreExists' | 'unknown' = 'unknown' ;
12+
13+ const attachHandlers = ( db : IDBDatabase ) => {
14+ // It seems like Safari sometimes likes to just close the connection.
15+ // It's supposed to fire this event when that happens. Let's hope it does!
16+ // eslint-disable-next-line no-param-reassign
17+ db . onclose = ( ) => {
18+ logInfo ( 'IDB connection closed by browser' , { dbName, storeName} ) ;
19+ closedBy = 'browser' ;
20+ dbp = undefined ;
21+ } ;
22+
23+ // When another tab triggers a DB version upgrade, we must close the connection
24+ // to unblock the upgrade; otherwise the other tab's open request hangs indefinitely.
25+ // https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/versionchange_event
26+ // eslint-disable-next-line no-param-reassign
27+ db . onversionchange = ( ) => {
28+ logInfo ( 'IDB connection closing due to versionchange' , { dbName, storeName} ) ;
29+ closedBy = 'versionchange' ;
30+ db . close ( ) ;
31+ dbp = undefined ;
32+ } ;
33+ } ;
34+
1135 const getDB = ( ) => {
1236 if ( dbp ) return dbp ;
1337 const request = indexedDB . open ( dbName ) ;
1438 request . onupgradeneeded = ( ) => request . result . createObjectStore ( storeName ) ;
1539 dbp = IDB . promisifyRequest ( request ) ;
1640
1741 dbp . then (
18- ( db ) => {
19- // It seems like Safari sometimes likes to just close the connection.
20- // It's supposed to fire this event when that happens. Let's hope it does!
21- // eslint-disable-next-line no-param-reassign
22- db . onclose = ( ) => ( dbp = undefined ) ;
23- } ,
42+ attachHandlers ,
2443 // eslint-disable-next-line @typescript-eslint/no-empty-function
2544 ( ) => { } ,
2645 ) ;
@@ -36,6 +55,7 @@ function createStore(dbName: string, storeName: string): UseStore {
3655
3756 logInfo ( `Store ${ storeName } does not exist in database ${ dbName } .` ) ;
3857 const nextVersion = db . version + 1 ;
58+ closedBy = 'verifyStoreExists' ;
3959 db . close ( ) ;
4060
4161 const request = indexedDB . open ( dbName , nextVersion ) ;
@@ -50,13 +70,34 @@ function createStore(dbName: string, storeName: string): UseStore {
5070 } ;
5171
5272 dbp = IDB . promisifyRequest ( request ) ;
73+ // eslint-disable-next-line @typescript-eslint/no-empty-function
74+ dbp . then ( attachHandlers , ( ) => { } ) ;
5375 return dbp ;
5476 } ;
5577
56- return ( txMode , callback ) =>
57- getDB ( )
78+ function executeTransaction < T > ( txMode : IDBTransactionMode , callback : ( store : IDBObjectStore ) => T | PromiseLike < T > ) : Promise < T > {
79+ return getDB ( )
5880 . then ( verifyStoreExists )
5981 . then ( ( db ) => callback ( db . transaction ( storeName , txMode ) . objectStore ( storeName ) ) ) ;
82+ }
83+
84+ return ( txMode , callback ) =>
85+ executeTransaction ( txMode , callback ) . catch ( ( error ) => {
86+ if ( error instanceof DOMException && error . name === 'InvalidStateError' ) {
87+ logAlert ( 'IDB InvalidStateError, retrying with fresh connection' , {
88+ dbName,
89+ storeName,
90+ txMode,
91+ closedBy,
92+ errorMessage : error . message ,
93+ } ) ;
94+ dbp = undefined ;
95+ closedBy = 'unknown' ;
96+ // Retry only once — this call is not wrapped, so if it also fails the error propagates normally.
97+ return executeTransaction ( txMode , callback ) ;
98+ }
99+ throw error ;
100+ } ) ;
60101}
61102
62103export default createStore ;
0 commit comments