Merge pull request #2168 from matrix-org/bwindels/fixstalerr
Fix stale RR and improve LL reliability in RoomView & MemberList.
This commit is contained in:
commit
b431292f6e
5 changed files with 65 additions and 90 deletions
|
@ -144,26 +144,25 @@ function createRoomTimelineAction(matrixClient, timelineEvent, room, toStartOfTi
|
||||||
/**
|
/**
|
||||||
* @typedef RoomMembershipAction
|
* @typedef RoomMembershipAction
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
* @property {string} action 'MatrixActions.RoomMember.membership'.
|
* @property {string} action 'MatrixActions.Room.myMembership'.
|
||||||
* @property {RoomMember} member the member whose membership was updated.
|
* @property {Room} room to room for which the self-membership changed.
|
||||||
|
* @property {string} membership the new membership
|
||||||
|
* @property {string} oldMembership the previous membership, can be null.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a MatrixActions.Room.selfMembership action that represents
|
* Create a MatrixActions.Room.myMembership action that represents
|
||||||
* a MatrixClient `RoomMember.membership` matrix event for the syncing user,
|
* a MatrixClient `Room.myMembership` event for the syncing user,
|
||||||
* emitted when the member's membership is updated.
|
* emitted when the syncing user's membership is updated for a room.
|
||||||
*
|
*
|
||||||
* @param {MatrixClient} matrixClient the matrix client.
|
* @param {MatrixClient} matrixClient the matrix client.
|
||||||
* @param {MatrixEvent} membershipEvent the m.room.member event.
|
* @param {Room} room to room for which the self-membership changed.
|
||||||
* @param {RoomMember} member the member whose membership was updated.
|
* @param {string} membership the new membership
|
||||||
* @param {string} oldMembership the member's previous membership.
|
* @param {string} oldMembership the previous membership, can be null.
|
||||||
* @returns {RoomMembershipAction} an action of type `MatrixActions.RoomMember.membership`.
|
* @returns {RoomMembershipAction} an action of type `MatrixActions.Room.myMembership`.
|
||||||
*/
|
*/
|
||||||
function createSelfRoomMembershipAction(matrixClient, membershipEvent, member, oldMembership) {
|
function createSelfMembershipAction(matrixClient, room, membership, oldMembership) {
|
||||||
if (member.userId === matrixClient.getUserId()) {
|
return { action: 'MatrixActions.Room.myMembership', room, membership, oldMembership};
|
||||||
return { action: 'MatrixActions.Room.selfMembership', member };
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -205,7 +204,7 @@ export default {
|
||||||
this._addMatrixClientListener(matrixClient, 'Room', createRoomAction);
|
this._addMatrixClientListener(matrixClient, 'Room', createRoomAction);
|
||||||
this._addMatrixClientListener(matrixClient, 'Room.tags', createRoomTagsAction);
|
this._addMatrixClientListener(matrixClient, 'Room.tags', createRoomTagsAction);
|
||||||
this._addMatrixClientListener(matrixClient, 'Room.timeline', createRoomTimelineAction);
|
this._addMatrixClientListener(matrixClient, 'Room.timeline', createRoomTimelineAction);
|
||||||
this._addMatrixClientListener(matrixClient, 'RoomMember.membership', createSelfRoomMembershipAction);
|
this._addMatrixClientListener(matrixClient, 'Room.myMembership', createSelfMembershipAction);
|
||||||
this._addMatrixClientListener(matrixClient, 'Event.decrypted', createEventDecryptedAction);
|
this._addMatrixClientListener(matrixClient, 'Event.decrypted', createEventDecryptedAction);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -91,13 +91,16 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
const llMembers = MatrixClientPeg.get().hasLazyLoadMembersEnabled();
|
||||||
return {
|
return {
|
||||||
room: null,
|
room: null,
|
||||||
roomId: null,
|
roomId: null,
|
||||||
roomLoading: true,
|
roomLoading: true,
|
||||||
peekLoading: false,
|
peekLoading: false,
|
||||||
shouldPeek: true,
|
shouldPeek: true,
|
||||||
|
// used to trigger a rerender in TimelinePanel once the members are loaded,
|
||||||
|
// so RR are rendered again (now with the members available), ...
|
||||||
|
membersLoaded: !llMembers,
|
||||||
// The event to be scrolled to initially
|
// The event to be scrolled to initially
|
||||||
initialEventId: null,
|
initialEventId: null,
|
||||||
// The offset in pixels from the event with which to scroll vertically
|
// The offset in pixels from the event with which to scroll vertically
|
||||||
|
@ -148,7 +151,7 @@ module.exports = React.createClass({
|
||||||
MatrixClientPeg.get().on("Room.name", this.onRoomName);
|
MatrixClientPeg.get().on("Room.name", this.onRoomName);
|
||||||
MatrixClientPeg.get().on("Room.accountData", this.onRoomAccountData);
|
MatrixClientPeg.get().on("Room.accountData", this.onRoomAccountData);
|
||||||
MatrixClientPeg.get().on("RoomState.members", this.onRoomStateMember);
|
MatrixClientPeg.get().on("RoomState.members", this.onRoomStateMember);
|
||||||
MatrixClientPeg.get().on("RoomMember.membership", this.onRoomMemberMembership);
|
MatrixClientPeg.get().on("Room.myMembership", this.onMyMembership);
|
||||||
MatrixClientPeg.get().on("accountData", this.onAccountData);
|
MatrixClientPeg.get().on("accountData", this.onAccountData);
|
||||||
|
|
||||||
// Start listening for RoomViewStore updates
|
// Start listening for RoomViewStore updates
|
||||||
|
@ -412,8 +415,8 @@ module.exports = React.createClass({
|
||||||
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
|
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
|
||||||
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
|
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
|
||||||
MatrixClientPeg.get().removeListener("Room.accountData", this.onRoomAccountData);
|
MatrixClientPeg.get().removeListener("Room.accountData", this.onRoomAccountData);
|
||||||
|
MatrixClientPeg.get().removeListener("Room.myMembership", this.onMyMembership);
|
||||||
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
|
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
|
||||||
MatrixClientPeg.get().removeListener("RoomMember.membership", this.onRoomMemberMembership);
|
|
||||||
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -582,21 +585,25 @@ module.exports = React.createClass({
|
||||||
this._warnAboutEncryption(room);
|
this._warnAboutEncryption(room);
|
||||||
this._calculatePeekRules(room);
|
this._calculatePeekRules(room);
|
||||||
this._updatePreviewUrlVisibility(room);
|
this._updatePreviewUrlVisibility(room);
|
||||||
this._loadMembersIfJoined();
|
this._loadMembersIfJoined(room);
|
||||||
},
|
},
|
||||||
|
|
||||||
_loadMembersIfJoined: function() {
|
_loadMembersIfJoined: async function(room) {
|
||||||
// lazy load members if enabled
|
// lazy load members if enabled
|
||||||
if (SettingsStore.isFeatureEnabled('feature_lazyloading')) {
|
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const room = cli.getRoom(this.state.roomId);
|
if (cli.hasLazyLoadMembersEnabled()) {
|
||||||
if (room && room.getMyMembership() === 'join') {
|
if (room && room.getMyMembership() === 'join') {
|
||||||
room.loadMembersIfNeeded().catch((err) => {
|
try {
|
||||||
|
await room.loadMembersIfNeeded();
|
||||||
|
if (!this.unmounted) {
|
||||||
|
this.setState({membersLoaded: true});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
const errorMessage = `Fetching room members for ${room.roomId} failed.` +
|
const errorMessage = `Fetching room members for ${room.roomId} failed.` +
|
||||||
" Room members will appear incomplete.";
|
" Room members will appear incomplete.";
|
||||||
console.error(errorMessage);
|
console.error(errorMessage);
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -710,10 +717,10 @@ module.exports = React.createClass({
|
||||||
this._updateRoomMembers();
|
this._updateRoomMembers();
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoomMemberMembership: function(ev, member, oldMembership) {
|
onMyMembership: function(room, membership, oldMembership) {
|
||||||
if (member.userId == MatrixClientPeg.get().credentials.userId) {
|
if (room.roomId === this.state.roomId) {
|
||||||
this._loadMembersIfJoined();
|
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
|
this._loadMembersIfJoined(room);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1761,6 +1768,7 @@ module.exports = React.createClass({
|
||||||
onReadMarkerUpdated={this._updateTopUnreadMessagesBar}
|
onReadMarkerUpdated={this._updateTopUnreadMessagesBar}
|
||||||
showUrlPreview = {this.state.showUrlPreview}
|
showUrlPreview = {this.state.showUrlPreview}
|
||||||
className="mx_RoomView_messagePanel"
|
className="mx_RoomView_messagePanel"
|
||||||
|
membersLoaded={this.state.membersLoaded}
|
||||||
/>);
|
/>);
|
||||||
|
|
||||||
let topUnreadMessagesBar = null;
|
let topUnreadMessagesBar = null;
|
||||||
|
|
|
@ -34,20 +34,23 @@ module.exports = React.createClass({
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
if (cli.hasLazyLoadMembersEnabled()) {
|
if (cli.hasLazyLoadMembersEnabled()) {
|
||||||
return {loading: true};
|
// show an empty list
|
||||||
|
return this._getMembersState([]);
|
||||||
} else {
|
} else {
|
||||||
return this._getMembersState();
|
return this._getMembersState(this.roomMembers());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
this._mounted = false;
|
this._mounted = true;
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
if (!cli.hasLazyLoadMembersEnabled()) {
|
if (cli.hasLazyLoadMembersEnabled()) {
|
||||||
|
this._showMembersAccordingToMembershipWithLL();
|
||||||
|
cli.on("Room.myMembership", this.onMyMembership);
|
||||||
|
} else {
|
||||||
this._listenForMembersChanges();
|
this._listenForMembersChanges();
|
||||||
}
|
}
|
||||||
cli.on("Room", this.onRoom); // invites & joining after peek
|
cli.on("Room", this.onRoom); // invites & joining after peek
|
||||||
cli.on("RoomMember.membership", this.onRoomMembership); // update when accepting an invite
|
|
||||||
const enablePresenceByHsUrl = SdkConfig.get()["enable_presence_by_hs_url"];
|
const enablePresenceByHsUrl = SdkConfig.get()["enable_presence_by_hs_url"];
|
||||||
const hsUrl = MatrixClientPeg.get().baseUrl;
|
const hsUrl = MatrixClientPeg.get().baseUrl;
|
||||||
this._showPresence = true;
|
this._showPresence = true;
|
||||||
|
@ -68,51 +71,50 @@ module.exports = React.createClass({
|
||||||
// cli.on("Room.timeline", this.onRoomTimeline);
|
// cli.on("Room.timeline", this.onRoomTimeline);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: async function() {
|
|
||||||
this._mounted = true;
|
|
||||||
this._loadMembersIfNeeded(true);
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
this._mounted = false;
|
this._mounted = false;
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
if (cli) {
|
if (cli) {
|
||||||
cli.removeListener("RoomState.members", this.onRoomStateMember);
|
cli.removeListener("RoomState.members", this.onRoomStateMember);
|
||||||
cli.removeListener("RoomMember.name", this.onRoomMemberName);
|
cli.removeListener("RoomMember.name", this.onRoomMemberName);
|
||||||
cli.removeListener("RoomMember.membership", this.onRoomMembership);
|
cli.removeListener("Room.myMembership", this.onMyMembership);
|
||||||
cli.removeListener("RoomState.events", this.onRoomStateEvent);
|
cli.removeListener("RoomState.events", this.onRoomStateEvent);
|
||||||
cli.removeListener("Room", this.onRoom);
|
cli.removeListener("Room", this.onRoom);
|
||||||
cli.removeListener("User.lastPresenceTs", this.onUserLastPresenceTs);
|
cli.removeListener("User.lastPresenceTs", this.onUserLastPresenceTs);
|
||||||
// cli.removeListener("Room.timeline", this.onRoomTimeline);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// cancel any pending calls to the rate_limited_funcs
|
// cancel any pending calls to the rate_limited_funcs
|
||||||
this._updateList.cancelPendingCall();
|
this._updateList.cancelPendingCall();
|
||||||
},
|
},
|
||||||
|
|
||||||
_loadMembersIfNeeded: async function(initial) {
|
/**
|
||||||
|
* If lazy loading is enabled, either:
|
||||||
|
* show a spinner and load the members if the user is joined,
|
||||||
|
* or show the members available so far if the user is invited
|
||||||
|
*/
|
||||||
|
_showMembersAccordingToMembershipWithLL: async function() {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
if (cli.hasLazyLoadMembersEnabled()) {
|
if (cli.hasLazyLoadMembersEnabled()) {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const room = cli.getRoom(this.props.roomId);
|
const room = cli.getRoom(this.props.roomId);
|
||||||
if (room && room.getMyMembership() === 'join') {
|
const membership = room && room.getMyMembership();
|
||||||
|
if (membership === "join") {
|
||||||
this.setState({loading: true});
|
this.setState({loading: true});
|
||||||
try {
|
try {
|
||||||
await room.loadMembersIfNeeded();
|
await room.loadMembersIfNeeded();
|
||||||
} catch(ex) {/* already logged in RoomView */}
|
} catch (ex) {/* already logged in RoomView */}
|
||||||
if (this._mounted) {
|
if (this._mounted) {
|
||||||
this.setState(this._getMembersState());
|
this.setState(this._getMembersState(this.roomMembers()));
|
||||||
this._listenForMembersChanges();
|
this._listenForMembersChanges();
|
||||||
}
|
}
|
||||||
} else if(initial) {
|
} else if (membership === "invite") {
|
||||||
// show the members we've got
|
// show the members we've got when invited
|
||||||
this.setState(this._getMembersState());
|
this.setState(this._getMembersState(this.roomMembers()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_getMembersState: function() {
|
_getMembersState: function(members) {
|
||||||
const members = this.roomMembers();
|
|
||||||
// set the state after determining _showPresence to make sure it's
|
// set the state after determining _showPresence to make sure it's
|
||||||
// taken into account while rerendering
|
// taken into account while rerendering
|
||||||
return {
|
return {
|
||||||
|
@ -129,34 +131,6 @@ module.exports = React.createClass({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
|
||||||
onRoomTimeline: function(ev, room, toStartOfTimeline, removed, data) {
|
|
||||||
// ignore anything but real-time updates at the end of the room:
|
|
||||||
// updates from pagination will happen when the paginate completes.
|
|
||||||
if (toStartOfTimeline || !data || !data.liveEvent) return;
|
|
||||||
|
|
||||||
// treat any activity from a user as implicit presence to update the
|
|
||||||
// ordering of the list whenever someone says something.
|
|
||||||
// Except right now we're not tiebreaking "active now" users in this way
|
|
||||||
// so don't bother for now.
|
|
||||||
if (ev.getSender()) {
|
|
||||||
// console.log("implicit presence from " + ev.getSender());
|
|
||||||
|
|
||||||
var tile = this.refs[ev.getSender()];
|
|
||||||
if (tile) {
|
|
||||||
// work around a race where you might have a room member object
|
|
||||||
// before the user object exists. XXX: why does this ever happen?
|
|
||||||
var all_members = room.currentState.members;
|
|
||||||
var userId = ev.getSender();
|
|
||||||
if (all_members[userId].user === null) {
|
|
||||||
all_members[userId].user = MatrixClientPeg.get().getUser(userId);
|
|
||||||
}
|
|
||||||
this._updateList(); // reorder the membership list
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
|
|
||||||
onUserLastPresenceTs(event, user) {
|
onUserLastPresenceTs(event, user) {
|
||||||
// Attach a SINGLE listener for global presence changes then locate the
|
// Attach a SINGLE listener for global presence changes then locate the
|
||||||
// member tile and re-render it. This is more efficient than every tile
|
// member tile and re-render it. This is more efficient than every tile
|
||||||
|
@ -175,20 +149,12 @@ module.exports = React.createClass({
|
||||||
// We listen for room events because when we accept an invite
|
// We listen for room events because when we accept an invite
|
||||||
// we need to wait till the room is fully populated with state
|
// we need to wait till the room is fully populated with state
|
||||||
// before refreshing the member list else we get a stale list.
|
// before refreshing the member list else we get a stale list.
|
||||||
|
this._showMembersAccordingToMembershipWithLL();
|
||||||
// also when peeking, we need to await the members being loaded
|
|
||||||
// before showing them.
|
|
||||||
|
|
||||||
this._loadMembersIfNeeded();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoomMembership: function(ev, member, oldMembership) {
|
onMyMembership: function(room, membership, oldMembership) {
|
||||||
const cli = MatrixClientPeg.get();
|
if (room.roomId === this.props.roomId && membership === "join") {
|
||||||
const myId = cli.getUserId();
|
this._showMembersAccordingToMembershipWithLL();
|
||||||
if (member.userId === myId && oldMembership !== "join" && member.membership === "join") {
|
|
||||||
// once we've joined, no need to listen for membership anymore
|
|
||||||
cli.removeListener("RoomMember.membership", this.onRoomMembership);
|
|
||||||
this._loadMembersIfNeeded();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -120,7 +120,7 @@ class RoomListStore extends Store {
|
||||||
this._generateRoomLists();
|
this._generateRoomLists();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'MatrixActions.Room.selfMembership': {
|
case 'MatrixActions.Room.myMembership': {
|
||||||
this._generateRoomLists();
|
this._generateRoomLists();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -74,6 +74,7 @@ describe('RoomList', () => {
|
||||||
// Mock joined member
|
// Mock joined member
|
||||||
myMember = new RoomMember(movingRoomId, myUserId);
|
myMember = new RoomMember(movingRoomId, myUserId);
|
||||||
myMember.membership = 'join';
|
myMember.membership = 'join';
|
||||||
|
movingRoom.updateMyMembership('join');
|
||||||
movingRoom.getMember = (userId) => ({
|
movingRoom.getMember = (userId) => ({
|
||||||
[client.credentials.userId]: myMember,
|
[client.credentials.userId]: myMember,
|
||||||
}[userId]);
|
}[userId]);
|
||||||
|
@ -81,6 +82,7 @@ describe('RoomList', () => {
|
||||||
otherRoom = createRoom({name: 'Other room'});
|
otherRoom = createRoom({name: 'Other room'});
|
||||||
myOtherMember = new RoomMember(otherRoom.roomId, myUserId);
|
myOtherMember = new RoomMember(otherRoom.roomId, myUserId);
|
||||||
myOtherMember.membership = 'join';
|
myOtherMember.membership = 'join';
|
||||||
|
otherRoom.updateMyMembership('join');
|
||||||
otherRoom.getMember = (userId) => ({
|
otherRoom.getMember = (userId) => ({
|
||||||
[client.credentials.userId]: myOtherMember,
|
[client.credentials.userId]: myOtherMember,
|
||||||
}[userId]);
|
}[userId]);
|
||||||
|
|
Loading…
Reference in a new issue