Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Track live events in timeline and use for read receipts and read markers #3184

Merged
merged 2 commits into from
Jul 5, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 40 additions & 25 deletions src/components/structures/TimelinePanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Copyright 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -141,6 +142,7 @@ const TimelinePanel = React.createClass({

return {
events: [],
liveEvents: [],
timelineLoading: true, // track whether our room timeline is loading

// canBackPaginate == false may mean:
Expand Down Expand Up @@ -322,9 +324,11 @@ const TimelinePanel = React.createClass({

// We can now paginate in the unpaginated direction
const canPaginateKey = (backwards) ? 'canBackPaginate' : 'canForwardPaginate';
const { events, liveEvents } = this._getEvents();
this.setState({
[canPaginateKey]: true,
events: this._getEvents(),
events,
liveEvents,
});
}
},
Expand Down Expand Up @@ -356,10 +360,12 @@ const TimelinePanel = React.createClass({

debuglog("TimelinePanel: paginate complete backwards:"+backwards+"; success:"+r);

const { events, liveEvents } = this._getEvents();
const newState = {
[paginatingKey]: false,
[canPaginateKey]: r,
events: this._getEvents(),
events,
liveEvents,
};

// moving the window in this direction may mean that we can now
Expand Down Expand Up @@ -453,15 +459,13 @@ const TimelinePanel = React.createClass({
this._timelineWindow.paginate(EventTimeline.FORWARDS, 1, false).done(() => {
if (this.unmounted) { return; }

const events = this._timelineWindow.getEvents();
const lastEv = events[events.length-1];
const { events, liveEvents } = this._getEvents();
const lastLiveEvent = liveEvents[liveEvents.length - 1];

// if we're at the end of the live timeline, append the pending events
if (this.props.timelineSet.room && !this._timelineWindow.canPaginate(EventTimeline.FORWARDS)) {
events.push(...this.props.timelineSet.room.getPendingEvents());
}

const updatedState = {events: events};
const updatedState = {
events,
liveEvents,
};

let callRMUpdated;
if (this.props.manageReadMarkers) {
Expand All @@ -478,13 +482,13 @@ const TimelinePanel = React.createClass({
callRMUpdated = false;
if (sender != myUserId && !UserActivity.sharedInstance().userActiveRecently()) {
updatedState.readMarkerVisible = true;
} else if (lastEv && this.getReadMarkerPosition() === 0) {
} else if (lastLiveEvent && this.getReadMarkerPosition() === 0) {
// we know we're stuckAtBottom, so we can advance the RM
// immediately, to save a later render cycle

this._setReadMarker(lastEv.getId(), lastEv.getTs(), true);
this._setReadMarker(lastLiveEvent.getId(), lastLiveEvent.getTs(), true);
updatedState.readMarkerVisible = false;
updatedState.readMarkerEventId = lastEv.getId();
updatedState.readMarkerEventId = lastLiveEvent.getId();
callRMUpdated = true;
}
}
Expand Down Expand Up @@ -694,9 +698,12 @@ const TimelinePanel = React.createClass({
if (e.errcode === 'M_UNRECOGNIZED' && lastReadEvent) {
return MatrixClientPeg.get().sendReadReceipt(
lastReadEvent,
).catch(() => {
).catch((e) => {
console.error(e);
this.lastRRSentEventId = undefined;
});
} else {
console.error(e);
}
// it failed, so allow retries next time the user is active
this.lastRRSentEventId = undefined;
Expand Down Expand Up @@ -762,9 +769,9 @@ const TimelinePanel = React.createClass({
_advanceReadMarkerPastMyEvents: function() {
if (!this.props.manageReadMarkers) return;

// we call _timelineWindow.getEvents() rather than using
// this.state.events, because react batches the update to the latter, so it
// may not have been updated yet.
// we call `_timelineWindow.getEvents()` rather than using
// `this.state.liveEvents`, because React batches the update to the
// latter, so it may not have been updated yet.
const events = this._timelineWindow.getEvents();

// first find where the current RM is
Expand Down Expand Up @@ -1067,6 +1074,7 @@ const TimelinePanel = React.createClass({
} else {
this.setState({
events: [],
liveEvents: [],
canBackPaginate: false,
canForwardPaginate: false,
timelineLoading: true,
Expand All @@ -1086,21 +1094,26 @@ const TimelinePanel = React.createClass({
// the results if so.
if (this.unmounted) return;

this.setState({
events: this._getEvents(),
});
this.setState(this._getEvents());
},

// get the list of events from the timeline window and the pending event list
_getEvents: function() {
const events = this._timelineWindow.getEvents();

// Hold onto the live events separately. The read receipt and read marker
// should use this list, so that they don't advance into pending events.
const liveEvents = [...events];

// if we're at the end of the live timeline, append the pending events
if (!this._timelineWindow.canPaginate(EventTimeline.FORWARDS)) {
events.push(...this.props.timelineSet.getPendingEvents());
}

return events;
return {
events,
liveEvents,
};
},

_indexForEventId: function(evId) {
Expand Down Expand Up @@ -1128,8 +1141,10 @@ const TimelinePanel = React.createClass({
const myUserId = MatrixClientPeg.get().credentials.userId;

let lastDisplayedIndex = null;
for (let i = this.state.events.length - 1; i >= 0; --i) {
const ev = this.state.events[i];
// Use `liveEvents` here because we don't want the read marker or read
// receipt to advance into pending events.
for (let i = this.state.liveEvents.length - 1; i >= 0; --i) {
const ev = this.state.liveEvents[i];

if (ignoreOwn && ev.sender && ev.sender.userId == myUserId) {
continue;
Expand Down Expand Up @@ -1164,8 +1179,8 @@ const TimelinePanel = React.createClass({
// easier to reason about, so let's start there and optimise later if
// needed.
if (allowEventsWithoutTiles) {
for (let i = lastDisplayedIndex + 1; i < this.state.events.length; i++) {
const ev = this.state.events[i];
for (let i = lastDisplayedIndex + 1; i < this.state.liveEvents.length; i++) {
const ev = this.state.liveEvents[i];
if (EventTile.haveTileForEvent(ev)) {
break;
}
Expand Down