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'; 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( async stashAccountData(
userId: string, userId: string,
token: string | null, token: string | null,
@ -388,6 +397,13 @@ class AccountStorage {
return; 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); logger.error(`Failed to stash account data for ${userId}`, err);
throw err; throw err;
} }
@ -523,17 +539,26 @@ class AccountStorage {
return this.memoryStore.get(userId) ?? null; return this.memoryStore.get(userId) ?? null;
} }
return await withTimeout( try {
new Promise<StoredAccount | null>((resolve, reject) => { return await withTimeout(
const tx = this.db!.transaction([STORE_NAME], 'readonly'); new Promise<StoredAccount | null>((resolve, reject) => {
const store = tx.objectStore(STORE_NAME); const tx = this.db!.transaction([STORE_NAME], 'readonly');
const req = store.get(userId); const store = tx.objectStore(STORE_NAME);
req.onsuccess = () => resolve((req.result as StoredAccount | undefined) ?? null); const req = store.get(userId);
req.onerror = () => reject(req.error ?? new Error('IndexedDB get failed')); req.onsuccess = () => resolve((req.result as StoredAccount | undefined) ?? null);
}), req.onerror = () => reject(req.error ?? new Error('IndexedDB get failed'));
5000, }),
'IndexedDB get account', 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> { private async putRecord(record: StoredAccount): Promise<void> {
@ -544,19 +569,29 @@ class AccountStorage {
return; return;
} }
await withTimeout( try {
new Promise<void>((resolve, reject) => { await withTimeout(
const tx = this.db!.transaction([STORE_NAME], 'readwrite'); new Promise<void>((resolve, reject) => {
const store = tx.objectStore(STORE_NAME); const tx = this.db!.transaction([STORE_NAME], 'readwrite');
const req = store.put(normalized); const store = tx.objectStore(STORE_NAME);
req.onsuccess = () => resolve(); const req = store.put(normalized);
req.onerror = () => reject(req.error ?? new Error('IndexedDB put failed')); req.onsuccess = () => resolve();
}), req.onerror = () => reject(req.error ?? new Error('IndexedDB put failed'));
5000, }),
'IndexedDB put account record', 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> { private async updateLastActive(userId: string): Promise<void> {