diff --git a/src/matrix/storage/idb/Storage.js b/src/matrix/storage/idb/Storage.ts similarity index 81% rename from src/matrix/storage/idb/Storage.js rename to src/matrix/storage/idb/Storage.ts index a0814a53..bffdb642 100644 --- a/src/matrix/storage/idb/Storage.js +++ b/src/matrix/storage/idb/Storage.ts @@ -14,28 +14,33 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {Transaction} from "./Transaction.js"; +import {Transaction} from "./Transaction"; import { STORE_NAMES, StoreNames, StorageError } from "../common"; import { reqAsPromise } from "./utils"; const WEBKITEARLYCLOSETXNBUG_BOGUS_KEY = "782rh281re38-boguskey"; export class Storage { - constructor(idbDatabase, IDBKeyRange, hasWebkitEarlyCloseTxnBug) { + private _db: IDBDatabase; + private _hasWebkitEarlyCloseTxnBug: boolean; + private _IDBKeyRange: typeof IDBKeyRange + storeNames: typeof StoreNames; + + constructor(idbDatabase: IDBDatabase, _IDBKeyRange: typeof IDBKeyRange, hasWebkitEarlyCloseTxnBug: boolean) { this._db = idbDatabase; - this._IDBKeyRange = IDBKeyRange; + this._IDBKeyRange = _IDBKeyRange; this._hasWebkitEarlyCloseTxnBug = hasWebkitEarlyCloseTxnBug; this.storeNames = StoreNames; } - _validateStoreNames(storeNames) { + _validateStoreNames(storeNames: StoreNames[]): void { const idx = storeNames.findIndex(name => !STORE_NAMES.includes(name)); if (idx !== -1) { throw new StorageError(`Tried top, a transaction unknown store ${storeNames[idx]}`); } } - async readTxn(storeNames) { + async readTxn(storeNames: StoreNames[]): Promise { this._validateStoreNames(storeNames); try { const txn = this._db.transaction(storeNames, "readonly"); @@ -50,7 +55,7 @@ export class Storage { } } - async readWriteTxn(storeNames) { + async readWriteTxn(storeNames: StoreNames[]): Promise { this._validateStoreNames(storeNames); try { const txn = this._db.transaction(storeNames, "readwrite"); @@ -65,7 +70,7 @@ export class Storage { } } - close() { + close(): void { this._db.close(); } } diff --git a/src/matrix/storage/idb/StorageFactory.js b/src/matrix/storage/idb/StorageFactory.ts similarity index 67% rename from src/matrix/storage/idb/StorageFactory.js rename to src/matrix/storage/idb/StorageFactory.ts index df68823f..6db78c36 100644 --- a/src/matrix/storage/idb/StorageFactory.js +++ b/src/matrix/storage/idb/StorageFactory.ts @@ -14,19 +14,24 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {Storage} from "./Storage.js"; +import {Storage} from "./Storage"; import { openDatabase, reqAsPromise } from "./utils"; -import { exportSession, importSession } from "./export.js"; -import { schema } from "./schema.js"; -import { detectWebkitEarlyCloseTxnBug } from "./quirks.js"; +import { exportSession, importSession, Export } from "./export"; +import { schema } from "./schema"; +import { detectWebkitEarlyCloseTxnBug } from "./quirks"; +import { BaseLogger } from "../../../logging/BaseLogger.js"; -const sessionName = sessionId => `hydrogen_session_${sessionId}`; -const openDatabaseWithSessionId = function(sessionId, idbFactory, log) { +const sessionName = (sessionId: string) => `hydrogen_session_${sessionId}`; +const openDatabaseWithSessionId = function(sessionId: string, idbFactory: IDBFactory, log?: BaseLogger) { const create = (db, txn, oldVersion, version) => createStores(db, txn, oldVersion, version, log); return openDatabase(sessionName(sessionId), create, schema.length, idbFactory); } -async function requestPersistedStorage() { +interface ServiceWorkerHandler { + preventConcurrentSessionAccess: (sessionId: string) => Promise; +} + +async function requestPersistedStorage(): Promise { // don't assume browser so we can run in node with fake-idb const glob = this; if (glob?.navigator?.storage?.persist) { @@ -44,13 +49,17 @@ async function requestPersistedStorage() { } export class StorageFactory { - constructor(serviceWorkerHandler, idbFactory = window.indexedDB, IDBKeyRange = window.IDBKeyRange) { + private _serviceWorkerHandler: ServiceWorkerHandler; + private _idbFactory: IDBFactory; + private _IDBKeyRange: typeof IDBKeyRange + + constructor(serviceWorkerHandler: ServiceWorkerHandler, idbFactory: IDBFactory = window.indexedDB, _IDBKeyRange = window.IDBKeyRange) { this._serviceWorkerHandler = serviceWorkerHandler; this._idbFactory = idbFactory; - this._IDBKeyRange = IDBKeyRange; + this._IDBKeyRange = _IDBKeyRange; } - async create(sessionId, log) { + async create(sessionId: string, log?: BaseLogger): Promise { await this._serviceWorkerHandler?.preventConcurrentSessionAccess(sessionId); requestPersistedStorage().then(persisted => { // Firefox lies here though, and returns true even if the user denied the request @@ -64,24 +73,24 @@ export class StorageFactory { return new Storage(db, this._IDBKeyRange, hasWebkitEarlyCloseTxnBug); } - delete(sessionId) { + delete(sessionId: string): Promise { const databaseName = sessionName(sessionId); const req = this._idbFactory.deleteDatabase(databaseName); return reqAsPromise(req); } - async export(sessionId) { + async export(sessionId: string): Promise { const db = await openDatabaseWithSessionId(sessionId, this._idbFactory); return await exportSession(db); } - async import(sessionId, data) { + async import(sessionId: string, data: Export): Promise { const db = await openDatabaseWithSessionId(sessionId, this._idbFactory); return await importSession(db, data); } } -async function createStores(db, txn, oldVersion, version, log) { +async function createStores(db: IDBDatabase, txn: IDBTransaction, oldVersion: number | null, version: number, log?: BaseLogger): Promise { const startIdx = oldVersion || 0; return log.wrap({l: "storage migration", oldVersion, version}, async log => { for(let i = startIdx; i < version; ++i) { diff --git a/src/matrix/storage/idb/Transaction.js b/src/matrix/storage/idb/Transaction.js deleted file mode 100644 index 8b85b795..00000000 --- a/src/matrix/storage/idb/Transaction.js +++ /dev/null @@ -1,142 +0,0 @@ -/* -Copyright 2020 Bruno Windels - -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. -*/ - -import {txnAsPromise} from "./utils"; -import {StorageError} from "../common"; -import {Store} from "./Store"; -import {SessionStore} from "./stores/SessionStore"; -import {RoomSummaryStore} from "./stores/RoomSummaryStore"; -import {InviteStore} from "./stores/InviteStore"; -import {TimelineEventStore} from "./stores/TimelineEventStore"; -import {TimelineRelationStore} from "./stores/TimelineRelationStore"; -import {RoomStateStore} from "./stores/RoomStateStore"; -import {RoomMemberStore} from "./stores/RoomMemberStore"; -import {TimelineFragmentStore} from "./stores/TimelineFragmentStore"; -import {PendingEventStore} from "./stores/PendingEventStore"; -import {UserIdentityStore} from "./stores/UserIdentityStore"; -import {DeviceIdentityStore} from "./stores/DeviceIdentityStore"; -import {OlmSessionStore} from "./stores/OlmSessionStore"; -import {InboundGroupSessionStore} from "./stores/InboundGroupSessionStore"; -import {OutboundGroupSessionStore} from "./stores/OutboundGroupSessionStore"; -import {GroupSessionDecryptionStore} from "./stores/GroupSessionDecryptionStore"; -import {OperationStore} from "./stores/OperationStore"; -import {AccountDataStore} from "./stores/AccountDataStore"; - -export class Transaction { - constructor(txn, allowedStoreNames, IDBKeyRange) { - this._txn = txn; - this._allowedStoreNames = allowedStoreNames; - this._stores = {}; - this.IDBKeyRange = IDBKeyRange; - } - - _idbStore(name) { - if (!this._allowedStoreNames.includes(name)) { - // 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), this); - } - - _store(name, mapStore) { - if (!this._stores[name]) { - const idbStore = this._idbStore(name); - this._stores[name] = mapStore(idbStore); - } - return this._stores[name]; - } - - get session() { - return this._store("session", idbStore => new SessionStore(idbStore)); - } - - get roomSummary() { - return this._store("roomSummary", idbStore => new RoomSummaryStore(idbStore)); - } - - get archivedRoomSummary() { - return this._store("archivedRoomSummary", idbStore => new RoomSummaryStore(idbStore)); - } - - get invites() { - return this._store("invites", idbStore => new InviteStore(idbStore)); - } - - get timelineFragments() { - return this._store("timelineFragments", idbStore => new TimelineFragmentStore(idbStore)); - } - - get timelineEvents() { - return this._store("timelineEvents", idbStore => new TimelineEventStore(idbStore)); - } - - get timelineRelations() { - return this._store("timelineRelations", idbStore => new TimelineRelationStore(idbStore)); - } - - get roomState() { - return this._store("roomState", idbStore => new RoomStateStore(idbStore)); - } - - get roomMembers() { - return this._store("roomMembers", idbStore => new RoomMemberStore(idbStore)); - } - - get pendingEvents() { - return this._store("pendingEvents", idbStore => new PendingEventStore(idbStore)); - } - - get userIdentities() { - return this._store("userIdentities", idbStore => new UserIdentityStore(idbStore)); - } - - get deviceIdentities() { - return this._store("deviceIdentities", idbStore => new DeviceIdentityStore(idbStore)); - } - - get olmSessions() { - return this._store("olmSessions", idbStore => new OlmSessionStore(idbStore)); - } - - get inboundGroupSessions() { - return this._store("inboundGroupSessions", idbStore => new InboundGroupSessionStore(idbStore)); - } - - get outboundGroupSessions() { - return this._store("outboundGroupSessions", idbStore => new OutboundGroupSessionStore(idbStore)); - } - - get groupSessionDecryptions() { - return this._store("groupSessionDecryptions", idbStore => new GroupSessionDecryptionStore(idbStore)); - } - - get operations() { - return this._store("operations", idbStore => new OperationStore(idbStore)); - } - - get accountData() { - return this._store("accountData", idbStore => new AccountDataStore(idbStore)); - } - - complete() { - return txnAsPromise(this._txn); - } - - abort() { - // TODO: should we wrap the exception in a StorageError? - this._txn.abort(); - } -} diff --git a/src/matrix/storage/idb/Transaction.ts b/src/matrix/storage/idb/Transaction.ts new file mode 100644 index 00000000..ea21b745 --- /dev/null +++ b/src/matrix/storage/idb/Transaction.ts @@ -0,0 +1,148 @@ +/* +Copyright 2020 Bruno Windels + +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. +*/ + +import {StoreNames} from "../common"; +import {txnAsPromise} from "./utils"; +import {StorageError} from "../common"; +import {Store} from "./Store"; +import {SessionStore} from "./stores/SessionStore"; +import {RoomSummaryStore} from "./stores/RoomSummaryStore"; +import {InviteStore} from "./stores/InviteStore"; +import {TimelineEventStore} from "./stores/TimelineEventStore"; +import {TimelineRelationStore} from "./stores/TimelineRelationStore"; +import {RoomStateStore} from "./stores/RoomStateStore"; +import {RoomMemberStore} from "./stores/RoomMemberStore"; +import {TimelineFragmentStore} from "./stores/TimelineFragmentStore"; +import {PendingEventStore} from "./stores/PendingEventStore"; +import {UserIdentityStore} from "./stores/UserIdentityStore"; +import {DeviceIdentityStore} from "./stores/DeviceIdentityStore"; +import {OlmSessionStore} from "./stores/OlmSessionStore"; +import {InboundGroupSessionStore} from "./stores/InboundGroupSessionStore"; +import {OutboundGroupSessionStore} from "./stores/OutboundGroupSessionStore"; +import {GroupSessionDecryptionStore} from "./stores/GroupSessionDecryptionStore"; +import {OperationStore} from "./stores/OperationStore"; +import {AccountDataStore} from "./stores/AccountDataStore"; + +export class Transaction { + private _txn: IDBTransaction; + private _allowedStoreNames: StoreNames[]; + private _stores: { [storeName in StoreNames]?: any }; + + constructor(txn: IDBTransaction, allowedStoreNames: StoreNames[], IDBKeyRange) { + this._txn = txn; + this._allowedStoreNames = allowedStoreNames; + this._stores = {}; + // @ts-ignore + this.IDBKeyRange = IDBKeyRange; + } + + _idbStore(name: StoreNames): Store { + if (!this._allowedStoreNames.includes(name)) { + // 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), this); + } + + _store(name: StoreNames, mapStore: (idbStore: Store) => T): T { + if (!this._stores[name]) { + const idbStore = this._idbStore(name); + this._stores[name] = mapStore(idbStore); + } + return this._stores[name]; + } + + get session(): SessionStore { + return this._store(StoreNames.session, idbStore => new SessionStore(idbStore)); + } + + get roomSummary(): RoomSummaryStore { + return this._store(StoreNames.roomSummary, idbStore => new RoomSummaryStore(idbStore)); + } + + get archivedRoomSummary(): RoomSummaryStore { + return this._store(StoreNames.archivedRoomSummary, idbStore => new RoomSummaryStore(idbStore)); + } + + get invites(): InviteStore { + return this._store(StoreNames.invites, idbStore => new InviteStore(idbStore)); + } + + get timelineFragments(): TimelineFragmentStore { + return this._store(StoreNames.timelineFragments, idbStore => new TimelineFragmentStore(idbStore)); + } + + get timelineEvents(): TimelineEventStore { + return this._store(StoreNames.timelineEvents, idbStore => new TimelineEventStore(idbStore)); + } + + get timelineRelations(): TimelineRelationStore { + return this._store(StoreNames.timelineRelations, idbStore => new TimelineRelationStore(idbStore)); + } + + get roomState(): RoomStateStore { + return this._store(StoreNames.roomState, idbStore => new RoomStateStore(idbStore)); + } + + get roomMembers(): RoomMemberStore { + return this._store(StoreNames.roomMembers, idbStore => new RoomMemberStore(idbStore)); + } + + get pendingEvents(): PendingEventStore { + return this._store(StoreNames.pendingEvents, idbStore => new PendingEventStore(idbStore)); + } + + get userIdentities(): UserIdentityStore { + return this._store(StoreNames.userIdentities, idbStore => new UserIdentityStore(idbStore)); + } + + get deviceIdentities(): DeviceIdentityStore { + return this._store(StoreNames.deviceIdentities, idbStore => new DeviceIdentityStore(idbStore)); + } + + get olmSessions(): OlmSessionStore { + return this._store(StoreNames.olmSessions, idbStore => new OlmSessionStore(idbStore)); + } + + get inboundGroupSessions(): InboundGroupSessionStore { + return this._store(StoreNames.inboundGroupSessions, idbStore => new InboundGroupSessionStore(idbStore)); + } + + get outboundGroupSessions(): OutboundGroupSessionStore { + return this._store(StoreNames.outboundGroupSessions, idbStore => new OutboundGroupSessionStore(idbStore)); + } + + get groupSessionDecryptions(): GroupSessionDecryptionStore { + return this._store(StoreNames.groupSessionDecryptions, idbStore => new GroupSessionDecryptionStore(idbStore)); + } + + get operations(): OperationStore { + return this._store(StoreNames.operations, idbStore => new OperationStore(idbStore)); + } + + get accountData(): AccountDataStore { + return this._store(StoreNames.accountData, idbStore => new AccountDataStore(idbStore)); + } + + complete(): Promise { + return txnAsPromise(this._txn); + } + + abort(): void { + // TODO: should we wrap the exception in a StorageError? + this._txn.abort(); + } +} diff --git a/src/matrix/storage/idb/export.js b/src/matrix/storage/idb/export.ts similarity index 68% rename from src/matrix/storage/idb/export.js rename to src/matrix/storage/idb/export.ts index 27979ce0..a9d58ee4 100644 --- a/src/matrix/storage/idb/export.js +++ b/src/matrix/storage/idb/export.ts @@ -14,25 +14,26 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { iterateCursor, txnAsPromise } from "./utils"; -import { STORE_NAMES } from "../common"; +import { iterateCursor, NOT_DONE, txnAsPromise } from "./utils"; +import { STORE_NAMES, StoreNames } from "../common"; -export async function exportSession(db) { - const NOT_DONE = {done: false}; +export type Export = { [storeName in StoreNames] : any[] } + +export async function exportSession(db: IDBDatabase): Promise { const txn = db.transaction(STORE_NAMES, "readonly"); const data = {}; await Promise.all(STORE_NAMES.map(async name => { - const results = data[name] = []; // initialize in deterministic order + const results: any[] = data[name] = []; // initialize in deterministic order const store = txn.objectStore(name); - await iterateCursor(store.openCursor(), (value) => { + await iterateCursor(store.openCursor(), (value) => { results.push(value); return NOT_DONE; }); })); - return data; + return data as Export; } -export async function importSession(db, data) { +export async function importSession(db: IDBDatabase, data: Export): Promise { const txn = db.transaction(STORE_NAMES, "readwrite"); for (const name of STORE_NAMES) { const store = txn.objectStore(name); diff --git a/src/matrix/storage/idb/quirks.js b/src/matrix/storage/idb/quirks.ts similarity index 94% rename from src/matrix/storage/idb/quirks.js rename to src/matrix/storage/idb/quirks.ts index 5eaa6836..e739da84 100644 --- a/src/matrix/storage/idb/quirks.js +++ b/src/matrix/storage/idb/quirks.ts @@ -18,7 +18,7 @@ limitations under the License. import {openDatabase, txnAsPromise, reqAsPromise} from "./utils"; // filed as https://bugs.webkit.org/show_bug.cgi?id=222746 -export async function detectWebkitEarlyCloseTxnBug(idbFactory) { +export async function detectWebkitEarlyCloseTxnBug(idbFactory: IDBFactory): Promise { const dbName = "hydrogen_webkit_test_inactive_txn_bug"; try { const db = await openDatabase(dbName, db => { diff --git a/src/matrix/storage/idb/schema.js b/src/matrix/storage/idb/schema.ts similarity index 76% rename from src/matrix/storage/idb/schema.js rename to src/matrix/storage/idb/schema.ts index 8313c02c..a072e34f 100644 --- a/src/matrix/storage/idb/schema.js +++ b/src/matrix/storage/idb/schema.ts @@ -1,7 +1,9 @@ -import {iterateCursor, reqAsPromise} from "./utils"; +import {iterateCursor, NOT_DONE, reqAsPromise} from "./utils"; import {RoomMember, EVENT_TYPE as MEMBER_EVENT_TYPE} from "../../room/members/RoomMember.js"; import {addRoomToIdentity} from "../../e2ee/DeviceTracker.js"; -import {RoomMemberStore} from "./stores/RoomMemberStore"; +import {SummaryData} from "../../room/RoomSummary"; +import {RoomMemberStore, MemberData} from "./stores/RoomMemberStore"; +import {RoomStateEntry} from "./stores/RoomStateStore"; import {SessionStore} from "./stores/SessionStore"; import {encodeScopeTypeKey} from "./stores/OperationStore"; import {MAX_UNICODE} from "./stores/common"; @@ -23,10 +25,12 @@ export const schema = [ ]; // TODO: how to deal with git merge conflicts of this array? +// TypeScript note: for now, do not bother introducing interfaces / alias +// for old schemas. Just take them as `any`. // how do we deal with schema updates vs existing data migration in a way that //v1 -function createInitialStores(db) { +function createInitialStores(db: IDBDatabase): void { db.createObjectStore("session", {keyPath: "key"}); // any way to make keys unique here? (just use put?) db.createObjectStore("roomSummary", {keyPath: "roomId"}); @@ -43,11 +47,12 @@ function createInitialStores(db) { db.createObjectStore("pendingEvents", {keyPath: "key"}); } //v2 -async function createMemberStore(db, txn) { - const roomMembers = new RoomMemberStore(db.createObjectStore("roomMembers", {keyPath: "key"})); +async function createMemberStore(db: IDBDatabase, txn: IDBTransaction): Promise { + // Cast ok here because only "set" is used + const roomMembers = new RoomMemberStore(db.createObjectStore("roomMembers", {keyPath: "key"}) as any); // migrate existing member state events over const roomState = txn.objectStore("roomState"); - await iterateCursor(roomState.openCursor(), entry => { + await iterateCursor(roomState.openCursor(), entry => { if (entry.event.type === MEMBER_EVENT_TYPE) { roomState.delete(entry.key); const member = RoomMember.fromMemberEvent(entry.roomId, entry.event); @@ -55,10 +60,11 @@ async function createMemberStore(db, txn) { roomMembers.set(member.serialize()); } } + return NOT_DONE; }); } //v3 -async function migrateSession(db, txn) { +async function migrateSession(db: IDBDatabase, txn: IDBTransaction): Promise { const session = txn.objectStore("session"); try { const PRE_MIGRATION_KEY = 1; @@ -66,7 +72,8 @@ async function migrateSession(db, txn) { if (entry) { session.delete(PRE_MIGRATION_KEY); const {syncToken, syncFilterId, serverVersions} = entry.value; - const store = new SessionStore(session); + // Cast ok here because only "set" is used and we don't look into return + const store = new SessionStore(session as any); store.set("sync", {token: syncToken, filterId: syncFilterId}); store.set("serverVersions", serverVersions); } @@ -76,7 +83,7 @@ async function migrateSession(db, txn) { } } //v4 -function createE2EEStores(db) { +function createE2EEStores(db: IDBDatabase): void { db.createObjectStore("userIdentities", {keyPath: "userId"}); const deviceIdentities = db.createObjectStore("deviceIdentities", {keyPath: "key"}); deviceIdentities.createIndex("byCurve25519Key", "curve25519Key", {unique: true}); @@ -89,13 +96,14 @@ function createE2EEStores(db) { } // v5 -async function migrateEncryptionFlag(db, txn) { +async function migrateEncryptionFlag(db: IDBDatabase, txn: IDBTransaction): Promise { // migrate room summary isEncrypted -> encryption prop const roomSummary = txn.objectStore("roomSummary"); const roomState = txn.objectStore("roomState"); - const summaries = []; - await iterateCursor(roomSummary.openCursor(), summary => { + const summaries: any[] = []; + await iterateCursor(roomSummary.openCursor(), summary => { summaries.push(summary); + return NOT_DONE; }); for (const summary of summaries) { const encryptionEntry = await reqAsPromise(roomState.get(`${summary.roomId}|m.room.encryption|`)); @@ -108,31 +116,32 @@ async function migrateEncryptionFlag(db, txn) { } // v6 -function createAccountDataStore(db) { +function createAccountDataStore(db: IDBDatabase): void { db.createObjectStore("accountData", {keyPath: "type"}); } // v7 -function createInviteStore(db) { +function createInviteStore(db: IDBDatabase): void { db.createObjectStore("invites", {keyPath: "roomId"}); } // v8 -function createArchivedRoomSummaryStore(db) { +function createArchivedRoomSummaryStore(db: IDBDatabase): void { db.createObjectStore("archivedRoomSummary", {keyPath: "summary.roomId"}); } // v9 -async function migrateOperationScopeIndex(db, txn) { +async function migrateOperationScopeIndex(db: IDBDatabase, txn: IDBTransaction): Promise { try { const operations = txn.objectStore("operations"); operations.deleteIndex("byTypeAndScope"); - await iterateCursor(operations.openCursor(), (op, key, cur) => { + await iterateCursor(operations.openCursor(), (op, key, cur) => { const {typeScopeKey} = op; delete op.typeScopeKey; const [type, scope] = typeScopeKey.split("|"); op.scopeTypeKey = encodeScopeTypeKey(scope, type); cur.update(op); + return NOT_DONE; }); operations.createIndex("byScopeAndType", "scopeTypeKey", {unique: false}); } catch (err) { @@ -142,31 +151,33 @@ async function migrateOperationScopeIndex(db, txn) { } //v10 -function createTimelineRelationsStore(db) { +function createTimelineRelationsStore(db: IDBDatabase) : void { db.createObjectStore("timelineRelations", {keyPath: "key"}); } //v11 doesn't change the schema, but ensures all userIdentities have all the roomIds they should (see #470) async function fixMissingRoomsInUserIdentities(db, txn, log) { const roomSummaryStore = txn.objectStore("roomSummary"); - const trackedRoomIds = []; - await iterateCursor(roomSummaryStore.openCursor(), roomSummary => { + const trackedRoomIds: string[] = []; + await iterateCursor(roomSummaryStore.openCursor(), roomSummary => { if (roomSummary.isTrackingMembers) { trackedRoomIds.push(roomSummary.roomId); } + return NOT_DONE; }); const outboundGroupSessionsStore = txn.objectStore("outboundGroupSessions"); - const userIdentitiesStore = txn.objectStore("userIdentities"); + const userIdentitiesStore: IDBObjectStore = txn.objectStore("userIdentities"); const roomMemberStore = txn.objectStore("roomMembers"); for (const roomId of trackedRoomIds) { let foundMissing = false; - const joinedUserIds = []; + const joinedUserIds: string[] = []; const memberRange = IDBKeyRange.bound(roomId, `${roomId}|${MAX_UNICODE}`, true, true); await log.wrap({l: "room", id: roomId}, async log => { - await iterateCursor(roomMemberStore.openCursor(memberRange), member => { + await iterateCursor(roomMemberStore.openCursor(memberRange), member => { if (member.membership === "join") { joinedUserIds.push(member.userId); } + return NOT_DONE; }); log.set("joinedUserIds", joinedUserIds.length); for (const userId of joinedUserIds) { diff --git a/src/mocks/Storage.js b/src/mocks/Storage.js index 48e7056c..359ffa2c 100644 --- a/src/mocks/Storage.js +++ b/src/mocks/Storage.js @@ -15,7 +15,7 @@ limitations under the License. */ import {FDBFactory, FDBKeyRange} from "../../lib/fake-indexeddb/index.js"; -import {StorageFactory} from "../matrix/storage/idb/StorageFactory.js"; +import {StorageFactory} from "../matrix/storage/idb/StorageFactory"; import {NullLogItem} from "../logging/NullLogger.js"; export function createMockStorage() { diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 40f47101..1530ed12 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -16,7 +16,7 @@ limitations under the License. import {createFetchRequest} from "./dom/request/fetch.js"; import {xhrRequest} from "./dom/request/xhr.js"; -import {StorageFactory} from "../../matrix/storage/idb/StorageFactory.js"; +import {StorageFactory} from "../../matrix/storage/idb/StorageFactory"; import {SessionInfoStorage} from "../../matrix/sessioninfo/localstorage/SessionInfoStorage.js"; import {SettingsStorage} from "./dom/SettingsStorage.js"; import {Encoding} from "./utils/Encoding.js";