wrap everything that can throw a idb DOMException in StorageError

as lumia gives very cryptic errors without a stacktrace.
This commit is contained in:
Bruno Windels 2019-06-26 22:00:50 +02:00
parent 0fd52be710
commit bbb5e35bcb
9 changed files with 148 additions and 58 deletions

View file

@ -5,9 +5,6 @@ export class HomeServerError extends Error {
}
}
export class StorageError extends Error {
}
export class RequestAbortError extends Error {
}

View file

@ -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);
}
}

View file

@ -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);
}
}
}

View file

@ -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));
}
}
}

View file

@ -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);
}
}
}

View file

@ -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) {

View file

@ -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));
}

View file

@ -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;
}

View file

@ -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) {