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

Commit 0a4c240

Browse files
committed
Exclude functional members from DM detection
1 parent 82fb21a commit 0a4c240

File tree

9 files changed

+325
-28
lines changed

9 files changed

+325
-28
lines changed

src/CallHandler.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
6161
import { KIND_CALL_TRANSFER } from "./components/views/dialogs/InviteDialogTypes";
6262
import { OpenInviteDialogPayload } from "./dispatcher/payloads/OpenInviteDialogPayload";
6363
import { findDMForUser } from './utils/dm/findDMForUser';
64+
import { getJoinedNonFunctionalMembers } from './utils/room/getJoinedNonFunctionalMembers';
6465

6566
export const PROTOCOL_PSTN = 'm.protocol.pstn';
6667
export const PROTOCOL_PSTN_PREFIXED = 'im.vector.protocol.pstn';
@@ -861,7 +862,7 @@ export default class CallHandler extends EventEmitter {
861862
// We leave the check for whether there's already a call in this room until later,
862863
// otherwise it can race.
863864

864-
const members = room.getJoinedMembers();
865+
const members = getJoinedNonFunctionalMembers(room);
865866
if (members.length <= 1) {
866867
Modal.createDialog(ErrorDialog, {
867868
description: _t('You cannot place a call with yourself.'),

src/utils/dm/findDMForUser.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
1919
import DMRoomMap from "../DMRoomMap";
2020
import { isLocalRoom } from "../localRoom/isLocalRoom";
2121
import { isJoinedOrNearlyJoined } from "../membership";
22+
import { getFunctionalMembers } from "../room/getFunctionalMembers";
2223

2324
/**
2425
* Tries to find a DM room with a specific user.
@@ -39,9 +40,12 @@ export function findDMForUser(client: MatrixClient, userId: string): Room {
3940
if (r && r.getMyMembership() === "join") {
4041
if (isLocalRoom(r)) return false;
4142

43+
const functionalUsers = getFunctionalMembers(r);
4244
const members = r.currentState.getMembers();
43-
const joinedMembers = members.filter(m => isJoinedOrNearlyJoined(m.membership));
44-
const otherMember = joinedMembers.find(m => m.userId === userId);
45+
const joinedMembers = members.filter(
46+
m => !functionalUsers.includes(m.userId) && isJoinedOrNearlyJoined(m.membership),
47+
);
48+
const otherMember = joinedMembers.find(m => !functionalUsers.includes(m.userId) && m.userId === userId);
4549
return otherMember && joinedMembers.length === 2;
4650
}
4751
return false;
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
Copyright 2022 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 { Room, UNSTABLE_ELEMENT_FUNCTIONAL_USERS } from "matrix-js-sdk/src/matrix";
18+
19+
export const getFunctionalMembers = (room: Room): string[] => {
20+
const functionalUsersStateEvents = room.currentState.getStateEvents(UNSTABLE_ELEMENT_FUNCTIONAL_USERS.name);
21+
22+
if (functionalUsersStateEvents.length > 0) {
23+
const functionalUsersStateEvent = functionalUsersStateEvents.shift();
24+
if (Array.isArray(functionalUsersStateEvent.getContent().service_members)) {
25+
return functionalUsersStateEvent.getContent().service_members;
26+
}
27+
}
28+
29+
return [];
30+
};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
Copyright 2022 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 { Room, RoomMember } from "matrix-js-sdk/src/matrix";
18+
19+
import { getFunctionalMembers } from "./getFunctionalMembers";
20+
21+
/**
22+
* Returns all room members that are non-functional (bots etc.).
23+
*/
24+
export const getJoinedNonFunctionalMembers = (room: Room): RoomMember[] => {
25+
const functionalMembers = getFunctionalMembers(room);
26+
return room.getJoinedMembers().filter(m => !functionalMembers.includes(m.userId));
27+
};

test/CallHandler-test.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
import { IProtocol } from 'matrix-js-sdk/src/matrix';
1818
import { CallEvent, CallState, CallType } from 'matrix-js-sdk/src/webrtc/call';
1919
import EventEmitter from 'events';
20+
import { mocked } from 'jest-mock';
2021

2122
import CallHandler, {
2223
CallHandlerEvent, PROTOCOL_PSTN, PROTOCOL_PSTN_PREFIXED, PROTOCOL_SIP_NATIVE, PROTOCOL_SIP_VIRTUAL,
@@ -26,6 +27,11 @@ import { MatrixClientPeg } from '../src/MatrixClientPeg';
2627
import DMRoomMap from '../src/utils/DMRoomMap';
2728
import SdkConfig from '../src/SdkConfig';
2829
import { Action } from "../src/dispatcher/actions";
30+
import { getFunctionalMembers } from "../src/utils/room/getFunctionalMembers";
31+
32+
jest.mock("../src/utils/room/getFunctionalMembers", () => ({
33+
getFunctionalMembers: jest.fn(),
34+
}));
2935

3036
// The Matrix IDs that the user sees when talking to Alice & Bob
3137
const NATIVE_ALICE = "@alice:example.org";
@@ -41,6 +47,8 @@ const NATIVE_ROOM_ALICE = "$alice_room:example.org";
4147
const NATIVE_ROOM_BOB = "$bob_room:example.org";
4248
const NATIVE_ROOM_CHARLIE = "$charlie_room:example.org";
4349

50+
const FUNCTIONAL_USER = "@bot:example.com";
51+
4452
// The room we use to talk to virtual Bob (but that the user does not see)
4553
// Bob has a virtual room, but Alice doesn't
4654
const VIRTUAL_ROOM_BOB = "$virtual_bob_room:example.org";
@@ -69,9 +77,17 @@ function mkStubDM(roomId, userId) {
6977
getAvatarUrl: () => 'mxc://avatar.url/image.png',
7078
getMxcAvatarUrl: () => 'mxc://avatar.url/image.png',
7179
},
80+
{
81+
userId: FUNCTIONAL_USER,
82+
name: 'Bot user',
83+
rawDisplayName: 'Bot user',
84+
roomId: roomId,
85+
membership: 'join',
86+
getAvatarUrl: () => 'mxc://avatar.url/image.png',
87+
getMxcAvatarUrl: () => 'mxc://avatar.url/image.png',
88+
},
7289
]);
7390
room.currentState.getMembers = room.getJoinedMembers;
74-
7591
return room;
7692
}
7793

@@ -132,6 +148,10 @@ describe('CallHandler', () => {
132148
callHandler = new CallHandler();
133149
callHandler.start();
134150

151+
mocked(getFunctionalMembers).mockReturnValue([
152+
FUNCTIONAL_USER,
153+
]);
154+
135155
const nativeRoomAlice = mkStubDM(NATIVE_ROOM_ALICE, NATIVE_ALICE);
136156
const nativeRoomBob = mkStubDM(NATIVE_ROOM_BOB, NATIVE_BOB);
137157
const nativeRoomCharie = mkStubDM(NATIVE_ROOM_CHARLIE, NATIVE_CHARLIE);

test/test-utils/test-utils.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,8 @@ export function mkEvent(opts: MakeEventProps): MatrixEvent {
210210
};
211211
if (opts.skey !== undefined) {
212212
event.state_key = opts.skey;
213+
// @ts-ignore
214+
event.isState = () => opts.skey !== undefined;
213215
} else if ([
214216
"m.room.name", "m.room.topic", "m.room.create", "m.room.join_rules",
215217
"m.room.power_levels", "m.room.topic", "m.room.history_visibility",
@@ -219,6 +221,15 @@ export function mkEvent(opts: MakeEventProps): MatrixEvent {
219221
event.state_key = "";
220222
}
221223

224+
// @ts-ignore
225+
event.getRoomId = () => opts.room;
226+
// @ts-ignore
227+
event.getType = () => opts.type;
228+
// @ts-ignore
229+
event.getStateKey = () => opts.skey;
230+
// @ts-ignore
231+
event.getContent = () => opts.content;
232+
222233
const mxEvent = opts.event ? new MatrixEvent(event) : event as unknown as MatrixEvent;
223234
if (!mxEvent.sender && opts.user && opts.room) {
224235
mxEvent.sender = {

test/utils/dm/findDMForUser-test.ts

Lines changed: 80 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,56 +17,112 @@ limitations under the License.
1717
import { mocked } from "jest-mock";
1818
import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
1919

20-
import { DirectoryMember, ThreepidMember } from "../../../src/utils/direct-messages";
21-
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
2220
import DMRoomMap from "../../../src/utils/DMRoomMap";
23-
import { createTestClient } from "../../test-utils";
24-
import { findDMRoom } from "../../../src/utils/dm/findDMRoom";
21+
import { createTestClient, makeMembershipEvent } from "../../test-utils";
22+
import { LocalRoom } from "../../../src/models/LocalRoom";
2523
import { findDMForUser } from "../../../src/utils/dm/findDMForUser";
24+
import { getFunctionalMembers } from "../../../src/utils/room/getFunctionalMembers";
2625

27-
jest.mock("../../../src/utils/dm/findDMForUser", () => ({
28-
findDMForUser: jest.fn(),
26+
jest.mock("../../../src/utils/room/getFunctionalMembers", () => ({
27+
getFunctionalMembers: jest.fn(),
2928
}));
3029

31-
describe("findDMRoom", () => {
30+
describe("findDMForUser", () => {
3231
const userId1 = "@user1:example.com";
33-
const member1 = new DirectoryMember({ user_id: userId1 });
34-
const member2 = new ThreepidMember("user2");
35-
let mockClient: MatrixClient;
32+
const userId2 = "@user2:example.com";
33+
const botId = "@bot:example.com";
3634
let room1: Room;
35+
let room2: LocalRoom;
36+
let room3: Room;
37+
let room4: Room;
38+
let room5: Room;
3739
let dmRoomMap: DMRoomMap;
40+
let mockClient: MatrixClient;
3841

3942
beforeEach(() => {
4043
mockClient = createTestClient();
41-
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient);
44+
45+
// always return the bot user as functional member
46+
mocked(getFunctionalMembers).mockReturnValue([botId]);
4247

4348
room1 = new Room("!room1:example.com", mockClient, userId1);
4449
room1.getMyMembership = () => "join";
50+
room1.currentState.setStateEvents([
51+
makeMembershipEvent(room1.roomId, userId1, "join"),
52+
makeMembershipEvent(room1.roomId, userId2, "join"),
53+
]);
54+
55+
// this should not be a DM room because it is a local room
56+
room2 = new LocalRoom("!room2:example.com", mockClient, userId1);
57+
room2.getMyMembership = () => "join";
58+
room2.getLastActiveTimestamp = () => 100;
59+
60+
room3 = new Room("!room3:example.com", mockClient, userId1);
61+
room3.getMyMembership = () => "join";
62+
room3.currentState.setStateEvents([
63+
makeMembershipEvent(room3.roomId, userId1, "join"),
64+
makeMembershipEvent(room3.roomId, userId2, "join"),
65+
// Adding the bot user here. Should be excluded when determining if the room is a DM.
66+
makeMembershipEvent(room3.roomId, botId, "join"),
67+
]);
68+
69+
// this should not be a DM room because it has only one joined user
70+
room4 = new Room("!room4:example.com", mockClient, userId1);
71+
room4.getMyMembership = () => "join";
72+
room4.currentState.setStateEvents([
73+
makeMembershipEvent(room4.roomId, userId1, "invite"),
74+
makeMembershipEvent(room4.roomId, userId2, "join"),
75+
]);
76+
77+
// this should not be a DM room because it has no users
78+
room5 = new Room("!room5:example.com", mockClient, userId1);
79+
room5.getLastActiveTimestamp = () => 100;
80+
81+
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];
89+
});
4590

4691
dmRoomMap = {
4792
getDMRoomForIdentifiers: jest.fn(),
4893
getDMRoomsForUserId: jest.fn(),
4994
} as unknown as DMRoomMap;
5095
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+
]);
51103
});
52104

53-
it("should return the room for a single target with a room", () => {
54-
mocked(findDMForUser).mockReturnValue(room1);
55-
expect(findDMRoom(mockClient, [member1])).toBe(room1);
56-
});
105+
describe("for an empty DM room list", () => {
106+
beforeEach(() => {
107+
mocked(dmRoomMap.getDMRoomsForUserId).mockReturnValue([]);
108+
});
57109

58-
it("should return null for a single target without a room", () => {
59-
mocked(findDMForUser).mockReturnValue(null);
60-
expect(findDMRoom(mockClient, [member1])).toBeNull();
110+
it("should return undefined", () => {
111+
expect(findDMForUser(mockClient, userId1)).toBeUndefined();
112+
});
61113
});
62114

63-
it("should return the room for 2 targets with a room", () => {
64-
mocked(dmRoomMap.getDMRoomForIdentifiers).mockReturnValue(room1);
65-
expect(findDMRoom(mockClient, [member1, member2])).toBe(room1);
115+
it("should find a room ordered by last activity 1", () => {
116+
room1.getLastActiveTimestamp = () => 2;
117+
room3.getLastActiveTimestamp = () => 1;
118+
119+
expect(findDMForUser(mockClient, userId1)).toBe(room1);
66120
});
67121

68-
it("should return null for 2 targets without a room", () => {
69-
mocked(dmRoomMap.getDMRoomForIdentifiers).mockReturnValue(null);
70-
expect(findDMRoom(mockClient, [member1, member2])).toBeNull();
122+
it("should find a room ordered by last activity 2", () => {
123+
room1.getLastActiveTimestamp = () => 1;
124+
room3.getLastActiveTimestamp = () => 2;
125+
126+
expect(findDMForUser(mockClient, userId1)).toBe(room3);
71127
});
72128
});

0 commit comments

Comments
 (0)