Spaces:
Sleeping
Sleeping
// https://github.com/jakearchibald/safari-14-idb-fix/blob/582bbdc7230891113bfb5743391550cbf29d21f2/src/index.ts | |
const idbReady = () => { | |
const isSafari = | |
!navigator.userAgentData && | |
/Safari\//.test(navigator.userAgent) && | |
!/Chrom(e|ium)\//.test(navigator.userAgent); | |
// No point putting other browsers or older versions of Safari through this mess. | |
if (!isSafari || !indexedDB.databases) return Promise.resolve(); | |
let intervalId; | |
return new Promise((resolve) => { | |
const tryIdb = () => indexedDB.databases().finally(resolve); | |
intervalId = setInterval(tryIdb, 100); | |
tryIdb(); | |
}).finally(() => clearInterval(intervalId)); | |
}; | |
const allDatabases = []; | |
class Database { | |
constructor (name, version, storeName) { | |
this.name = name; | |
this.version = version; | |
this.storeName = storeName; | |
this.db = null; | |
this.dbPromise = null; | |
allDatabases.push(this); | |
} | |
open () { | |
if (this.db) { | |
return this.db; | |
} | |
if (this.dbPromise) { | |
return this.dbPromise; | |
} | |
if (typeof indexedDB === 'undefined') { | |
throw new Error('indexedDB is not supported'); | |
} | |
this.dbPromise = idbReady() | |
.then(() => new Promise((resolve, reject) => { | |
const request = indexedDB.open(this.name, this.version); | |
request.onupgradeneeded = (e) => { | |
const db = e.target.result; | |
db.createObjectStore(this.storeName, { | |
keyPath: 'id' | |
}); | |
}; | |
request.onsuccess = (e) => { | |
const db = e.target.result; | |
resolve(db); | |
}; | |
request.onerror = (e) => { | |
reject(new Error(`IDB Error ${e.target.error}`)); | |
}; | |
})) | |
.then((db) => { | |
this.dbPromise = null; | |
this.db = db; | |
return db; | |
}) | |
.catch((err) => { | |
this.dbPromise = null; | |
throw err; | |
}); | |
return this.dbPromise; | |
} | |
close () { | |
if (this.db) { | |
this.db.close(); | |
this.db = null; | |
} | |
if (this.dbPromise) { | |
this.dbPromise.then((db) => { | |
db.close(); | |
}); | |
this.dbPromise = null; | |
} | |
} | |
async createTransaction (readwrite) { | |
const db = await this.open(); | |
const transaction = db.transaction(this.storeName, readwrite); | |
const store = transaction.objectStore(this.storeName); | |
return { | |
db, | |
transaction, | |
store | |
}; | |
} | |
async deleteEverything () { | |
const {transaction, store} = await this.createTransaction('readwrite'); | |
return new Promise((resolve, reject) => { | |
Database.setTransactionErrorHandler(transaction, reject); | |
const request = store.clear(); | |
request.onsuccess = () => { | |
resolve(); | |
}; | |
}); | |
} | |
} | |
Database.setTransactionErrorHandler = (transaction, reject) => { | |
transaction.onerror = () => { | |
reject(new Error(`Transaction error: ${transaction.error}`)) | |
}; | |
}; | |
const closeAllDatabases = () => { | |
for (const database of allDatabases) { | |
database.close(); | |
} | |
}; | |
// Closing databases makes us more likely to be put in the browser's back/forward cache | |
window.addEventListener('pagehide', closeAllDatabases); | |
export default Database; | |