diff --git a/lib/models/room.js b/lib/models/room.js index d56c353c8..c56374547 100644 --- a/lib/models/room.js +++ b/lib/models/room.js @@ -595,18 +595,22 @@ Room.prototype._addLiveEvents = function(events) { // remote echo of an event we sent earlier var existingEvent = this._txnToEvent[events[i].getUnsigned().transaction_id]; if (existingEvent) { + var oldEventId = existingEvent.getId(); + // no longer pending delete this._txnToEvent[events[i].getUnsigned().transaction_id]; // update the timeline map, because the event id has changed - var existingTimeline = this._eventIdToTimeline[existingEvent.getId()]; + var existingTimeline = this._eventIdToTimeline[oldEventId]; if (existingTimeline) { - delete this._eventIdToTimeline[existingEvent.getId()]; + delete this._eventIdToTimeline[oldEventId]; this._eventIdToTimeline[events[i].getId()] = existingTimeline; } // replace the event source existingEvent.event = events[i].event; + + this.emit("Room.localEchoUpdated", existingEvent, this, oldEventId); continue; } } @@ -1315,3 +1319,21 @@ module.exports = Room; * } * }); */ + +/** + * Fires when a local-echo of a transmitted event is replaced by its remote-echo. + * + *
When an event is first transmitted, then (assuming the /send completes + * before the event comes back from /sync), a temporary copy of the event is + * inserted into the timeline, with a temporary event id. Once the echo comes + * back from the server, the event is replaced by the complete event from the + * homeserver, thus updating its event id. + * + *
If the event comes back from /sync before the /send completes, the + * temporary event id is never stored, and this event is not raised. + * + * @event module:client~MatrixClient#"Room.localEchoUpdated" + * @param {MatrixEvent} event The matrix event which has been updated + * @param {Room} room The room containing the redacted event + * @param {string} oldEventId The temporary event id which has been replaced + */ diff --git a/lib/sync.js b/lib/sync.js index a0e1442c1..73b695643 100644 --- a/lib/sync.js +++ b/lib/sync.js @@ -86,7 +86,9 @@ SyncApi.prototype.createRoom = function(roomId) { }); reEmit(client, room, ["Room.name", "Room.timeline", "Room.redaction", "Room.receipt", "Room.tags", - "Room.timelineReset"]); + "Room.timelineReset", + "Room.localEchoUpdated", + ]); this._registerStateListeners(room); return room; }; diff --git a/spec/unit/room.spec.js b/spec/unit/room.spec.js index da6ae7e1d..1e3a6919b 100644 --- a/spec/unit/room.spec.js +++ b/spec/unit/room.spec.js @@ -328,6 +328,39 @@ describe("Room", function() { room.addEventsToTimeline(events); expect(room.getEventReadUpTo(userA)).toEqual(events[1].getId()); }); + + it("should emit Room.localEchoUpdated when a local echo is updated", function() { + var localEvent = utils.mkMessage({ + room: roomId, user: userA, event: true, + }); + localEvent._txnId = "TXN_ID"; + localEvent.status = EventStatus.SENDING; + var localEventId = localEvent.getId(); + + var remoteEvent = utils.mkMessage({ + room: roomId, user: userA, event: true, + }); + remoteEvent.event.unsigned = {transaction_id: "TXN_ID"}; + var remoteEventId = remoteEvent.getId(); + + var callCount = 0; + room.on("Room.localEchoUpdated", function(event, emitRoom, oldEventId) { + callCount += 1; + expect(event.getId()).toEqual(remoteEventId); + expect(emitRoom).toEqual(room); + expect(oldEventId).toEqual(localEventId); + }); + + // first add the local echo to the timeline + room.addEventsToTimeline([localEvent]); + expect(room.timeline.length).toEqual(1); + + // then the remoteEvent + room.addEventsToTimeline([remoteEvent]); + expect(room.timeline.length).toEqual(1); + + expect(callCount).toEqual(1); + }); }); var resetTimelineTests = function(timelineSupport) {