fix(idb-cleanup): swallow TransactionInactiveError in idempotent IDB cursor loops (#3335)

Sentry WORLDMONITOR-NX: iOS Safari kills in-flight IDB transactions when
the tab backgrounds. Our idle detector fires `[App] User idle - pausing
animations to save resources` right before the browser suspends — any
`cursor.delete()` / `cursor.continue()` mid-iteration then throws
TransactionInactiveError synchronously inside onsuccess.

Both affected sites are idempotent cleanup (`cleanOldSnapshots`,
`deleteFromIndexedDbByPrefix`); swallowing the throw lets the next run
resume from where we left off. `main.ts` beforeSend keeps TransactionInactiveError
surfaced for first-party stacks (storage.ts, persistent-cache.ts, vector-db.ts),
so this is the correct layer to handle the background-kill case.
This commit is contained in:
Elie Habib
2026-04-23 11:47:20 +04:00
committed by GitHub
parent 8b12ecdf43
commit dd95a4e06d
2 changed files with 11 additions and 4 deletions

View File

@@ -82,9 +82,13 @@ async function deleteFromIndexedDbByPrefix(prefix: string): Promise<void> {
request.onsuccess = () => {
const cursor = request.result;
if (!cursor) return;
store.delete(cursor.primaryKey);
cursor.continue();
// iOS Safari kills in-flight IDB transactions when the tab backgrounds;
// prefix-invalidation is idempotent so swallow TransactionInactiveError
// and let the next invalidation call resume.
try {
store.delete(cursor.primaryKey);
cursor.continue();
} catch { /* tx died mid-iteration */ }
};
request.onerror = () => reject(request.error);
});

View File

@@ -214,7 +214,10 @@ export async function cleanOldSnapshots(): Promise<void> {
const request = store.index('by_time').openCursor(IDBKeyRange.upperBound(cutoff));
request.onsuccess = () => {
const cursor = request.result;
if (cursor) { cursor.delete(); cursor.continue(); }
if (!cursor) return;
// iOS Safari kills in-flight IDB transactions when the tab backgrounds;
// cleanup is idempotent so swallow TransactionInactiveError and resume next run.
try { cursor.delete(); cursor.continue(); } catch { /* tx died mid-cleanup */ }
};
void tx;
},