fix(app): IDB invalid state error on mobile safari (#8)

This commit is contained in:
hampus-fluxer 2026-01-03 14:55:09 +01:00 committed by GitHub
parent 84b6bb2a5b
commit 75f9afc843
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -325,6 +325,15 @@ class AccountStorage {
return name === 'DataCloneError';
}
private isInvalidStateError(error: unknown): boolean {
if (!error || typeof error !== 'object') {
return false;
}
const name = (error as {name?: unknown}).name;
return name === 'InvalidStateError';
}
async stashAccountData(
userId: string,
token: string | null,
@ -388,6 +397,13 @@ class AccountStorage {
return;
}
if (this.isInvalidStateError(err)) {
logger.warn(`InvalidStateError (database closing) while stashing account ${userId}; using memory store`, err);
this.memoryStore.set(userId, safeRecord);
logger.debug(`Stashed account data for ${userId} (memory fallback after InvalidStateError)`);
return;
}
logger.error(`Failed to stash account data for ${userId}`, err);
throw err;
}
@ -523,17 +539,26 @@ class AccountStorage {
return this.memoryStore.get(userId) ?? null;
}
return await withTimeout(
new Promise<StoredAccount | null>((resolve, reject) => {
const tx = this.db!.transaction([STORE_NAME], 'readonly');
const store = tx.objectStore(STORE_NAME);
const req = store.get(userId);
req.onsuccess = () => resolve((req.result as StoredAccount | undefined) ?? null);
req.onerror = () => reject(req.error ?? new Error('IndexedDB get failed'));
}),
5000,
'IndexedDB get account',
);
try {
return await withTimeout(
new Promise<StoredAccount | null>((resolve, reject) => {
const tx = this.db!.transaction([STORE_NAME], 'readonly');
const store = tx.objectStore(STORE_NAME);
const req = store.get(userId);
req.onsuccess = () => resolve((req.result as StoredAccount | undefined) ?? null);
req.onerror = () => reject(req.error ?? new Error('IndexedDB get failed'));
}),
5000,
'IndexedDB get account',
);
} catch (err) {
if (this.isInvalidStateError(err)) {
logger.warn(`InvalidStateError (database closing) in getRecord for ${userId}; using memory store`, err);
return this.memoryStore.get(userId) ?? null;
}
throw err;
}
}
private async putRecord(record: StoredAccount): Promise<void> {
@ -544,19 +569,29 @@ class AccountStorage {
return;
}
await withTimeout(
new Promise<void>((resolve, reject) => {
const tx = this.db!.transaction([STORE_NAME], 'readwrite');
const store = tx.objectStore(STORE_NAME);
const req = store.put(normalized);
req.onsuccess = () => resolve();
req.onerror = () => reject(req.error ?? new Error('IndexedDB put failed'));
}),
5000,
'IndexedDB put account record',
);
try {
await withTimeout(
new Promise<void>((resolve, reject) => {
const tx = this.db!.transaction([STORE_NAME], 'readwrite');
const store = tx.objectStore(STORE_NAME);
const req = store.put(normalized);
req.onsuccess = () => resolve();
req.onerror = () => reject(req.error ?? new Error('IndexedDB put failed'));
}),
5000,
'IndexedDB put account record',
);
this.memoryStore.set(record.userId, normalized);
this.memoryStore.set(record.userId, normalized);
} catch (err) {
if (this.isInvalidStateError(err)) {
logger.warn(`InvalidStateError (database closing) in putRecord for ${record.userId}; using memory store`, err);
this.memoryStore.set(record.userId, normalized);
return;
}
throw err;
}
}
private async updateLastActive(userId: string): Promise<void> {