Skip to content

Instantly share code, notes, and snippets.

@codinronan
Created May 5, 2020 20:48
Show Gist options
  • Save codinronan/89d764b42d70aff6fa8e1c601f3092d2 to your computer and use it in GitHub Desktop.
Save codinronan/89d764b42d70aff6fa8e1c601f3092d2 to your computer and use it in GitHub Desktop.
idb-keyval temporary improvements
// This is a private copy of idb-keyval vNext, which has a simpler API with many bug fixes.
// https://raw.githubusercontent.com/jakearchibald/idb-keyval/next/src/index.ts
// https://github.com/jakearchibald/idb-keyval/issues/80 (description of API)
// The old implementation with bug fixes from several PRs in idb-keyval is at
// https://github.com/DestinyItemManager/DIM/blob/master/src/app/storage/idb-keyval.ts
// https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB#Version_changes_while_a_web_app_is_open_in_another_tab
const DB_NAME = 'rg-app-data'; // 'keyval-store'
const DB_STORE = 'rg-data'; // 'keyval'
let defaultGetStoreFunc:
| ((txMode: IDBTransactionMode) => Promise<IDBObjectStore>)
| undefined;
let indexedDBSupported = false;
let _close = () => undefined;
export function checkSupported(): Promise<boolean> {
if (indexedDBSupported) {
return Promise.resolve(true);
}
(window as any).indexedDB =
window.indexedDB || (window as any).mozIndexedDB || (window as any).webkitIndexedDB || (window as any).msIndexedDB;
window.IDBTransaction = window.IDBTransaction || (window as any).webkitIDBTransaction || (window as any).msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || (window as any).webkitIDBKeyRange || (window as any).msIDBKeyRange;
const isSupported = !!window.indexedDB && !!window.IDBTransaction && !!window.IDBKeyRange;
if (isSupported) {
const request = indexedDB.open(DB_NAME, 1);
const dbp = promisifyRequest<IDBDatabase>(request);
return dbp.then(() => { indexedDBSupported = true; return true; }).catch(() => false);
}
return Promise.resolve(false);
}
function promisifyRequest<T = undefined>(
request: IDBRequest<T> | IDBTransaction,
): Promise<T> {
return new Promise<T>((resolve, reject) => {
// @ts-ignore - file size hacks
request.oncomplete = request.onsuccess = () => resolve(request.result);
// @ts-ignore - file size hacks
request.onabort = request.onerror = () => reject(request.error);
});
}
function useDatabase(db: IDBDatabase) {
_close = () => {
db.close();
defaultGetStoreFunc = null; // we must null this so that a new open() call occurs.
};
// Make sure to add a handler to be notified if another page requests a version
// change. We must close the database. This allows the other page to upgrade the database.
// If you don't do this then the upgrade won't happen until the user closes the tab.
db.onversionchange = function () {
_close();
console.log('A new version of this page is ready. Please reload or close this tab!');
};
db.onclose = () => (defaultGetStoreFunc = null);
}
export function createStore(dbName: string, storeName: string) {
const request = indexedDB.open(dbName, 1);
request.onupgradeneeded = () => {
request.result.createObjectStore(storeName);
useDatabase(request.result);
};
request.onblocked = function () {
// If some other tab is loaded with the database, then it needs to be closed before we can proceed.
console.log('Please close all other tabs with this site open!');
};
const dbp = promisifyRequest<IDBDatabase>(request);
dbp.then(useDatabase);
return (txMode?: IDBTransactionMode) =>
dbp.then((db) => db.transaction(storeName, txMode).objectStore(storeName));
}
function defaultGetStore() {
if (!defaultGetStoreFunc) {
defaultGetStoreFunc = createStore(DB_NAME, DB_STORE);
}
return defaultGetStoreFunc;
}
export function get<T = any>(
key: IDBValidKey,
getStore = defaultGetStore(),
): Promise<T> {
return getStore('readonly').then((store) => promisifyRequest(store.get(key)));
}
export function set(
key: IDBValidKey,
value: any,
getStore = defaultGetStore(),
): Promise<void> {
return getStore('readwrite').then((store) => {
store.put(value, key);
return promisifyRequest(store.transaction);
});
}
export function del(
key: IDBValidKey,
getStore = defaultGetStore(),
): Promise<void> {
return getStore('readwrite').then((store) => {
store.delete(key);
return promisifyRequest(store.transaction);
});
}
export function clear(getStore = defaultGetStore()): Promise<void> {
return getStore('readwrite').then((store) => {
store.clear();
return promisifyRequest(store.transaction);
});
}
export function keys(getStore = defaultGetStore()): Promise<IDBValidKey[]> {
const keyset: IDBValidKey[] = [];
return getStore('readonly')
.then((store) => {
// This would be store.getAllKeys(), but it isn't supported by Edge or Safari.
// And openKeyCursor isn't supported by Safari.
(store.openKeyCursor || store.openCursor).call(
store,
).onsuccess = function () {
if (!this.result) { return; }
keyset.push(this.result.key);
this.result.continue();
};
return promisifyRequest(store.transaction);
})
.then(() => keyset);
}
export function close(): Promise<void> {
return _close();
}
// When the app gets frozen (iOS PWA), close the IDBDatabase connection
window.addEventListener('freeze', () => {
close();
});
checkSupported();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment