diff --git a/lib/models/room-state.js b/lib/models/room-state.js index 61d4ea89a..12d3f9251 100644 --- a/lib/models/room-state.js +++ b/lib/models/room-state.js @@ -26,6 +26,10 @@ function RoomState(roomId) { // eventType: { stateKey: MatrixEvent } }; this.paginationToken = null; + + this._sentinels = { + // userId: RoomMember + }; } utils.inherits(RoomState, EventEmitter); @@ -46,6 +50,19 @@ RoomState.prototype.getMember = function(userId) { return this.members[userId] || null; }; +/** + * Get a room member whose properties will not change with this room state. You + * typically want this if you want to attach a RoomMember to a MatrixEvent which + * may no longer be represented correctly by Room.currentState or Room.oldState. + * The term 'sentinel' refers to the fact that this RoomMember is an unchanging + * guardian for state at this particular point in time. + * @param {string} userId The room member's user ID. + * @return {RoomMember} The member or null if they do not exist. + */ +RoomState.prototype.getSentinelMember = function(userId) { + return this._sentinels[userId] || null; +}; + /** * Get state events from the state of the room. * @param {string} eventType The event type of the state event. @@ -89,19 +106,29 @@ RoomState.prototype.setStateEvents = function(stateEvents) { self.emit("RoomState.events", event, self); if (event.getType() === "m.room.member") { - // we *always* need to create a new RoomMember because there may be - // MatrixEvents with their .sender property set to a RoomMember which - // was right for that point in time. We are now diverging from that - // point so in order to not corrupt the existing messages we need to - // make a new RoomMember. - var member = new RoomMember(event.getRoomId(), event.getStateKey()); - member.setMembershipEvent(event, self); - // this member may have a power level already, so set it. - var pwrLvlEvent = self.getStateEvents("m.room.power_levels", ""); - if (pwrLvlEvent) { - member.setPowerLevelEvent(pwrLvlEvent); + var userId = event.getStateKey(); + var member = self.members[userId]; + if (!member) { + member = new RoomMember(event.getRoomId(), userId); + self.emit("RoomState.newMember", event, self, member); } - self.members[event.getStateKey()] = member; + // Add a new sentinel for this change. We apply the same + // operations to both sentinel and member rather than deep copying + // so we don't make assumptions about the properties of RoomMember + // (e.g. and manage to break it because deep copying doesn't do + // everything). + var sentinel = new RoomMember(event.getRoomId(), userId); + utils.forEach([member, sentinel], function(roomMember) { + roomMember.setMembershipEvent(event, self); + // this member may have a power level already, so set it. + var pwrLvlEvent = self.getStateEvents("m.room.power_levels", ""); + if (pwrLvlEvent) { + roomMember.setPowerLevelEvent(pwrLvlEvent); + } + }); + + self._sentinels[userId] = sentinel; + self.members[userId] = member; self.emit("RoomState.members", event, self, member); } else if (event.getType() === "m.room.power_levels") { diff --git a/lib/models/room.js b/lib/models/room.js index de007b066..e331c69bd 100644 --- a/lib/models/room.js +++ b/lib/models/room.js @@ -68,11 +68,15 @@ Room.prototype.addEventsToTimeline = function(events, toStartOfTimeline) { var stateContext = toStartOfTimeline ? this.oldState : this.currentState; for (var i = 0; i < events.length; i++) { // set sender and target properties - events[i].sender = stateContext.getMember(events[i].getSender()); + events[i].sender = stateContext.getSentinelMember( + events[i].getSender() + ); if (events[i].getType() === "m.room.member") { - events[i].target = stateContext.getMember(events[i].getStateKey()); + events[i].target = stateContext.getSentinelMember( + events[i].getStateKey() + ); } - + // modify state if (events[i].isState()) { // room state has no concept of 'old' or 'current', but we want the diff --git a/lib/utils.js b/lib/utils.js index e3f996016..2f35a8398 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -159,7 +159,22 @@ module.exports.checkObjectHasNoAdditionalKeys = function(obj, allowedKeys) { }; /** - * Deep copy the given object. The object MUST NOT have circular references. + * Assigns all the properties in src to dst. If these properties are Objects, + * then both src and dst will refer to the same thing. + * @param {Object} src The object to copy properties from. + * @param {Object} dst The object to write properties to. + */ +module.exports.shallowCopy = function(src, dst) { + for (var i in src) { + if (src.hasOwnProperty(i)) { + dst[i] = src[i]; + } + } +}; + +/** + * Deep copy the given object. The object MUST NOT have circular references and + * MUST NOT have functions. * @param {Object} obj The object to deep copy. * @return {Object} A copy of the object without any references to the original. */ diff --git a/spec/integ/matrix-client.spec.js b/spec/integ/matrix-client.spec.js index 8e5e0b670..1dbcafe53 100644 --- a/spec/integ/matrix-client.spec.js +++ b/spec/integ/matrix-client.spec.js @@ -156,7 +156,6 @@ describe("MatrixClient", function() { var fired = false; client.on("User.presence", function(event, user) { fired = true; - console.log("handle"); expect(user).toBeDefined(); expect(event).toBeDefined(); if (!user || !event) { return; }