From 43baaf36ae49d39c4941886c82a34dcd8b654d29 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Wed, 8 May 2019 20:53:33 -0700 Subject: [PATCH] fix: close IDB connections when page is frozen (#1196) OK let's try this --- package.json | 1 - src/routes/_database/databaseLifecycle.js | 13 ++- src/routes/_database/knownInstances.js | 12 ++- src/routes/_thirdparty/idb-keyval/LICENCE | 13 +++ .../_thirdparty/idb-keyval/idb-keyval.js | 98 +++++++++++++++++++ 5 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 src/routes/_thirdparty/idb-keyval/LICENCE create mode 100644 src/routes/_thirdparty/idb-keyval/idb-keyval.js diff --git a/package.json b/package.json index 4089eae8..5b694ea4 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,6 @@ "file-drop-element": "0.2.0", "form-data": "^2.3.3", "glob": "^7.1.3", - "idb-keyval": "^3.2.0", "indexeddb-getall-shim": "^1.3.5", "intersection-observer": "^0.6.0", "localstorage-memory": "^1.0.3", diff --git a/src/routes/_database/databaseLifecycle.js b/src/routes/_database/databaseLifecycle.js index 1c653728..f778febb 100644 --- a/src/routes/_database/databaseLifecycle.js +++ b/src/routes/_database/databaseLifecycle.js @@ -2,6 +2,7 @@ import { DB_VERSION_CURRENT } from './constants' import { addKnownInstance, deleteKnownInstance } from './knownInstances' import { migrations } from './migrations' import { clearAllCaches } from './cache' +import lifecycle from 'page-lifecycle/dist/lifecycle.mjs' const openReqs = {} const databaseCache = {} @@ -77,7 +78,6 @@ export function deleteDatabase (instanceName) { .then(() => clearAllCaches(instanceName)) } -// this should probably only be used in unit tests export function closeDatabase (instanceName) { // close any open requests let openReq = openReqs[instanceName] @@ -88,3 +88,14 @@ export function closeDatabase (instanceName) { delete databaseCache[instanceName] clearAllCaches(instanceName) } + +if (process.browser) { + lifecycle.addEventListener('statechange', event => { + if (event.newState === 'frozen') { // page is frozen, close IDB connections + Object.keys(openReqs).forEach(instanceName => { + closeDatabase(instanceName) + console.log('closed instance DBs') + }) + } + }) +} diff --git a/src/routes/_database/knownInstances.js b/src/routes/_database/knownInstances.js index 8c5469bc..81f529bb 100644 --- a/src/routes/_database/knownInstances.js +++ b/src/routes/_database/knownInstances.js @@ -1,4 +1,5 @@ -import { set, keys, del } from 'idb-keyval' +import { set, keys, del, close } from '../_thirdparty/idb-keyval/idb-keyval' +import lifecycle from 'page-lifecycle/dist/lifecycle.mjs' const PREFIX = 'known-instance-' @@ -15,3 +16,12 @@ export async function addKnownInstance (instanceName) { export async function deleteKnownInstance (instanceName) { return del(PREFIX + instanceName) } + +if (process.browser) { + lifecycle.addEventListener('statechange', async event => { + if (event.newState === 'frozen') { // page is frozen, close IDB connections + await close() + console.log('closed knownInstances DB') + } + }) +} diff --git a/src/routes/_thirdparty/idb-keyval/LICENCE b/src/routes/_thirdparty/idb-keyval/LICENCE new file mode 100644 index 00000000..e2349883 --- /dev/null +++ b/src/routes/_thirdparty/idb-keyval/LICENCE @@ -0,0 +1,13 @@ +Copyright 2016, Jake Archibald + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/src/routes/_thirdparty/idb-keyval/idb-keyval.js b/src/routes/_thirdparty/idb-keyval/idb-keyval.js new file mode 100644 index 00000000..e54908be --- /dev/null +++ b/src/routes/_thirdparty/idb-keyval/idb-keyval.js @@ -0,0 +1,98 @@ +// Forked from https://github.com/jakearchibald/idb-keyval/commit/ea7d507 +// Adds a function for closing the database, ala https://github.com/jakearchibald/idb-keyval/pull/65 +class Store { + constructor (dbName = 'keyval-store', storeName = 'keyval') { + this.storeName = storeName + this._dbName = dbName + this._storeName = storeName + this._init() + } + + _withIDBStore (type, callback) { + this._init() + return this._dbp.then(db => new Promise((resolve, reject) => { + const transaction = db.transaction(this.storeName, type) + transaction.oncomplete = () => resolve() + transaction.onabort = transaction.onerror = () => reject(transaction.error) + callback(transaction.objectStore(this.storeName)) + })) + } + + _init () { + if (this._dbp) { + return + } + this._dbp = new Promise((resolve, reject) => { + const openreq = indexedDB.open(this._dbName, 1) + openreq.onerror = () => reject(openreq.error) + openreq.onsuccess = () => resolve(openreq.result) + // First time setup: create an empty object store + openreq.onupgradeneeded = () => { + openreq.result.createObjectStore(this._storeName) + } + }) + } + + _close () { + this._init() + return this._dbp.then(db => { + db.close() + this._dbp = undefined + }) + } +} + +let store + +function getDefaultStore () { + if (!store) { + store = new Store() + } + return store +} + +function get (key, store = getDefaultStore()) { + let req + return store._withIDBStore('readonly', store => { + req = store.get(key) + }).then(() => req.result) +} + +function set (key, value, store = getDefaultStore()) { + return store._withIDBStore('readwrite', store => { + store.put(value, key) + }) +} + +function del (key, store = getDefaultStore()) { + return store._withIDBStore('readwrite', store => { + store.delete(key) + }) +} + +function clear (store = getDefaultStore()) { + return store._withIDBStore('readwrite', store => { + store.clear() + }) +} + +function keys (store = getDefaultStore()) { + const keys = [] + return store._withIDBStore('readonly', 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 + } + keys.push(this.result.key) + this.result.continue() + } + }).then(() => keys) +} + +function close (store = getDefaultStore()) { + return store._close() +} + +export { Store, get, set, del, clear, keys, close }