diff --git a/src/models/event-timeline-set.js b/src/models/event-timeline-set.js index 4b46eccf523ac891051aeb9900341990432e14e6..8d96b56e1110a57305dca46cd2211d2dda480900 100644 --- a/src/models/event-timeline-set.js +++ b/src/models/event-timeline-set.js @@ -150,10 +150,12 @@ EventTimelineSet.prototype.replaceEventId = function(oldEventId, newEventId) { * * @param {string=} backPaginationToken token for back-paginating the new timeline * @param {?bool} flush Whether to flush the non-live timelines too. + * @param {?function} onNewLiveTimeline Called with the new unfiltered, live timeline + * as soon as it's available. This can be used to set event listeners. * * @fires module:client~MatrixClient#event:"Room.timelineReset" */ -EventTimelineSet.prototype.resetLiveTimeline = function(backPaginationToken, flush) { +EventTimelineSet.prototype.resetLiveTimeline = function(backPaginationToken, flush, onNewLiveTimeline) { // if timeline support is disabled, forget about the old timelines const resetAllTimelines = !this._timelineSupport || flush; @@ -166,6 +168,12 @@ EventTimelineSet.prototype.resetLiveTimeline = function(backPaginationToken, flu newTimeline = this.addTimeline(); } + // Allow event listeners to be set up before we start injecting events + // as otherwise events will be missed: most importantly the RoomState.newMember + // events which are used to set up reEmit on Member events and only fired when + // room members are first created. + if (onNewLiveTimeline) onNewLiveTimeline(newTimeline); + // initialise the state in the new timeline from our last known state const evMap = this._liveTimeline.getState(EventTimeline.FORWARDS).events; const events = []; diff --git a/src/models/room.js b/src/models/room.js index a1f88cd7c246cb1448ff1f3e349e644692731124..ce86e142f3f43174630610273e9e5454cbc5f823 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -207,9 +207,16 @@ Room.prototype.getLiveTimeline = function() { * @param {string=} backPaginationToken token for back-paginating the new timeline * @param {boolean=} flush True to remove all events in all timelines. If false, only * the live timeline is reset. + * @param {Object=} onNewLiveTimeline Function called with the new live timeline object + * once it has been created, but before it has had any event inserted into it. This can be + * used to add any event listeners. */ -Room.prototype.resetLiveTimeline = function(backPaginationToken, flush) { - for (let i = 0; i < this._timelineSets.length; i++) { +Room.prototype.resetLiveTimeline = function(backPaginationToken, flush, onNewLiveTimeline) { + // The unfiltered timeline set (we pass the onNewLiveTimeline callback here) + this._timelineSets[0].resetLiveTimeline(backPaginationToken, flush, onNewLiveTimeline); + + // any other timeline sets + for (let i = 1; i < this._timelineSets.length; i++) { this._timelineSets[i].resetLiveTimeline(backPaginationToken, flush); } diff --git a/src/sync.js b/src/sync.js index 284b967efb11991b152a01f38d72a1335fe6cbae..6aee97b072d0cb198a2fdb40693164418e6d2d89 100644 --- a/src/sync.js +++ b/src/sync.js @@ -120,7 +120,7 @@ SyncApi.prototype.createRoom = function(roomId) { "Room.localEchoUpdated", "Room.accountData", ]); - this._registerStateListeners(room); + this._registerStateListeners(room, room.currentState); return room; }; @@ -128,15 +128,15 @@ SyncApi.prototype.createRoom = function(roomId) { * @param {Room} room * @private */ -SyncApi.prototype._registerStateListeners = function(room) { +SyncApi.prototype._registerStateListeners = function(room, currentState) { const client = this.client; // we need to also re-emit room state and room member events, so hook it up // to the client now. We need to add a listener for RoomState.members in // order to hook them correctly. (TODO: find a better way?) - reEmit(client, room.currentState, [ + reEmit(client, currentState, [ "RoomState.events", "RoomState.members", "RoomState.newMember", ]); - room.currentState.on("RoomState.newMember", function(event, state, member) { + currentState.on("RoomState.newMember", function(event, state, member) { member.user = client.getUser(member.userId); reEmit( client, member, @@ -892,14 +892,15 @@ SyncApi.prototype._processSyncResponse = function(syncToken, data) { room.resetLiveTimeline( joinObj.timeline.prev_batch, self.opts.canResetEntireTimeline(room.roomId), + (newLiveTimeline) => { + self._registerStateListeners(room, newLiveTimeline.getState(EventTimeline.FORWARDS)); + } ); // We have to assume any gap in any timeline is // reason to stop incrementally tracking notifications and // reset the timeline. client.resetNotifTimelineSet(); - - self._registerStateListeners(room); } }