diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js index 9d86a62de45..82d18d307dd 100644 --- a/src/MatrixClientPeg.js +++ b/src/MatrixClientPeg.js @@ -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. @@ -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`); diff --git a/src/Rooms.js b/src/Rooms.js index ffa39141ff1..2fda46450f4 100644 --- a/src/Rooms.js +++ b/src/Rooms.js @@ -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; } @@ -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; } } @@ -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; } @@ -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(); } @@ -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; } diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 0325b3d9a66..c7429753778 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -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); + }); + } } } }, @@ -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); } }, diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index 67189ac90fc..8b465cef9d3 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -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); diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index 8533e3f61a0..9d48ed32c96 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -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; } diff --git a/src/components/views/rooms/RoomPreviewBar.js b/src/components/views/rooms/RoomPreviewBar.js index 536093807ae..5ec19d185ea 100644 --- a/src/components/views/rooms/RoomPreviewBar.js +++ b/src/components/views/rooms/RoomPreviewBar.js @@ -98,15 +98,11 @@ module.exports = React.createClass({ ); } - 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; diff --git a/src/settings/Settings.js b/src/settings/Settings.js index e3f5855f0d7..d76c1fd8e8d 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -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'), diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index b6d0949dd3f..e415389423e 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -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); @@ -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"); } }); diff --git a/src/utils/DMRoomMap.js b/src/utils/DMRoomMap.js index d6242719ba4..acb1573cbfb 100644 --- a/src/utils/DMRoomMap.js +++ b/src/utils/DMRoomMap.js @@ -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]; diff --git a/test/components/views/rooms/RoomList-test.js b/test/components/views/rooms/RoomList-test.js index f40a89777bf..c0a0b8eb67e 100644 --- a/test/components/views/rooms/RoomList-test.js +++ b/test/components/views/rooms/RoomList-test.js @@ -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'; @@ -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();