You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-26 17:03:12 +03:00
Merge pull request #1684 from matrix-org/gsouquet/cache-decrypt
This commit is contained in:
@@ -484,8 +484,9 @@ describe("megolm", function() {
|
|||||||
return aliceTestClient.flushSync().then(() => {
|
return aliceTestClient.flushSync().then(() => {
|
||||||
return aliceTestClient.flushSync();
|
return aliceTestClient.flushSync();
|
||||||
});
|
});
|
||||||
}).then(function() {
|
}).then(async function() {
|
||||||
const room = aliceTestClient.client.getRoom(ROOM_ID);
|
const room = aliceTestClient.client.getRoom(ROOM_ID);
|
||||||
|
await room.decryptCriticalEvents();
|
||||||
const event = room.getLiveTimeline().getEvents()[0];
|
const event = room.getLiveTimeline().getEvents()[0];
|
||||||
expect(event.getContent().body).toEqual('42');
|
expect(event.getContent().body).toEqual('42');
|
||||||
});
|
});
|
||||||
@@ -933,8 +934,9 @@ describe("megolm", function() {
|
|||||||
|
|
||||||
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse);
|
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse);
|
||||||
return aliceTestClient.flushSync();
|
return aliceTestClient.flushSync();
|
||||||
}).then(function() {
|
}).then(async function() {
|
||||||
const room = aliceTestClient.client.getRoom(ROOM_ID);
|
const room = aliceTestClient.client.getRoom(ROOM_ID);
|
||||||
|
await room.decryptCriticalEvents();
|
||||||
const event = room.getLiveTimeline().getEvents()[0];
|
const event = room.getLiveTimeline().getEvents()[0];
|
||||||
expect(event.getContent().body).toEqual('42');
|
expect(event.getContent().body).toEqual('42');
|
||||||
|
|
||||||
|
|||||||
@@ -212,18 +212,21 @@ MockStorageApi.prototype = {
|
|||||||
* @returns {Promise} promise which resolves (to `event`) when the event has been decrypted
|
* @returns {Promise} promise which resolves (to `event`) when the event has been decrypted
|
||||||
*/
|
*/
|
||||||
export function awaitDecryption(event) {
|
export function awaitDecryption(event) {
|
||||||
if (!event.isBeingDecrypted()) {
|
// An event is not always decrypted ahead of time
|
||||||
return Promise.resolve(event);
|
// getClearContent is a good signal to know whether an event has been decrypted
|
||||||
}
|
// already
|
||||||
|
if (event.getClearContent() !== null) {
|
||||||
|
return event;
|
||||||
|
} else {
|
||||||
|
logger.log(`${Date.now()} event ${event.getId()} is being decrypted; waiting`);
|
||||||
|
|
||||||
logger.log(`${Date.now()} event ${event.getId()} is being decrypted; waiting`);
|
return new Promise((resolve, reject) => {
|
||||||
|
event.once('Event.decrypted', (ev) => {
|
||||||
return new Promise((resolve, reject) => {
|
logger.log(`${Date.now()} event ${event.getId()} now decrypted`);
|
||||||
event.once('Event.decrypted', (ev) => {
|
resolve(ev);
|
||||||
logger.log(`${Date.now()} event ${event.getId()} now decrypted`);
|
});
|
||||||
resolve(ev);
|
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5554,8 +5554,9 @@ function _resolve(callback, resolve, res) {
|
|||||||
resolve(res);
|
resolve(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _PojoToMatrixEventMapper(client, options) {
|
function _PojoToMatrixEventMapper(client, options = {}) {
|
||||||
const preventReEmit = Boolean(options && options.preventReEmit);
|
const preventReEmit = Boolean(options.preventReEmit);
|
||||||
|
const decrypt = options.decrypt !== false;
|
||||||
function mapper(plainOldJsObject) {
|
function mapper(plainOldJsObject) {
|
||||||
const event = new MatrixEvent(plainOldJsObject);
|
const event = new MatrixEvent(plainOldJsObject);
|
||||||
if (event.isEncrypted()) {
|
if (event.isEncrypted()) {
|
||||||
@@ -5564,7 +5565,9 @@ function _PojoToMatrixEventMapper(client, options) {
|
|||||||
"Event.decrypted",
|
"Event.decrypted",
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
event.attemptDecryption(client._crypto);
|
if (decrypt) {
|
||||||
|
event.attemptDecryption(client._crypto);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!preventReEmit) {
|
if (!preventReEmit) {
|
||||||
client.reEmitter.reEmit(event, ["Event.replaced"]);
|
client.reEmitter.reEmit(event, ["Event.replaced"]);
|
||||||
@@ -5577,6 +5580,7 @@ function _PojoToMatrixEventMapper(client, options) {
|
|||||||
/**
|
/**
|
||||||
* @param {object} [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.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}
|
* @return {Function}
|
||||||
*/
|
*/
|
||||||
MatrixClient.prototype.getEventMapper = function(options = undefined) {
|
MatrixClient.prototype.getEventMapper = function(options = undefined) {
|
||||||
|
|||||||
@@ -1692,7 +1692,7 @@ MegolmDecryption.prototype._retryDecryption = async function(senderKey, sessionI
|
|||||||
|
|
||||||
await Promise.all([...pending].map(async (ev) => {
|
await Promise.all([...pending].map(async (ev) => {
|
||||||
try {
|
try {
|
||||||
await ev.attemptDecryption(this._crypto, true);
|
await ev.attemptDecryption(this._crypto, { isRetry: true });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// don't die if something goes wrong
|
// don't die if something goes wrong
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -399,6 +399,12 @@ utils.extend(MatrixEvent.prototype, {
|
|||||||
this._clearEvent.content.msgtype === "m.bad.encrypted";
|
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.
|
* Start the process of trying to decrypt this event.
|
||||||
*
|
*
|
||||||
@@ -407,12 +413,22 @@ utils.extend(MatrixEvent.prototype, {
|
|||||||
* @internal
|
* @internal
|
||||||
*
|
*
|
||||||
* @param {module:crypto} crypto crypto module
|
* @param {module:crypto} crypto crypto module
|
||||||
* @param {bool} isRetry True if this is a retry (enables more logging)
|
* @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
|
||||||
*
|
*
|
||||||
* @returns {Promise} promise which resolves (to undefined) when the decryption
|
* @returns {Promise} promise which resolves (to undefined) when the decryption
|
||||||
* attempt is completed.
|
* attempt is completed.
|
||||||
*/
|
*/
|
||||||
attemptDecryption: async function(crypto, isRetry) {
|
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.
|
// start with a couple of sanity checks.
|
||||||
if (!this.isEncrypted()) {
|
if (!this.isEncrypted()) {
|
||||||
throw new Error("Attempt to decrypt event which isn't encrypted");
|
throw new Error("Attempt to decrypt event which isn't encrypted");
|
||||||
@@ -442,7 +458,7 @@ utils.extend(MatrixEvent.prototype, {
|
|||||||
return this._decryptionPromise;
|
return this._decryptionPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._decryptionPromise = this._decryptionLoop(crypto, isRetry);
|
this._decryptionPromise = this._decryptionLoop(crypto, options);
|
||||||
return this._decryptionPromise;
|
return this._decryptionPromise;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -487,7 +503,7 @@ utils.extend(MatrixEvent.prototype, {
|
|||||||
return recipients;
|
return recipients;
|
||||||
},
|
},
|
||||||
|
|
||||||
_decryptionLoop: async function(crypto, isRetry) {
|
_decryptionLoop: async function(crypto, options = {}) {
|
||||||
// make sure that this method never runs completely synchronously.
|
// make sure that this method never runs completely synchronously.
|
||||||
// (doing so would mean that we would clear _decryptionPromise *before*
|
// (doing so would mean that we would clear _decryptionPromise *before*
|
||||||
// it is set in attemptDecryption - and hence end up with a stuck
|
// it is set in attemptDecryption - and hence end up with a stuck
|
||||||
@@ -504,7 +520,7 @@ utils.extend(MatrixEvent.prototype, {
|
|||||||
res = this._badEncryptedMessage("Encryption not enabled");
|
res = this._badEncryptedMessage("Encryption not enabled");
|
||||||
} else {
|
} else {
|
||||||
res = await crypto.decryptEvent(this);
|
res = await crypto.decryptEvent(this);
|
||||||
if (isRetry) {
|
if (options.isRetry === true) {
|
||||||
logger.info(`Decrypted event on retry (id=${this.getId()})`);
|
logger.info(`Decrypted event on retry (id=${this.getId()})`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -512,7 +528,7 @@ utils.extend(MatrixEvent.prototype, {
|
|||||||
if (e.name !== "DecryptionError") {
|
if (e.name !== "DecryptionError") {
|
||||||
// not a decryption error: log the whole exception as an error
|
// not a decryption error: log the whole exception as an error
|
||||||
// (and don't bother with a retry)
|
// (and don't bother with a retry)
|
||||||
const re = isRetry ? 're' : '';
|
const re = options.isRetry ? 're' : '';
|
||||||
logger.error(
|
logger.error(
|
||||||
`Error ${re}decrypting event ` +
|
`Error ${re}decrypting event ` +
|
||||||
`(id=${this.getId()}): ${e.stack || e}`,
|
`(id=${this.getId()}): ${e.stack || e}`,
|
||||||
@@ -578,7 +594,9 @@ utils.extend(MatrixEvent.prototype, {
|
|||||||
// pick up the wrong contents.
|
// pick up the wrong contents.
|
||||||
this.setPushActions(null);
|
this.setPushActions(null);
|
||||||
|
|
||||||
this.emit("Event.decrypted", this, err);
|
if (options.emit !== false) {
|
||||||
|
this.emit("Event.decrypted", this, err);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,6 +228,51 @@ function pendingEventsKey(roomId) {
|
|||||||
|
|
||||||
utils.inherits(Room, EventEmitter);
|
utils.inherits(Room, EventEmitter);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bulk decrypt critical events in a room
|
||||||
|
*
|
||||||
|
* Critical events represents the minimal set of events to decrypt
|
||||||
|
* for a typical UI to function properly
|
||||||
|
*
|
||||||
|
* - 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
|
||||||
|
*/
|
||||||
|
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.shouldAttemptDecryption())
|
||||||
|
.reverse()
|
||||||
|
.map(event => event.attemptDecryption(this._client._crypto, { isRetry: true }));
|
||||||
|
|
||||||
|
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()
|
||||||
|
.getLiveTimeline()
|
||||||
|
.getEvents()
|
||||||
|
.filter(event => event.shouldAttemptDecryption())
|
||||||
|
.reverse()
|
||||||
|
.map(event => event.attemptDecryption(this._client._crypto, { isRetry: true }));
|
||||||
|
|
||||||
|
return Promise.allSettled(decryptionPromises);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the version of the room
|
* Gets the version of the room
|
||||||
* @returns {string} The version of the room, or null if it could not be determined
|
* @returns {string} The version of the room, or null if it could not be determined
|
||||||
|
|||||||
18
src/sync.js
18
src/sync.js
@@ -1158,10 +1158,15 @@ SyncApi.prototype._processSyncResponse = async function(
|
|||||||
await utils.promiseMapSeries(joinRooms, async function(joinObj) {
|
await utils.promiseMapSeries(joinRooms, async function(joinObj) {
|
||||||
const room = joinObj.room;
|
const room = joinObj.room;
|
||||||
const stateEvents = self._mapSyncEventsFormat(joinObj.state, room);
|
const stateEvents = self._mapSyncEventsFormat(joinObj.state, room);
|
||||||
const timelineEvents = self._mapSyncEventsFormat(joinObj.timeline, 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 ephemeralEvents = self._mapSyncEventsFormat(joinObj.ephemeral);
|
||||||
const accountDataEvents = self._mapSyncEventsFormat(joinObj.account_data);
|
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
|
// we do this first so it's correct when any of the events fire
|
||||||
if (joinObj.unread_notifications) {
|
if (joinObj.unread_notifications) {
|
||||||
room.setUnreadNotificationCount(
|
room.setUnreadNotificationCount(
|
||||||
@@ -1172,7 +1177,6 @@ SyncApi.prototype._processSyncResponse = async function(
|
|||||||
// bother setting it here. We trust our calculations better than the
|
// bother setting it here. We trust our calculations better than the
|
||||||
// server's for this case, and therefore will assume that our non-zero
|
// server's for this case, and therefore will assume that our non-zero
|
||||||
// count is accurate.
|
// count is accurate.
|
||||||
const encrypted = client.isRoomEncrypted(room.roomId);
|
|
||||||
if (!encrypted
|
if (!encrypted
|
||||||
|| (encrypted && room.getUnreadNotificationCount('highlight') <= 0)) {
|
|| (encrypted && room.getUnreadNotificationCount('highlight') <= 0)) {
|
||||||
room.setUnreadNotificationCount(
|
room.setUnreadNotificationCount(
|
||||||
@@ -1294,6 +1298,11 @@ SyncApi.prototype._processSyncResponse = async function(
|
|||||||
});
|
});
|
||||||
|
|
||||||
room.updateMyMembership("join");
|
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)
|
// Handle leaves (e.g. kicked rooms)
|
||||||
@@ -1516,13 +1525,14 @@ SyncApi.prototype._mapSyncResponseToRoomArray = function(obj) {
|
|||||||
/**
|
/**
|
||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
* @param {Room} room
|
* @param {Room} room
|
||||||
|
* @param {bool} decrypt
|
||||||
* @return {MatrixEvent[]}
|
* @return {MatrixEvent[]}
|
||||||
*/
|
*/
|
||||||
SyncApi.prototype._mapSyncEventsFormat = function(obj, room) {
|
SyncApi.prototype._mapSyncEventsFormat = function(obj, room, decrypt = true) {
|
||||||
if (!obj || !utils.isArray(obj.events)) {
|
if (!obj || !utils.isArray(obj.events)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const mapper = this.client.getEventMapper();
|
const mapper = this.client.getEventMapper({ decrypt });
|
||||||
return obj.events.map(function(e) {
|
return obj.events.map(function(e) {
|
||||||
if (room) {
|
if (room) {
|
||||||
e.room_id = room.roomId;
|
e.room_id = room.roomId;
|
||||||
|
|||||||
Reference in New Issue
Block a user