Skip to content

Commit 2946a6f

Browse files
committed
fix the database connection is closing issue.
Signed-off-by: krishna2323 <belivethatkg@gmail.com>
1 parent 634a309 commit 2946a6f

2 files changed

Lines changed: 370 additions & 9 deletions

File tree

lib/storage/providers/IDBKeyValProvider/createStore.ts

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,45 @@
11
import * as IDB from 'idb-keyval';
22
import 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
99
function 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

62103
export default createStore;

0 commit comments

Comments
 (0)