diff --git a/src/matrix/Session.js b/src/matrix/Session.js index 3a89dcf3..36a9b866 100644 --- a/src/matrix/Session.js +++ b/src/matrix/Session.js @@ -147,6 +147,7 @@ export class Session { this.needsSessionBackup.set(true) } }, + clock: this._clock }); } diff --git a/src/matrix/e2ee/RoomEncryption.js b/src/matrix/e2ee/RoomEncryption.js index 44caae1b..87c64cbe 100644 --- a/src/matrix/e2ee/RoomEncryption.js +++ b/src/matrix/e2ee/RoomEncryption.js @@ -21,8 +21,17 @@ import {makeTxnId} from "../common.js"; const ENCRYPTED_TYPE = "m.room.encrypted"; +function encodeMissingSessionKey(senderKey, sessionId) { + return `${senderKey}|${sessionId}`; +} + +function decodeMissingSessionKey(key) { + const [senderKey, sessionId] = key.split("|"); + return {senderKey, sessionId}; +} + export class RoomEncryption { - constructor({room, deviceTracker, olmEncryption, megolmEncryption, megolmDecryption, encryptionParams, storage, sessionBackup, notifyMissingMegolmSession}) { + constructor({room, deviceTracker, olmEncryption, megolmEncryption, megolmDecryption, encryptionParams, storage, sessionBackup, notifyMissingMegolmSession, clock}) { this._room = room; this._deviceTracker = deviceTracker; this._olmEncryption = olmEncryption; @@ -39,6 +48,7 @@ export class RoomEncryption { this._storage = storage; this._sessionBackup = sessionBackup; this._notifyMissingMegolmSession = notifyMissingMegolmSession; + this._clock = clock; this._disposed = false; } @@ -48,8 +58,8 @@ export class RoomEncryption { } this._sessionBackup = sessionBackup; for(const key of this._eventIdsByMissingSession.keys()) { - const [senderKey, sessionId] = key.split("|"); - await this._requestMissingSessionFromBackup(senderKey, sessionId); + const {senderKey, sessionId} = decodeMissingSessionKey(key); + await this._requestMissingSessionFromBackup(senderKey, sessionId, null); } } @@ -95,7 +105,7 @@ export class RoomEncryption { } else if (source === DecryptionSource.Retry) { // when retrying, we could have mixed events from at the bottom of the timeline (sync) // and somewhere else, so create a custom cache we use just for this operation. - customCache = this._megolmEncryption.createSessionCache(); + customCache = this._megolmDecryption.createSessionCache(); sessionCache = customCache; } else { throw new Error("Unknown source: " + source); @@ -105,13 +115,13 @@ export class RoomEncryption { if (customCache) { customCache.dispose(); } - return new DecryptionPreparation(preparation, errors, {isTimelineOpen}, this); + return new DecryptionPreparation(preparation, errors, {isTimelineOpen, source}, this); } async _processDecryptionResults(results, errors, flags, txn) { for (const error of errors.values()) { if (error.code === "MEGOLM_NO_SESSION") { - this._addMissingSessionEvent(error.event); + this._addMissingSessionEvent(error.event, flags.source); } } if (flags.isTimelineOpen) { @@ -134,25 +144,34 @@ export class RoomEncryption { } } - _addMissingSessionEvent(event) { + _addMissingSessionEvent(event, source) { const senderKey = event.content?.["sender_key"]; const sessionId = event.content?.["session_id"]; - const key = `${senderKey}|${sessionId}`; + const key = encodeMissingSessionKey(senderKey, sessionId); let eventIds = this._eventIdsByMissingSession.get(key); // new missing session if (!eventIds) { - this._requestMissingSessionFromBackup(senderKey, sessionId); + this._requestMissingSessionFromBackup(senderKey, sessionId, source); eventIds = new Set(); this._eventIdsByMissingSession.set(key, eventIds); } eventIds.add(event.event_id); } - async _requestMissingSessionFromBackup(senderKey, sessionId) { + async _requestMissingSessionFromBackup(senderKey, sessionId, source) { if (!this._sessionBackup) { this._notifyMissingMegolmSession(); return; } + // if the message came from sync, wait 10s to see if the room key arrives, + // and only after that proceed to request from backup + if (source === DecryptionSource.Sync) { + await this._clock.createTimeout(10000).elapsed(); + if (this._disposed || !this._eventIdsByMissingSession.has(encodeMissingSessionKey(senderKey, sessionId))) { + return; + } + } + try { const session = await this._sessionBackup.getSession(this._room.id, sessionId); if (session?.algorithm === MEGOLM_ALGORITHM) { @@ -196,7 +215,7 @@ export class RoomEncryption { // retry decryption with the new sessions const retryEventIds = []; for (const roomKey of roomKeys) { - const key = `${roomKey.senderKey}|${roomKey.sessionId}`; + const key = encodeMissingSessionKey(roomKey.senderKey, roomKey.sessionId); const entriesForSession = this._eventIdsByMissingSession.get(key); if (entriesForSession) { this._eventIdsByMissingSession.delete(key);