From 2c4fa73a4571ff3da3ae55e60ae8facc7748006e Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 2 Jun 2021 17:39:13 +0100 Subject: [PATCH 1/4] Map phone number lookup results to their native rooms When dialing a phone number, also look to see if there's a corresponding native user for the resulting user, and if so, go to the native room for that user. --- src/CallHandler.tsx | 33 +++++++++- src/VoipUserMapper.ts | 6 +- src/components/views/voip/DialPadModal.tsx | 23 ++----- src/dispatcher/actions.ts | 8 +++ test/CallHandler-test.ts | 73 ++++++++++++++-------- 5 files changed, 95 insertions(+), 48 deletions(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index a05d3a25c8..ba0178fa59 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -521,7 +521,9 @@ export default class CallHandler extends EventEmitter { let newNativeAssertedIdentity = newAssertedIdentity; if (newAssertedIdentity) { const response = await this.sipNativeLookup(newAssertedIdentity); - if (response.length) newNativeAssertedIdentity = response[0].userid; + if (response.length && response[0].fields.lookup_success) { + newNativeAssertedIdentity = response[0].userid; + } } console.log(`Asserted identity ${newAssertedIdentity} mapped to ${newNativeAssertedIdentity}`); @@ -862,9 +864,38 @@ export default class CallHandler extends EventEmitter { }); break; } + case Action.DialNumber: + this.dialNumber(payload.number); + break; } } + private async dialNumber(number: string) { + const results = await this.pstnLookup(number); + if (!results || results.length === 0 || !results[0].userid) { + Modal.createTrackedDialog('', '', ErrorDialog, { + title: _t("Unable to look up phone number"), + description: _t("There was an error looking up the phone number"), + }); + return; + } + const userId = results[0].userid; + + // Now check to see if this is a virtual user, in which case we should find the + // native user + const nativeLookupResults = await this.sipNativeLookup(userId); + const lookupSuccess = nativeLookupResults.length > 0 && nativeLookupResults[0].fields.lookup_success; + const nativeUserId = lookupSuccess ? nativeLookupResults[0].userid : userId; + console.log("Looked up " + number + " to " + userId + " and mapped to native user " + nativeUserId); + + const roomId = await ensureDMExists(MatrixClientPeg.get(), nativeUserId); + + dis.dispatch({ + action: 'view_room', + room_id: roomId, + }); + } + setActiveCallRoomId(activeCallRoomId: string) { logger.info("Setting call in room " + activeCallRoomId + " active"); diff --git a/src/VoipUserMapper.ts b/src/VoipUserMapper.ts index e5bed2e812..d576a5434c 100644 --- a/src/VoipUserMapper.ts +++ b/src/VoipUserMapper.ts @@ -33,7 +33,7 @@ export default class VoipUserMapper { private async userToVirtualUser(userId: string): Promise { const results = await CallHandler.sharedInstance().sipVirtualLookup(userId); - if (results.length === 0) return null; + if (results.length === 0 || !results[0].fields.lookup_success) return null; return results[0].userid; } @@ -82,14 +82,14 @@ export default class VoipUserMapper { return Boolean(claimedNativeRoomId); } - public async onNewInvitedRoom(invitedRoom: Room) { + public async onNewInvitedRoom(invitedRoom: Room): Promise { if (!CallHandler.sharedInstance().getSupportsVirtualRooms()) return; const inviterId = invitedRoom.getDMInviter(); console.log(`Checking virtual-ness of room ID ${invitedRoom.roomId}, invited by ${inviterId}`); const result = await CallHandler.sharedInstance().sipNativeLookup(inviterId); if (result.length === 0) { - return true; + return; } if (result[0].fields.is_virtual) { diff --git a/src/components/views/voip/DialPadModal.tsx b/src/components/views/voip/DialPadModal.tsx index cdd5bc6641..f3ee49e4ac 100644 --- a/src/components/views/voip/DialPadModal.tsx +++ b/src/components/views/voip/DialPadModal.tsx @@ -15,17 +15,13 @@ limitations under the License. */ import * as React from "react"; -import { ensureDMExists } from "../../../createRoom"; import { _t } from "../../../languageHandler"; -import { MatrixClientPeg } from "../../../MatrixClientPeg"; import AccessibleButton from "../elements/AccessibleButton"; import Field from "../elements/Field"; import DialPad from './DialPad'; import dis from '../../../dispatcher/dispatcher'; -import Modal from "../../../Modal"; -import ErrorDialog from "../../views/dialogs/ErrorDialog"; -import CallHandler from "../../../CallHandler"; import {replaceableComponent} from "../../../utils/replaceableComponent"; +import { Action } from "../../../dispatcher/actions"; interface IProps { onFinished: (boolean) => void; @@ -67,21 +63,10 @@ export default class DialpadModal extends React.PureComponent { } onDialPress = async () => { - const results = await CallHandler.sharedInstance().pstnLookup(this.state.value); - if (!results || results.length === 0 || !results[0].userid) { - Modal.createTrackedDialog('', '', ErrorDialog, { - title: _t("Unable to look up phone number"), - description: _t("There was an error looking up the phone number"), - }); - } - const userId = results[0].userid; - - const roomId = await ensureDMExists(MatrixClientPeg.get(), userId); - dis.dispatch({ - action: 'view_room', - room_id: roomId, - }); + action: Action.DialNumber, + number: this.state.value, + }) this.props.onFinished(true); } diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index 9fc0b54eea..073e82bef6 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -100,6 +100,14 @@ export enum Action { */ OpenDialPad = "open_dial_pad", + /** + * Dial the phone number in the payload + * payload: { + * number: , + * } + */ + DialNumber = "dial_number", + /** * Fired when CallHandler has checked for PSTN protocol support * payload: none diff --git a/test/CallHandler-test.ts b/test/CallHandler-test.ts index 1e3f92e788..a93a134133 100644 --- a/test/CallHandler-test.ts +++ b/test/CallHandler-test.ts @@ -23,8 +23,8 @@ 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'; +import { ActionPayload } from '../src/dispatcher/payloads'; const REAL_ROOM_ID = '$room1:example.org'; const MAPPED_ROOM_ID = '$room2:example.org'; @@ -75,6 +75,18 @@ class FakeCall extends EventEmitter { } } +function untilDispatch(waitForAction: string): Promise { + let dispatchHandle; + return new Promise(resolve => { + dispatchHandle = dis.register(payload => { + if (payload.action === waitForAction) { + dis.unregister(dispatchHandle); + resolve(payload); + } + }); + }); +} + describe('CallHandler', () => { let dmRoomMap; let callHandler; @@ -94,6 +106,21 @@ describe('CallHandler', () => { callHandler = new CallHandler(); callHandler.start(); + 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; + } + }; + dmRoomMap = { getUserIdForRoomId: roomId => { if (roomId === REAL_ROOM_ID) { @@ -134,38 +161,34 @@ describe('CallHandler', () => { SdkConfig.unset(); }); + it('should look up the correct user and open the room when a phone number is dialled', async () => { + MatrixClientPeg.get().getThirdpartyUser = jest.fn().mockResolvedValue([{ + userid: '@user2:example.org', + protocol: "im.vector.protocol.sip_native", + fields: { + is_native: true, + lookup_success: true, + }, + }]); + + dis.dispatch({ + action: 'dial_number', + number: '01818118181', + }, true); + + const viewRoomPayload = await untilDispatch('view_room'); + expect(viewRoomPayload.room_id).toEqual(MAPPED_ROOM_ID); + }); + 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); + await untilDispatch('call_state'); // should start off in the actual room ID it's in at the protocol level expect(callHandler.getCallForRoom(REAL_ROOM_ID)).toBe(fakeCall); From 0aeddea30f2f54d7cb06ae0a1c58be52b654d75a Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 2 Jun 2021 17:47:29 +0100 Subject: [PATCH 2/4] Only do native lookup if it's supported Also fix as bug where we were checking the wrong field to check for native/virtual support: oops. --- src/CallHandler.tsx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index ba0178fa59..0d87451b5f 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -264,7 +264,7 @@ export default class CallHandler extends EventEmitter { } public getSupportsVirtualRooms() { - return this.supportsPstnProtocol; + return this.supportsSipNativeVirtual; } public pstnLookup(phoneNumber: string): Promise { @@ -883,10 +883,15 @@ export default class CallHandler extends EventEmitter { // Now check to see if this is a virtual user, in which case we should find the // native user - const nativeLookupResults = await this.sipNativeLookup(userId); - const lookupSuccess = nativeLookupResults.length > 0 && nativeLookupResults[0].fields.lookup_success; - const nativeUserId = lookupSuccess ? nativeLookupResults[0].userid : userId; - console.log("Looked up " + number + " to " + userId + " and mapped to native user " + nativeUserId); + let nativeUserId; + if (this.getSupportsVirtualRooms()) { + const nativeLookupResults = await this.sipNativeLookup(userId); + const lookupSuccess = nativeLookupResults.length > 0 && nativeLookupResults[0].fields.lookup_success; + nativeUserId = lookupSuccess ? nativeLookupResults[0].userid : userId; + console.log("Looked up " + number + " to " + userId + " and mapped to native user " + nativeUserId); + } else { + nativeUserId = userId; + } const roomId = await ensureDMExists(MatrixClientPeg.get(), nativeUserId); From 58652152c13039c485ccd4a72a92a7915698a6e2 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 2 Jun 2021 17:52:26 +0100 Subject: [PATCH 3/4] Strings are now in a different place... --- src/i18n/strings/en_EN.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3d6fcb8643..ada264d110 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -63,6 +63,8 @@ "Already in call": "Already in call", "You're already in a call with this person.": "You're already in a call with this person.", "You cannot place a call with yourself.": "You cannot place a call with yourself.", + "Unable to look up phone number": "Unable to look up phone number", + "There was an error looking up the phone number": "There was an error looking up the phone number", "Call in Progress": "Call in Progress", "A call is currently being placed!": "A call is currently being placed!", "Permission Required": "Permission Required", @@ -898,8 +900,6 @@ "Fill Screen": "Fill Screen", "Return to call": "Return to call", "%(name)s on hold": "%(name)s on hold", - "Unable to look up phone number": "Unable to look up phone number", - "There was an error looking up the phone number": "There was an error looking up the phone number", "Dial pad": "Dial pad", "Unknown caller": "Unknown caller", "Incoming voice call": "Incoming voice call", From 8ef95a62378a8fca563aa094ee897da06e05c438 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 3 Jun 2021 14:38:13 +0100 Subject: [PATCH 4/4] Interface dispatcher payload & use constant in test --- src/components/views/voip/DialPadModal.tsx | 6 +++-- src/dispatcher/actions.ts | 4 +--- src/dispatcher/payloads/DialNumberPayload.ts | 23 ++++++++++++++++++++ test/CallHandler-test.ts | 4 +++- 4 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 src/dispatcher/payloads/DialNumberPayload.ts diff --git a/src/components/views/voip/DialPadModal.tsx b/src/components/views/voip/DialPadModal.tsx index f3ee49e4ac..8c0af5e81a 100644 --- a/src/components/views/voip/DialPadModal.tsx +++ b/src/components/views/voip/DialPadModal.tsx @@ -21,6 +21,7 @@ import Field from "../elements/Field"; import DialPad from './DialPad'; import dis from '../../../dispatcher/dispatcher'; import {replaceableComponent} from "../../../utils/replaceableComponent"; +import { DialNumberPayload } from "../../../dispatcher/payloads/DialNumberPayload"; import { Action } from "../../../dispatcher/actions"; interface IProps { @@ -63,10 +64,11 @@ export default class DialpadModal extends React.PureComponent { } onDialPress = async () => { - dis.dispatch({ + const payload: DialNumberPayload = { action: Action.DialNumber, number: this.state.value, - }) + }; + dis.dispatch(payload); this.props.onFinished(true); } diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index 073e82bef6..300eed2b98 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -102,9 +102,7 @@ export enum Action { /** * Dial the phone number in the payload - * payload: { - * number: , - * } + * payload: DialNumberPayload */ DialNumber = "dial_number", diff --git a/src/dispatcher/payloads/DialNumberPayload.ts b/src/dispatcher/payloads/DialNumberPayload.ts new file mode 100644 index 0000000000..1b591b9f6b --- /dev/null +++ b/src/dispatcher/payloads/DialNumberPayload.ts @@ -0,0 +1,23 @@ +/* +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. +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 { ActionPayload } from "../payloads"; +import { Action } from "../actions"; + +export interface DialNumberPayload extends ActionPayload { + action: Action.DialNumber; + number: string; +} diff --git a/test/CallHandler-test.ts b/test/CallHandler-test.ts index a93a134133..12316ac01c 100644 --- a/test/CallHandler-test.ts +++ b/test/CallHandler-test.ts @@ -25,6 +25,8 @@ import DMRoomMap from '../src/utils/DMRoomMap'; import EventEmitter from 'events'; import SdkConfig from '../src/SdkConfig'; import { ActionPayload } from '../src/dispatcher/payloads'; +import { Actions } from '../src/notifications/types'; +import { Action } from '../src/dispatcher/actions'; const REAL_ROOM_ID = '$room1:example.org'; const MAPPED_ROOM_ID = '$room2:example.org'; @@ -172,7 +174,7 @@ describe('CallHandler', () => { }]); dis.dispatch({ - action: 'dial_number', + action: Action.DialNumber, number: '01818118181', }, true);