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

Lazy load room members - Part I #2072

Merged
merged 19 commits into from
Jul 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
12 changes: 12 additions & 0 deletions src/MatrixClientPeg.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ interface MatrixClientCreds {
guest: boolean,
}

const FILTER_CONTENT = {
room: {
state: {
lazy_load_members: true,
},
},
};

/**
* Wrapper object for handling the js-sdk Matrix Client object in the react-sdk
* Handles the creation/initialisation of client objects.
Expand Down Expand Up @@ -99,6 +107,10 @@ class MatrixClientPeg {
// the react sdk doesn't work without this, so don't allow
opts.pendingEventOrdering = "detached";

if (SettingsStore.isFeatureEnabled('feature_lazyloading')) {
opts.filter = await this.matrixClient.createFilter(FILTER_CONTENT);
}

try {
const promise = this.matrixClient.store.startup();
console.log(`MatrixClientPeg: waiting for MatrixClient store to initialise`);
Expand Down
51 changes: 26 additions & 25 deletions src/Rooms.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,26 +31,26 @@ export function getDisplayAliasForRoom(room) {
* If the room contains only two members including the logged-in user,
* return the other one. Otherwise, return null.
*/
export function getOnlyOtherMember(room, me) {
const joinedMembers = room.getJoinedMembers();
export function getOnlyOtherMember(room, myUserId) {

if (joinedMembers.length === 2) {
return joinedMembers.filter(function(m) {
return m.userId !== me.userId;
if (room.currentState.getJoinedMemberCount() === 2) {
return room.getJoinedMembers().filter(function(m) {
return m.userId !== myUserId;
})[0];
}

return null;
}

function _isConfCallRoom(room, me, conferenceHandler) {
function _isConfCallRoom(room, myUserId, conferenceHandler) {
if (!conferenceHandler) return false;

if (me.membership != "join") {
const myMembership = room.getMyMembership(myUserId);
if (myMembership != "join") {
return false;
}

const otherMember = getOnlyOtherMember(room, me);
const otherMember = getOnlyOtherMember(room, myUserId);
if (otherMember === null) {
return false;
}
Expand All @@ -68,29 +68,30 @@ const isConfCallRoomCache = {
// $roomId: bool
};

export function isConfCallRoom(room, me, conferenceHandler) {
export function isConfCallRoom(room, myUserId, conferenceHandler) {
if (isConfCallRoomCache[room.roomId] !== undefined) {
return isConfCallRoomCache[room.roomId];
}

const result = _isConfCallRoom(room, me, conferenceHandler);
const result = _isConfCallRoom(room, myUserId, conferenceHandler);

isConfCallRoomCache[room.roomId] = result;

return result;
}

export function looksLikeDirectMessageRoom(room, me) {
if (me.membership == "join" || me.membership === "ban" ||
(me.membership === "leave" && me.events.member.getSender() !== me.events.member.getStateKey())) {
export function looksLikeDirectMessageRoom(room, myUserId) {
const myMembership = room.getMyMembership(myUserId);
const me = room.getMember(myUserId);

if (myMembership == "join" || myMembership === "ban" || (me && me.isKicked())) {
// Used to split rooms via tags
const tagNames = Object.keys(room.tags);
// Used for 1:1 direct chats
const members = room.currentState.getMembers();

// Show 1:1 chats in seperate "Direct Messages" section as long as they haven't
// been moved to a different tag section
if (members.length === 2 && !tagNames.length) {
// TODO: Use SUMMARYAPI to take invited users into account
if (room.currentState.getJoinedMemberCount() === 2 && !tagNames.length) {
return true;
}
}
Expand All @@ -100,10 +101,10 @@ export function looksLikeDirectMessageRoom(room, me) {
export function guessAndSetDMRoom(room, isDirect) {
let newTarget;
if (isDirect) {
const guessedTarget = guessDMRoomTarget(
room, room.getMember(MatrixClientPeg.get().credentials.userId),
const guessedUserId = guessDMRoomTargetId(
room, MatrixClientPeg.get().getUserId()
);
newTarget = guessedTarget.userId;
newTarget = guessedUserId;
} else {
newTarget = null;
}
Expand Down Expand Up @@ -159,15 +160,15 @@ export function setDMRoom(roomId, userId) {
* Given a room, estimate which of its members is likely to
* be the target if the room were a DM room and return that user.
*/
export function guessDMRoomTarget(room, me) {
function guessDMRoomTargetId(room, myUserId) {
let oldestTs;
let oldestUser;

// Pick the joined user who's been here longest (and isn't us),
for (const user of room.getJoinedMembers()) {
if (user.userId == me.userId) continue;
if (user.userId == myUserId) continue;

if (oldestTs === undefined || user.events.member.getTs() < oldestTs) {
if (oldestTs === undefined || (user.events.member && user.events.member.getTs() < oldestTs)) {
oldestUser = user;
oldestTs = user.events.member.getTs();
}
Expand All @@ -176,14 +177,14 @@ export function guessDMRoomTarget(room, me) {

// if there are no joined members other than us, use the oldest member
for (const user of room.currentState.getMembers()) {
if (user.userId == me.userId) continue;
if (user.userId == myUserId) continue;

if (oldestTs === undefined || user.events.member.getTs() < oldestTs) {
if (oldestTs === undefined || (user.events.member && user.events.member.getTs() < oldestTs)) {
oldestUser = user;
oldestTs = user.events.member.getTs();
}
}

if (oldestUser === undefined) return me;
if (oldestUser === undefined) return myUserId;
return oldestUser;
}
47 changes: 17 additions & 30 deletions src/components/structures/RoomView.js
Original file line number Diff line number Diff line change
Expand Up @@ -309,9 +309,21 @@ module.exports = React.createClass({
}
});
} else if (room) {
//viewing a previously joined room, try to lazy load members

// Stop peeking because we have joined this room previously
MatrixClientPeg.get().stopPeeking();
this.setState({isPeeking: false});

// lazy load members if enabled
if (SettingsStore.isFeatureEnabled('feature_lazyloading')) {
MatrixClientPeg.get().loadRoomMembersIfNeeded(room.roomId).catch((err) => {
const errorMessage = `Fetching room members for ${this.roomId} failed.` +
" Room members will appear incomplete.";
console.error(errorMessage);
console.error(err);
});
}
}
}
},
Expand Down Expand Up @@ -746,40 +758,15 @@ module.exports = React.createClass({
},

_updateDMState() {
const me = this.state.room.getMember(MatrixClientPeg.get().credentials.userId);
const me = this.state.room.getMember(MatrixClientPeg.get().getUserId());
if (!me || me.membership !== "join") {
return;
}
const roomId = this.state.room.roomId;
const dmInviter = me.getDMInviter();

// The user may have accepted an invite with is_direct set
if (me.events.member.getPrevContent().membership === "invite" &&
me.events.member.getPrevContent().is_direct
) {
// This is a DM with the sender of the invite event (which we assume
// preceded the join event)
Rooms.setDMRoom(
this.state.room.roomId,
me.events.member.getUnsigned().prev_sender,
);
return;
}

const invitedMembers = this.state.room.getMembersWithMembership("invite");
const joinedMembers = this.state.room.getMembersWithMembership("join");

// There must be one invited member and one joined member
if (invitedMembers.length !== 1 || joinedMembers.length !== 1) {
return;
}

// The user may have sent an invite with is_direct sent
const other = invitedMembers[0];
if (other &&
other.membership === "invite" &&
other.events.member.getContent().is_direct
) {
Rooms.setDMRoom(this.state.room.roomId, other.userId);
return;
if (dmInviter) {
Rooms.setDMRoom(roomId, dmInviter);
}
},

Expand Down
2 changes: 1 addition & 1 deletion src/components/views/rooms/MemberInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,7 @@ module.exports = withMatrixClient(React.createClass({

onMemberAvatarClick: function() {
const member = this.props.member;
const avatarUrl = member.user ? member.user.avatarUrl : member.events.member.getContent().avatar_url;
const avatarUrl = member.getMxcAvatarUrl();
if (!avatarUrl) return;

const httpUrl = this.props.matrixClient.mxcUrlToHttp(avatarUrl);
Expand Down
4 changes: 2 additions & 2 deletions src/components/views/rooms/RoomList.js
Original file line number Diff line number Diff line change
Expand Up @@ -342,8 +342,8 @@ module.exports = React.createClass({
if (!taggedRoom) {
return;
}
const me = taggedRoom.getMember(MatrixClientPeg.get().credentials.userId);
if (HIDE_CONFERENCE_CHANS && Rooms.isConfCallRoom(taggedRoom, me, this.props.ConferenceHandler)) {
const myUserId = MatrixClientPeg.get().getUserId();
if (HIDE_CONFERENCE_CHANS && Rooms.isConfCallRoom(taggedRoom, myUserId, this.props.ConferenceHandler)) {
return;
}

Expand Down
14 changes: 5 additions & 9 deletions src/components/views/rooms/RoomPreviewBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,11 @@ module.exports = React.createClass({
</div>);
}

const myMember = this.props.room ? this.props.room.currentState.members[
MatrixClientPeg.get().credentials.userId
] : null;
const kicked = (
myMember &&
myMember.membership == 'leave' &&
myMember.events.member.getSender() != MatrixClientPeg.get().credentials.userId
);
const banned = myMember && myMember.membership == 'ban';
const myMember = this.props.room ?
this.props.room.getMember(MatrixClientPeg.get().getUserId()) :
null;
const kicked = myMember && myMember.isKicked();
const banned = myMember && myMember && myMember.membership == 'ban';

if (this.props.inviterName) {
let emailMatchBlock;
Expand Down
5 changes: 5 additions & 0 deletions src/settings/Settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ export const SETTINGS = {
supportedLevels: LEVELS_FEATURE,
default: false,
},
"feature_lazyloading": {
isFeature: true,
displayName: _td("Increase performance by loading room members on first view"),
supportedLevels: LEVELS_FEATURE,
},
"MessageComposerInput.dontSuggestEmoji": {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
displayName: _td('Disable Emoji suggestions while typing'),
Expand Down
14 changes: 7 additions & 7 deletions src/stores/RoomListStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,13 +173,13 @@ class RoomListStore extends Store {
if (!this._matrixClient) return;

this._matrixClient.getRooms().forEach((room, index) => {
const me = room.getMember(this._matrixClient.credentials.userId);
if (!me) return;
const myUserId = this._matrixClient.getUserId();
const membership = room.getMyMembership(myUserId);
const me = room.getMember(myUserId);

if (me.membership == "invite") {
if (membership == "invite") {
lists["im.vector.fake.invite"].push(room);
} else if (me.membership == "join" || me.membership === "ban" ||
(me.membership === "leave" && me.events.member.getSender() !== me.events.member.getStateKey())) {
} else if (membership == "join" || membership === "ban" || (me && me.isKicked())) {
// Used to split rooms via tags
let tagNames = Object.keys(room.tags);

Expand All @@ -206,10 +206,10 @@ class RoomListStore extends Store {
} else {
lists["im.vector.fake.recent"].push(room);
}
} else if (me.membership === "leave") {
} else if (membership === "leave") {
lists["im.vector.fake.archived"].push(room);
} else {
console.error("unrecognised membership: " + me.membership + " - this should never happen");
console.error("unrecognised membership: " + membership + " - this should never happen");
}
});

Expand Down
11 changes: 3 additions & 8 deletions src/utils/DMRoomMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,10 @@ export default class DMRoomMap {
if (this.roomToUser[roomId] === undefined) {
// no entry? if the room is an invite, look for the is_direct hint.
const room = this.matrixClient.getRoom(roomId);
// TODO Use SUMMARYAPI to fix DM detection?
if (room) {
const me = room.getMember(this.matrixClient.credentials.userId);
if (me.membership == 'invite') {
// The 'direct' hihnt is there, so declare that this is a DM room for
// whoever invited us.
if (me.events.member.getContent().is_direct) {
return me.events.member.getSender();
}
}
const me = room.getMember(this.matrixClient.getUserId());
return me && me.getDMInviter();
}
}
return this.roomToUser[roomId];
Expand Down
4 changes: 3 additions & 1 deletion test/components/views/rooms/RoomList-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import dis from '../../../../src/dispatcher';
import DMRoomMap from '../../../../src/utils/DMRoomMap.js';
import GroupStore from '../../../../src/stores/GroupStore.js';

import { Room, RoomMember } from 'matrix-js-sdk';
import { MatrixClient, Room, RoomMember } from 'matrix-js-sdk';

function generateRoomId() {
return '!' + Math.random().toString().slice(2, 10) + ':domain';
Expand Down Expand Up @@ -48,6 +48,8 @@ describe('RoomList', () => {
sandbox = TestUtils.stubClient(sandbox);
client = MatrixClientPeg.get();
client.credentials = {userId: myUserId};
//revert this to prototype method as the test-utils monkey-patches this to return a hardcoded value
client.getUserId = MatrixClient.prototype.getUserId;

clock = lolex.install();

Expand Down