1
0
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:
Richard van der Hoff
2016-06-03 17:19:09 +01:00
parent 4bf24f0fe4
commit ce59b72308
2 changed files with 114 additions and 53 deletions

View File

@@ -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) {