/ proof-of-concept / midiboot
Sign in
proof-of-concept / midiboot
Do not hang on a blocked IndexedDB upgrade
The version bump to 2 waits forever if another tab holds the old version. Reject on blocked, yield to future upgrades via onversionchange, retry after a failed open, and fall back to an in-memory session with a status message instead of the loading screen.
Jeremy Magland <jmagland@flatironinstitute.org> committed commit fa5c12ee3edb parent 0557e3b Browse files
2 changed files+27−4
src/state/store.tsmodified+11−1View file
@@ -86,7 +86,17 @@ class Store {
8686 }
8787
8888 async init(): Promise<void> {
89- const songs = await db.listSongs();
89+ let songs: SongMeta[] = [];
90+ try {
91+ songs = await db.listSongs();
92+ } catch (err) {
93+ // IndexedDB unavailable or blocked: run in memory rather than
94+ // sitting on the loading screen; edits will not persist.
95+ const rec = this.freshRecord('Untitled song');
96+ this.set({ ready: true, songs: [], rec, activeTrackId: rec.song.tracks[0]?.id ?? null,
97+ status: `storage unavailable (${err instanceof Error ? err.message : String(err)}); changes will not be saved` });
98+ return;
99+ }
90100 let rec: SongRecord | null = null;
91101 try {
92102 const last = localStorage.getItem(LAST_SONG_KEY);
src/storage/db.tsmodified+16−3View file
@@ -11,17 +11,30 @@ let dbPromise: Promise<IDBDatabase> | null = null;
1111
1212 function openDb(): Promise<IDBDatabase> {
1313 if (dbPromise) return dbPromise;
14- dbPromise = new Promise((resolve, reject) => {
14+ const attempt: Promise<IDBDatabase> = new Promise((resolve, reject) => {
1515 const req = indexedDB.open(DB_NAME, 2);
1616 req.onupgradeneeded = () => {
1717 const db = req.result;
1818 if (!db.objectStoreNames.contains(STORE)) db.createObjectStore(STORE, { keyPath: 'id' });
1919 if (!db.objectStoreNames.contains(INSTRUMENTS)) db.createObjectStore(INSTRUMENTS, { keyPath: 'id' });
2020 };
21- req.onsuccess = () => resolve(req.result);
21+ // A tab holding an older version blocks the upgrade indefinitely;
22+ // surface that instead of hanging behind the loading screen.
23+ req.onblocked = () => reject(new Error('database upgrade blocked; close other midiboot tabs and reload'));
24+ req.onsuccess = () => {
25+ // Yield to a future upgrade (a newer tab) instead of blocking it.
26+ req.result.onversionchange = () => req.result.close();
27+ resolve(req.result);
28+ };
2229 req.onerror = () => reject(req.error);
2330 });
24- return dbPromise;
31+ // Do not cache a failure: once a blocking tab closes, the next call
32+ // should get to try again.
33+ dbPromise = attempt;
34+ attempt.catch(() => {
35+ if (dbPromise === attempt) dbPromise = null;
36+ });
37+ return attempt;
2538 }
2639
2740 function txDone(tx: IDBTransaction): Promise<void> {
↑↓ move↵openescclose