Merge pull request #4036 from element-hq/metadata-leak

Fix a minor resource leak with display names and avatars
This commit is contained in:
Robin
2026-06-12 10:05:50 +02:00
committed by GitHub
3 changed files with 63 additions and 34 deletions

View File

@@ -766,11 +766,13 @@ export function createCallViewModel$(
pretendToBeDisconnected$: localMembership.reconnecting$, pretendToBeDisconnected$: localMembership.reconnecting$,
displayName$: scope.behavior( displayName$: scope.behavior(
matrixMemberMetadataStore matrixMemberMetadataStore
.createDisplayNameBehavior$(userId) .createDisplayNameBehavior$(scope, userId)
.pipe(map((name) => name ?? userId)), .pipe(map((name) => name ?? userId)),
), ),
mxcAvatarUrl$: mxcAvatarUrl$: matrixMemberMetadataStore.createAvatarUrlBehavior$(
matrixMemberMetadataStore.createAvatarUrlBehavior$(userId), scope,
userId,
),
handRaised$: scope.behavior( handRaised$: scope.behavior(
handsRaised$.pipe(map((v) => v[mediaId]?.time ?? null)), handsRaised$.pipe(map((v) => v[mediaId]?.time ?? null)),
), ),
@@ -811,8 +813,10 @@ export function createCallViewModel$(
map((members) => members.get(userId)?.rawDisplayName || userId), map((members) => members.get(userId)?.rawDisplayName || userId),
), ),
), ),
mxcAvatarUrl$: mxcAvatarUrl$: matrixMemberMetadataStore.createAvatarUrlBehavior$(
matrixMemberMetadataStore.createAvatarUrlBehavior$(userId), scope,
userId,
),
pickupState$, pickupState$,
muteStates, muteStates,
}), }),

View File

@@ -105,7 +105,7 @@ describe("MatrixMemberMetadata", () => {
} }
it("should show our own user if present in rtc session and room", () => { it("should show our own user if present in rtc session and room", () => {
withTestScheduler(({ behavior, expectObservable }) => { withTestScheduler(({ scope, behavior, expectObservable }) => {
fakeMemberWith({ fakeMemberWith({
userId: "@local:example.com", userId: "@local:example.com",
rawDisplayName: "it's a me", rawDisplayName: "it's a me",
@@ -118,8 +118,10 @@ describe("MatrixMemberMetadata", () => {
memberships$, memberships$,
createRoomMembers$(testScope, mockMatrixRoom), createRoomMembers$(testScope, mockMatrixRoom),
); );
const dn$ = const dn$ = metadataStore.createDisplayNameBehavior$(
metadataStore.createDisplayNameBehavior$("@local:example.com"); scope,
"@local:example.com",
);
expectObservable(dn$).toBe("a", { expectObservable(dn$).toBe("a", {
a: "it's a me", a: "it's a me",
@@ -146,7 +148,7 @@ describe("MatrixMemberMetadata", () => {
it("should get displayName for users", () => { it("should get displayName for users", () => {
setUpBasicRoom(); setUpBasicRoom();
withTestScheduler(({ behavior, expectObservable }) => { withTestScheduler(({ scope, behavior, expectObservable }) => {
const memberships$ = behavior("a", { const memberships$ = behavior("a", {
a: [ a: [
mockRtcMembership("@alice:example.com", "DEVICE1"), mockRtcMembership("@alice:example.com", "DEVICE1"),
@@ -158,8 +160,10 @@ describe("MatrixMemberMetadata", () => {
memberships$, memberships$,
createRoomMembers$(testScope, mockMatrixRoom), createRoomMembers$(testScope, mockMatrixRoom),
); );
const aliceDispName$ = const aliceDispName$ = metadataStore.createDisplayNameBehavior$(
metadataStore.createDisplayNameBehavior$("@alice:example.com"); scope,
"@alice:example.com",
);
expectObservable(aliceDispName$).toBe("a", { expectObservable(aliceDispName$).toBe("a", {
a: "Alice", a: "Alice",
@@ -322,7 +326,7 @@ describe("MatrixMemberMetadata", () => {
}); });
it("should track individual member id with createDisplayNameBehavior", () => { it("should track individual member id with createDisplayNameBehavior", () => {
withTestScheduler(({ behavior, schedule, expectObservable }) => { withTestScheduler(({ scope, behavior, schedule, expectObservable }) => {
setUpBasicRoom(); setUpBasicRoom();
const BOB = "@bob:example.com"; const BOB = "@bob:example.com";
const CARL = "@carl:example.com"; const CARL = "@carl:example.com";
@@ -356,8 +360,8 @@ describe("MatrixMemberMetadata", () => {
createRoomMembers$(testScope, mockMatrixRoom), createRoomMembers$(testScope, mockMatrixRoom),
); );
const bob$ = metadataStore.createDisplayNameBehavior$(BOB); const bob$ = metadataStore.createDisplayNameBehavior$(scope, BOB);
const carl$ = metadataStore.createDisplayNameBehavior$(CARL); const carl$ = metadataStore.createDisplayNameBehavior$(scope, CARL);
expectObservable(bob$).toBe("abc-", { expectObservable(bob$).toBe("abc-", {
a: undefined, a: undefined,
@@ -378,7 +382,7 @@ describe("MatrixMemberMetadata", () => {
}); });
it("should disambiguate users with invisible characters", () => { it("should disambiguate users with invisible characters", () => {
withTestScheduler(({ behavior, expectObservable }) => { withTestScheduler(({ scope, behavior, expectObservable }) => {
const bobRtcMember = mockRtcMembership("@bob:example.org", "BBBB"); const bobRtcMember = mockRtcMembership("@bob:example.org", "BBBB");
const bobZeroWidthSpaceRtcMember = mockRtcMembership( const bobZeroWidthSpaceRtcMember = mockRtcMembership(
"@bob2:example.org", "@bob2:example.org",
@@ -411,12 +415,18 @@ describe("MatrixMemberMetadata", () => {
createRoomMembers$(testScope, mockMatrixRoom), createRoomMembers$(testScope, mockMatrixRoom),
); );
const bob$ = const bob$ = metadataStore.createDisplayNameBehavior$(
metadataStore.createDisplayNameBehavior$("@bob:example.org"); scope,
const bob2$ = "@bob:example.org",
metadataStore.createDisplayNameBehavior$("@bob2:example.org"); );
const carol$ = const bob2$ = metadataStore.createDisplayNameBehavior$(
metadataStore.createDisplayNameBehavior$("@carol:example.org"); scope,
"@bob2:example.org",
);
const carol$ = metadataStore.createDisplayNameBehavior$(
scope,
"@carol:example.org",
);
expectObservable(bob$).toBe("ab", { expectObservable(bob$).toBe("ab", {
a: "Bob", a: "Bob",
b: "Bob (@bob:example.org)", b: "Bob (@bob:example.org)",
@@ -517,7 +527,7 @@ describe("MatrixMemberMetadata", () => {
} }
it("should use avatar url from room members", () => { it("should use avatar url from room members", () => {
withTestScheduler(({ behavior, expectObservable }) => { withTestScheduler(({ scope, behavior, expectObservable }) => {
fakeMemberWith({ fakeMemberWith({
userId: "@local:example.com", userId: "@local:example.com",
}); });
@@ -536,11 +546,15 @@ describe("MatrixMemberMetadata", () => {
memberships$, memberships$,
createRoomMembers$(testScope, mockMatrixRoom), createRoomMembers$(testScope, mockMatrixRoom),
); );
const local$ = const local$ = metadataStore.createAvatarUrlBehavior$(
metadataStore.createAvatarUrlBehavior$("@local:example.com"); scope,
"@local:example.com",
);
const alice$ = const alice$ = metadataStore.createAvatarUrlBehavior$(
metadataStore.createAvatarUrlBehavior$("@alice:example.com"); scope,
"@alice:example.com",
);
expectObservable(local$).toBe("a", { expectObservable(local$).toBe("a", {
a: "mxc://example.com/@local:example.com", a: "mxc://example.com/@local:example.com",
@@ -558,7 +572,7 @@ describe("MatrixMemberMetadata", () => {
}); });
it("should update on avatar change and user join/leave", () => { it("should update on avatar change and user join/leave", () => {
withTestScheduler(({ behavior, schedule, expectObservable }) => { withTestScheduler(({ scope, behavior, schedule, expectObservable }) => {
fakeMemberWith({ userId: "@carl:example.com" }); fakeMemberWith({ userId: "@carl:example.com" });
fakeMemberWith({ userId: "@bob:example.com" }); fakeMemberWith({ userId: "@bob:example.com" });
const memberships$ = behavior("ab-d", { const memberships$ = behavior("ab-d", {
@@ -585,9 +599,14 @@ describe("MatrixMemberMetadata", () => {
}, },
}); });
const bob$ = metadataStore.createAvatarUrlBehavior$("@bob:example.com"); const bob$ = metadataStore.createAvatarUrlBehavior$(
const carl$ = scope,
metadataStore.createAvatarUrlBehavior$("@carl:example.com"); "@bob:example.com",
);
const carl$ = metadataStore.createAvatarUrlBehavior$(
scope,
"@carl:example.com",
);
expectObservable(bob$).toBe("a---", { expectObservable(bob$).toBe("a---", {
a: "mxc://example.com/@bob:example.com", a: "mxc://example.com/@bob:example.com",
}); });

View File

@@ -115,8 +115,14 @@ export const createMatrixMemberMetadata$ = (
memberships$: Behavior<Pick<CallMembership, "userId">[]>, memberships$: Behavior<Pick<CallMembership, "userId">[]>,
roomMembers$: Behavior<RoomMemberMap>, roomMembers$: Behavior<RoomMemberMap>,
): { ): {
createDisplayNameBehavior$: (userId: string) => Behavior<string | undefined>; createDisplayNameBehavior$: (
createAvatarUrlBehavior$: (userId: string) => Behavior<string | undefined>; scope: ObservableScope,
userId: string,
) => Behavior<string | undefined>;
createAvatarUrlBehavior$: (
scope: ObservableScope,
userId: string,
) => Behavior<string | undefined>;
displaynameMap$: Behavior<Map<string, string>>; displaynameMap$: Behavior<Map<string, string>>;
avatarMap$: Behavior<Map<string, string | undefined>>; avatarMap$: Behavior<Map<string, string | undefined>>;
} => { } => {
@@ -136,13 +142,13 @@ export const createMatrixMemberMetadata$ = (
), ),
); );
return { return {
createDisplayNameBehavior$: (userId: string) => createDisplayNameBehavior$: (scope: ObservableScope, userId: string) =>
scope.behavior( scope.behavior(
displaynameMap$.pipe( displaynameMap$.pipe(
map((displaynameMap) => displaynameMap.get(userId)), map((displaynameMap) => displaynameMap.get(userId)),
), ),
), ),
createAvatarUrlBehavior$: (userId: string) => createAvatarUrlBehavior$: (scope: ObservableScope, userId: string) =>
scope.behavior( scope.behavior(
roomMembers$.pipe( roomMembers$.pipe(
map((roomMembers) => roomMembers.get(userId)?.getMxcAvatarUrl()), map((roomMembers) => roomMembers.get(userId)?.getMxcAvatarUrl()),