From fb33bc7e0790272b5644296ca665769e553d6649 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 6 May 2021 11:46:14 +0100 Subject: [PATCH 01/15] Lazily decrypt event on room view --- src/client.js | 10 +++++++--- src/models/room.js | 14 ++++++++++++++ src/sync.js | 32 ++++++++++++++++++++++++++++---- 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/src/client.js b/src/client.js index 63514c2e5eb..fc76aef3358 100644 --- a/src/client.js +++ b/src/client.js @@ -5554,8 +5554,9 @@ function _resolve(callback, resolve, res) { resolve(res); } -function _PojoToMatrixEventMapper(client, options) { - const preventReEmit = Boolean(options && options.preventReEmit); +function _PojoToMatrixEventMapper(client, options = {}) { + const preventReEmit = Boolean(options.preventReEmit); + const decrypt = options.decrypt === true; function mapper(plainOldJsObject) { const event = new MatrixEvent(plainOldJsObject); if (event.isEncrypted()) { @@ -5564,7 +5565,9 @@ function _PojoToMatrixEventMapper(client, options) { "Event.decrypted", ]); } - event.attemptDecryption(client._crypto); + if (decrypt) { + event.attemptDecryption(client._crypto); + } } if (!preventReEmit) { client.reEmitter.reEmit(event, ["Event.replaced"]); @@ -5577,6 +5580,7 @@ function _PojoToMatrixEventMapper(client, options) { /** * @param {object} [options] * @param {bool} options.preventReEmit don't reemit events emitted on an event mapped by this mapper on the client + * @param {bool} options.decrypt decrypt event proactively * @return {Function} */ MatrixClient.prototype.getEventMapper = function(options = undefined) { diff --git a/src/models/room.js b/src/models/room.js index b573c062248..d928e63e50b 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -228,6 +228,20 @@ function pendingEventsKey(roomId) { utils.inherits(Room, EventEmitter); +Room.prototype.lazyDecryptEvents = async function() { + return Promise.allSettled(this + .getUnfilteredTimelineSet() + .getTimelines() + .reduce((decryptionPromises, timeline) => { + return decryptionPromises.concat( + timeline + .getEvents() + .filter(event => event.isEncrypted() && !event.isBeingDecrypted()) + .map(event => event.attemptDecryption(this._client._crypto, true)) + ); + }, [])); +} + /** * Gets the version of the room * @returns {string} The version of the room, or null if it could not be determined diff --git a/src/sync.js b/src/sync.js index d4809109181..14e602a26b1 100644 --- a/src/sync.js +++ b/src/sync.js @@ -701,7 +701,31 @@ SyncApi.prototype._syncFromCache = async function(savedSync) { }; try { - await this._processSyncResponse(syncEventData, data); + await this._processSyncResponse(syncEventData, data, false); + + const breadcrumbs = this.client.store.getAccountData("im.vector.setting.breadcrumbs"); + const breadcrumbsRooms = breadcrumbs?.getContent().recent_rooms || []; + + this.client.getRooms().forEach(room => { + const readReceipt = room.getAccountData("m.fully_read"); + + const events = room.getLiveTimeline().getEvents(); + const isInBreadcrumb = breadcrumbsRooms.includes(room.roomId); + const readReceiptTimelineIndex = events.findIndex(matrixEvent => { + return matrixEvent.event.event_id === readReceipt?.getContent().event_id + }); + const decryptFromIndex = isInBreadcrumb + ? 0 + : readReceiptTimelineIndex + + events + .slice(decryptFromIndex) + .forEach(event => { + if (event.isEncrypted() && !event.isBeingDecrypted()) { + event.attemptDecryption(this.client._crypto, true); + } + }); + }); } catch (e) { logger.error("Error processing cached sync", e.stack || e); } @@ -942,7 +966,7 @@ SyncApi.prototype._onSyncError = function(err, syncOptions) { * @param {Object} data The response from /sync */ SyncApi.prototype._processSyncResponse = async function( - syncEventData, data, + syncEventData, data, decrypt = true ) { const client = this.client; const self = this; @@ -1158,7 +1182,7 @@ SyncApi.prototype._processSyncResponse = async function( await utils.promiseMapSeries(joinRooms, async function(joinObj) { const room = joinObj.room; const stateEvents = self._mapSyncEventsFormat(joinObj.state, room); - const timelineEvents = self._mapSyncEventsFormat(joinObj.timeline, room); + const timelineEvents = self._mapSyncEventsFormat(joinObj.timeline, room, decrypt); const ephemeralEvents = self._mapSyncEventsFormat(joinObj.ephemeral); const accountDataEvents = self._mapSyncEventsFormat(joinObj.account_data); @@ -1518,7 +1542,7 @@ SyncApi.prototype._mapSyncResponseToRoomArray = function(obj) { * @param {Room} room * @return {MatrixEvent[]} */ -SyncApi.prototype._mapSyncEventsFormat = function(obj, room) { +SyncApi.prototype._mapSyncEventsFormat = function(obj, room, decrypt = true) { if (!obj || !utils.isArray(obj.events)) { return []; } From 8820619e82d3b6f5859309386732ed45a3b71ed3 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 6 May 2021 12:34:21 +0100 Subject: [PATCH 02/15] Pass decrypt flag to event mapper --- src/sync.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sync.js b/src/sync.js index 14e602a26b1..177e063bbc1 100644 --- a/src/sync.js +++ b/src/sync.js @@ -1547,6 +1547,7 @@ SyncApi.prototype._mapSyncEventsFormat = function(obj, room, decrypt = true) { return []; } const mapper = this.client.getEventMapper(); + const mapper = this.client.getEventMapper({ decrypt }); return obj.events.map(function(e) { if (room) { e.room_id = room.roomId; From 19d6dbaa5284d4085c7c6b0a1315552a02693426 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 6 May 2021 14:07:38 +0100 Subject: [PATCH 03/15] Use read receipt instead of read marker --- src/sync.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sync.js b/src/sync.js index 177e063bbc1..5db8ec7fbe0 100644 --- a/src/sync.js +++ b/src/sync.js @@ -707,12 +707,12 @@ SyncApi.prototype._syncFromCache = async function(savedSync) { const breadcrumbsRooms = breadcrumbs?.getContent().recent_rooms || []; this.client.getRooms().forEach(room => { - const readReceipt = room.getAccountData("m.fully_read"); + const readReceiptEventId = room.getEventReadUpTo(this.client.getUserId(), true); const events = room.getLiveTimeline().getEvents(); const isInBreadcrumb = breadcrumbsRooms.includes(room.roomId); const readReceiptTimelineIndex = events.findIndex(matrixEvent => { - return matrixEvent.event.event_id === readReceipt?.getContent().event_id + return matrixEvent.event.event_id === readReceiptEventId }); const decryptFromIndex = isInBreadcrumb ? 0 From 444eac5c6e045f2b99f01a4e14964624dd030980 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Fri, 7 May 2021 11:23:59 +0100 Subject: [PATCH 04/15] consolidate critical event decryption implementation --- src/models/room.js | 36 +++++++++++++++++++++++++----------- src/sync.js | 38 +++++++++----------------------------- 2 files changed, 34 insertions(+), 40 deletions(-) diff --git a/src/models/room.js b/src/models/room.js index d928e63e50b..3d6eaa08d07 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -228,18 +228,32 @@ function pendingEventsKey(roomId) { utils.inherits(Room, EventEmitter); -Room.prototype.lazyDecryptEvents = async function() { - return Promise.allSettled(this +Room.prototype.decryptCriticalEvents = function() { + const readReceiptEventId = this.getEventReadUpTo(this._client.getUserId(), true); + const events = this.getLiveTimeline().getEvents(); + const readReceiptTimelineIndex = events.findIndex(matrixEvent => { + return matrixEvent.event.event_id === readReceiptEventId + }); + + const decryptionPromises = events + .slice(readReceiptTimelineIndex) + .filter(event => event.isEncrypted() && !event.isBeingDecrypted()) + .reverse() + .map(event => event.attemptDecryption(this._client._crypto, true)); + + return Promise.allSettled(decryptionPromises); +} + +Room.prototype.decryptAllEvents = function() { + const decryptionPromises = this .getUnfilteredTimelineSet() - .getTimelines() - .reduce((decryptionPromises, timeline) => { - return decryptionPromises.concat( - timeline - .getEvents() - .filter(event => event.isEncrypted() && !event.isBeingDecrypted()) - .map(event => event.attemptDecryption(this._client._crypto, true)) - ); - }, [])); + .getLiveTimeline() + .getEvents() + .filter(event => event.isEncrypted() && !event.isBeingDecrypted()) + .reverse() + .map(event => event.attemptDecryption(this._client._crypto, true)); + + return Promise.allSettled(decryptionPromises); } /** diff --git a/src/sync.js b/src/sync.js index 5db8ec7fbe0..e6d1edcc96f 100644 --- a/src/sync.js +++ b/src/sync.js @@ -701,31 +701,7 @@ SyncApi.prototype._syncFromCache = async function(savedSync) { }; try { - await this._processSyncResponse(syncEventData, data, false); - - const breadcrumbs = this.client.store.getAccountData("im.vector.setting.breadcrumbs"); - const breadcrumbsRooms = breadcrumbs?.getContent().recent_rooms || []; - - this.client.getRooms().forEach(room => { - const readReceiptEventId = room.getEventReadUpTo(this.client.getUserId(), true); - - const events = room.getLiveTimeline().getEvents(); - const isInBreadcrumb = breadcrumbsRooms.includes(room.roomId); - const readReceiptTimelineIndex = events.findIndex(matrixEvent => { - return matrixEvent.event.event_id === readReceiptEventId - }); - const decryptFromIndex = isInBreadcrumb - ? 0 - : readReceiptTimelineIndex - - events - .slice(decryptFromIndex) - .forEach(event => { - if (event.isEncrypted() && !event.isBeingDecrypted()) { - event.attemptDecryption(this.client._crypto, true); - } - }); - }); + await this._processSyncResponse(syncEventData, data); } catch (e) { logger.error("Error processing cached sync", e.stack || e); } @@ -966,7 +942,7 @@ SyncApi.prototype._onSyncError = function(err, syncOptions) { * @param {Object} data The response from /sync */ SyncApi.prototype._processSyncResponse = async function( - syncEventData, data, decrypt = true + syncEventData, data ) { const client = this.client; const self = this; @@ -1182,10 +1158,11 @@ SyncApi.prototype._processSyncResponse = async function( await utils.promiseMapSeries(joinRooms, async function(joinObj) { const room = joinObj.room; const stateEvents = self._mapSyncEventsFormat(joinObj.state, room); - const timelineEvents = self._mapSyncEventsFormat(joinObj.timeline, room, decrypt); + const timelineEvents = self._mapSyncEventsFormat(joinObj.timeline, room, false); const ephemeralEvents = self._mapSyncEventsFormat(joinObj.ephemeral); const accountDataEvents = self._mapSyncEventsFormat(joinObj.account_data); + const encrypted = client.isRoomEncrypted(room.roomId); // we do this first so it's correct when any of the events fire if (joinObj.unread_notifications) { room.setUnreadNotificationCount( @@ -1196,7 +1173,6 @@ SyncApi.prototype._processSyncResponse = async function( // bother setting it here. We trust our calculations better than the // server's for this case, and therefore will assume that our non-zero // count is accurate. - const encrypted = client.isRoomEncrypted(room.roomId); if (!encrypted || (encrypted && room.getUnreadNotificationCount('highlight') <= 0)) { room.setUnreadNotificationCount( @@ -1318,6 +1294,11 @@ SyncApi.prototype._processSyncResponse = async function( }); room.updateMyMembership("join"); + + // Decrypt only the last message in all rooms to make sure we can generate a preview + // And decrypt all events after the recorded read receipt to ensure an accurate + // notification count + room.decryptCriticalEvents(); }); // Handle leaves (e.g. kicked rooms) @@ -1546,7 +1527,6 @@ SyncApi.prototype._mapSyncEventsFormat = function(obj, room, decrypt = true) { if (!obj || !utils.isArray(obj.events)) { return []; } - const mapper = this.client.getEventMapper(); const mapper = this.client.getEventMapper({ decrypt }); return obj.events.map(function(e) { if (room) { From e9132abc253d7bf8be12d728d866ee5e3bcea620 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Fri, 7 May 2021 12:58:53 +0100 Subject: [PATCH 05/15] Do not attempt to decrypt already clear events --- src/models/room.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/models/room.js b/src/models/room.js index 3d6eaa08d07..22b06e8173d 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -228,6 +228,12 @@ function pendingEventsKey(roomId) { utils.inherits(Room, EventEmitter); +function shouldAttemptDecryption(event) { + return event.isEncrypted() + && !event.isBeingDecrypted() + && event.getClearContent() === null +} + Room.prototype.decryptCriticalEvents = function() { const readReceiptEventId = this.getEventReadUpTo(this._client.getUserId(), true); const events = this.getLiveTimeline().getEvents(); @@ -237,7 +243,7 @@ Room.prototype.decryptCriticalEvents = function() { const decryptionPromises = events .slice(readReceiptTimelineIndex) - .filter(event => event.isEncrypted() && !event.isBeingDecrypted()) + .filter(event => shouldAttemptDecryption(event)) .reverse() .map(event => event.attemptDecryption(this._client._crypto, true)); @@ -249,7 +255,7 @@ Room.prototype.decryptAllEvents = function() { .getUnfilteredTimelineSet() .getLiveTimeline() .getEvents() - .filter(event => event.isEncrypted() && !event.isBeingDecrypted()) + .filter(event => shouldAttemptDecryption(event)) .reverse() .map(event => event.attemptDecryption(this._client._crypto, true)); From 91eee8587ed847bc10ad59e546ac36afb1023db2 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Fri, 7 May 2021 15:16:04 +0100 Subject: [PATCH 06/15] extract shouldAttemptDecryption to event model --- src/models/event.js | 6 ++++++ src/models/room.js | 10 ++-------- src/sync.js | 4 ++++ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/models/event.js b/src/models/event.js index 767539895aa..58cb49da417 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -399,6 +399,12 @@ utils.extend(MatrixEvent.prototype, { this._clearEvent.content.msgtype === "m.bad.encrypted"; }, + shouldAttemptDecryption: function() { + return this.isEncrypted() + && !this.isBeingDecrypted() + && this.getClearContent() === null + }, + /** * Start the process of trying to decrypt this event. * diff --git a/src/models/room.js b/src/models/room.js index 22b06e8173d..634ebd0250b 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -228,12 +228,6 @@ function pendingEventsKey(roomId) { utils.inherits(Room, EventEmitter); -function shouldAttemptDecryption(event) { - return event.isEncrypted() - && !event.isBeingDecrypted() - && event.getClearContent() === null -} - Room.prototype.decryptCriticalEvents = function() { const readReceiptEventId = this.getEventReadUpTo(this._client.getUserId(), true); const events = this.getLiveTimeline().getEvents(); @@ -243,7 +237,7 @@ Room.prototype.decryptCriticalEvents = function() { const decryptionPromises = events .slice(readReceiptTimelineIndex) - .filter(event => shouldAttemptDecryption(event)) + .filter(event => event.shouldAttemptDecryption()) .reverse() .map(event => event.attemptDecryption(this._client._crypto, true)); @@ -255,7 +249,7 @@ Room.prototype.decryptAllEvents = function() { .getUnfilteredTimelineSet() .getLiveTimeline() .getEvents() - .filter(event => shouldAttemptDecryption(event)) + .filter(event => event.shouldAttemptDecryption()) .reverse() .map(event => event.attemptDecryption(this._client._crypto, true)); diff --git a/src/sync.js b/src/sync.js index e6d1edcc96f..e91af6bb4bf 100644 --- a/src/sync.js +++ b/src/sync.js @@ -1158,6 +1158,10 @@ SyncApi.prototype._processSyncResponse = async function( await utils.promiseMapSeries(joinRooms, async function(joinObj) { const room = joinObj.room; const stateEvents = self._mapSyncEventsFormat(joinObj.state, room); + // Prevent events from being decrypted ahead of time + // this helps large account to speed up faster + // room::decryptCriticalEvent is in charge of decrypting all the events + // required for a client to function properly const timelineEvents = self._mapSyncEventsFormat(joinObj.timeline, room, false); const ephemeralEvents = self._mapSyncEventsFormat(joinObj.ephemeral); const accountDataEvents = self._mapSyncEventsFormat(joinObj.account_data); From 576f46cb889a9fe8ac7420113b4ea12e802180c7 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 10 May 2021 15:25:07 +0100 Subject: [PATCH 07/15] Add flag to prevent emitting event.decrypted --- src/crypto/algorithms/megolm.js | 2 +- src/models/event.js | 17 ++++++++++------- src/models/room.js | 4 ++-- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/crypto/algorithms/megolm.js b/src/crypto/algorithms/megolm.js index 0bf77cd295d..f2552ed1c81 100644 --- a/src/crypto/algorithms/megolm.js +++ b/src/crypto/algorithms/megolm.js @@ -1692,7 +1692,7 @@ MegolmDecryption.prototype._retryDecryption = async function(senderKey, sessionI await Promise.all([...pending].map(async (ev) => { try { - await ev.attemptDecryption(this._crypto, true); + await ev.attemptDecryption(this._crypto, { isRetry: true }); } catch (e) { // don't die if something goes wrong } diff --git a/src/models/event.js b/src/models/event.js index 58cb49da417..401d0fd0348 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -413,12 +413,13 @@ utils.extend(MatrixEvent.prototype, { * @internal * * @param {module:crypto} crypto crypto module - * @param {bool} isRetry True if this is a retry (enables more logging) + * @param {bool} options.isRetry True if this is a retry (enables more logging) + * @param {bool} options.emit Emits "event.decrypted" if set to true * * @returns {Promise} promise which resolves (to undefined) when the decryption * attempt is completed. */ - attemptDecryption: async function(crypto, isRetry) { + attemptDecryption: async function(crypto, options = {}) { // start with a couple of sanity checks. if (!this.isEncrypted()) { throw new Error("Attempt to decrypt event which isn't encrypted"); @@ -448,7 +449,7 @@ utils.extend(MatrixEvent.prototype, { return this._decryptionPromise; } - this._decryptionPromise = this._decryptionLoop(crypto, isRetry); + this._decryptionPromise = this._decryptionLoop(crypto, options); return this._decryptionPromise; }, @@ -493,7 +494,7 @@ utils.extend(MatrixEvent.prototype, { return recipients; }, - _decryptionLoop: async function(crypto, isRetry) { + _decryptionLoop: async function(crypto, options = {}) { // make sure that this method never runs completely synchronously. // (doing so would mean that we would clear _decryptionPromise *before* // it is set in attemptDecryption - and hence end up with a stuck @@ -510,7 +511,7 @@ utils.extend(MatrixEvent.prototype, { res = this._badEncryptedMessage("Encryption not enabled"); } else { res = await crypto.decryptEvent(this); - if (isRetry) { + if (options.isRetry === true) { logger.info(`Decrypted event on retry (id=${this.getId()})`); } } @@ -518,7 +519,7 @@ utils.extend(MatrixEvent.prototype, { if (e.name !== "DecryptionError") { // not a decryption error: log the whole exception as an error // (and don't bother with a retry) - const re = isRetry ? 're' : ''; + const re = options.isRetry ? 're' : ''; logger.error( `Error ${re}decrypting event ` + `(id=${this.getId()}): ${e.stack || e}`, @@ -584,7 +585,9 @@ utils.extend(MatrixEvent.prototype, { // pick up the wrong contents. this.setPushActions(null); - this.emit("Event.decrypted", this, err); + if (options.emit !== false) { + this.emit("Event.decrypted", this, err); + } return; } diff --git a/src/models/room.js b/src/models/room.js index 634ebd0250b..5621cff0833 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -239,7 +239,7 @@ Room.prototype.decryptCriticalEvents = function() { .slice(readReceiptTimelineIndex) .filter(event => event.shouldAttemptDecryption()) .reverse() - .map(event => event.attemptDecryption(this._client._crypto, true)); + .map(event => event.attemptDecryption(this._client._crypto, { isRetry: true })); return Promise.allSettled(decryptionPromises); } @@ -251,7 +251,7 @@ Room.prototype.decryptAllEvents = function() { .getEvents() .filter(event => event.shouldAttemptDecryption()) .reverse() - .map(event => event.attemptDecryption(this._client._crypto, true)); + .map(event => event.attemptDecryption(this._client._crypto, { isRetry: true })); return Promise.allSettled(decryptionPromises); } From 202a4fa6f1ae8ad4c7dcdf024ffe27ecff6050cb Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 10 May 2021 15:55:16 +0100 Subject: [PATCH 08/15] Better document new room methods --- src/models/room.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/models/room.js b/src/models/room.js index 5621cff0833..cf346f6641a 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -228,6 +228,18 @@ function pendingEventsKey(roomId) { utils.inherits(Room, EventEmitter); + +/** + * Bulk decrypt critical events in a room + * + * Critical events represents the minimal set of events to decrypt + * for the UI to function properly + * + * - Last event of every room (to generate message preview) + * - All events are the read receipt (to calculate an accurate notification count) + * + * @returns {Promise} Signals when all events have been decrypted + */ Room.prototype.decryptCriticalEvents = function() { const readReceiptEventId = this.getEventReadUpTo(this._client.getUserId(), true); const events = this.getLiveTimeline().getEvents(); @@ -244,6 +256,11 @@ Room.prototype.decryptCriticalEvents = function() { return Promise.allSettled(decryptionPromises); } +/** + * Bulk decrypt events in a room + * + * @returns {Promise} Signals when all events have been decrypted + */ Room.prototype.decryptAllEvents = function() { const decryptionPromises = this .getUnfilteredTimelineSet() From 95e08253a6d96aeb37b5ab5bb7561038315834b9 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 10 May 2021 16:59:54 +0100 Subject: [PATCH 09/15] Appease linter --- src/models/event.js | 3 ++- src/models/room.js | 6 +++--- src/sync.js | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/models/event.js b/src/models/event.js index 401d0fd0348..0a11832dc69 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -402,7 +402,7 @@ utils.extend(MatrixEvent.prototype, { shouldAttemptDecryption: function() { return this.isEncrypted() && !this.isBeingDecrypted() - && this.getClearContent() === null + && this.getClearContent() === null; }, /** @@ -413,6 +413,7 @@ utils.extend(MatrixEvent.prototype, { * @internal * * @param {module:crypto} crypto crypto module + * @param {object} options * @param {bool} options.isRetry True if this is a retry (enables more logging) * @param {bool} options.emit Emits "event.decrypted" if set to true * diff --git a/src/models/room.js b/src/models/room.js index cf346f6641a..74006afb122 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -244,7 +244,7 @@ Room.prototype.decryptCriticalEvents = function() { const readReceiptEventId = this.getEventReadUpTo(this._client.getUserId(), true); const events = this.getLiveTimeline().getEvents(); const readReceiptTimelineIndex = events.findIndex(matrixEvent => { - return matrixEvent.event.event_id === readReceiptEventId + return matrixEvent.event.event_id === readReceiptEventId; }); const decryptionPromises = events @@ -254,7 +254,7 @@ Room.prototype.decryptCriticalEvents = function() { .map(event => event.attemptDecryption(this._client._crypto, { isRetry: true })); return Promise.allSettled(decryptionPromises); -} +}; /** * Bulk decrypt events in a room @@ -271,7 +271,7 @@ Room.prototype.decryptAllEvents = function() { .map(event => event.attemptDecryption(this._client._crypto, { isRetry: true })); return Promise.allSettled(decryptionPromises); -} +}; /** * Gets the version of the room diff --git a/src/sync.js b/src/sync.js index e91af6bb4bf..3cb82fa31f8 100644 --- a/src/sync.js +++ b/src/sync.js @@ -942,7 +942,7 @@ SyncApi.prototype._onSyncError = function(err, syncOptions) { * @param {Object} data The response from /sync */ SyncApi.prototype._processSyncResponse = async function( - syncEventData, data + syncEventData, data, ) { const client = this.client; const self = this; @@ -1525,6 +1525,7 @@ SyncApi.prototype._mapSyncResponseToRoomArray = function(obj) { /** * @param {Object} obj * @param {Room} room + * @param {bool} decrypt * @return {MatrixEvent[]} */ SyncApi.prototype._mapSyncEventsFormat = function(obj, room, decrypt = true) { From f242e460eda1a444919a44306acd4821cf54634b Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 10 May 2021 17:28:00 +0100 Subject: [PATCH 10/15] fix tests --- spec/integ/megolm-integ.spec.js | 8 +++++--- spec/test-utils.js | 20 ++++++++++---------- src/client.js | 2 +- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/spec/integ/megolm-integ.spec.js b/spec/integ/megolm-integ.spec.js index 8c49b1c2818..e43aab4ef87 100644 --- a/spec/integ/megolm-integ.spec.js +++ b/spec/integ/megolm-integ.spec.js @@ -484,8 +484,9 @@ describe("megolm", function() { return aliceTestClient.flushSync().then(() => { return aliceTestClient.flushSync(); }); - }).then(function() { + }).then(async function() { const room = aliceTestClient.client.getRoom(ROOM_ID); + await room.decryptCriticalEvents(); const event = room.getLiveTimeline().getEvents()[0]; expect(event.getContent().body).toEqual('42'); }); @@ -698,7 +699,7 @@ describe("megolm", function() { // the crypto stuff can take a while, so give the requests a whole second. aliceTestClient.httpBackend.flushAllExpected({ - timeout: 1000, + timeout: 10000, }), ]); }).then(function() { @@ -933,8 +934,9 @@ describe("megolm", function() { aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse); return aliceTestClient.flushSync(); - }).then(function() { + }).then(async function() { const room = aliceTestClient.client.getRoom(ROOM_ID); + await room.decryptCriticalEvents(); const event = room.getLiveTimeline().getEvents()[0]; expect(event.getContent().body).toEqual('42'); diff --git a/spec/test-utils.js b/spec/test-utils.js index 3ca1a5b9a0f..1a657c80bcd 100644 --- a/spec/test-utils.js +++ b/spec/test-utils.js @@ -212,18 +212,18 @@ MockStorageApi.prototype = { * @returns {Promise} promise which resolves (to `event`) when the event has been decrypted */ export function awaitDecryption(event) { - if (!event.isBeingDecrypted()) { - return Promise.resolve(event); - } - - logger.log(`${Date.now()} event ${event.getId()} is being decrypted; waiting`); + if (event.getClearContent() !== null) { + return event; + } else { + logger.log(`${Date.now()} event ${event.getId()} is being decrypted; waiting`); - return new Promise((resolve, reject) => { - event.once('Event.decrypted', (ev) => { - logger.log(`${Date.now()} event ${event.getId()} now decrypted`); - resolve(ev); + return new Promise((resolve, reject) => { + event.once('Event.decrypted', (ev) => { + logger.log(`${Date.now()} event ${event.getId()} now decrypted`); + resolve(ev); + }); }); - }); + } } diff --git a/src/client.js b/src/client.js index fc76aef3358..5d9d1d2cfd5 100644 --- a/src/client.js +++ b/src/client.js @@ -5556,7 +5556,7 @@ function _resolve(callback, resolve, res) { function _PojoToMatrixEventMapper(client, options = {}) { const preventReEmit = Boolean(options.preventReEmit); - const decrypt = options.decrypt === true; + const decrypt = options.decrypt !== false; function mapper(plainOldJsObject) { const event = new MatrixEvent(plainOldJsObject); if (event.isEncrypted()) { From 01cd82bc6a6b4d403d8de13aa3fa80a4be41fc77 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 10 May 2021 18:12:47 +0100 Subject: [PATCH 11/15] undo test timeout --- spec/integ/megolm-integ.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/integ/megolm-integ.spec.js b/spec/integ/megolm-integ.spec.js index e43aab4ef87..c8b117953ad 100644 --- a/spec/integ/megolm-integ.spec.js +++ b/spec/integ/megolm-integ.spec.js @@ -699,7 +699,7 @@ describe("megolm", function() { // the crypto stuff can take a while, so give the requests a whole second. aliceTestClient.httpBackend.flushAllExpected({ - timeout: 10000, + timeout: 1000, }), ]); }).then(function() { From f21e0228b4f67b4d571139a3ac1fa1ce93814651 Mon Sep 17 00:00:00 2001 From: Germain Date: Tue, 11 May 2021 09:20:14 +0100 Subject: [PATCH 12/15] Update documentation wording Co-authored-by: Travis Ralston --- src/models/room.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/models/room.js b/src/models/room.js index 74006afb122..c99df771863 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -233,10 +233,10 @@ utils.inherits(Room, EventEmitter); * Bulk decrypt critical events in a room * * Critical events represents the minimal set of events to decrypt - * for the UI to function properly + * for a typical UI to function properly * - * - Last event of every room (to generate message preview) - * - All events are the read receipt (to calculate an accurate notification count) + * - Last event of every room (to generate likely message preview) + * - All events up to the read receipt (to calculate an accurate notification count) * * @returns {Promise} Signals when all events have been decrypted */ From 874cb3b7794d5b042148867709c4680a6f788508 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 11 May 2021 10:02:28 +0100 Subject: [PATCH 13/15] make attemptDecryption backwards compatible --- src/models/event.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/models/event.js b/src/models/event.js index 0a11832dc69..04801ca58bf 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -421,6 +421,15 @@ utils.extend(MatrixEvent.prototype, { * attempt is completed. */ attemptDecryption: async function(crypto, options = {}) { + + // For backwards compatibility purposes + // The function signature used to be attemptDecryption(crypto, isRetry) + if (typeof options === "boolean") { + options = { + isRetry: options + }; + } + // start with a couple of sanity checks. if (!this.isEncrypted()) { throw new Error("Attempt to decrypt event which isn't encrypted"); From 72f86258d0f6645c64e7e72e139dc50d3985ca72 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 11 May 2021 10:05:24 +0100 Subject: [PATCH 14/15] Add explainer for awaitDecryption --- spec/test-utils.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/test-utils.js b/spec/test-utils.js index 1a657c80bcd..12d2cb6480b 100644 --- a/spec/test-utils.js +++ b/spec/test-utils.js @@ -212,6 +212,9 @@ MockStorageApi.prototype = { * @returns {Promise} promise which resolves (to `event`) when the event has been decrypted */ export function awaitDecryption(event) { + // An event is not always decrypted ahead of time + // getClearContent is a good signal to know whether an event has been decrypted + // already if (event.getClearContent() !== null) { return event; } else { From e484a2ebb580176b7cf902adaef8be9fff5fe4b4 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 11 May 2021 13:02:42 +0100 Subject: [PATCH 15/15] add missing coma to appease linter --- src/models/event.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/models/event.js b/src/models/event.js index 04801ca58bf..c2bd9472496 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -421,12 +421,11 @@ utils.extend(MatrixEvent.prototype, { * attempt is completed. */ attemptDecryption: async function(crypto, options = {}) { - // For backwards compatibility purposes // The function signature used to be attemptDecryption(crypto, isRetry) if (typeof options === "boolean") { options = { - isRetry: options + isRetry: options, }; }