From 14a48c118271d2ee2f1f1a42a43b40fdecebf1be Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 6 Nov 2015 15:13:30 +0000 Subject: [PATCH] Synthesize implicit read receipts in recalculateRoom to make them correct when the room is first loaded. --- lib/models/room.js | 66 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/lib/models/room.js b/lib/models/room.js index bf3bdfae7..857f401cf 100644 --- a/lib/models/room.js +++ b/lib/models/room.js @@ -201,16 +201,10 @@ Room.prototype.addEventsToTimeline = function(events, toStartOfTimeline) { // This is really ugly because JS has no way to express an object literal // where the name of a key comes from an expression - if (events[i].sender) { - var fakeReceipt = {content: {}}; - fakeReceipt.content[events[i].getId()] = { - 'm.read': { - } - }; - fakeReceipt.content[events[i].getId()]['m.read'][events[i].sender.userId] = { - ts: events[i].getTs() - }; - this.addReceipt(new MatrixEvent(fakeReceipt)); + if (!toStartOfTimeline && events[i].sender) { + this.addReceipt(new MatrixEvent(this._synthesizeReceipt( + events[i].sender.userId, events[i], "m.read" + ))); } this.emit("Room.timeline", events[i], this, Boolean(toStartOfTimeline), false); @@ -347,8 +341,60 @@ Room.prototype.recalculate = function(userId) { if (oldName !== this.name) { this.emit("Room.name", this); } + + + + // recalculate read receipts, adding implicit ones where necessary + // NB. This is a duplication of logic for injecting implicit receipts, + // it would be technically possible to only ever generate these + // receipts in addEventsToTimeline but doing so means correctly + // choosing whether to keep or replace the existing receipt which + // is complex and slow. This is faster and more understandable. + + var usersFound = {}; + for (var i = this.timeline.length - 1; i >= 0; --i) { + // loop through the timeline backwards looking for either an + // event sent by each user or a real receipt from them. + // Replace the read receipt for that user with whichever + // occurs later in the timeline (ie. first because we're going + // backwards). + var e = this.timeline[i]; + + var readReceiptsForEvent = this.getReceiptsForEvent(e); + + for (var receiptIt = 0; receiptIt < readReceiptsForEvent.length; ++receiptIt) { + var receipt = readReceiptsForEvent[receiptIt]; + if (receipt.type !== "m.read") { continue; } + + var userId = receipt.userId; + if (usersFound[userId]) { continue; } + + // Then this is the receipt we keep for this user + usersFound[userId] = 1; + } + + if (e.sender && usersFound[e.sender.userId] === undefined) { + // no receipt yet for this sender, so we synthesize one. + + this.addReceipt(this._synthesizeReceipt(e.sender.userId, e, "m.read")); + + usersFound[e.sender.userId] = 1; + } + } }; +Room.prototype._synthesizeReceipt = function(userId, event, receiptType) { + // This is really ugly because JS has no way to express an object literal + // where the name of a key comes from an expression + var fakeReceipt = {content: {}}; + fakeReceipt.content[event.getId()] = {}; + fakeReceipt.content[event.getId()][receiptType] = {}; + fakeReceipt.content[event.getId()][receiptType][userId] = { + ts: event + }; + return new MatrixEvent(fakeReceipt); +} + /** * Get a list of user IDs who have read up to the given event.