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:
commit
7421efe8f9
7 changed files with 129 additions and 52 deletions
|
@ -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");
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
23
src/dispatcher/payloads/DialNumberPayload.ts
Normal file
23
src/dispatcher/payloads/DialNumberPayload.ts
Normal 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;
|
||||||
|
}
|
|
@ -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",
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue