From bbb5e35bcb049be0103f44e9962b44fdfb041e6b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Jun 2019 22:00:50 +0200 Subject: [PATCH] wrap everything that can throw a idb DOMException in StorageError as lumia gives very cryptic errors without a stacktrace. --- src/matrix/error.js | 3 - src/matrix/storage/common.js | 14 ++++ src/matrix/storage/idb/storage.js | 20 ++++-- src/matrix/storage/idb/store.js | 61 +++++++++++++++- .../storage/idb/stores/TimelineEventStore.js | 69 ++++++++++--------- .../idb/stores/TimelineFragmentStore.js | 13 ++-- src/matrix/storage/idb/transaction.js | 3 +- src/matrix/storage/idb/utils.js | 14 +++- src/matrix/sync.js | 9 +-- 9 files changed, 148 insertions(+), 58 deletions(-) diff --git a/src/matrix/error.js b/src/matrix/error.js index 520698d9..847d50b2 100644 --- a/src/matrix/error.js +++ b/src/matrix/error.js @@ -5,9 +5,6 @@ export class HomeServerError extends Error { } } -export class StorageError extends Error { -} - export class RequestAbortError extends Error { } diff --git a/src/matrix/storage/common.js b/src/matrix/storage/common.js index d1050e5a..aa72fa0e 100644 --- a/src/matrix/storage/common.js +++ b/src/matrix/storage/common.js @@ -4,3 +4,17 @@ export const STORE_MAP = Object.freeze(STORE_NAMES.reduce((nameMap, name) => { nameMap[name] = name; return nameMap; }, {})); + +export class StorageError extends Error { + constructor(message, cause) { + let fullMessage = message; + if (cause) { + fullMessage += ": "; + if (cause.name) { + fullMessage += `(${cause.name}) `; + } + fullMessage += cause.message; + } + super(fullMessage); + } +} diff --git a/src/matrix/storage/idb/storage.js b/src/matrix/storage/idb/storage.js index ca7e2256..25bb0d39 100644 --- a/src/matrix/storage/idb/storage.js +++ b/src/matrix/storage/idb/storage.js @@ -1,5 +1,5 @@ import Transaction from "./transaction.js"; -import { STORE_NAMES } from "../common.js"; +import { STORE_NAMES, StorageError } from "../common.js"; export default class Storage { constructor(idbDatabase) { @@ -14,19 +14,27 @@ export default class Storage { _validateStoreNames(storeNames) { const idx = storeNames.findIndex(name => !STORE_NAMES.includes(name)); if (idx !== -1) { - throw new Error(`Tried to open a transaction for unknown store ${storeNames[idx]}`); + throw new StorageError(`Tried top, a transaction unknown store ${storeNames[idx]}`); } } async readTxn(storeNames) { this._validateStoreNames(storeNames); - const txn = this._db.transaction(storeNames, "readonly"); - return new Transaction(txn, storeNames); + try { + const txn = this._db.transaction(storeNames, "readonly"); + return new Transaction(txn, storeNames); + } catch(err) { + throw new StorageError("readTxn failed", err); + } } async readWriteTxn(storeNames) { this._validateStoreNames(storeNames); - const txn = this._db.transaction(storeNames, "readwrite"); - return new Transaction(txn, storeNames); + try { + const txn = this._db.transaction(storeNames, "readwrite"); + return new Transaction(txn, storeNames); + } catch(err) { + throw new StorageError("readWriteTxn failed", err); + } } } diff --git a/src/matrix/storage/idb/store.js b/src/matrix/storage/idb/store.js index 9b5daf46..fd2cc84d 100644 --- a/src/matrix/storage/idb/store.js +++ b/src/matrix/storage/idb/store.js @@ -1,9 +1,64 @@ import QueryTarget from "./query-target.js"; import { reqAsPromise } from "./utils.js"; +import { StorageError } from "../common.js"; + +class QueryTargetWrapper { + constructor(qt) { + this._qt = qt; + } + + openKeyCursor(...params) { + try { + return this._qt.openKeyCursor(...params); + } catch(err) { + throw new StorageError("openKeyCursor failed", err); + } + } + + openCursor(...params) { + try { + return this._qt.openCursor(...params); + } catch(err) { + throw new StorageError("openCursor failed", err); + } + } + + put(...params) { + try { + return this._qt.put(...params); + } catch(err) { + throw new StorageError("put failed", err); + } + } + + add(...params) { + try { + return this._qt.add(...params); + } catch(err) { + throw new StorageError("add failed", err); + } + } + + get(...params) { + try { + return this._qt.get(...params); + } catch(err) { + throw new StorageError("get failed", err); + } + } + + index(...params) { + try { + return this._qt.index(...params); + } catch(err) { + throw new StorageError("index failed", err); + } + } +} export default class Store extends QueryTarget { constructor(idbStore) { - super(idbStore); + super(new QueryTargetWrapper(idbStore)); } get _idbStore() { @@ -11,7 +66,7 @@ export default class Store extends QueryTarget { } index(indexName) { - return new QueryTarget(this._idbStore.index(indexName)); + return new QueryTarget(new QueryTargetWrapper(this._idbStore.index(indexName))); } put(value) { @@ -21,4 +76,4 @@ export default class Store extends QueryTarget { add(value) { return reqAsPromise(this._idbStore.add(value)); } -} \ No newline at end of file +} diff --git a/src/matrix/storage/idb/stores/TimelineEventStore.js b/src/matrix/storage/idb/stores/TimelineEventStore.js index 1b4e6e00..d88884bf 100644 --- a/src/matrix/storage/idb/stores/TimelineEventStore.js +++ b/src/matrix/storage/idb/stores/TimelineEventStore.js @@ -1,4 +1,5 @@ import EventKey from "../../../room/timeline/EventKey.js"; +import { StorageError } from "../../common.js"; import Platform from "../../../../Platform.js"; // storage keys are defined to be unsigned 32bit numbers in WebPlatform.js, which is assumed by idb @@ -30,38 +31,42 @@ class Range { } asIDBKeyRange(roomId) { - // only - if (this._only) { - return IDBKeyRange.only(encodeKey(roomId, this._only.fragmentId, this._only.eventIndex)); - } - // lowerBound - // also bound as we don't want to move into another roomId - if (this._lower && !this._upper) { - return IDBKeyRange.bound( - encodeKey(roomId, this._lower.fragmentId, this._lower.eventIndex), - encodeKey(roomId, this._lower.fragmentId, Platform.maxStorageKey), - this._lowerOpen, - false - ); - } - // upperBound - // also bound as we don't want to move into another roomId - if (!this._lower && this._upper) { - return IDBKeyRange.bound( - encodeKey(roomId, this._upper.fragmentId, Platform.minStorageKey), - encodeKey(roomId, this._upper.fragmentId, this._upper.eventIndex), - false, - this._upperOpen - ); - } - // bound - if (this._lower && this._upper) { - return IDBKeyRange.bound( - encodeKey(roomId, this._lower.fragmentId, this._lower.eventIndex), - encodeKey(roomId, this._upper.fragmentId, this._upper.eventIndex), - this._lowerOpen, - this._upperOpen - ); + try { + // only + if (this._only) { + return IDBKeyRange.only(encodeKey(roomId, this._only.fragmentId, this._only.eventIndex)); + } + // lowerBound + // also bound as we don't want to move into another roomId + if (this._lower && !this._upper) { + return IDBKeyRange.bound( + encodeKey(roomId, this._lower.fragmentId, this._lower.eventIndex), + encodeKey(roomId, this._lower.fragmentId, Platform.maxStorageKey), + this._lowerOpen, + false + ); + } + // upperBound + // also bound as we don't want to move into another roomId + if (!this._lower && this._upper) { + return IDBKeyRange.bound( + encodeKey(roomId, this._upper.fragmentId, Platform.minStorageKey), + encodeKey(roomId, this._upper.fragmentId, this._upper.eventIndex), + false, + this._upperOpen + ); + } + // bound + if (this._lower && this._upper) { + return IDBKeyRange.bound( + encodeKey(roomId, this._lower.fragmentId, this._lower.eventIndex), + encodeKey(roomId, this._upper.fragmentId, this._upper.eventIndex), + this._lowerOpen, + this._upperOpen + ); + } + } catch(err) { + throw new StorageError(`IDBKeyRange failed with data: ` + JSON.stringify(this), err); } } } diff --git a/src/matrix/storage/idb/stores/TimelineFragmentStore.js b/src/matrix/storage/idb/stores/TimelineFragmentStore.js index 2d86a100..b84759bd 100644 --- a/src/matrix/storage/idb/stores/TimelineFragmentStore.js +++ b/src/matrix/storage/idb/stores/TimelineFragmentStore.js @@ -1,3 +1,4 @@ +import { StorageError } from "../../common.js"; import Platform from "../../../../Platform.js"; function encodeKey(roomId, fragmentId) { @@ -12,10 +13,14 @@ export default class RoomFragmentStore { } _allRange(roomId) { - return IDBKeyRange.bound( - encodeKey(roomId, Platform.minStorageKey), - encodeKey(roomId, Platform.maxStorageKey) - ); + try { + return IDBKeyRange.bound( + encodeKey(roomId, Platform.minStorageKey), + encodeKey(roomId, Platform.maxStorageKey) + ); + } catch (err) { + throw new StorageError(`error from IDBKeyRange with roomId ${roomId}`, err); + } } all(roomId) { diff --git a/src/matrix/storage/idb/transaction.js b/src/matrix/storage/idb/transaction.js index 1a16d08d..a005706d 100644 --- a/src/matrix/storage/idb/transaction.js +++ b/src/matrix/storage/idb/transaction.js @@ -1,4 +1,5 @@ import {txnAsPromise} from "./utils.js"; +import {StorageError} from "../common.js"; import Store from "./store.js"; import SessionStore from "./stores/SessionStore.js"; import RoomSummaryStore from "./stores/RoomSummaryStore.js"; @@ -21,7 +22,7 @@ export default class Transaction { _idbStore(name) { if (!this._allowedStoreNames.includes(name)) { // more specific error? this is a bug, so maybe not ... - throw new Error(`Invalid store for transaction: ${name}, only ${this._allowedStoreNames.join(", ")} are allowed.`); + throw new StorageError(`Invalid store for transaction: ${name}, only ${this._allowedStoreNames.join(", ")} are allowed.`); } return new Store(this._txn.objectStore(name)); } diff --git a/src/matrix/storage/idb/utils.js b/src/matrix/storage/idb/utils.js index 83ef3411..f5b73cf3 100644 --- a/src/matrix/storage/idb/utils.js +++ b/src/matrix/storage/idb/utils.js @@ -1,3 +1,5 @@ +import { StorageError } from "../common.js"; + export function openDatabase(name, createObjectStore, version) { const req = window.indexedDB.open(name, version); req.onupgradeneeded = (ev) => { @@ -8,17 +10,21 @@ export function openDatabase(name, createObjectStore, version) { return reqAsPromise(req); } +function wrapError(err) { + return new StorageError(`wrapped DOMException`, err); +} + export function reqAsPromise(req) { return new Promise((resolve, reject) => { req.addEventListener("success", event => resolve(event.target.result)); - req.addEventListener("error", event => reject(event.target.error)); + req.addEventListener("error", event => reject(wrapError(event.target.error))); }); } export function txnAsPromise(txn) { return new Promise((resolve, reject) => { txn.addEventListener("complete", resolve); - txn.addEventListener("abort", reject); + txn.addEventListener("abort", e => reject(wrapError(e))); }); } @@ -42,6 +48,8 @@ export function iterateCursor(cursor, processValue) { cursor.continue(jumpTo); } }; + }).catch(err => { + throw new StorageError("iterateCursor failed", err); }); } @@ -97,7 +105,7 @@ export async function findStoreValue(db, storeName, toCursor, matchesValue) { } }); if (!matched) { - throw new Error("Value not found"); + throw new StorageError("Value not found"); } return match; } diff --git a/src/matrix/sync.js b/src/matrix/sync.js index ad6074cc..fd0d2bf6 100644 --- a/src/matrix/sync.js +++ b/src/matrix/sync.js @@ -1,8 +1,4 @@ -import { - RequestAbortError, - HomeServerError, - StorageError -} from "./error.js"; +import {RequestAbortError} from "./error.js"; import EventEmitter from "../EventEmitter.js"; const INCREMENTAL_TIMEOUT = 30000; @@ -111,7 +107,8 @@ export default class Sync extends EventEmitter { await syncTxn.complete(); console.info("syncTxn committed!!"); } catch (err) { - throw new StorageError("unable to commit sync tranaction", err); + console.error("unable to commit sync tranaction", err.message); + throw err; } // emit room related events after txn has been closed for(let {room, changes} of roomChanges) {