diff --git a/routes/_utils/database/cache.js b/routes/_utils/database/cache.js new file mode 100644 index 0000000..52b358f --- /dev/null +++ b/routes/_utils/database/cache.js @@ -0,0 +1,2 @@ +export const openReqs = {} +export const databaseCache = {} \ No newline at end of file diff --git a/routes/_utils/database/database.js b/routes/_utils/database/database.js index ca06ed3..99725c2 100644 --- a/routes/_utils/database/database.js +++ b/routes/_utils/database/database.js @@ -1,9 +1,5 @@ -import worker from 'workerize-loader!./databaseInsideWorker' - -import * as databaseInsideWorker from './databaseInsideWorker' - -// workerize-loader causes weirdness during development -let database = process.browser && process.env.NODE_ENV === 'production' ? worker() : databaseInsideWorker +import worker from 'workerize-loader!./databaseCore' +const database = process.browser && worker() export { database diff --git a/routes/_utils/database/databaseCore.js b/routes/_utils/database/databaseCore.js new file mode 100644 index 0000000..106d44b --- /dev/null +++ b/routes/_utils/database/databaseCore.js @@ -0,0 +1,62 @@ +import { META_STORE, getMetaDatabase } from './meta' +import { cleanupOldStatuses } from './cleanupTimelines' +import { TIMELINE_STORE, getTimelineDatabase } from './timelines' +import { toReversePaddedBigInt, transformStatusForStorage, dbPromise, deleteDbPromise } from './utils' +import { getKnownDbsForInstance, deleteInstanceFromKnownDbs } from './knownDbs' + +export async function getTimeline(instanceName, timeline, maxId = null, limit = 20) { + const db = await getTimelineDatabase(instanceName, timeline) + return await dbPromise(db, TIMELINE_STORE, 'readonly', (store, callback) => { + const index = store.index('pinafore_id_as_negative_big_int') + let sinceAsNegativeBigInt = maxId ? toReversePaddedBigInt(maxId) : null + let query = sinceAsNegativeBigInt ? IDBKeyRange.lowerBound(sinceAsNegativeBigInt, false) : null + + index.getAll(query, limit).onsuccess = (e) => { + callback(e.target.result) + } + }) +} + +export async function insertStatuses(instanceName, timeline, statuses) { + const db = await getTimelineDatabase(instanceName, timeline) + await dbPromise(db, TIMELINE_STORE, 'readwrite', (store) => { + for (let status of statuses) { + store.put(transformStatusForStorage(status)) + } + }) + /* no await */ cleanupOldStatuses() +} + +export async function getInstanceVerifyCredentials(instanceName) { + const db = await getMetaDatabase(instanceName) + return await dbPromise(db, META_STORE, 'readonly', (store, callback) => { + store.get('verifyCredentials').onsuccess = (e) => { + callback(e.target.result && e.target.result.value) + } + }) +} + +export async function setInstanceVerifyCredentials(instanceName, verifyCredentials) { + const db = await getMetaDatabase(instanceName) + return await dbPromise(db, META_STORE, 'readwrite', (store) => { + store.put({ + key: 'verifyCredentials', + value: verifyCredentials + }) + }) +} + +export async function clearDatabasesForInstance(instanceName) { + console.log('clearDatabasesForInstance', instanceName) + const knownDbsForInstance = await getKnownDbsForInstance(instanceName) + for (let knownDb of knownDbsForInstance) { + let { dbName } = knownDb + try { + await deleteDbPromise(dbName) + console.error(`deleted database ${dbName}`) + } catch (e) { + console.error(`failed to delete database ${dbName}`) + } + } + await deleteInstanceFromKnownDbs(instanceName) +} \ No newline at end of file diff --git a/routes/_utils/database/databaseInsideWorker.js b/routes/_utils/database/databaseInsideWorker.js deleted file mode 100644 index 54243c5..0000000 --- a/routes/_utils/database/databaseInsideWorker.js +++ /dev/null @@ -1,65 +0,0 @@ -import { META_STORE, getMetaDatabase } from './meta' -import { cleanupOldStatuses } from './cleanupTimelines' -import { TIMELINE_STORE, getTimelineDatabase } from './timelines' -import { toReversePaddedBigInt, transformStatusForStorage } from './utils' - -export async function getTimeline(instanceName, timeline, maxId = null, limit = 20) { - const db = await getTimelineDatabase(instanceName, timeline) - return await new Promise((resolve, reject) => { - const tx = db.transaction(TIMELINE_STORE, 'readonly') - const store = tx.objectStore(TIMELINE_STORE) - const index = store.index('pinafore_id_as_negative_big_int') - let sinceAsNegativeBigInt = maxId ? toReversePaddedBigInt(maxId) : null - let query = sinceAsNegativeBigInt ? IDBKeyRange.lowerBound(sinceAsNegativeBigInt, false) : null - - let res - index.getAll(query, limit).onsuccess = (e) => { - res = e.target.result - } - - tx.oncomplete = () => resolve(res) - tx.onerror = () => reject(tx.error.name + ' ' + tx.error.message) - }) -} - -export async function insertStatuses(instanceName, timeline, statuses) { - const db = await getTimelineDatabase(instanceName, timeline) - await new Promise((resolve, reject) => { - const tx = db.transaction(TIMELINE_STORE, 'readwrite') - const store = tx.objectStore(TIMELINE_STORE) - for (let status of statuses) { - store.put(transformStatusForStorage(status)) - } - tx.oncomplete = () => resolve() - tx.onerror = () => reject(tx.error.name + ' ' + tx.error.message) - }) - /* no await */ cleanupOldStatuses() -} - -export async function setInstanceVerifyCredentials(instanceName, verifyCredentials) { - const db = await getMetaDatabase(instanceName) - return await new Promise((resolve, reject) => { - const tx = db.transaction(META_STORE, 'readwrite') - const store = tx.objectStore(META_STORE) - store.put({ - key: 'verifyCredentials', - value: verifyCredentials - }) - tx.oncomplete = () => resolve() - tx.onerror = () => reject(tx.error.name + ' ' + tx.error.message) - }) -} - -export async function getInstanceVerifyCredentials(instanceName, verifyCredentials) { - const db = await getMetaDatabase(instanceName) - return await new Promise((resolve, reject) => { - const tx = db.transaction(META_STORE, 'readwrite') - const store = tx.objectStore(META_STORE) - let res - store.get('verifyCredentials').onsuccess = (e) => { - res = e.target.result && e.target.result.value - } - tx.oncomplete = () => resolve(res) - tx.onerror = () => reject(tx.error.name + ' ' + tx.error.message) - }) -} \ No newline at end of file diff --git a/routes/_utils/database/knownDbs.js b/routes/_utils/database/knownDbs.js index b5d5f91..9db4dea 100644 --- a/routes/_utils/database/knownDbs.js +++ b/routes/_utils/database/knownDbs.js @@ -1,7 +1,7 @@ import keyval from "idb-keyval" export async function addKnownDb(instanceName, type, dbName) { - let knownDbs = (await keyval.get('known_dbs')) || {} + let knownDbs = (await getKnownDbs()) if (!knownDbs[instanceName]) { knownDbs[instanceName] = [] } @@ -18,4 +18,10 @@ export async function getKnownDbs() { export async function getKnownDbsForInstance(instanceName) { let knownDbs = await getKnownDbs() return knownDbs[instanceName] || [] +} + +export async function deleteInstanceFromKnownDbs(instanceName) { + let knownDbs = await getKnownDbs() + delete knownDbs[instanceName] + await keyval.set('known_dbs', knownDbs) } \ No newline at end of file diff --git a/routes/_utils/database/meta.js b/routes/_utils/database/meta.js index 2b087d2..e72747c 100644 --- a/routes/_utils/database/meta.js +++ b/routes/_utils/database/meta.js @@ -1,20 +1,19 @@ import { addKnownDb } from './knownDbs' +import { openReqs, databaseCache } from './cache' -const databaseCache = {} export const META_STORE = 'meta' export function getMetaDatabase(instanceName) { - const key = `${instanceName}_${META_STORE}` - if (databaseCache[key]) { - return Promise.resolve(databaseCache[key]) + const dbName = `${instanceName}_${META_STORE}` + if (databaseCache[dbName]) { + return Promise.resolve(databaseCache[dbName]) } - let dbName = key - addKnownDb(instanceName, 'meta', dbName) - databaseCache[key] = new Promise((resolve, reject) => { + databaseCache[dbName] = new Promise((resolve, reject) => { let req = indexedDB.open(dbName, 1) + openReqs[dbName] = req req.onerror = reject req.onblocked = () => { console.log('idb blocked') @@ -25,5 +24,5 @@ export function getMetaDatabase(instanceName) { } req.onsuccess = () => resolve(req.result) }) - return databaseCache[key] + return databaseCache[dbName] } \ No newline at end of file diff --git a/routes/_utils/database/timelines.js b/routes/_utils/database/timelines.js index 64b746e..a6fdb8c 100644 --- a/routes/_utils/database/timelines.js +++ b/routes/_utils/database/timelines.js @@ -1,6 +1,6 @@ import { addKnownDb } from './knownDbs' +import { openReqs, databaseCache } from './cache' -const databaseCache = {} export const TIMELINE_STORE = 'statuses' export function createTimelineDbName(instanceName, timeline) { @@ -8,17 +8,17 @@ export function createTimelineDbName(instanceName, timeline) { } export function getTimelineDatabase(instanceName, timeline) { - const key = `${instanceName}_${timeline}` - if (databaseCache[key]) { - return Promise.resolve(databaseCache[key]) - } - let dbName = createTimelineDbName(instanceName, timeline) + if (databaseCache[dbName]) { + return Promise.resolve(databaseCache[dbName]) + } + addKnownDb(instanceName, 'timeline', dbName) - databaseCache[key] = new Promise((resolve, reject) => { + databaseCache[dbName] = new Promise((resolve, reject) => { let req = indexedDB.open(dbName, 1) + openReqs[dbName] = req req.onerror = reject req.onblocked = () => { console.log('idb blocked') @@ -32,5 +32,5 @@ export function getTimelineDatabase(instanceName, timeline) { } req.onsuccess = () => resolve(req.result) }) - return databaseCache[key] + return databaseCache[dbName] } \ No newline at end of file diff --git a/routes/_utils/database/utils.js b/routes/_utils/database/utils.js index 7fd3d21..2cf70ef 100644 --- a/routes/_utils/database/utils.js +++ b/routes/_utils/database/utils.js @@ -1,5 +1,6 @@ import cloneDeep from 'lodash/cloneDeep' import padStart from 'lodash/padStart' +import { databaseCache, openReqs } from './cache' export function toPaddedBigInt (id) { return padStart(id, 30, '0') @@ -19,4 +20,33 @@ export function transformStatusForStorage (status) { status.pinafore_id_as_negative_big_int = toReversePaddedBigInt(status.id) status.pinafore_stale = true return status +} + +export async function dbPromise(db, storeName, readOnlyOrReadWrite, cb) { + return await new Promise((resolve, reject) => { + const tx = db.transaction(storeName, readOnlyOrReadWrite) + const store = tx.objectStore(storeName) + let res + cb(store, (result) => { + res = result + }) + + tx.oncomplete = () => resolve(res) + tx.onerror = () => reject(tx.error.name + ' ' + tx.error.message) + }) +} + +export function deleteDbPromise(dbName) { + return new Promise((resolve, reject) => { + // close any open requests + let openReq = openReqs[dbName]; + if (openReq && openReq.result) { + openReq.result.close() + } + delete openReqs[dbName] + delete databaseCache[dbName] + let req = indexedDB.deleteDatabase(dbName) + req.onsuccess = () => resolve() + req.onerror = () => reject(req.error.name + ' ' + req.error.message) + }) } \ No newline at end of file diff --git a/routes/settings/instances/[instanceName].html b/routes/settings/instances/[instanceName].html index 3f30c84..dc615f8 100644 --- a/routes/settings/instances/[instanceName].html +++ b/routes/settings/instances/[instanceName].html @@ -30,11 +30,11 @@
{{#if $loggedInInstancesInOrder.length > 1}} {{/if}} - +
{{/if}} @@ -145,7 +145,8 @@ switchToTheme(newTheme) } }, - onSwitchToThisInstance() { + onSwitchToThisInstance(e) { + e.preventDefault() let instanceName = this.get('params').instanceName let instanceThemes = this.store.get('instanceThemes') this.store.set({ @@ -154,7 +155,8 @@ this.store.save() switchToTheme(instanceThemes[instanceName]) }, - onLogOut() { + onLogOut(e) { + e.preventDefault() let loggedInInstances = this.store.get('loggedInInstances') let instanceThemes = this.store.get('instanceThemes') let loggedInInstancesInOrder = this.store.get('loggedInInstancesInOrder') @@ -173,6 +175,7 @@ currentInstance: newInstance }) this.store.save() + database.clearDatabasesForInstance(instanceName) switchToTheme(instanceThemes[newInstance] || 'default') toast.say(`Logged out of ${instanceName}`) goto('/settings/instances') diff --git a/templates/2xx.html b/templates/2xx.html index 3d5aebe..c5b0303 100644 --- a/templates/2xx.html +++ b/templates/2xx.html @@ -47,7 +47,8 @@ body.offline,body.theme-hotpants.offline,body.theme-majesty.offline,body.theme-o offline: "#999999" } if (localStorage.store_currentInstance && localStorage.store_instanceThemes) { - let theme = JSON.parse(localStorage.store_instanceThemes)[JSON.parse(localStorage.store_currentInstance)] + let safeParse = (str) => str === 'undefined' ? undefined : JSON.parse(str) + let theme = safeParse(localStorage.store_instanceThemes)[safeParse(localStorage.store_currentInstance)] if (theme !== 'default') { document.body.classList.add(`theme-${theme}`) let link = document.createElement('link')