prepare storage to work with alternative idb impl
This commit is contained in:
parent
8dfed73524
commit
edbac25613
16 changed files with 61 additions and 48 deletions
|
@ -21,8 +21,9 @@ import { reqAsPromise } from "./utils.js";
|
|||
const WEBKITEARLYCLOSETXNBUG_BOGUS_KEY = "782rh281re38-boguskey";
|
||||
|
||||
export class Storage {
|
||||
constructor(idbDatabase, hasWebkitEarlyCloseTxnBug) {
|
||||
constructor(idbDatabase, IDBKeyRange, hasWebkitEarlyCloseTxnBug) {
|
||||
this._db = idbDatabase;
|
||||
this._IDBKeyRange = IDBKeyRange;
|
||||
this._hasWebkitEarlyCloseTxnBug = hasWebkitEarlyCloseTxnBug;
|
||||
const nameMap = STORE_NAMES.reduce((nameMap, name) => {
|
||||
nameMap[name] = name;
|
||||
|
@ -47,7 +48,7 @@ export class Storage {
|
|||
if (this._hasWebkitEarlyCloseTxnBug) {
|
||||
await reqAsPromise(txn.objectStore(storeNames[0]).get(WEBKITEARLYCLOSETXNBUG_BOGUS_KEY));
|
||||
}
|
||||
return new Transaction(txn, storeNames);
|
||||
return new Transaction(txn, storeNames, this._IDBKeyRange);
|
||||
} catch(err) {
|
||||
throw new StorageError("readTxn failed", err);
|
||||
}
|
||||
|
@ -62,7 +63,7 @@ export class Storage {
|
|||
if (this._hasWebkitEarlyCloseTxnBug) {
|
||||
await reqAsPromise(txn.objectStore(storeNames[0]).get(WEBKITEARLYCLOSETXNBUG_BOGUS_KEY));
|
||||
}
|
||||
return new Transaction(txn, storeNames);
|
||||
return new Transaction(txn, storeNames, this._IDBKeyRange);
|
||||
} catch(err) {
|
||||
throw new StorageError("readWriteTxn failed", err);
|
||||
}
|
||||
|
|
|
@ -21,14 +21,18 @@ import { schema } from "./schema.js";
|
|||
import { detectWebkitEarlyCloseTxnBug } from "./quirks.js";
|
||||
|
||||
const sessionName = sessionId => `hydrogen_session_${sessionId}`;
|
||||
const openDatabaseWithSessionId = sessionId => openDatabase(sessionName(sessionId), createStores, schema.length);
|
||||
const openDatabaseWithSessionId = function(sessionId, idbFactory) {
|
||||
return openDatabase(sessionName(sessionId), createStores, schema.length, idbFactory);
|
||||
}
|
||||
|
||||
async function requestPersistedStorage() {
|
||||
if (navigator?.storage?.persist) {
|
||||
return await navigator.storage.persist();
|
||||
} else if (document.requestStorageAccess) {
|
||||
// don't assume browser so we can run in node with fake-idb
|
||||
const glob = this;
|
||||
if (glob?.navigator?.storage?.persist) {
|
||||
return await glob.navigator.storage.persist();
|
||||
} else if (glob?.document.requestStorageAccess) {
|
||||
try {
|
||||
await document.requestStorageAccess();
|
||||
await glob.document.requestStorageAccess();
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
|
@ -39,8 +43,10 @@ async function requestPersistedStorage() {
|
|||
}
|
||||
|
||||
export class StorageFactory {
|
||||
constructor(serviceWorkerHandler) {
|
||||
constructor(serviceWorkerHandler, idbFactory = window.indexedDB, IDBKeyRange = window.IDBKeyRange) {
|
||||
this._serviceWorkerHandler = serviceWorkerHandler;
|
||||
this._idbFactory = idbFactory;
|
||||
this._IDBKeyRange = IDBKeyRange;
|
||||
}
|
||||
|
||||
async create(sessionId) {
|
||||
|
@ -52,24 +58,24 @@ export class StorageFactory {
|
|||
}
|
||||
});
|
||||
|
||||
const hasWebkitEarlyCloseTxnBug = await detectWebkitEarlyCloseTxnBug();
|
||||
const db = await openDatabaseWithSessionId(sessionId);
|
||||
return new Storage(db, hasWebkitEarlyCloseTxnBug);
|
||||
const hasWebkitEarlyCloseTxnBug = await detectWebkitEarlyCloseTxnBug(this._idbFactory);
|
||||
const db = await openDatabaseWithSessionId(sessionId, this._idbFactory);
|
||||
return new Storage(db, this._IDBKeyRange, hasWebkitEarlyCloseTxnBug);
|
||||
}
|
||||
|
||||
delete(sessionId) {
|
||||
const databaseName = sessionName(sessionId);
|
||||
const req = indexedDB.deleteDatabase(databaseName);
|
||||
const req = this._idbFactory.deleteDatabase(databaseName);
|
||||
return reqAsPromise(req);
|
||||
}
|
||||
|
||||
async export(sessionId) {
|
||||
const db = await openDatabaseWithSessionId(sessionId);
|
||||
const db = await openDatabaseWithSessionId(sessionId, this._idbFactory);
|
||||
return await exportSession(db);
|
||||
}
|
||||
|
||||
async import(sessionId, data) {
|
||||
const db = await openDatabaseWithSessionId(sessionId);
|
||||
const db = await openDatabaseWithSessionId(sessionId, this._idbFactory);
|
||||
return await importSession(db, data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -126,6 +126,10 @@ export class Store extends QueryTarget {
|
|||
this._transaction = transaction;
|
||||
}
|
||||
|
||||
get IDBKeyRange() {
|
||||
return this._transaction.IDBKeyRange;
|
||||
}
|
||||
|
||||
get _idbStore() {
|
||||
return this._target;
|
||||
}
|
||||
|
|
|
@ -35,10 +35,11 @@ import {OperationStore} from "./stores/OperationStore.js";
|
|||
import {AccountDataStore} from "./stores/AccountDataStore.js";
|
||||
|
||||
export class Transaction {
|
||||
constructor(txn, allowedStoreNames) {
|
||||
constructor(txn, allowedStoreNames, IDBKeyRange) {
|
||||
this._txn = txn;
|
||||
this._allowedStoreNames = allowedStoreNames;
|
||||
this._stores = {};
|
||||
this.IDBKeyRange = IDBKeyRange;
|
||||
}
|
||||
|
||||
_idbStore(name) {
|
||||
|
@ -46,7 +47,7 @@ export class Transaction {
|
|||
// more specific error? this is a bug, so maybe not ...
|
||||
throw new StorageError(`Invalid store for transaction: ${name}, only ${this._allowedStoreNames.join(", ")} are allowed.`);
|
||||
}
|
||||
return new Store(this._txn.objectStore(name));
|
||||
return new Store(this._txn.objectStore(name), this);
|
||||
}
|
||||
|
||||
_store(name, mapStore) {
|
||||
|
|
|
@ -18,12 +18,12 @@ limitations under the License.
|
|||
import {openDatabase, txnAsPromise, reqAsPromise} from "./utils.js";
|
||||
|
||||
// filed as https://bugs.webkit.org/show_bug.cgi?id=222746
|
||||
export async function detectWebkitEarlyCloseTxnBug() {
|
||||
export async function detectWebkitEarlyCloseTxnBug(idbFactory) {
|
||||
const dbName = "hydrogen_webkit_test_inactive_txn_bug";
|
||||
try {
|
||||
const db = await openDatabase(dbName, db => {
|
||||
db.createObjectStore("test", {keyPath: "key"});
|
||||
}, 1);
|
||||
}, 1, idbFactory);
|
||||
const readTxn = db.transaction(["test"], "readonly");
|
||||
await reqAsPromise(readTxn.objectStore("test").get("somekey"));
|
||||
// schedule a macro task in between the two txns
|
||||
|
|
|
@ -31,7 +31,7 @@ export class DeviceIdentityStore {
|
|||
}
|
||||
|
||||
getAllForUserId(userId) {
|
||||
const range = IDBKeyRange.lowerBound(encodeKey(userId, ""));
|
||||
const range = this._store.IDBKeyRange.lowerBound(encodeKey(userId, ""));
|
||||
return this._store.selectWhile(range, device => {
|
||||
return device.userId === userId;
|
||||
});
|
||||
|
@ -39,7 +39,7 @@ export class DeviceIdentityStore {
|
|||
|
||||
async getAllDeviceIds(userId) {
|
||||
const deviceIds = [];
|
||||
const range = IDBKeyRange.lowerBound(encodeKey(userId, ""));
|
||||
const range = this._store.IDBKeyRange.lowerBound(encodeKey(userId, ""));
|
||||
await this._store.iterateKeys(range, key => {
|
||||
const decodedKey = decodeKey(key);
|
||||
// prevent running into the next room
|
||||
|
@ -72,7 +72,7 @@ export class DeviceIdentityStore {
|
|||
removeAllForUser(userId) {
|
||||
// exclude both keys as they are theoretical min and max,
|
||||
// but we should't have a match for just the room id, or room id with max
|
||||
const range = IDBKeyRange.bound(encodeKey(userId, MIN_UNICODE), encodeKey(userId, MAX_UNICODE), true, true);
|
||||
const range = this._store.IDBKeyRange.bound(encodeKey(userId, MIN_UNICODE), encodeKey(userId, MAX_UNICODE), true, true);
|
||||
this._store.delete(range);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ export class GroupSessionDecryptionStore {
|
|||
}
|
||||
|
||||
removeAllForRoom(roomId) {
|
||||
const range = IDBKeyRange.bound(
|
||||
const range = this._store.IDBKeyRange.bound(
|
||||
encodeKey(roomId, MIN_UNICODE, MIN_UNICODE),
|
||||
encodeKey(roomId, MAX_UNICODE, MAX_UNICODE)
|
||||
);
|
||||
|
|
|
@ -41,7 +41,7 @@ export class InboundGroupSessionStore {
|
|||
}
|
||||
|
||||
removeAllForRoom(roomId) {
|
||||
const range = IDBKeyRange.bound(
|
||||
const range = this._store.IDBKeyRange.bound(
|
||||
encodeKey(roomId, MIN_UNICODE, MIN_UNICODE),
|
||||
encodeKey(roomId, MAX_UNICODE, MAX_UNICODE)
|
||||
);
|
||||
|
|
|
@ -30,7 +30,7 @@ export class OlmSessionStore {
|
|||
|
||||
async getSessionIds(senderKey) {
|
||||
const sessionIds = [];
|
||||
const range = IDBKeyRange.lowerBound(encodeKey(senderKey, ""));
|
||||
const range = this._store.IDBKeyRange.lowerBound(encodeKey(senderKey, ""));
|
||||
await this._store.iterateKeys(range, key => {
|
||||
const decodedKey = decodeKey(key);
|
||||
// prevent running into the next room
|
||||
|
@ -44,7 +44,7 @@ export class OlmSessionStore {
|
|||
}
|
||||
|
||||
getAll(senderKey) {
|
||||
const range = IDBKeyRange.lowerBound(encodeKey(senderKey, ""));
|
||||
const range = this._store.IDBKeyRange.lowerBound(encodeKey(senderKey, ""));
|
||||
return this._store.selectWhile(range, session => {
|
||||
return session.senderKey === senderKey;
|
||||
});
|
||||
|
|
|
@ -55,7 +55,7 @@ export class OperationStore {
|
|||
}
|
||||
|
||||
async removeAllForScope(scope) {
|
||||
const range = IDBKeyRange.bound(
|
||||
const range = this._store.IDBKeyRange.bound(
|
||||
encodeScopeTypeKey(scope, MIN_UNICODE),
|
||||
encodeScopeTypeKey(scope, MAX_UNICODE)
|
||||
);
|
||||
|
|
|
@ -33,7 +33,7 @@ export class PendingEventStore {
|
|||
}
|
||||
|
||||
async getMaxQueueIndex(roomId) {
|
||||
const range = IDBKeyRange.bound(
|
||||
const range = this._eventStore.IDBKeyRange.bound(
|
||||
encodeKey(roomId, KeyLimits.minStorageKey),
|
||||
encodeKey(roomId, KeyLimits.maxStorageKey),
|
||||
false,
|
||||
|
@ -46,12 +46,12 @@ export class PendingEventStore {
|
|||
}
|
||||
|
||||
remove(roomId, queueIndex) {
|
||||
const keyRange = IDBKeyRange.only(encodeKey(roomId, queueIndex));
|
||||
const keyRange = this._eventStore.IDBKeyRange.only(encodeKey(roomId, queueIndex));
|
||||
this._eventStore.delete(keyRange);
|
||||
}
|
||||
|
||||
async exists(roomId, queueIndex) {
|
||||
const keyRange = IDBKeyRange.only(encodeKey(roomId, queueIndex));
|
||||
const keyRange = this._eventStore.IDBKeyRange.only(encodeKey(roomId, queueIndex));
|
||||
const key = await this._eventStore.getKey(keyRange);
|
||||
return !!key;
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ export class PendingEventStore {
|
|||
removeAllForRoom(roomId) {
|
||||
const minKey = encodeKey(roomId, KeyLimits.minStorageKey);
|
||||
const maxKey = encodeKey(roomId, KeyLimits.maxStorageKey);
|
||||
const range = IDBKeyRange.bound(minKey, maxKey);
|
||||
const range = this._eventStore.IDBKeyRange.bound(minKey, maxKey);
|
||||
this._eventStore.delete(range);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ export class RoomMemberStore {
|
|||
}
|
||||
|
||||
getAll(roomId) {
|
||||
const range = IDBKeyRange.lowerBound(encodeKey(roomId, ""));
|
||||
const range = this._roomMembersStore.IDBKeyRange.lowerBound(encodeKey(roomId, ""));
|
||||
return this._roomMembersStore.selectWhile(range, member => {
|
||||
return member.roomId === roomId;
|
||||
});
|
||||
|
@ -50,7 +50,7 @@ export class RoomMemberStore {
|
|||
|
||||
async getAllUserIds(roomId) {
|
||||
const userIds = [];
|
||||
const range = IDBKeyRange.lowerBound(encodeKey(roomId, ""));
|
||||
const range = this._roomMembersStore.IDBKeyRange.lowerBound(encodeKey(roomId, ""));
|
||||
await this._roomMembersStore.iterateKeys(range, key => {
|
||||
const decodedKey = decodeKey(key);
|
||||
// prevent running into the next room
|
||||
|
@ -66,7 +66,7 @@ export class RoomMemberStore {
|
|||
removeAllForRoom(roomId) {
|
||||
// exclude both keys as they are theoretical min and max,
|
||||
// but we should't have a match for just the room id, or room id with max
|
||||
const range = IDBKeyRange.bound(roomId, `${roomId}|${MAX_UNICODE}`, true, true);
|
||||
const range = this._roomMembersStore.IDBKeyRange.bound(roomId, `${roomId}|${MAX_UNICODE}`, true, true);
|
||||
this._roomMembersStore.delete(range);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ export class RoomStateStore {
|
|||
removeAllForRoom(roomId) {
|
||||
// exclude both keys as they are theoretical min and max,
|
||||
// but we should't have a match for just the room id, or room id with max
|
||||
const range = IDBKeyRange.bound(roomId, `${roomId}|${MAX_UNICODE}`, true, true);
|
||||
const range = this._roomStateStore.IDBKeyRange.bound(roomId, `${roomId}|${MAX_UNICODE}`, true, true);
|
||||
this._roomStateStore.delete(range);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,8 @@ function decodeEventIdKey(eventIdKey) {
|
|||
}
|
||||
|
||||
class Range {
|
||||
constructor(only, lower, upper, lowerOpen, upperOpen) {
|
||||
constructor(IDBKeyRange, only, lower, upper, lowerOpen, upperOpen) {
|
||||
this._IDBKeyRange = IDBKeyRange;
|
||||
this._only = only;
|
||||
this._lower = lower;
|
||||
this._upper = upper;
|
||||
|
@ -45,12 +46,12 @@ class Range {
|
|||
try {
|
||||
// only
|
||||
if (this._only) {
|
||||
return IDBKeyRange.only(encodeKey(roomId, this._only.fragmentId, this._only.eventIndex));
|
||||
return this._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(
|
||||
return this._IDBKeyRange.bound(
|
||||
encodeKey(roomId, this._lower.fragmentId, this._lower.eventIndex),
|
||||
encodeKey(roomId, this._lower.fragmentId, KeyLimits.maxStorageKey),
|
||||
this._lowerOpen,
|
||||
|
@ -60,7 +61,7 @@ class Range {
|
|||
// upperBound
|
||||
// also bound as we don't want to move into another roomId
|
||||
if (!this._lower && this._upper) {
|
||||
return IDBKeyRange.bound(
|
||||
return this._IDBKeyRange.bound(
|
||||
encodeKey(roomId, this._upper.fragmentId, KeyLimits.minStorageKey),
|
||||
encodeKey(roomId, this._upper.fragmentId, this._upper.eventIndex),
|
||||
false,
|
||||
|
@ -69,7 +70,7 @@ class Range {
|
|||
}
|
||||
// bound
|
||||
if (this._lower && this._upper) {
|
||||
return IDBKeyRange.bound(
|
||||
return this._IDBKeyRange.bound(
|
||||
encodeKey(roomId, this._lower.fragmentId, this._lower.eventIndex),
|
||||
encodeKey(roomId, this._upper.fragmentId, this._upper.eventIndex),
|
||||
this._lowerOpen,
|
||||
|
@ -107,7 +108,7 @@ export class TimelineEventStore {
|
|||
* @return {Range} the created range
|
||||
*/
|
||||
onlyRange(eventKey) {
|
||||
return new Range(eventKey);
|
||||
return new Range(this._timelineStore.IDBKeyRange, eventKey);
|
||||
}
|
||||
|
||||
/** Creates a range that includes all keys before eventKey, and optionally also the key itself.
|
||||
|
@ -116,7 +117,7 @@ export class TimelineEventStore {
|
|||
* @return {Range} the created range
|
||||
*/
|
||||
upperBoundRange(eventKey, open=false) {
|
||||
return new Range(undefined, undefined, eventKey, undefined, open);
|
||||
return new Range(this._timelineStore.IDBKeyRange, undefined, undefined, eventKey, undefined, open);
|
||||
}
|
||||
|
||||
/** Creates a range that includes all keys after eventKey, and optionally also the key itself.
|
||||
|
@ -125,7 +126,7 @@ export class TimelineEventStore {
|
|||
* @return {Range} the created range
|
||||
*/
|
||||
lowerBoundRange(eventKey, open=false) {
|
||||
return new Range(undefined, eventKey, undefined, open);
|
||||
return new Range(this._timelineStore.IDBKeyRange, undefined, eventKey, undefined, open);
|
||||
}
|
||||
|
||||
/** Creates a range that includes all keys between `lower` and `upper`, and optionally the given keys as well.
|
||||
|
@ -136,7 +137,7 @@ export class TimelineEventStore {
|
|||
* @return {Range} the created range
|
||||
*/
|
||||
boundRange(lower, upper, lowerOpen=false, upperOpen=false) {
|
||||
return new Range(undefined, lower, upper, lowerOpen, upperOpen);
|
||||
return new Range(this._timelineStore.IDBKeyRange, undefined, lower, upper, lowerOpen, upperOpen);
|
||||
}
|
||||
|
||||
/** Looks up the last `amount` entries in the timeline for `roomId`.
|
||||
|
@ -261,7 +262,7 @@ export class TimelineEventStore {
|
|||
removeAllForRoom(roomId) {
|
||||
const minKey = encodeKey(roomId, KeyLimits.minStorageKey, KeyLimits.minStorageKey);
|
||||
const maxKey = encodeKey(roomId, KeyLimits.maxStorageKey, KeyLimits.maxStorageKey);
|
||||
const range = IDBKeyRange.bound(minKey, maxKey);
|
||||
const range = this._timelineStore.IDBKeyRange.bound(minKey, maxKey);
|
||||
this._timelineStore.delete(range);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ export class TimelineFragmentStore {
|
|||
|
||||
_allRange(roomId) {
|
||||
try {
|
||||
return IDBKeyRange.bound(
|
||||
return this._store.IDBKeyRange.bound(
|
||||
encodeKey(roomId, KeyLimits.minStorageKey),
|
||||
encodeKey(roomId, KeyLimits.maxStorageKey)
|
||||
);
|
||||
|
|
|
@ -64,8 +64,8 @@ export function decodeUint32(str) {
|
|||
return parseInt(str, 16);
|
||||
}
|
||||
|
||||
export function openDatabase(name, createObjectStore, version) {
|
||||
const req = indexedDB.open(name, version);
|
||||
export function openDatabase(name, createObjectStore, version, idbFactory = window.indexedDB) {
|
||||
const req = idbFactory.open(name, version);
|
||||
req.onupgradeneeded = (ev) => {
|
||||
const db = ev.target.result;
|
||||
const txn = ev.target.transaction;
|
||||
|
|
Reference in a new issue