diff --git a/src/matrix/storage/idb/schema.ts b/src/matrix/storage/idb/schema.ts index 2aca60a2..2da4d65c 100644 --- a/src/matrix/storage/idb/schema.ts +++ b/src/matrix/storage/idb/schema.ts @@ -6,6 +6,8 @@ import {addRoomToIdentity} from "../../e2ee/DeviceTracker.js"; import {SESSION_E2EE_KEY_PREFIX} from "../../e2ee/common.js"; import {SummaryData} from "../../room/RoomSummary"; import {RoomMemberStore, MemberData} from "./stores/RoomMemberStore"; +import {encodeKey as encodeBackupKey} from "./stores/SessionNeedingBackupStore"; +import {InboundGroupSessionStore, InboundGroupSessionEntry} from "./stores/InboundGroupSessionStore"; import {RoomStateEntry} from "./stores/RoomStateStore"; import {SessionStore} from "./stores/SessionStore"; import {Store} from "./Store"; @@ -39,6 +41,21 @@ export const schema: MigrationFunc[] = [ // TypeScript note: for now, do not bother introducing interfaces / alias // for old schemas. Just take them as `any`. +function createDatabaseNameHelper(db: IDBDatabase): ITransaction { + // the Store object gets passed in several things through the Transaction class (a wrapper around IDBTransaction), + // the only thing we should need here is the databaseName though, so we mock it out. + // ideally we should have an easier way to go from the idb primitive layer to the specific store classes where + // we implement logic, but for now we need this. + const databaseNameHelper: ITransaction = { + databaseName: db.name, + get idbFactory(): IDBFactory { throw new Error("unused");}, + get IDBKeyRange(): typeof IDBKeyRange { throw new Error("unused");}, + addWriteError() {}, + }; + return databaseNameHelper; +} + + // how do we deal with schema updates vs existing data migration in a way that //v1 function createInitialStores(db: IDBDatabase): void { @@ -223,17 +240,7 @@ async function changeSSSSKeyPrefix(db: IDBDatabase, txn: IDBTransaction) { // v13 async function backupAndRestoreE2EEAccountToLocalStorage(db: IDBDatabase, txn: IDBTransaction, localStorage: IDOMStorage, log: ILogItem) { const session = txn.objectStore("session"); - // the Store object gets passed in several things through the Transaction class (a wrapper around IDBTransaction), - // the only thing we should need here is the databaseName though, so we mock it out. - // ideally we should have an easier way to go from the idb primitive layer to the specific store classes where - // we implement logic, but for now we need this. - const databaseNameHelper: ITransaction = { - databaseName: db.name, - get idbFactory(): IDBFactory { throw new Error("unused");}, - get IDBKeyRange(): typeof IDBKeyRange { throw new Error("unused");}, - addWriteError() {}, - }; - const sessionStore = new SessionStore(new Store(session, databaseNameHelper), localStorage); + const sessionStore = new SessionStore(new Store(session, createDatabaseNameHelper(db)), localStorage); // if we already have an e2ee identity, write a backup to local storage. // further updates to e2ee keys in the session store will also write to local storage from 0.2.15 on, // but here we make sure a backup is immediately created after installing the update and we don't wait until @@ -273,6 +280,25 @@ async function clearAllStores(db: IDBDatabase, txn: IDBTransaction) { } // v15 adds the sessionsNeedingBackup store, for key backup -function createSessionsNeedingBackup(db: IDBDatabase): void { - db.createObjectStore("sessionsNeedingBackup", {keyPath: "key"}); +async function createSessionsNeedingBackup(db: IDBDatabase, txn: IDBTransaction, localStorage: IDOMStorage, log: ILogItem): Promise { + const backupStore = db.createObjectStore("sessionsNeedingBackup", {keyPath: "key"}); + const session = txn.objectStore("session"); + const ssssKey = await reqAsPromise(session.get(`${SESSION_E2EE_KEY_PREFIX}ssssKey`) as IDBRequest); + const keyBackupEnabled = !!ssssKey; + log.set("key backup", keyBackupEnabled); + if (keyBackupEnabled) { + let count = 0; + try { + const inboundGroupSessions = txn.objectStore("inboundGroupSessions"); + await iterateCursor(inboundGroupSessions.openCursor(), session => { + backupStore.add({key: encodeBackupKey(session.roomId, session.senderKey, session.sessionId)}); + count += 1; + return NOT_DONE; + }); + } catch (err) { + txn.abort(); + log.log("could not migrate operations").catch(err); + } + log.set("count", count); + } } diff --git a/src/matrix/storage/idb/stores/SessionNeedingBackupStore.ts b/src/matrix/storage/idb/stores/SessionNeedingBackupStore.ts index 71a383bb..327fce9a 100644 --- a/src/matrix/storage/idb/stores/SessionNeedingBackupStore.ts +++ b/src/matrix/storage/idb/stores/SessionNeedingBackupStore.ts @@ -26,7 +26,7 @@ type StorageEntry = { key: string }; -function encodeKey(roomId: string, senderKey: string, sessionId: string): string { +export function encodeKey(roomId: string, senderKey: string, sessionId: string): string { return `${roomId}|${senderKey}|${sessionId}`; }