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

Commit a6eee32

Browse files
authored
Examine all m.direct rooms to find a DM as fallback (#10127)
1 parent 1c6b06b commit a6eee32

File tree

7 files changed

+147
-32
lines changed

7 files changed

+147
-32
lines changed

src/utils/DMRoomMap.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,16 @@ export default class DMRoomMap {
192192
.reduce((obj, r) => (obj[r.userId] = r.room) && obj, {});
193193
}
194194

195+
/**
196+
* @returns all room Ids from m.direct
197+
*/
198+
public getRoomIds(): Set<string> {
199+
return Object.values(this.mDirectEvent).reduce((prevRoomIds: Set<string>, roomIds: string[]): Set<string> => {
200+
roomIds.forEach((roomId) => prevRoomIds.add(roomId));
201+
return prevRoomIds;
202+
}, new Set<string>());
203+
}
204+
195205
private getUserToRooms(): { [key: string]: string[] } {
196206
if (!this.userToRooms) {
197207
const userToRooms = this.mDirectEvent;

src/utils/dm/findDMForUser.ts

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,8 @@ import { isLocalRoom } from "../localRoom/isLocalRoom";
2121
import { isJoinedOrNearlyJoined } from "../membership";
2222
import { getFunctionalMembers } from "../room/getFunctionalMembers";
2323

24-
/**
25-
* Tries to find a DM room with a specific user.
26-
*
27-
* @param {MatrixClient} client
28-
* @param {string} userId ID of the user to find the DM for
29-
* @returns {Room} Room if found
30-
*/
31-
export function findDMForUser(client: MatrixClient, userId: string): Room {
32-
const roomIds = DMRoomMap.shared().getDMRoomsForUserId(userId);
33-
const rooms = roomIds.map((id) => client.getRoom(id));
34-
const suitableDMRooms = rooms
24+
function extractSuitableRoom(rooms: Room[], userId: string): Room | undefined {
25+
const suitableRooms = rooms
3526
.filter((r) => {
3627
// Validate that we are joined and the other person is also joined. We'll also make sure
3728
// that the room also looks like a DM (until we have canonical DMs to tell us). For now,
@@ -44,7 +35,7 @@ export function findDMForUser(client: MatrixClient, userId: string): Room {
4435
const functionalUsers = getFunctionalMembers(r);
4536
const members = r.currentState.getMembers();
4637
const joinedMembers = members.filter(
47-
(m) => !functionalUsers.includes(m.userId) && isJoinedOrNearlyJoined(m.membership),
38+
(m) => !functionalUsers.includes(m.userId) && m.membership && isJoinedOrNearlyJoined(m.membership),
4839
);
4940
const otherMember = joinedMembers.find((m) => m.userId === userId);
5041
return otherMember && joinedMembers.length === 2;
@@ -54,7 +45,34 @@ export function findDMForUser(client: MatrixClient, userId: string): Room {
5445
.sort((r1, r2) => {
5546
return r2.getLastActiveTimestamp() - r1.getLastActiveTimestamp();
5647
});
57-
if (suitableDMRooms.length) {
58-
return suitableDMRooms[0];
48+
49+
if (suitableRooms.length) {
50+
return suitableRooms[0];
5951
}
52+
53+
return undefined;
54+
}
55+
56+
/**
57+
* Tries to find a DM room with a specific user.
58+
*
59+
* @param {MatrixClient} client
60+
* @param {string} userId ID of the user to find the DM for
61+
* @returns {Room | undefined} Room if found
62+
*/
63+
export function findDMForUser(client: MatrixClient, userId: string): Room | undefined {
64+
const roomIdsForUserId = DMRoomMap.shared().getDMRoomsForUserId(userId);
65+
const roomsForUserId = roomIdsForUserId.map((id) => client.getRoom(id)).filter((r): r is Room => r !== null);
66+
const suitableRoomForUserId = extractSuitableRoom(roomsForUserId, userId);
67+
68+
if (suitableRoomForUserId) {
69+
return suitableRoomForUserId;
70+
}
71+
72+
// Try to find in all rooms as a fallback
73+
const allRoomIds = DMRoomMap.shared().getRoomIds();
74+
const allRooms = Array.from(allRoomIds)
75+
.map((id) => client.getRoom(id))
76+
.filter((r): r is Room => r !== null);
77+
return extractSuitableRoom(allRooms, userId);
6078
}

src/utils/dm/findDMRoom.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { findDMForUser } from "./findDMForUser";
2929
*/
3030
export function findDMRoom(client: MatrixClient, targets: Member[]): Room | null {
3131
const targetIds = targets.map((t) => t.userId);
32-
let existingRoom: Room;
32+
let existingRoom: Room | undefined;
3333
if (targetIds.length === 1) {
3434
existingRoom = findDMForUser(client, targetIds[0]);
3535
} else {

test/LegacyCallHandler-test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ describe("LegacyCallHandler", () => {
238238
return [];
239239
}
240240
},
241-
} as DMRoomMap;
241+
} as unknown as DMRoomMap;
242242
DMRoomMap.setShared(dmRoomMap);
243243

244244
pstnLookup = null;

test/utils/DMRoomMap-test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
Copyright 2023 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { mocked, Mocked } from "jest-mock";
18+
import { EventType, IContent, MatrixClient } from "matrix-js-sdk/src/matrix";
19+
20+
import DMRoomMap from "../../src/utils/DMRoomMap";
21+
import { mkEvent, stubClient } from "../test-utils";
22+
23+
describe("DMRoomMap", () => {
24+
const roomId1 = "!room1:example.com";
25+
const roomId2 = "!room2:example.com";
26+
const roomId3 = "!room3:example.com";
27+
const roomId4 = "!room4:example.com";
28+
29+
const mDirectContent = {
30+
"user@example.com": [roomId1, roomId2],
31+
"@user:example.com": [roomId1, roomId3, roomId4],
32+
"@user2:example.com": [] as string[],
33+
} satisfies IContent;
34+
35+
let client: Mocked<MatrixClient>;
36+
let dmRoomMap: DMRoomMap;
37+
38+
beforeEach(() => {
39+
client = mocked(stubClient());
40+
41+
const mDirectEvent = mkEvent({
42+
event: true,
43+
type: EventType.Direct,
44+
user: client.getSafeUserId(),
45+
content: mDirectContent,
46+
});
47+
client.getAccountData.mockReturnValue(mDirectEvent);
48+
dmRoomMap = new DMRoomMap(client);
49+
});
50+
51+
it("getRoomIds should return the room Ids", () => {
52+
expect(dmRoomMap.getRoomIds()).toEqual(new Set([roomId1, roomId2, roomId3, roomId4]));
53+
});
54+
});

test/utils/dm/findDMForUser-test.ts

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,15 @@ jest.mock("../../../src/utils/room/getFunctionalMembers", () => ({
3030
describe("findDMForUser", () => {
3131
const userId1 = "@user1:example.com";
3232
const userId2 = "@user2:example.com";
33+
const userId3 = "@user3:example.com";
3334
const botId = "@bot:example.com";
3435
let room1: Room;
3536
let room2: LocalRoom;
3637
let room3: Room;
3738
let room4: Room;
3839
let room5: Room;
40+
let room6: Room;
41+
const room7Id = "!room7:example.com";
3942
let dmRoomMap: DMRoomMap;
4043
let mockClient: MatrixClient;
4144

@@ -78,33 +81,56 @@ describe("findDMForUser", () => {
7881
room5 = new Room("!room5:example.com", mockClient, userId1);
7982
room5.getLastActiveTimestamp = () => 100;
8083

84+
// room not correctly stored in userId → room map; should be found by the "all rooms" fallback
85+
room6 = new Room("!room6:example.com", mockClient, userId1);
86+
room6.getMyMembership = () => "join";
87+
room6.currentState.setStateEvents([
88+
makeMembershipEvent(room6.roomId, userId1, "join"),
89+
makeMembershipEvent(room6.roomId, userId3, "join"),
90+
]);
91+
8192
mocked(mockClient.getRoom).mockImplementation((roomId: string) => {
82-
return {
83-
[room1.roomId]: room1,
84-
[room2.roomId]: room2,
85-
[room3.roomId]: room3,
86-
[room4.roomId]: room4,
87-
[room5.roomId]: room5,
88-
}[roomId];
93+
return (
94+
{
95+
[room1.roomId]: room1,
96+
[room2.roomId]: room2,
97+
[room3.roomId]: room3,
98+
[room4.roomId]: room4,
99+
[room5.roomId]: room5,
100+
[room6.roomId]: room6,
101+
}[roomId] || null
102+
);
89103
});
90104

91105
dmRoomMap = {
92106
getDMRoomForIdentifiers: jest.fn(),
93107
getDMRoomsForUserId: jest.fn(),
108+
getRoomIds: jest.fn().mockReturnValue(
109+
new Set([
110+
room1.roomId,
111+
room2.roomId,
112+
room3.roomId,
113+
room4.roomId,
114+
room5.roomId,
115+
room6.roomId,
116+
room7Id, // this room does not exist in client
117+
]),
118+
),
94119
} as unknown as DMRoomMap;
95120
jest.spyOn(DMRoomMap, "shared").mockReturnValue(dmRoomMap);
96-
mocked(dmRoomMap.getDMRoomsForUserId).mockReturnValue([
97-
room1.roomId,
98-
room2.roomId,
99-
room3.roomId,
100-
room4.roomId,
101-
room5.roomId,
102-
]);
121+
mocked(dmRoomMap.getDMRoomsForUserId).mockImplementation((userId: string) => {
122+
if (userId === userId1) {
123+
return [room1.roomId, room2.roomId, room3.roomId, room4.roomId, room5.roomId, room7Id];
124+
}
125+
126+
return [];
127+
});
103128
});
104129

105130
describe("for an empty DM room list", () => {
106131
beforeEach(() => {
107132
mocked(dmRoomMap.getDMRoomsForUserId).mockReturnValue([]);
133+
mocked(dmRoomMap.getRoomIds).mockReturnValue(new Set());
108134
});
109135

110136
it("should return undefined", () => {
@@ -125,4 +151,11 @@ describe("findDMForUser", () => {
125151

126152
expect(findDMForUser(mockClient, userId1)).toBe(room3);
127153
});
154+
155+
it("should find a room by the 'all rooms' fallback", () => {
156+
room1.getLastActiveTimestamp = () => 1;
157+
room6.getLastActiveTimestamp = () => 2;
158+
159+
expect(findDMForUser(mockClient, userId3)).toBe(room6);
160+
});
128161
});

test/utils/dm/findDMRoom-test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ describe("findDMRoom", () => {
5353
expect(findDMRoom(mockClient, [member1])).toBe(room1);
5454
});
5555

56-
it("should return null for a single target without a room", () => {
57-
mocked(findDMForUser).mockReturnValue(null);
56+
it("should return undefined for a single target without a room", () => {
57+
mocked(findDMForUser).mockReturnValue(undefined);
5858
expect(findDMRoom(mockClient, [member1])).toBeNull();
5959
});
6060

0 commit comments

Comments
 (0)