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.
This commit is contained in:
David Baker 2021-06-02 17:39:13 +01:00
parent 4290237bdf
commit 2c4fa73a45
5 changed files with 95 additions and 48 deletions

View file

@ -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,38 @@ 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
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) { 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,13 @@ 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 { Action } from "../../../dispatcher/actions";
interface IProps { interface IProps {
onFinished: (boolean) => void; onFinished: (boolean) => void;
@ -67,21 +63,10 @@ export default class DialpadModal extends React.PureComponent<IProps, IState> {
} }
onDialPress = async () => { 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({ dis.dispatch({
action: 'view_room', action: Action.DialNumber,
room_id: roomId, number: this.state.value,
}); })
this.props.onFinished(true); this.props.onFinished(true);
} }

View file

@ -100,6 +100,14 @@ export enum Action {
*/ */
OpenDialPad = "open_dial_pad", OpenDialPad = "open_dial_pad",
/**
* Dial the phone number in the payload
* payload: {
* number: <phone number>,
* }
*/
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

@ -23,8 +23,8 @@ 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';
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 +75,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 +106,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 +161,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: '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 () => { 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);