Merge pull request #6136 from matrix-org/dbkr/map_phone_number_lookup_to_native

Map phone number lookup results to their native rooms
This commit is contained in:
David Baker 2021-06-03 19:01:46 +01:00 committed by GitHub
commit 7421efe8f9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 129 additions and 52 deletions

View file

@ -264,7 +264,7 @@ export default class CallHandler extends EventEmitter {
} }
public getSupportsVirtualRooms() { public getSupportsVirtualRooms() {
return this.supportsPstnProtocol; return this.supportsSipNativeVirtual;
} }
public pstnLookup(phoneNumber: string): Promise<ThirdpartyLookupResponse[]> { public pstnLookup(phoneNumber: string): Promise<ThirdpartyLookupResponse[]> {
@ -521,7 +521,9 @@ export default class CallHandler extends EventEmitter {
let newNativeAssertedIdentity = newAssertedIdentity; let newNativeAssertedIdentity = newAssertedIdentity;
if (newAssertedIdentity) { if (newAssertedIdentity) {
const response = await this.sipNativeLookup(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}`); console.log(`Asserted identity ${newAssertedIdentity} mapped to ${newNativeAssertedIdentity}`);
@ -862,9 +864,43 @@ export default class CallHandler extends EventEmitter {
}); });
break; 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
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);
dis.dispatch({
action: 'view_room',
room_id: roomId,
});
}
setActiveCallRoomId(activeCallRoomId: string) { setActiveCallRoomId(activeCallRoomId: string) {
logger.info("Setting call in room " + activeCallRoomId + " active"); logger.info("Setting call in room " + activeCallRoomId + " active");

View file

@ -33,7 +33,7 @@ export default class VoipUserMapper {
private async userToVirtualUser(userId: string): Promise<string> { private async userToVirtualUser(userId: string): Promise<string> {
const results = await CallHandler.sharedInstance().sipVirtualLookup(userId); 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; return results[0].userid;
} }
@ -82,14 +82,14 @@ export default class VoipUserMapper {
return Boolean(claimedNativeRoomId); return Boolean(claimedNativeRoomId);
} }
public async onNewInvitedRoom(invitedRoom: Room) { public async onNewInvitedRoom(invitedRoom: Room): Promise<void> {
if (!CallHandler.sharedInstance().getSupportsVirtualRooms()) return; if (!CallHandler.sharedInstance().getSupportsVirtualRooms()) return;
const inviterId = invitedRoom.getDMInviter(); const inviterId = invitedRoom.getDMInviter();
console.log(`Checking virtual-ness of room ID ${invitedRoom.roomId}, invited by ${inviterId}`); console.log(`Checking virtual-ness of room ID ${invitedRoom.roomId}, invited by ${inviterId}`);
const result = await CallHandler.sharedInstance().sipNativeLookup(inviterId); const result = await CallHandler.sharedInstance().sipNativeLookup(inviterId);
if (result.length === 0) { if (result.length === 0) {
return true; return;
} }
if (result[0].fields.is_virtual) { if (result[0].fields.is_virtual) {

View file

@ -15,17 +15,14 @@ limitations under the License.
*/ */
import * as React from "react"; import * as React from "react";
import { ensureDMExists } from "../../../createRoom";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import AccessibleButton from "../elements/AccessibleButton"; import AccessibleButton from "../elements/AccessibleButton";
import Field from "../elements/Field"; import Field from "../elements/Field";
import DialPad from './DialPad'; import DialPad from './DialPad';
import dis from '../../../dispatcher/dispatcher'; 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 {replaceableComponent} from "../../../utils/replaceableComponent";
import { DialNumberPayload } from "../../../dispatcher/payloads/DialNumberPayload";
import { Action } from "../../../dispatcher/actions";
interface IProps { interface IProps {
onFinished: (boolean) => void; onFinished: (boolean) => void;
@ -67,21 +64,11 @@ export default class DialpadModal extends React.PureComponent<IProps, IState> {
} }
onDialPress = async () => { onDialPress = async () => {
const results = await CallHandler.sharedInstance().pstnLookup(this.state.value); const payload: DialNumberPayload = {
if (!results || results.length === 0 || !results[0].userid) { action: Action.DialNumber,
Modal.createTrackedDialog('', '', ErrorDialog, { number: this.state.value,
title: _t("Unable to look up phone number"), };
description: _t("There was an error looking up the phone number"), dis.dispatch(payload);
});
}
const userId = results[0].userid;
const roomId = await ensureDMExists(MatrixClientPeg.get(), userId);
dis.dispatch({
action: 'view_room',
room_id: roomId,
});
this.props.onFinished(true); this.props.onFinished(true);
} }

View file

@ -100,6 +100,12 @@ export enum Action {
*/ */
OpenDialPad = "open_dial_pad", OpenDialPad = "open_dial_pad",
/**
* Dial the phone number in the payload
* payload: DialNumberPayload
*/
DialNumber = "dial_number",
/** /**
* Fired when CallHandler has checked for PSTN protocol support * Fired when CallHandler has checked for PSTN protocol support
* payload: none * payload: none

View file

@ -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;
}

View file

@ -63,6 +63,8 @@
"Already in call": "Already in call", "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'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.", "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", "Call in Progress": "Call in Progress",
"A call is currently being placed!": "A call is currently being placed!", "A call is currently being placed!": "A call is currently being placed!",
"Permission Required": "Permission Required", "Permission Required": "Permission Required",
@ -898,8 +900,6 @@
"Fill Screen": "Fill Screen", "Fill Screen": "Fill Screen",
"Return to call": "Return to call", "Return to call": "Return to call",
"%(name)s on hold": "%(name)s on hold", "%(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", "Dial pad": "Dial pad",
"Unknown caller": "Unknown caller", "Unknown caller": "Unknown caller",
"Incoming voice call": "Incoming voice call", "Incoming voice call": "Incoming voice call",

View file

@ -23,8 +23,10 @@ import dis from '../src/dispatcher/dispatcher';
import { CallEvent, CallState } from 'matrix-js-sdk/src/webrtc/call'; import { CallEvent, CallState } from 'matrix-js-sdk/src/webrtc/call';
import DMRoomMap from '../src/utils/DMRoomMap'; import DMRoomMap from '../src/utils/DMRoomMap';
import EventEmitter from 'events'; import EventEmitter from 'events';
import { Action } from '../src/dispatcher/actions';
import SdkConfig from '../src/SdkConfig'; 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 REAL_ROOM_ID = '$room1:example.org';
const MAPPED_ROOM_ID = '$room2:example.org'; const MAPPED_ROOM_ID = '$room2:example.org';
@ -75,6 +77,18 @@ class FakeCall extends EventEmitter {
} }
} }
function untilDispatch(waitForAction: string): Promise<ActionPayload> {
let dispatchHandle;
return new Promise<ActionPayload>(resolve => {
dispatchHandle = dis.register(payload => {
if (payload.action === waitForAction) {
dis.unregister(dispatchHandle);
resolve(payload);
}
});
});
}
describe('CallHandler', () => { describe('CallHandler', () => {
let dmRoomMap; let dmRoomMap;
let callHandler; let callHandler;
@ -94,6 +108,21 @@ describe('CallHandler', () => {
callHandler = new CallHandler(); callHandler = new CallHandler();
callHandler.start(); 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 = { dmRoomMap = {
getUserIdForRoomId: roomId => { getUserIdForRoomId: roomId => {
if (roomId === REAL_ROOM_ID) { if (roomId === REAL_ROOM_ID) {
@ -134,38 +163,34 @@ describe('CallHandler', () => {
SdkConfig.unset(); 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: Action.DialNumber,
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 () => { 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({ dis.dispatch({
action: 'place_call', action: 'place_call',
type: PlaceCallType.Voice, type: PlaceCallType.Voice,
room_id: REAL_ROOM_ID, room_id: REAL_ROOM_ID,
}, true); }, true);
let dispatchHandle;
// wait for the call to be set up // wait for the call to be set up
await new Promise<void>(resolve => { await untilDispatch('call_state');
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 // should start off in the actual room ID it's in at the protocol level
expect(callHandler.getCallForRoom(REAL_ROOM_ID)).toBe(fakeCall); expect(callHandler.getCallForRoom(REAL_ROOM_ID)).toBe(fakeCall);