diff --git a/src/matrix/room/members/Heroes.js b/src/matrix/room/members/Heroes.js index 79902579..74f69db1 100644 --- a/src/matrix/room/members/Heroes.js +++ b/src/matrix/room/members/Heroes.js @@ -22,12 +22,12 @@ function calculateRoomName(sortedMembers, summary) { if (sortedMembers.length > 1) { const lastMember = sortedMembers[sortedMembers.length - 1]; const firstMembers = sortedMembers.slice(0, sortedMembers.length - 1); - return firstMembers.map(m => m.displayName).join(", ") + " and " + lastMember.displayName; + return firstMembers.map(m => m.name).join(", ") + " and " + lastMember.name; } else { - return sortedMembers[0].displayName; + return sortedMembers[0].name; } } else if (sortedMembers.length < countWithoutMe) { - return sortedMembers.map(m => m.displayName).join(", ") + ` and ${countWithoutMe} others`; + return sortedMembers.map(m => m.name).join(", ") + ` and ${countWithoutMe} others`; } else { // Empty Room return null; @@ -81,7 +81,7 @@ export class Heroes { for (const member of updatedHeroMembers) { this._members.set(member.userId, member); } - const sortedMembers = Array.from(this._members.values()).sort((a, b) => a.displayName.localeCompare(b.displayName)); + const sortedMembers = Array.from(this._members.values()).sort((a, b) => a.name.localeCompare(b.name)); this._roomName = calculateRoomName(sortedMembers, summary); } diff --git a/src/matrix/room/members/RoomMember.js b/src/matrix/room/members/RoomMember.js index e6303fe4..02c3c292 100644 --- a/src/matrix/room/members/RoomMember.js +++ b/src/matrix/room/members/RoomMember.js @@ -27,21 +27,33 @@ export class RoomMember { if (typeof userId !== "string") { return; } - return this._fromMemberEventContent(roomId, userId, memberEvent.content); + const content = memberEvent.content; + const prevContent = memberEvent.unsigned?.prev_content; + const membership = content?.membership; + // fall back to prev_content for these as synapse doesn't (always?) + // put them on content for "leave" memberships + const displayName = content?.displayname || prevContent?.displayname; + const avatarUrl = content?.avatar_url || prevContent?.avatar_url; + return this._validateAndCreateMember(roomId, userId, membership, displayName, avatarUrl); } - + /** + * Creates a (historical) member from a member event that is the next member event + * after the point in time where we need a member for. This will use `prev_content`. + */ static fromReplacingMemberEvent(roomId, memberEvent) { const userId = memberEvent && memberEvent.state_key; if (typeof userId !== "string") { return; } - return this._fromMemberEventContent(roomId, userId, memberEvent.prev_content); + const content = memberEvent.unsigned?.prev_content + return this._validateAndCreateMember(roomId, userId, + content?.membership, + content?.displayname, + content?.avatar_url + ); } - static _fromMemberEventContent(roomId, userId, content) { - const membership = content?.membership; - const avatarUrl = content?.avatar_url; - const displayName = content?.displayname; + static _validateAndCreateMember(roomId, userId, membership, displayName, avatarUrl) { if (typeof membership !== "string") { return; } @@ -54,10 +66,23 @@ export class RoomMember { }); } + /** + * @return {String?} the display name, if any + */ get displayName() { return this._data.displayName; } + /** + * @return {String} the display name or userId + */ + get name() { + return this._data.displayName || this._data.userId; + } + + /** + * @return {String?} the avatar mxc url, if any + */ get avatarUrl() { return this._data.avatarUrl; } diff --git a/src/matrix/room/timeline/persistence/GapWriter.js b/src/matrix/room/timeline/persistence/GapWriter.js index 834239f7..e9abded6 100644 --- a/src/matrix/room/timeline/persistence/GapWriter.js +++ b/src/matrix/room/timeline/persistence/GapWriter.js @@ -142,8 +142,9 @@ export class GapWriter { return RoomMember.fromReplacingMemberEvent(this._roomId, event)?.serialize(); } } - // assuming the member hasn't changed within the chunk, just take it from state if it's there - const stateMemberEvent = state.find(isOurUser); + // assuming the member hasn't changed within the chunk, just take it from state if it's there. + // Don't assume state is set though, as it can be empty at the top of the timeline in some circumstances + const stateMemberEvent = state?.find(isOurUser); if (stateMemberEvent) { return RoomMember.fromMemberEvent(this._roomId, stateMemberEvent)?.serialize(); }