fix(dm-name): drop matrix-js-sdk mxid disambiguation suffix from DM room names by using peer rawDisplayName

This commit is contained in:
heaven 2026-05-13 22:54:26 +03:00
parent d3e69e042f
commit 1ee1d50c41

View file

@ -1,9 +1,54 @@
import { useEffect, useState } from 'react';
import { RoomJoinRulesEventContent } from 'matrix-js-sdk/lib/types';
import { Room, RoomEvent, RoomEventHandlerMap } from 'matrix-js-sdk';
import {
Room,
RoomEvent,
RoomEventHandlerMap,
RoomStateEvent,
} from 'matrix-js-sdk';
import { StateEvent } from '../../types/matrix/room';
import { useStateEvent } from './useStateEvent';
// matrix-js-sdk's `Room.name` for a DM with no explicit `m.room.name`
// event falls back to a disambiguated peer label and, in many SDK
// versions, suffixes the peer's MXID as `Display Name (@id:server)` —
// either always or whenever a display-name collision is suspected. That
// produces the «Alex (@test6:vojo.chat)» pattern users see in the DM
// list, chat header and room intro even though no two members share
// the same display name. The handle is already shown as a separate
// subline in the chat header, so the parenthetical is pure noise.
//
// Workaround: when the room has no explicit `m.room.name` and is a 1:1
// with exactly one other joined/invited member, take the peer's raw
// display name directly (`RoomMember.rawDisplayName` is the value from
// `m.room.member.content.displayname` without SDK disambiguation).
// Fall back to the local-part of the MXID, then the bare MXID. Group
// rooms and rooms with an explicit name keep the SDK output.
const resolveRoomName = (room: Room): string => {
const explicit = room.currentState.getStateEvents(StateEvent.RoomName, '');
if (explicit) return room.name;
const myUserId = room.client.getUserId();
if (myUserId) {
const peers = room
.getMembersWithMembership('join')
.concat(room.getMembersWithMembership('invite'))
.filter((m) => m.userId !== myUserId);
if (peers.length === 1) {
const peer = peers[0];
if (peer.rawDisplayName && peer.rawDisplayName.trim()) {
return peer.rawDisplayName.trim();
}
const local = peer.userId.startsWith('@')
? peer.userId.slice(1).split(':')[0]
: peer.userId;
return local || peer.userId;
}
}
return room.name;
};
export const useRoomAvatar = (room: Room, dm?: boolean): string | undefined => {
const avatarEvent = useStateEvent(room, StateEvent.RoomAvatar);
@ -17,17 +62,23 @@ export const useRoomAvatar = (room: Room, dm?: boolean): string | undefined => {
};
export const useRoomName = (room: Room): string => {
const [name, setName] = useState(room.name);
const [name, setName] = useState(() => resolveRoomName(room));
useEffect(() => {
setName(room.name);
setName(resolveRoomName(room));
const handleRoomNameChange: RoomEventHandlerMap[RoomEvent.Name] = () => {
setName(room.name);
const recompute: RoomEventHandlerMap[RoomEvent.Name] = () => {
setName(resolveRoomName(room));
};
room.on(RoomEvent.Name, handleRoomNameChange);
// RoomEvent.Name fires when m.room.name changes;
// RoomStateEvent.Members fires on every m.room.member event,
// covering both display-name updates of the peer and membership
// flips that could change which member counts as the DM peer.
room.on(RoomEvent.Name, recompute);
room.currentState.on(RoomStateEvent.Members, recompute);
return () => {
room.removeListener(RoomEvent.Name, handleRoomNameChange);
room.removeListener(RoomEvent.Name, recompute);
room.currentState.removeListener(RoomStateEvent.Members, recompute);
};
}, [room]);