From 59c5ab31ded093a5825e5c2a0d2e948ad680644e Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 19 Apr 2021 20:30:51 +0100 Subject: [PATCH 1/7] Support MSC3086 asserted identity --- src/CallHandler.tsx | 58 +++++++++++++++++-- src/VoipUserMapper.ts | 6 +- src/components/views/voip/CallPreview.tsx | 2 + src/components/views/voip/CallView.tsx | 18 +++--- src/components/views/voip/CallViewForRoom.tsx | 2 + src/components/views/voip/IncomingCallBox.tsx | 6 +- src/dispatcher/actions.ts | 3 + 7 files changed, 78 insertions(+), 17 deletions(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index be687a4474..886c594c94 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -86,6 +86,9 @@ import { Action } from './dispatcher/actions'; import VoipUserMapper from './VoipUserMapper'; import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from './widgets/ManagedHybrid'; import { randomUppercaseString, randomLowercaseString } from "matrix-js-sdk/src/randomstring"; +import SdkConfig from './SdkConfig'; +import DMRoomMap from './utils/DMRoomMap'; +import { ensureDMExists, findDMForUser } from './createRoom'; export const PROTOCOL_PSTN = 'm.protocol.pstn'; export const PROTOCOL_PSTN_PREFIXED = 'im.vector.protocol.pstn'; @@ -167,6 +170,11 @@ export default class CallHandler { private invitedRoomsAreVirtual = new Map(); private invitedRoomCheckInProgress = false; + // Map of the asserted identiy users after we've looked them up using the API. + // We need to be be able to determine the mapped room synchronously, so we + // do the async lookup when we get new information and then store these mappings here + private assertedIdentityNativeUsers = new Map(); + static sharedInstance() { if (!window.mxCallHandler) { window.mxCallHandler = new CallHandler() @@ -179,8 +187,17 @@ export default class CallHandler { * Gets the user-facing room associated with a call (call.roomId may be the call "virtual room" * if a voip_mxid_translate_pattern is set in the config) */ - public static roomIdForCall(call: MatrixCall): string { + public roomIdForCall(call: MatrixCall): string { if (!call) return null; + + if (SdkConfig.get()['voipObeyAssertedIdentity']) { + const nativeUser = this.assertedIdentityNativeUsers[call.callId]; + if (nativeUser) { + const room = findDMForUser(MatrixClientPeg.get(), nativeUser); + if (room) return room.roomId + } + } + return VoipUserMapper.sharedInstance().nativeRoomForVirtualRoom(call.roomId) || call.roomId; } @@ -379,14 +396,14 @@ export default class CallHandler { // We don't allow placing more than one call per room, but that doesn't mean there // can't be more than one, eg. in a glare situation. This checks that the given call // is the call we consider 'the' call for its room. - const mappedRoomId = CallHandler.roomIdForCall(call); + const mappedRoomId = CallHandler.sharedInstance().roomIdForCall(call); const callForThisRoom = this.getCallForRoom(mappedRoomId); return callForThisRoom && call.callId === callForThisRoom.callId; } private setCallListeners(call: MatrixCall) { - const mappedRoomId = CallHandler.roomIdForCall(call); + let mappedRoomId = CallHandler.sharedInstance().roomIdForCall(call); call.on(CallEvent.Error, (err: CallError) => { if (!this.matchesCallForThisRoom(call)) return; @@ -500,6 +517,37 @@ export default class CallHandler { this.setCallListeners(newCall); this.setCallState(newCall, newCall.state); }); + call.on(CallEvent.AssertedIdentityChanged, async () => { + if (!this.matchesCallForThisRoom(call)) return; + + console.log(`Call ID ${call.callId} got new asserted identity:`, call.getRemoteAssertedIdentity()); + + const newAssertedIdentity = call.getRemoteAssertedIdentity().id; + let newNativeAssertedIdentity = newAssertedIdentity; + if (newAssertedIdentity) { + const response = await this.sipNativeLookup(newAssertedIdentity); + if (response.length) newNativeAssertedIdentity = response[0].userid; + } + console.log(`Asserted identity ${newAssertedIdentity} mapped to ${newNativeAssertedIdentity}`); + + if (newNativeAssertedIdentity) { + this.assertedIdentityNativeUsers[call.callId] = newNativeAssertedIdentity; + + await ensureDMExists(MatrixClientPeg.get(), newNativeAssertedIdentity); + + const newMappedRoomId = CallHandler.sharedInstance().roomIdForCall(call); + console.log(`Old room ID: ${mappedRoomId}, new room ID: ${newMappedRoomId}`); + if (newMappedRoomId !== mappedRoomId) { + this.removeCallForRoom(mappedRoomId); + mappedRoomId = newMappedRoomId; + this.calls.set(mappedRoomId, call); + dis.dispatch({ + action: Action.CallChangeRoom, + call, + }); + } + } + }); } private async logCallStats(call: MatrixCall, mappedRoomId: string) { @@ -551,7 +599,7 @@ export default class CallHandler { } private setCallState(call: MatrixCall, status: CallState) { - const mappedRoomId = CallHandler.roomIdForCall(call); + const mappedRoomId = CallHandler.sharedInstance().roomIdForCall(call); console.log( `Call state in ${mappedRoomId} changed to ${status}`, @@ -772,7 +820,7 @@ export default class CallHandler { const call = payload.call as MatrixCall; - const mappedRoomId = CallHandler.roomIdForCall(call); + const mappedRoomId = CallHandler.sharedInstance().roomIdForCall(call); if (this.getCallForRoom(mappedRoomId)) { // ignore multiple incoming calls to the same room return; diff --git a/src/VoipUserMapper.ts b/src/VoipUserMapper.ts index 4f5613b4a8..e5bed2e812 100644 --- a/src/VoipUserMapper.ts +++ b/src/VoipUserMapper.ts @@ -57,7 +57,11 @@ export default class VoipUserMapper { if (!virtualRoom) return null; const virtualRoomEvent = virtualRoom.getAccountData(VIRTUAL_ROOM_EVENT_TYPE); if (!virtualRoomEvent || !virtualRoomEvent.getContent()) return null; - return virtualRoomEvent.getContent()['native_room'] || null; + const nativeRoomID = virtualRoomEvent.getContent()['native_room']; + const nativeRoom = MatrixClientPeg.get().getRoom(nativeRoomID); + if (!nativeRoom || nativeRoom.getMyMembership() !== 'join') return null; + + return nativeRoomID; } public isVirtualRoom(room: Room): boolean { diff --git a/src/components/views/voip/CallPreview.tsx b/src/components/views/voip/CallPreview.tsx index 29de068b0c..d31afddec9 100644 --- a/src/components/views/voip/CallPreview.tsx +++ b/src/components/views/voip/CallPreview.tsx @@ -27,6 +27,7 @@ import SettingsStore from "../../../settings/SettingsStore"; import { CallEvent, CallState, MatrixCall } from 'matrix-js-sdk/src/webrtc/call'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import {replaceableComponent} from "../../../utils/replaceableComponent"; +import { Action } from '../../../dispatcher/actions'; const SHOW_CALL_IN_STATES = [ CallState.Connected, @@ -142,6 +143,7 @@ export default class CallPreview extends React.Component { switch (payload.action) { // listen for call state changes to prod the render method, which // may hide the global CallView if the call it is tracking is dead + case Action.CallChangeRoom: case 'call_state': { const [primaryCall, secondaryCalls] = getPrimarySecondaryCalls( CallHandler.sharedInstance().getAllActiveCallsNotInRoom(this.state.roomId), diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 8a6ed75fee..6745713845 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -208,7 +208,7 @@ export default class CallView extends React.Component { }; private onExpandClick = () => { - const userFacingRoomId = CallHandler.roomIdForCall(this.props.call); + const userFacingRoomId = CallHandler.sharedInstance().roomIdForCall(this.props.call); dis.dispatch({ action: 'view_room', room_id: userFacingRoomId, @@ -337,7 +337,7 @@ export default class CallView extends React.Component { }; private onRoomAvatarClick = () => { - const userFacingRoomId = CallHandler.roomIdForCall(this.props.call); + const userFacingRoomId = CallHandler.sharedInstance().roomIdForCall(this.props.call); dis.dispatch({ action: 'view_room', room_id: userFacingRoomId, @@ -345,7 +345,7 @@ export default class CallView extends React.Component { } private onSecondaryRoomAvatarClick = () => { - const userFacingRoomId = CallHandler.roomIdForCall(this.props.secondaryCall); + const userFacingRoomId = CallHandler.sharedInstance().roomIdForCall(this.props.secondaryCall); dis.dispatch({ action: 'view_room', @@ -354,7 +354,7 @@ export default class CallView extends React.Component { } private onCallResumeClick = () => { - const userFacingRoomId = CallHandler.roomIdForCall(this.props.call); + const userFacingRoomId = CallHandler.sharedInstance().roomIdForCall(this.props.call); CallHandler.sharedInstance().setActiveCallRoomId(userFacingRoomId); } @@ -365,8 +365,8 @@ export default class CallView extends React.Component { public render() { const client = MatrixClientPeg.get(); - const callRoomId = CallHandler.roomIdForCall(this.props.call); - const secondaryCallRoomId = CallHandler.roomIdForCall(this.props.secondaryCall); + const callRoomId = CallHandler.sharedInstance().roomIdForCall(this.props.call); + const secondaryCallRoomId = CallHandler.sharedInstance().roomIdForCall(this.props.secondaryCall); const callRoom = client.getRoom(callRoomId); const secCallRoom = this.props.secondaryCall ? client.getRoom(secondaryCallRoomId) : null; @@ -482,11 +482,13 @@ export default class CallView extends React.Component { const isOnHold = this.state.isLocalOnHold || this.state.isRemoteOnHold; let holdTransferContent; if (transfereeCall) { - const transferTargetRoom = MatrixClientPeg.get().getRoom(CallHandler.roomIdForCall(this.props.call)); + const transferTargetRoom = MatrixClientPeg.get().getRoom( + CallHandler.sharedInstance().roomIdForCall(this.props.call), + ); const transferTargetName = transferTargetRoom ? transferTargetRoom.name : _t("unknown person"); const transfereeRoom = MatrixClientPeg.get().getRoom( - CallHandler.roomIdForCall(transfereeCall), + CallHandler.sharedInstance().roomIdForCall(transfereeCall), ); const transfereeName = transfereeRoom ? transfereeRoom.name : _t("unknown person"); diff --git a/src/components/views/voip/CallViewForRoom.tsx b/src/components/views/voip/CallViewForRoom.tsx index 878b6af20f..7540dbc8d9 100644 --- a/src/components/views/voip/CallViewForRoom.tsx +++ b/src/components/views/voip/CallViewForRoom.tsx @@ -22,6 +22,7 @@ import dis from '../../../dispatcher/dispatcher'; import {Resizable} from "re-resizable"; import ResizeNotifier from "../../../utils/ResizeNotifier"; import {replaceableComponent} from "../../../utils/replaceableComponent"; +import { Action } from '../../../dispatcher/actions'; interface IProps { // What room we should display the call for @@ -62,6 +63,7 @@ export default class CallViewForRoom extends React.Component { private onAction = (payload) => { switch (payload.action) { + case Action.CallChangeRoom: case 'call_state': { const newCall = this.getCall(); if (newCall !== this.state.call) { diff --git a/src/components/views/voip/IncomingCallBox.tsx b/src/components/views/voip/IncomingCallBox.tsx index 0ca2a196c2..2abdc0641d 100644 --- a/src/components/views/voip/IncomingCallBox.tsx +++ b/src/components/views/voip/IncomingCallBox.tsx @@ -72,7 +72,7 @@ export default class IncomingCallBox extends React.Component { e.stopPropagation(); dis.dispatch({ action: 'answer', - room_id: CallHandler.roomIdForCall(this.state.incomingCall), + room_id: CallHandler.sharedInstance().roomIdForCall(this.state.incomingCall), }); }; @@ -80,7 +80,7 @@ export default class IncomingCallBox extends React.Component { e.stopPropagation(); dis.dispatch({ action: 'reject', - room_id: CallHandler.roomIdForCall(this.state.incomingCall), + room_id: CallHandler.sharedInstance().roomIdForCall(this.state.incomingCall), }); }; @@ -91,7 +91,7 @@ export default class IncomingCallBox extends React.Component { let room = null; if (this.state.incomingCall) { - room = MatrixClientPeg.get().getRoom(CallHandler.roomIdForCall(this.state.incomingCall)); + room = MatrixClientPeg.get().getRoom(CallHandler.sharedInstance().roomIdForCall(this.state.incomingCall)); } const caller = room ? room.name : _t("Unknown caller"); diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index cd32c3743f..46c962f160 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -114,6 +114,9 @@ export enum Action { */ VirtualRoomSupportUpdated = "virtual_room_support_updated", + // Probably would be better to have a VoIP states in a store and have the store emit changes + CallChangeRoom = "call_change_room", + /** * Fired when an upload has started. Should be used with UploadStartedPayload. */ From 10d056eb4170e917d12e7d7a6334b9b5f8a7a316 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 19 Apr 2021 20:34:48 +0100 Subject: [PATCH 2/7] unused import --- src/CallHandler.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index 886c594c94..81172e2b46 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -87,7 +87,6 @@ import VoipUserMapper from './VoipUserMapper'; import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from './widgets/ManagedHybrid'; import { randomUppercaseString, randomLowercaseString } from "matrix-js-sdk/src/randomstring"; import SdkConfig from './SdkConfig'; -import DMRoomMap from './utils/DMRoomMap'; import { ensureDMExists, findDMForUser } from './createRoom'; export const PROTOCOL_PSTN = 'm.protocol.pstn'; From ee96201e33e15c3fdf2a659ef79944aa83fd60dd Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 19 Apr 2021 21:05:05 +0100 Subject: [PATCH 3/7] Comment room creation insanity --- src/CallHandler.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index 81172e2b46..1686a671ed 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -532,6 +532,11 @@ export default class CallHandler { if (newNativeAssertedIdentity) { this.assertedIdentityNativeUsers[call.callId] = newNativeAssertedIdentity; + // If we don't already have a room with this user, make one. This will be slightly odd + // if they called us because we'll be inviting them, but there's not much we can do about + // this if we want the actual, native room to exist (which we do). This is why it's + // important to only obey asserted identity in trusted environments, since anyone you're + // on a call with can cause you to send a room invite to someone. await ensureDMExists(MatrixClientPeg.get(), newNativeAssertedIdentity); const newMappedRoomId = CallHandler.sharedInstance().roomIdForCall(call); From dc3d05bc889259d524ae801da4a71429719ff41c Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 23 Apr 2021 14:39:39 +0100 Subject: [PATCH 4/7] Test for asserted identity This is out first CallHandler test(!) Switches react-sdk to use createCall on the client object so we can stub this out in the test. Add a bunch more stubs to the test client. There's more stuff in this test that has scope to be used more widely, like waiting for a certain dispatch and mocking out rooms with particular sets of users in them: we could consider moving these out to test utils if we wanted. --- src/CallHandler.tsx | 7 +- src/utils/DMRoomMap.ts | 9 ++ test/CallHandler-test.ts | 214 +++++++++++++++++++++++++++++++++++++++ test/test-utils.js | 5 + 4 files changed, 231 insertions(+), 4 deletions(-) create mode 100644 test/CallHandler-test.ts diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index 1686a671ed..605e5a4a89 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -59,7 +59,6 @@ import {MatrixClientPeg} from './MatrixClientPeg'; import PlatformPeg from './PlatformPeg'; import Modal from './Modal'; import { _t } from './languageHandler'; -import { createNewMatrixCall } from 'matrix-js-sdk/src/webrtc/call'; import dis from './dispatcher/dispatcher'; import WidgetUtils from './utils/WidgetUtils'; import WidgetEchoStore from './stores/WidgetEchoStore'; @@ -395,7 +394,7 @@ export default class CallHandler { // We don't allow placing more than one call per room, but that doesn't mean there // can't be more than one, eg. in a glare situation. This checks that the given call // is the call we consider 'the' call for its room. - const mappedRoomId = CallHandler.sharedInstance().roomIdForCall(call); + const mappedRoomId = this.roomIdForCall(call); const callForThisRoom = this.getCallForRoom(mappedRoomId); return callForThisRoom && call.callId === callForThisRoom.callId; @@ -539,7 +538,7 @@ export default class CallHandler { // on a call with can cause you to send a room invite to someone. await ensureDMExists(MatrixClientPeg.get(), newNativeAssertedIdentity); - const newMappedRoomId = CallHandler.sharedInstance().roomIdForCall(call); + const newMappedRoomId = this.roomIdForCall(call); console.log(`Old room ID: ${mappedRoomId}, new room ID: ${newMappedRoomId}`); if (newMappedRoomId !== mappedRoomId) { this.removeCallForRoom(mappedRoomId); @@ -691,7 +690,7 @@ export default class CallHandler { const timeUntilTurnCresExpire = MatrixClientPeg.get().getTurnServersExpiry() - Date.now(); console.log("Current turn creds expire in " + timeUntilTurnCresExpire + " ms"); - const call = createNewMatrixCall(MatrixClientPeg.get(), mappedRoomId); + const call = MatrixClientPeg.get().createCall(mappedRoomId); this.calls.set(roomId, call); if (transferee) { diff --git a/src/utils/DMRoomMap.ts b/src/utils/DMRoomMap.ts index e49b74c380..b166674043 100644 --- a/src/utils/DMRoomMap.ts +++ b/src/utils/DMRoomMap.ts @@ -55,6 +55,15 @@ export default class DMRoomMap { return DMRoomMap.sharedInstance; } + /** + * Set the shared instance to the instance supplied + * Used by tests + * @param inst the new shared instance + */ + public static setShared(inst: DMRoomMap) { + DMRoomMap.sharedInstance = inst; + } + /** * Returns a shared instance of the class * that uses the singleton matrix client diff --git a/test/CallHandler-test.ts b/test/CallHandler-test.ts new file mode 100644 index 0000000000..04dec8979f --- /dev/null +++ b/test/CallHandler-test.ts @@ -0,0 +1,214 @@ +/* +Copyright 2015-2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import './skinned-sdk'; + +import CallHandler, { PlaceCallType } from '../src/CallHandler'; +import { stubClient, mkStubRoom } from './test-utils'; +import { MatrixClientPeg } from '../src/MatrixClientPeg'; +import dis from '../src/dispatcher/dispatcher'; +import { CallEvent, CallState } from 'matrix-js-sdk/src/webrtc/call'; +import DMRoomMap from '../src/utils/DMRoomMap'; +import EventEmitter from 'events'; +import { Action } from '../src/dispatcher/actions'; +import SdkConfig from '../src/SdkConfig'; + +const REAL_ROOM_ID = '$room1:example.org'; +const MAPPED_ROOM_ID = '$room2:example.org'; +const MAPPED_ROOM_ID_2 = '$room3:example.org'; + +function mkStubDM(roomId, userId) { + const room = mkStubRoom(roomId); + room.getJoinedMembers = jest.fn().mockReturnValue([ + { + userId: '@me:example.org', + name: 'Member', + rawDisplayName: 'Member', + roomId: roomId, + membership: 'join', + getAvatarUrl: () => 'mxc://avatar.url/image.png', + getMxcAvatarUrl: () => 'mxc://avatar.url/image.png', + }, + { + userId: userId, + name: 'Member', + rawDisplayName: 'Member', + roomId: roomId, + membership: 'join', + getAvatarUrl: () => 'mxc://avatar.url/image.png', + getMxcAvatarUrl: () => 'mxc://avatar.url/image.png', + }, + ]); + room.currentState.getMembers = room.getJoinedMembers; + + return room; +} + +class FakeCall extends EventEmitter { + roomId: string; + callId = "fake call id"; + + constructor(roomId) { + super(); + + this.roomId = roomId; + } + + setRemoteOnHold() {} + setRemoteAudioElement() {} + + placeVoiceCall() { + this.emit(CallEvent.State, CallState.Connected, null); + } +} + +describe('CallHandler', () => { + let dmRoomMap; + let callHandler; + let audioElement; + let fakeCall; + + beforeEach(() => { + stubClient(); + MatrixClientPeg.get().createCall = roomId => { + if (fakeCall && fakeCall.roomId !== roomId) { + throw new Error("Only one call is supported!"); + } + fakeCall = new FakeCall(roomId); + return fakeCall; + }; + + callHandler = new CallHandler(); + callHandler.start(); + + dmRoomMap = { + getUserIdForRoomId: roomId => { + if (roomId === REAL_ROOM_ID) { + return '@user1:example.org'; + } else if (roomId === MAPPED_ROOM_ID) { + return '@user2:example.org'; + } else if (roomId === MAPPED_ROOM_ID_2) { + return '@user3:example.org'; + } else { + return null; + } + }, + getDMRoomsForUserId: userId => { + if (userId === '@user2:example.org') { + return [MAPPED_ROOM_ID]; + } else if (userId === '@user3:example.org') { + return [MAPPED_ROOM_ID_2]; + } else { + return []; + } + }, + }; + DMRoomMap.setShared(dmRoomMap); + + audioElement = document.createElement('audio'); + audioElement.id = "remoteAudio"; + document.body.appendChild(audioElement); + }); + + afterEach(() => { + callHandler.stop(); + DMRoomMap.setShared(null); + // @ts-ignore + window.mxCallHandler = null; + MatrixClientPeg.unset(); + + document.body.removeChild(audioElement); + SdkConfig.unset(); + }); + + it('should move calls between rooms when remote asserted identity changes', async () => { + const realRoom = mkStubDM(REAL_ROOM_ID, '@user1:example.org'); + const mappedRoom = mkStubDM(MAPPED_ROOM_ID, '@user2:example.org'); + const mappedRoom2 = mkStubDM(MAPPED_ROOM_ID_2, '@user3:example.org'); + + MatrixClientPeg.get().getRoom = roomId => { + switch (roomId) { + case REAL_ROOM_ID: + return realRoom; + case MAPPED_ROOM_ID: + return mappedRoom; + case MAPPED_ROOM_ID_2: + return mappedRoom2; + } + }; + + dis.dispatch({ + action: 'place_call', + type: PlaceCallType.Voice, + room_id: REAL_ROOM_ID, + }, true); + + let dispatchHandle; + // wait for the call to be set up + await new Promise(resolve => { + dispatchHandle = dis.register(payload => { + if (payload.action === 'call_state') { + resolve(); + } + }); + }); + dis.unregister(dispatchHandle); + + // should start off in the actual room ID it's in at the protocol level + expect(callHandler.getCallForRoom(REAL_ROOM_ID)).toBe(fakeCall); + + let callRoomChangeEventCount = 0; + const roomChangePromise = new Promise(resolve => { + dispatchHandle = dis.register(payload => { + if (payload.action === Action.CallChangeRoom) { + ++callRoomChangeEventCount; + resolve(); + } + }); + }); + + // Now emit an asserted identity for user2: this should be ignored + // because we haven't set the config option to obey asserted identity + fakeCall.getRemoteAssertedIdentity = jest.fn().mockReturnValue({ + id: "@user2:example.org", + }); + fakeCall.emit(CallEvent.AssertedIdentityChanged); + + // Now set the config option + SdkConfig.put({ + voipObeyAssertedIdentity: true, + }); + + // ...and send another asserted identity event for a different user + fakeCall.getRemoteAssertedIdentity = jest.fn().mockReturnValue({ + id: "@user3:example.org", + }); + fakeCall.emit(CallEvent.AssertedIdentityChanged); + + await roomChangePromise; + dis.unregister(dispatchHandle); + + // If everything's gone well, we should have seen only one room change + // event and the call should now be in user 3's room. + // If it's not obeying any, the call will still be in REAL_ROOM_ID. + // If it incorrectly obeyed both asserted identity changes, either it will + // have just processed one and the call will be in the wrong room, or we'll + // have seen two room change dispatches. + expect(callRoomChangeEventCount).toEqual(1); + expect(callHandler.getCallForRoom(REAL_ROOM_ID)).toBeNull(); + expect(callHandler.getCallForRoom(MAPPED_ROOM_ID_2)).toBe(fakeCall); + }); +}); diff --git a/test/test-utils.js b/test/test-utils.js index d259fcb95f..d9332580f7 100644 --- a/test/test-utils.js +++ b/test/test-utils.js @@ -64,6 +64,11 @@ export function createTestClient() { getRoomIdForAlias: jest.fn().mockResolvedValue(undefined), getRoomDirectoryVisibility: jest.fn().mockResolvedValue(undefined), getProfileInfo: jest.fn().mockResolvedValue({}), + getThirdpartyProtocols: jest.fn().mockResolvedValue({}), + getClientWellKnown: jest.fn().mockReturnValue(null), + supportsVoip: jest.fn().mockReturnValue(true), + getTurnServersExpiry: jest.fn().mockReturnValue(2^32), + getThirdpartyUser: jest.fn().mockResolvedValue([]), getAccountData: (type) => { return mkEvent({ type, From b6762c68afd8cec1e06c9544d046e9126c8bf6b4 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 27 Apr 2021 18:55:53 +0100 Subject: [PATCH 5/7] typo Co-authored-by: J. Ryan Stinnett --- src/CallHandler.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index 605e5a4a89..f2a2e71854 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -168,7 +168,7 @@ export default class CallHandler { private invitedRoomsAreVirtual = new Map(); private invitedRoomCheckInProgress = false; - // Map of the asserted identiy users after we've looked them up using the API. + // Map of the asserted identity users after we've looked them up using the API. // We need to be be able to determine the mapped room synchronously, so we // do the async lookup when we get new information and then store these mappings here private assertedIdentityNativeUsers = new Map(); From 705505fe85a85e2a14f725d5301ea3c78f19cfbb Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 27 Apr 2021 18:56:22 +0100 Subject: [PATCH 6/7] make copyright not lie --- test/CallHandler-test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/CallHandler-test.ts b/test/CallHandler-test.ts index 04dec8979f..cb801b4936 100644 --- a/test/CallHandler-test.ts +++ b/test/CallHandler-test.ts @@ -1,5 +1,5 @@ /* -Copyright 2015-2021 The Matrix.org Foundation C.I.C. +Copyright 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From be7d4d020bd9713a92e17bf268fc0e382815763a Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 27 Apr 2021 19:33:53 +0100 Subject: [PATCH 7/7] Put asserted identity option under a 'voip' section --- src/CallHandler.tsx | 4 +++- test/CallHandler-test.ts | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index f2a2e71854..16bc837aa2 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -188,7 +188,9 @@ export default class CallHandler { public roomIdForCall(call: MatrixCall): string { if (!call) return null; - if (SdkConfig.get()['voipObeyAssertedIdentity']) { + const voipConfig = SdkConfig.get()['voip']; + + if (voipConfig && voipConfig.obeyAssertedIdentity) { const nativeUser = this.assertedIdentityNativeUsers[call.callId]; if (nativeUser) { const room = findDMForUser(MatrixClientPeg.get(), nativeUser); diff --git a/test/CallHandler-test.ts b/test/CallHandler-test.ts index cb801b4936..754610b223 100644 --- a/test/CallHandler-test.ts +++ b/test/CallHandler-test.ts @@ -189,7 +189,9 @@ describe('CallHandler', () => { // Now set the config option SdkConfig.put({ - voipObeyAssertedIdentity: true, + voip: { + obeyAssertedIdentity: true, + }, }); // ...and send another asserted identity event for a different user