forked from mystiq/hydrogen-web
wrap everything that can throw a idb DOMException in StorageError
as lumia gives very cryptic errors without a stacktrace.
This commit is contained in:
parent
0fd52be710
commit
bbb5e35bcb
9 changed files with 148 additions and 58 deletions
|
@ -5,9 +5,6 @@ export class HomeServerError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
export class StorageError extends Error {
|
||||
}
|
||||
|
||||
export class RequestAbortError extends Error {
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue