Merge pull request #5639 from matrix-org/dbkr/virtual_rooms_v2
VoIP virtual rooms, mk II
This commit is contained in:
commit
68933c1a3d
10 changed files with 231 additions and 102 deletions
2
src/@types/global.d.ts
vendored
2
src/@types/global.d.ts
vendored
|
@ -37,6 +37,7 @@ import CountlyAnalytics from "../CountlyAnalytics";
|
||||||
import UserActivity from "../UserActivity";
|
import UserActivity from "../UserActivity";
|
||||||
import {ModalWidgetStore} from "../stores/ModalWidgetStore";
|
import {ModalWidgetStore} from "../stores/ModalWidgetStore";
|
||||||
import { WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore";
|
import { WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore";
|
||||||
|
import VoipUserMapper from "../VoipUserMapper";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
|
@ -66,6 +67,7 @@ declare global {
|
||||||
mxCountlyAnalytics: typeof CountlyAnalytics;
|
mxCountlyAnalytics: typeof CountlyAnalytics;
|
||||||
mxUserActivity: UserActivity;
|
mxUserActivity: UserActivity;
|
||||||
mxModalWidgetStore: ModalWidgetStore;
|
mxModalWidgetStore: ModalWidgetStore;
|
||||||
|
mxVoipUserMapper: VoipUserMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Document {
|
interface Document {
|
||||||
|
|
|
@ -83,11 +83,19 @@ import { CallError } from "matrix-js-sdk/src/webrtc/call";
|
||||||
import { logger } from 'matrix-js-sdk/src/logger';
|
import { logger } from 'matrix-js-sdk/src/logger';
|
||||||
import DesktopCapturerSourcePicker from "./components/views/elements/DesktopCapturerSourcePicker"
|
import DesktopCapturerSourcePicker from "./components/views/elements/DesktopCapturerSourcePicker"
|
||||||
import { Action } from './dispatcher/actions';
|
import { Action } from './dispatcher/actions';
|
||||||
import { roomForVirtualRoom, getOrCreateVirtualRoomForRoom } from './VoipUserMapper';
|
import VoipUserMapper from './VoipUserMapper';
|
||||||
import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from './widgets/ManagedHybrid';
|
import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from './widgets/ManagedHybrid';
|
||||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||||
|
|
||||||
const CHECK_PSTN_SUPPORT_ATTEMPTS = 3;
|
export const PROTOCOL_PSTN = 'm.protocol.pstn';
|
||||||
|
export const PROTOCOL_PSTN_PREFIXED = 'im.vector.protocol.pstn';
|
||||||
|
export const PROTOCOL_SIP_NATIVE = 'im.vector.protocol.sip_native';
|
||||||
|
export const PROTOCOL_SIP_VIRTUAL = 'im.vector.protocol.sip_virtual';
|
||||||
|
|
||||||
|
const CHECK_PROTOCOLS_ATTEMPTS = 3;
|
||||||
|
// Event type for room account data and room creation content used to mark rooms as virtual rooms
|
||||||
|
// (and store the ID of their native room)
|
||||||
|
export const VIRTUAL_ROOM_EVENT_TYPE = 'im.vector.is_virtual_room';
|
||||||
|
|
||||||
enum AudioID {
|
enum AudioID {
|
||||||
Ring = 'ringAudio',
|
Ring = 'ringAudio',
|
||||||
|
@ -96,6 +104,29 @@ enum AudioID {
|
||||||
Busy = 'busyAudio',
|
Busy = 'busyAudio',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ThirdpartyLookupResponseFields {
|
||||||
|
/* eslint-disable camelcase */
|
||||||
|
|
||||||
|
// im.vector.sip_native
|
||||||
|
virtual_mxid?: string;
|
||||||
|
is_virtual?: boolean;
|
||||||
|
|
||||||
|
// im.vector.sip_virtual
|
||||||
|
native_mxid?: string;
|
||||||
|
is_native?: boolean;
|
||||||
|
|
||||||
|
// common
|
||||||
|
lookup_success?: boolean;
|
||||||
|
|
||||||
|
/* eslint-enable camelcase */
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ThirdpartyLookupResponse {
|
||||||
|
userid: string,
|
||||||
|
protocol: string,
|
||||||
|
fields: ThirdpartyLookupResponseFields,
|
||||||
|
}
|
||||||
|
|
||||||
// Unlike 'CallType' in js-sdk, this one includes screen sharing
|
// Unlike 'CallType' in js-sdk, this one includes screen sharing
|
||||||
// (because a screen sharing call is only a screen sharing call to the caller,
|
// (because a screen sharing call is only a screen sharing call to the caller,
|
||||||
// to the callee it's just a video call, at least as far as the current impl
|
// to the callee it's just a video call, at least as far as the current impl
|
||||||
|
@ -126,7 +157,12 @@ export default class CallHandler {
|
||||||
private audioPromises = new Map<AudioID, Promise<void>>();
|
private audioPromises = new Map<AudioID, Promise<void>>();
|
||||||
private dispatcherRef: string = null;
|
private dispatcherRef: string = null;
|
||||||
private supportsPstnProtocol = null;
|
private supportsPstnProtocol = null;
|
||||||
|
private pstnSupportPrefixed = null; // True if the server only support the prefixed pstn protocol
|
||||||
|
private supportsSipNativeVirtual = null; // im.vector.protocol.sip_virtual and im.vector.protocol.sip_native
|
||||||
private pstnSupportCheckTimer: NodeJS.Timeout; // number actually because we're in the browser
|
private pstnSupportCheckTimer: NodeJS.Timeout; // number actually because we're in the browser
|
||||||
|
// For rooms we've been invited to, true if they're from virtual user, false if we've checked and they aren't.
|
||||||
|
private invitedRoomsAreVirtual = new Map<string, boolean>();
|
||||||
|
private invitedRoomCheckInProgress = false;
|
||||||
|
|
||||||
static sharedInstance() {
|
static sharedInstance() {
|
||||||
if (!window.mxCallHandler) {
|
if (!window.mxCallHandler) {
|
||||||
|
@ -140,9 +176,9 @@ export default class CallHandler {
|
||||||
* Gets the user-facing room associated with a call (call.roomId may be the call "virtual room"
|
* 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)
|
* if a voip_mxid_translate_pattern is set in the config)
|
||||||
*/
|
*/
|
||||||
public static roomIdForCall(call: MatrixCall) {
|
public static roomIdForCall(call: MatrixCall): string {
|
||||||
if (!call) return null;
|
if (!call) return null;
|
||||||
return roomForVirtualRoom(call.roomId) || call.roomId;
|
return VoipUserMapper.sharedInstance().nativeRoomForVirtualRoom(call.roomId) || call.roomId;
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
|
@ -163,7 +199,7 @@ export default class CallHandler {
|
||||||
MatrixClientPeg.get().on('Call.incoming', this.onCallIncoming);
|
MatrixClientPeg.get().on('Call.incoming', this.onCallIncoming);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.checkForPstnSupport(CHECK_PSTN_SUPPORT_ATTEMPTS);
|
this.checkProtocols(CHECK_PROTOCOLS_ATTEMPTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
|
@ -177,33 +213,73 @@ export default class CallHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async checkForPstnSupport(maxTries) {
|
private async checkProtocols(maxTries) {
|
||||||
try {
|
try {
|
||||||
const protocols = await MatrixClientPeg.get().getThirdpartyProtocols();
|
const protocols = await MatrixClientPeg.get().getThirdpartyProtocols();
|
||||||
if (protocols['im.vector.protocol.pstn'] !== undefined) {
|
|
||||||
this.supportsPstnProtocol = protocols['im.vector.protocol.pstn'];
|
if (protocols[PROTOCOL_PSTN] !== undefined) {
|
||||||
} else if (protocols['m.protocol.pstn'] !== undefined) {
|
this.supportsPstnProtocol = Boolean(protocols[PROTOCOL_PSTN]);
|
||||||
this.supportsPstnProtocol = protocols['m.protocol.pstn'];
|
if (this.supportsPstnProtocol) this.pstnSupportPrefixed = false;
|
||||||
|
} else if (protocols[PROTOCOL_PSTN_PREFIXED] !== undefined) {
|
||||||
|
this.supportsPstnProtocol = Boolean(protocols[PROTOCOL_PSTN_PREFIXED]);
|
||||||
|
if (this.supportsPstnProtocol) this.pstnSupportPrefixed = true;
|
||||||
} else {
|
} else {
|
||||||
this.supportsPstnProtocol = null;
|
this.supportsPstnProtocol = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
dis.dispatch({action: Action.PstnSupportUpdated});
|
dis.dispatch({action: Action.PstnSupportUpdated});
|
||||||
|
|
||||||
|
if (protocols[PROTOCOL_SIP_NATIVE] !== undefined && protocols[PROTOCOL_SIP_VIRTUAL] !== undefined) {
|
||||||
|
this.supportsSipNativeVirtual = Boolean(
|
||||||
|
protocols[PROTOCOL_SIP_NATIVE] && protocols[PROTOCOL_SIP_VIRTUAL],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
dis.dispatch({action: Action.VirtualRoomSupportUpdated});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (maxTries === 1) {
|
if (maxTries === 1) {
|
||||||
console.log("Failed to check for pstn protocol support and no retries remain: assuming no support", e);
|
console.log("Failed to check for protocol support and no retries remain: assuming no support", e);
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to check for pstn protocol support: will retry", e);
|
console.log("Failed to check for protocol support: will retry", e);
|
||||||
this.pstnSupportCheckTimer = setTimeout(() => {
|
this.pstnSupportCheckTimer = setTimeout(() => {
|
||||||
this.checkForPstnSupport(maxTries - 1);
|
this.checkProtocols(maxTries - 1);
|
||||||
}, 10000);
|
}, 10000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getSupportsPstnProtocol() {
|
public getSupportsPstnProtocol() {
|
||||||
return this.supportsPstnProtocol;
|
return this.supportsPstnProtocol;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getSupportsVirtualRooms() {
|
||||||
|
return this.supportsPstnProtocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
public pstnLookup(phoneNumber: string): Promise<ThirdpartyLookupResponse[]> {
|
||||||
|
return MatrixClientPeg.get().getThirdpartyUser(
|
||||||
|
this.pstnSupportPrefixed ? PROTOCOL_PSTN_PREFIXED : PROTOCOL_PSTN, {
|
||||||
|
'm.id.phone': phoneNumber,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sipVirtualLookup(nativeMxid: string): Promise<ThirdpartyLookupResponse[]> {
|
||||||
|
return MatrixClientPeg.get().getThirdpartyUser(
|
||||||
|
PROTOCOL_SIP_VIRTUAL, {
|
||||||
|
'native_mxid': nativeMxid,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sipNativeLookup(virtualMxid: string): Promise<ThirdpartyLookupResponse[]> {
|
||||||
|
return MatrixClientPeg.get().getThirdpartyUser(
|
||||||
|
PROTOCOL_SIP_NATIVE, {
|
||||||
|
'virtual_mxid': virtualMxid,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private onCallIncoming = (call) => {
|
private onCallIncoming = (call) => {
|
||||||
// we dispatch this synchronously to make sure that the event
|
// we dispatch this synchronously to make sure that the event
|
||||||
// handlers on the call are set up immediately (so that if
|
// handlers on the call are set up immediately (so that if
|
||||||
|
@ -550,7 +626,7 @@ export default class CallHandler {
|
||||||
Analytics.trackEvent('voip', 'placeCall', 'type', type);
|
Analytics.trackEvent('voip', 'placeCall', 'type', type);
|
||||||
CountlyAnalytics.instance.trackStartCall(roomId, type === PlaceCallType.Video, false);
|
CountlyAnalytics.instance.trackStartCall(roomId, type === PlaceCallType.Video, false);
|
||||||
|
|
||||||
const mappedRoomId = (await getOrCreateVirtualRoomForRoom(roomId)) || roomId;
|
const mappedRoomId = (await VoipUserMapper.sharedInstance().getOrCreateVirtualRoomForRoom(roomId)) || roomId;
|
||||||
logger.debug("Mapped real room " + roomId + " to room ID " + mappedRoomId);
|
logger.debug("Mapped real room " + roomId + " to room ID " + mappedRoomId);
|
||||||
|
|
||||||
const call = createNewMatrixCall(MatrixClientPeg.get(), mappedRoomId);
|
const call = createNewMatrixCall(MatrixClientPeg.get(), mappedRoomId);
|
||||||
|
|
|
@ -1040,9 +1040,7 @@ export const Commands = [
|
||||||
|
|
||||||
return success((async () => {
|
return success((async () => {
|
||||||
if (isPhoneNumber) {
|
if (isPhoneNumber) {
|
||||||
const results = await MatrixClientPeg.get().getThirdpartyUser('im.vector.protocol.pstn', {
|
const results = await CallHandler.sharedInstance().pstnLookup(this.state.value);
|
||||||
'm.id.phone': userId,
|
|
||||||
});
|
|
||||||
if (!results || results.length === 0 || !results[0].userid) {
|
if (!results || results.length === 0 || !results[0].userid) {
|
||||||
throw new Error("Unable to find Matrix ID for phone number");
|
throw new Error("Unable to find Matrix ID for phone number");
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,66 +14,97 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ensureDMExists, findDMForUser } from './createRoom';
|
import { ensureVirtualRoomExists, findDMForUser } from './createRoom';
|
||||||
import { MatrixClientPeg } from "./MatrixClientPeg";
|
import { MatrixClientPeg } from "./MatrixClientPeg";
|
||||||
import DMRoomMap from "./utils/DMRoomMap";
|
import DMRoomMap from "./utils/DMRoomMap";
|
||||||
import SdkConfig from "./SdkConfig";
|
import CallHandler, { VIRTUAL_ROOM_EVENT_TYPE } from './CallHandler';
|
||||||
|
import { Room } from 'matrix-js-sdk/src/models/room';
|
||||||
|
|
||||||
// Functions for mapping users & rooms for the voip_mxid_translate_pattern
|
// Functions for mapping virtual users & rooms. Currently the only lookup
|
||||||
// config option
|
// is sip virtual: there could be others in the future.
|
||||||
|
|
||||||
export function voipUserMapperEnabled(): boolean {
|
export default class VoipUserMapper {
|
||||||
return SdkConfig.get()['voip_mxid_translate_pattern'] !== undefined;
|
private virtualRoomIdCache = new Set<string>();
|
||||||
|
|
||||||
|
public static sharedInstance(): VoipUserMapper {
|
||||||
|
if (window.mxVoipUserMapper === undefined) window.mxVoipUserMapper = new VoipUserMapper();
|
||||||
|
return window.mxVoipUserMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
// only exported for tests
|
private async userToVirtualUser(userId: string): Promise<string> {
|
||||||
export function userToVirtualUser(userId: string, templateString?: string): string {
|
const results = await CallHandler.sharedInstance().sipVirtualLookup(userId);
|
||||||
if (templateString === undefined) templateString = SdkConfig.get()['voip_mxid_translate_pattern'];
|
if (results.length === 0) return null;
|
||||||
if (!templateString) return null;
|
return results[0].userid;
|
||||||
return templateString.replace('${mxid}', encodeURIComponent(userId).replace(/%/g, '=').toLowerCase());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// only exported for tests
|
public async getOrCreateVirtualRoomForRoom(roomId: string):Promise<string> {
|
||||||
export function virtualUserToUser(userId: string, templateString?: string): string {
|
const userId = DMRoomMap.shared().getUserIdForRoomId(roomId);
|
||||||
if (templateString === undefined) templateString = SdkConfig.get()['voip_mxid_translate_pattern'];
|
if (!userId) return null;
|
||||||
if (!templateString) return null;
|
|
||||||
|
|
||||||
const regexString = templateString.replace('${mxid}', '(.+)');
|
const virtualUser = await this.userToVirtualUser(userId);
|
||||||
|
|
||||||
const match = userId.match('^' + regexString + '$');
|
|
||||||
if (!match) return null;
|
|
||||||
|
|
||||||
return decodeURIComponent(match[1].replace(/=/g, '%'));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getOrCreateVirtualRoomForUser(userId: string):Promise<string> {
|
|
||||||
const virtualUser = userToVirtualUser(userId);
|
|
||||||
if (!virtualUser) return null;
|
if (!virtualUser) return null;
|
||||||
|
|
||||||
return await ensureDMExists(MatrixClientPeg.get(), virtualUser);
|
const virtualRoomId = await ensureVirtualRoomExists(MatrixClientPeg.get(), virtualUser, roomId);
|
||||||
|
MatrixClientPeg.get().setRoomAccountData(virtualRoomId, VIRTUAL_ROOM_EVENT_TYPE, {
|
||||||
|
native_room: roomId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return virtualRoomId;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getOrCreateVirtualRoomForRoom(roomId: string):Promise<string> {
|
public nativeRoomForVirtualRoom(roomId: string):string {
|
||||||
const user = DMRoomMap.shared().getUserIdForRoomId(roomId);
|
const virtualRoom = MatrixClientPeg.get().getRoom(roomId);
|
||||||
if (!user) return null;
|
if (!virtualRoom) return null;
|
||||||
return getOrCreateVirtualRoomForUser(user);
|
const virtualRoomEvent = virtualRoom.getAccountData(VIRTUAL_ROOM_EVENT_TYPE);
|
||||||
|
if (!virtualRoomEvent || !virtualRoomEvent.getContent()) return null;
|
||||||
|
return virtualRoomEvent.getContent()['native_room'] || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function roomForVirtualRoom(roomId: string):string {
|
public isVirtualRoom(room: Room):boolean {
|
||||||
const virtualUser = DMRoomMap.shared().getUserIdForRoomId(roomId);
|
if (this.nativeRoomForVirtualRoom(room.roomId)) return true;
|
||||||
if (!virtualUser) return null;
|
|
||||||
const realUser = virtualUserToUser(virtualUser);
|
if (this.virtualRoomIdCache.has(room.roomId)) return true;
|
||||||
const room = findDMForUser(MatrixClientPeg.get(), realUser);
|
|
||||||
if (room) {
|
// also look in the create event for the claimed native room ID, which is the only
|
||||||
return room.roomId;
|
// way we can recognise a virtual room we've created when it first arrives down
|
||||||
} else {
|
// our stream. We don't trust this in general though, as it could be faked by an
|
||||||
return null;
|
// inviter: our main source of truth is the DM state.
|
||||||
}
|
const roomCreateEvent = room.currentState.getStateEvents("m.room.create", "");
|
||||||
|
if (!roomCreateEvent || !roomCreateEvent.getContent()) return false;
|
||||||
|
// we only look at this for rooms we created (so inviters can't just cause rooms
|
||||||
|
// to be invisible)
|
||||||
|
if (roomCreateEvent.getSender() !== MatrixClientPeg.get().getUserId()) return false;
|
||||||
|
const claimedNativeRoomId = roomCreateEvent.getContent()[VIRTUAL_ROOM_EVENT_TYPE];
|
||||||
|
return Boolean(claimedNativeRoomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isVirtualRoom(roomId: string):boolean {
|
public async onNewInvitedRoom(invitedRoom: Room) {
|
||||||
const virtualUser = DMRoomMap.shared().getUserIdForRoomId(roomId);
|
const inviterId = invitedRoom.getDMInviter();
|
||||||
if (!virtualUser) return null;
|
console.log(`Checking virtual-ness of room ID ${invitedRoom.roomId}, invited by ${inviterId}`);
|
||||||
const realUser = virtualUserToUser(virtualUser);
|
const result = await CallHandler.sharedInstance().sipNativeLookup(inviterId);
|
||||||
return Boolean(realUser);
|
if (result.length === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result[0].fields.is_virtual) {
|
||||||
|
const nativeUser = result[0].userid;
|
||||||
|
const nativeRoom = findDMForUser(MatrixClientPeg.get(), nativeUser);
|
||||||
|
if (nativeRoom) {
|
||||||
|
// It's a virtual room with a matching native room, so set the room account data. This
|
||||||
|
// will make sure we know where how to map calls and also allow us know not to display
|
||||||
|
// it in the future.
|
||||||
|
MatrixClientPeg.get().setRoomAccountData(invitedRoom.roomId, VIRTUAL_ROOM_EVENT_TYPE, {
|
||||||
|
native_room: nativeRoom.roomId,
|
||||||
|
});
|
||||||
|
// also auto-join the virtual room if we have a matching native room
|
||||||
|
// (possibly we should only join if we've also joined the native room, then we'd also have
|
||||||
|
// to make sure we joined virtual rooms on joining a native one)
|
||||||
|
MatrixClientPeg.get().joinRoom(invitedRoom.roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// also put this room in the virtual room ID cache so isVirtualRoom return the right answer
|
||||||
|
// in however long it takes for the echo of setAccountData to come down the sync
|
||||||
|
this.virtualRoomIdCache.add(invitedRoom.roomId);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import DialPad from './DialPad';
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
import ErrorDialog from "../../views/dialogs/ErrorDialog";
|
import ErrorDialog from "../../views/dialogs/ErrorDialog";
|
||||||
|
import CallHandler from "../../../CallHandler";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
onFinished: (boolean) => void;
|
onFinished: (boolean) => void;
|
||||||
|
@ -64,9 +65,7 @@ export default class DialpadModal extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
onDialPress = async () => {
|
onDialPress = async () => {
|
||||||
const results = await MatrixClientPeg.get().getThirdpartyUser('im.vector.protocol.pstn', {
|
const results = await CallHandler.sharedInstance().pstnLookup(this.state.value);
|
||||||
'm.id.phone': this.state.value,
|
|
||||||
});
|
|
||||||
if (!results || results.length === 0 || !results[0].userid) {
|
if (!results || results.length === 0 || !results[0].userid) {
|
||||||
Modal.createTrackedDialog('', '', ErrorDialog, {
|
Modal.createTrackedDialog('', '', ErrorDialog, {
|
||||||
title: _t("Unable to look up phone number"),
|
title: _t("Unable to look up phone number"),
|
||||||
|
|
|
@ -30,6 +30,7 @@ import { getE2EEWellKnown } from "./utils/WellKnownUtils";
|
||||||
import GroupStore from "./stores/GroupStore";
|
import GroupStore from "./stores/GroupStore";
|
||||||
import CountlyAnalytics from "./CountlyAnalytics";
|
import CountlyAnalytics from "./CountlyAnalytics";
|
||||||
import { isJoinedOrNearlyJoined } from "./utils/membership";
|
import { isJoinedOrNearlyJoined } from "./utils/membership";
|
||||||
|
import { VIRTUAL_ROOM_EVENT_TYPE } from "./CallHandler";
|
||||||
|
|
||||||
// we define a number of interfaces which take their names from the js-sdk
|
// we define a number of interfaces which take their names from the js-sdk
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
|
@ -300,6 +301,34 @@ export async function canEncryptToAllUsers(client: MatrixClient, userIds: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Similar to ensureDMExists but also adds creation content
|
||||||
|
// without polluting ensureDMExists with unrelated stuff (also
|
||||||
|
// they're never encrypted).
|
||||||
|
export async function ensureVirtualRoomExists(
|
||||||
|
client: MatrixClient, userId: string, nativeRoomId: string,
|
||||||
|
): Promise<string> {
|
||||||
|
const existingDMRoom = findDMForUser(client, userId);
|
||||||
|
let roomId;
|
||||||
|
if (existingDMRoom) {
|
||||||
|
roomId = existingDMRoom.roomId;
|
||||||
|
} else {
|
||||||
|
roomId = await createRoom({
|
||||||
|
dmUserId: userId,
|
||||||
|
spinner: false,
|
||||||
|
andView: false,
|
||||||
|
createOpts: {
|
||||||
|
creation_content: {
|
||||||
|
// This allows us to recognise that the room is a virtual room
|
||||||
|
// when it comes down our sync stream (we also put the ID of the
|
||||||
|
// respective native room in there because why not?)
|
||||||
|
[VIRTUAL_ROOM_EVENT_TYPE]: nativeRoomId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return roomId;
|
||||||
|
}
|
||||||
|
|
||||||
export async function ensureDMExists(client: MatrixClient, userId: string): Promise<string> {
|
export async function ensureDMExists(client: MatrixClient, userId: string): Promise<string> {
|
||||||
const existingDMRoom = findDMForUser(client, userId);
|
const existingDMRoom = findDMForUser(client, userId);
|
||||||
let roomId;
|
let roomId;
|
||||||
|
@ -310,6 +339,7 @@ export async function ensureDMExists(client: MatrixClient, userId: string): Prom
|
||||||
if (privateShouldBeEncrypted()) {
|
if (privateShouldBeEncrypted()) {
|
||||||
encryption = await canEncryptToAllUsers(client, [userId]);
|
encryption = await canEncryptToAllUsers(client, [userId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
roomId = await createRoom({encryption, dmUserId: userId, spinner: false, andView: false});
|
roomId = await createRoom({encryption, dmUserId: userId, spinner: false, andView: false});
|
||||||
await _waitForMember(client, roomId, userId);
|
await _waitForMember(client, roomId, userId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,4 +106,11 @@ export enum Action {
|
||||||
* XXX: Is an action the right thing for this?
|
* XXX: Is an action the right thing for this?
|
||||||
*/
|
*/
|
||||||
PstnSupportUpdated = "pstn_support_updated",
|
PstnSupportUpdated = "pstn_support_updated",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Similar to PstnSupportUpdated, fired when CallHandler has checked for virtual room support
|
||||||
|
* payload: none
|
||||||
|
* XXX: Ditto
|
||||||
|
*/
|
||||||
|
VirtualRoomSupportUpdated = "virtual_room_support_updated",
|
||||||
}
|
}
|
||||||
|
|
|
@ -398,6 +398,15 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<any> {
|
private async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<any> {
|
||||||
|
if (cause === RoomUpdateCause.NewRoom) {
|
||||||
|
// Let the visibility provider know that there is a new invited room. It would be nice
|
||||||
|
// if this could just be an event that things listen for but the point of this is that
|
||||||
|
// we delay doing anything about this room until the VoipUserMapper had had a chance
|
||||||
|
// to do the things it needs to do to decide if we should show this room or not, so
|
||||||
|
// an even wouldn't et us do that.
|
||||||
|
await VisibilityProvider.instance.onNewInvitedRoom(room);
|
||||||
|
}
|
||||||
|
|
||||||
if (!VisibilityProvider.instance.isRoomVisible(room)) {
|
if (!VisibilityProvider.instance.isRoomVisible(room)) {
|
||||||
return; // don't do anything on rooms that aren't visible
|
return; // don't do anything on rooms that aren't visible
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Room} from "matrix-js-sdk/src/models/room";
|
import {Room} from "matrix-js-sdk/src/models/room";
|
||||||
|
import CallHandler from "../../../CallHandler";
|
||||||
import { RoomListCustomisations } from "../../../customisations/RoomList";
|
import { RoomListCustomisations } from "../../../customisations/RoomList";
|
||||||
import { isVirtualRoom, voipUserMapperEnabled } from "../../../VoipUserMapper";
|
import VoipUserMapper from "../../../VoipUserMapper";
|
||||||
|
|
||||||
export class VisibilityProvider {
|
export class VisibilityProvider {
|
||||||
private static internalInstance: VisibilityProvider;
|
private static internalInstance: VisibilityProvider;
|
||||||
|
@ -31,11 +32,18 @@ export class VisibilityProvider {
|
||||||
return VisibilityProvider.internalInstance;
|
return VisibilityProvider.internalInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async onNewInvitedRoom(room: Room) {
|
||||||
|
await VoipUserMapper.sharedInstance().onNewInvitedRoom(room);
|
||||||
|
}
|
||||||
|
|
||||||
public isRoomVisible(room: Room): boolean {
|
public isRoomVisible(room: Room): boolean {
|
||||||
let isVisible = true; // Returned at the end of this function
|
let isVisible = true; // Returned at the end of this function
|
||||||
let forced = false; // When true, this function won't bother calling the customisation points
|
let forced = false; // When true, this function won't bother calling the customisation points
|
||||||
|
|
||||||
if (voipUserMapperEnabled() && isVirtualRoom(room.roomId)) {
|
if (
|
||||||
|
CallHandler.sharedInstance().getSupportsVirtualRooms() &&
|
||||||
|
VoipUserMapper.sharedInstance().isVirtualRoom(room)
|
||||||
|
) {
|
||||||
isVisible = false;
|
isVisible = false;
|
||||||
forced = true;
|
forced = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
/*
|
|
||||||
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 { userToVirtualUser, virtualUserToUser } from '../src/VoipUserMapper';
|
|
||||||
|
|
||||||
const templateString = '@_greatappservice_${mxid}:frooble.example';
|
|
||||||
const realUser = '@alice:boop.example';
|
|
||||||
const virtualUser = "@_greatappservice_=40alice=3aboop.example:frooble.example";
|
|
||||||
|
|
||||||
describe('VoipUserMapper', function() {
|
|
||||||
it('translates users to virtual users', function() {
|
|
||||||
expect(userToVirtualUser(realUser, templateString)).toEqual(virtualUser);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('translates users to virtual users', function() {
|
|
||||||
expect(virtualUserToUser(virtualUser, templateString)).toEqual(realUser);
|
|
||||||
});
|
|
||||||
});
|
|
Loading…
Reference in a new issue