You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-29 16:43:09 +03:00
Catch exceptions when encrypting events
If an exception was thrown by the encryption process, the event would be queued, but the exception would not be handled. This meant that the event got stuck as a grey 'sending' event in the UI. Fixing this correctly is slightly more complex than just handling the exception correctly. A naive approach would mean that the event would be shown as a red 'unsent' message, and clicking 'resend' would then send the message *in the clear*. Hence, move the encryption to _sendEvent, where it will be called again when the event is resent.
This commit is contained in:
136
lib/client.js
136
lib/client.js
@@ -1117,26 +1117,56 @@ MatrixClient.prototype.sendEvent = function(roomId, eventType, content, txnId,
|
||||
room.addPendingEvent(localEvent, txnId);
|
||||
}
|
||||
|
||||
if (eventType === "m.room.message" && this.sessionStore && CRYPTO_ENABLED) {
|
||||
var e2eRoomInfo = this.sessionStore.getEndToEndRoom(roomId);
|
||||
if (e2eRoomInfo) {
|
||||
var encryptedContent = _encryptMessage(
|
||||
this, roomId, e2eRoomInfo, eventType, content, txnId, callback
|
||||
);
|
||||
localEvent.encryptedType = "m.room.encrypted";
|
||||
localEvent.encryptedContent = encryptedContent;
|
||||
|
||||
// TODO: Specify this in the event constructor rather than fiddling
|
||||
// with the event object internals.
|
||||
localEvent.encrypted = true;
|
||||
}
|
||||
}
|
||||
|
||||
return _sendEvent(this, room, localEvent, callback);
|
||||
};
|
||||
|
||||
function _encryptMessage(client, roomId, e2eRoomInfo, eventType, content,
|
||||
txnId, callback) {
|
||||
|
||||
/**
|
||||
* Encrypt an event according to the configuration of the room, if necessary.
|
||||
*
|
||||
* @param {MatrixClient} client
|
||||
* @param {module:models/event.MatrixEvent} event event to be sent
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function _encryptEventIfNeeded(client, event) {
|
||||
if (event.isEncrypted()) {
|
||||
// this event has already been encrypted; this happens if the
|
||||
// encryption step succeeded, but the send step failed on the first
|
||||
// attempt.
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getType() !== "m.room.message") {
|
||||
// we only encrypt m.room.message
|
||||
return;
|
||||
}
|
||||
|
||||
if (!client.sessionStore) {
|
||||
// End to end encryption isn't enabled if we don't have a session
|
||||
// store.
|
||||
return;
|
||||
}
|
||||
|
||||
var roomId = event.getRoomId();
|
||||
|
||||
var e2eRoomInfo = client.sessionStore.getEndToEndRoom(roomId);
|
||||
if (!e2eRoomInfo || !e2eRoomInfo.algorithm) {
|
||||
// not encrypting messages in this room
|
||||
return;
|
||||
}
|
||||
|
||||
var encryptedContent = _encryptMessage(
|
||||
client, roomId, e2eRoomInfo, event.getType(), event.getContent()
|
||||
);
|
||||
event.encryptedType = "m.room.encrypted";
|
||||
event.encryptedContent = encryptedContent;
|
||||
// TODO: Specify this in the event constructor rather than fiddling
|
||||
// with the event object internals.
|
||||
event.encrypted = true;
|
||||
}
|
||||
|
||||
function _encryptMessage(client, roomId, e2eRoomInfo, eventType, content) {
|
||||
if (!client.sessionStore) {
|
||||
throw new Error(
|
||||
"Client must have an end-to-end session store to encrypt messages"
|
||||
@@ -1200,6 +1230,16 @@ function _encryptMessage(client, roomId, e2eRoomInfo, eventType, content,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decrypt a received event according to the algorithm specified in the event.
|
||||
*
|
||||
* @param {MatrixClient} client
|
||||
* @param {MatrixEvent} event
|
||||
*
|
||||
* @return {MatrixEvent} a new MatrixEvent
|
||||
* @private
|
||||
*/
|
||||
function _decryptMessage(client, event) {
|
||||
if (client.sessionStore === null || !CRYPTO_ENABLED) {
|
||||
// End to end encryption isn't enabled if we don't have a session
|
||||
@@ -1289,41 +1329,55 @@ function _badEncryptedMessage(event, reason) {
|
||||
}, event);
|
||||
}
|
||||
|
||||
// encrypts the event if necessary
|
||||
// adds the event to the queue, or sends it
|
||||
// marks the event as sent/unsent
|
||||
// returns a promise which resolves with the result of the send request
|
||||
function _sendEvent(client, room, event, callback) {
|
||||
var defer = q.defer();
|
||||
var promise;
|
||||
// this event may be queued
|
||||
if (client.scheduler) {
|
||||
// if this returns a promsie then the scheduler has control now and will
|
||||
// resolve/reject when it is done. Internally, the scheduler will invoke
|
||||
// processFn which is set to this._sendEventHttpRequest so the same code
|
||||
// path is executed regardless.
|
||||
promise = client.scheduler.queueEvent(event);
|
||||
if (promise && client.scheduler.getQueueForEvent(event).length > 1) {
|
||||
// event is processed FIFO so if the length is 2 or more we know
|
||||
// this event is stuck behind an earlier event.
|
||||
_updatePendingEventStatus(room, event, EventStatus.QUEUED);
|
||||
// Add an extra q() to turn synchronous exceptions into promise rejections,
|
||||
// so that we can handle synchronous and asynchronous exceptions with the
|
||||
// same code path.
|
||||
return q().then(function() {
|
||||
_encryptEventIfNeeded(client, event);
|
||||
|
||||
var promise;
|
||||
// this event may be queued
|
||||
if (client.scheduler) {
|
||||
// if this returns a promsie then the scheduler has control now and will
|
||||
// resolve/reject when it is done. Internally, the scheduler will invoke
|
||||
// processFn which is set to this._sendEventHttpRequest so the same code
|
||||
// path is executed regardless.
|
||||
promise = client.scheduler.queueEvent(event);
|
||||
if (promise && client.scheduler.getQueueForEvent(event).length > 1) {
|
||||
// event is processed FIFO so if the length is 2 or more we know
|
||||
// this event is stuck behind an earlier event.
|
||||
_updatePendingEventStatus(room, event, EventStatus.QUEUED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!promise) {
|
||||
promise = _sendEventHttpRequest(client, event);
|
||||
}
|
||||
|
||||
promise.done(function(res) { // the request was sent OK
|
||||
if (!promise) {
|
||||
promise = _sendEventHttpRequest(client, event);
|
||||
}
|
||||
return promise;
|
||||
}).then(function(res) { // the request was sent OK
|
||||
if (room) {
|
||||
room.updatePendingEvent(event, EventStatus.SENT, res.event_id);
|
||||
}
|
||||
|
||||
_resolve(callback, defer, res);
|
||||
if (callback) {
|
||||
callback(null, res);
|
||||
}
|
||||
return res;
|
||||
}, function(err) {
|
||||
// the request failed to send.
|
||||
console.error("Error sending event", err.stack || err);
|
||||
|
||||
_updatePendingEventStatus(room, event, EventStatus.NOT_SENT);
|
||||
|
||||
_reject(callback, defer, err);
|
||||
if (callback) {
|
||||
callback(err);
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
|
||||
return defer.promise;
|
||||
}
|
||||
|
||||
function _updatePendingEventStatus(room, event, newStatus) {
|
||||
|
||||
Reference in New Issue
Block a user