From e67f6e5807a0496d3ff7231eb68163f80e499cfa Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Sat, 10 Jul 2021 18:00:16 -0500 Subject: [PATCH] Refactor IndexedDB --- src/packager/cache.js | 88 ++++++----------------------------------- src/packager/lib/idb.js | 81 +++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 77 deletions(-) create mode 100644 src/packager/lib/idb.js diff --git a/src/packager/cache.js b/src/packager/cache.js index 5ea2858..16f2385 100644 --- a/src/packager/cache.js +++ b/src/packager/cache.js @@ -1,90 +1,23 @@ import largeAssets from './large-assets'; import {buildId} from './lib/build-id'; +import Database from './lib/idb'; // We can't trust the HTTP cache to reliably cache these large assets const DATABASE_NAME = 'p4-large-assets'; const DATABASE_VERSION = 1; - const STORE_NAME = 'assets'; -let _db; +const db = new Database(DATABASE_NAME, DATABASE_VERSION, STORE_NAME); const getAssetId = (asset) => { return `${asset.src}-${asset.type}-${asset.sha256 || buildId}`; }; -// 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 openDB = async () => { - if (_db) { - return _db; - } - if (!window.indexedDB) { - throw new Error('indexedDB is not supported'); - } - await idbReady(); - return new Promise((resolve, reject) => { - const request = indexedDB.open(DATABASE_NAME, DATABASE_VERSION); - request.onupgradeneeded = e => { - const db = e.target.result; - db.createObjectStore(STORE_NAME, { - keyPath: 'id' - }); - }; - request.onsuccess = (e) => { - _db = e.target.result; - removeExtraneous() - .then(() => { - resolve(_db); - }) - .catch((err) => { - reject(err); - }); - }; - request.onerror = (e) => { - reject(new Error(`IDB Error ${e.target.error}`)); - }; - }); -}; - -const createTransaction = async (readwrite) => { - const db = await openDB(); - const transaction = db.transaction(STORE_NAME, readwrite); - const store = transaction.objectStore(STORE_NAME); - return { - db, - transaction, - store - }; -}; - -const setTransactionErrorHandler = (transaction, reject) => { - transaction.onerror = () => { - reject(new Error(`Transaction error: ${transaction.error}`)) - }; -}; - const removeExtraneous = async () => { - const {transaction, store} = await createTransaction('readwrite'); + const {transaction, store} = await db.createTransaction('readwrite'); return new Promise((resolve, reject) => { - setTransactionErrorHandler(transaction, reject); + Database.setTransactionErrorHandler(transaction, reject); const allValidAssetIds = Object.values(largeAssets).map(getAssetId); const request = store.openCursor(); request.onsuccess = e => { @@ -101,11 +34,12 @@ const removeExtraneous = async () => { }; }); }; +db.onopen = removeExtraneous; const get = async (asset) => { - const {transaction, store} = await createTransaction('readonly'); + const {transaction, store} = await db.createTransaction('readonly'); return new Promise((resolve, reject) => { - setTransactionErrorHandler(transaction, reject); + Database.setTransactionErrorHandler(transaction, reject); const assetId = getAssetId(asset); const request = store.get(assetId); request.onsuccess = (e) => { @@ -120,9 +54,9 @@ const get = async (asset) => { }; const set = async (asset, content) => { - const {transaction, store} = await createTransaction('readwrite'); + const {transaction, store} = await db.createTransaction('readwrite'); return new Promise((resolve, reject) => { - setTransactionErrorHandler(transaction, reject); + Database.setTransactionErrorHandler(transaction, reject); const assetId = getAssetId(asset); const request = store.put({ id: assetId, @@ -135,9 +69,9 @@ const set = async (asset, content) => { }; const resetAll = async () => { - const {transaction, store} = await createTransaction('readwrite'); + const {transaction, store} = await db.createTransaction('readwrite'); return new Promise((resolve, reject) => { - setTransactionErrorHandler(transaction, reject); + Database.setTransactionErrorHandler(transaction, reject); const request = store.openCursor(); request.onsuccess = e => { const cursor = e.target.result; diff --git a/src/packager/lib/idb.js b/src/packager/lib/idb.js new file mode 100644 index 0000000..2878af9 --- /dev/null +++ b/src/packager/lib/idb.js @@ -0,0 +1,81 @@ +// 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)); +}; + +class Database { + constructor (name, version, storeName) { + this.name = name; + this.version = version; + this.storeName = storeName; + this.db = null; + } + + async open () { + if (this.db) { + return this.db; + } + if (!window.indexedDB) { + throw new Error('indexedDB is not supported'); + } + await idbReady(); + return 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) => { + this.db = e.target.result; + this.onopen() + .then(() => { + resolve(this.db); + }) + .catch((err) => { + reject(err); + }); + }; + request.onerror = (e) => { + reject(new Error(`IDB Error ${e.target.error}`)); + }; + }); + } + + 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 + }; + } + + onopen () { + return Promise.resolve(); + } +} + +Database.setTransactionErrorHandler = (transaction, reject) => { + transaction.onerror = () => { + reject(new Error(`Transaction error: ${transaction.error}`)) + }; +}; + +export default Database;