VoIP virtual rooms, mk II
Does a thirdparty protocol lookup to the homeserver to get the corresponding native/virtual user for a matrix ID. Stores the mappings in room account data. Involves some slightly nasty workarounds for that fact that room account data has no local echo.
This commit is contained in:
parent
59069f95e9
commit
196507a730
9 changed files with 208 additions and 101 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 {
|
||||||
|
|
|
@ -84,10 +84,17 @@ 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';
|
||||||
|
|
||||||
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 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 +103,12 @@ enum AudioID {
|
||||||
Busy = 'busyAudio',
|
Busy = 'busyAudio',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ThirpartyLookupResponse {
|
||||||
|
userid: string,
|
||||||
|
protocol: string,
|
||||||
|
fields: {[key: string]: any},
|
||||||
|
}
|
||||||
|
|
||||||
// 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 +139,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 +158,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 +181,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 +195,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<ThirpartyLookupResponse[]> {
|
||||||
|
return MatrixClientPeg.get().getThirdpartyUser(
|
||||||
|
this.pstnSupportPrefixed ? PROTOCOL_PSTN_PREFIXED : PROTOCOL_PSTN, {
|
||||||
|
'm.id.phone': phoneNumber,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sipVirtualLookup(nativeMxid: string): Promise<ThirpartyLookupResponse[]> {
|
||||||
|
return MatrixClientPeg.get().getThirdpartyUser(
|
||||||
|
PROTOCOL_SIP_VIRTUAL, {
|
||||||
|
'native_mxid': nativeMxid,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sipNativeLookup(virtualMxid: string): Promise<ThirpartyLookupResponse[]> {
|
||||||
|
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
|
||||||
|
@ -543,7 +601,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");
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,63 +17,96 @@ limitations under the License.
|
||||||
import { ensureDMExists, findDMForUser } from './createRoom';
|
import { ensureDMExists, 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 RoomListStore from './stores/room-list/RoomListStore';
|
||||||
|
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>();
|
||||||
}
|
|
||||||
|
|
||||||
// only exported for tests
|
public static sharedInstance(): VoipUserMapper {
|
||||||
export function userToVirtualUser(userId: string, templateString?: string): string {
|
if (window.mxVoipUserMapper === undefined) window.mxVoipUserMapper = new VoipUserMapper();
|
||||||
if (templateString === undefined) templateString = SdkConfig.get()['voip_mxid_translate_pattern'];
|
return window.mxVoipUserMapper;
|
||||||
if (!templateString) return null;
|
}
|
||||||
return templateString.replace('${mxid}', encodeURIComponent(userId).replace(/%/g, '=').toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
// only exported for tests
|
private async userToVirtualUser(userId: string): Promise<string> {
|
||||||
export function virtualUserToUser(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;
|
||||||
|
}
|
||||||
|
|
||||||
const regexString = templateString.replace('${mxid}', '(.+)');
|
public async getOrCreateVirtualRoomForRoom(roomId: string):Promise<string> {
|
||||||
|
const userId = DMRoomMap.shared().getUserIdForRoomId(roomId);
|
||||||
|
if (!userId) return null;
|
||||||
|
|
||||||
const match = userId.match('^' + regexString + '$');
|
const virtualUser = await this.userToVirtualUser(userId);
|
||||||
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);
|
// There's quite a bit of acromatics here to prevent the virtual room being shown
|
||||||
}
|
// while it's being created: forstly, we have to stop the RoomListStore from showing
|
||||||
|
// new rooms for a bit, because we can't set the room account data to say it's a virtual
|
||||||
|
// room until we have the room ID. Secondly, once we have the new room ID, we have to
|
||||||
|
// temporarily cache the fact it's a virtual room because there's no local echo on
|
||||||
|
// room account data so it won't show up in the room model until it comes down the
|
||||||
|
// sync stream again. Ick.
|
||||||
|
RoomListStore.instance.startHoldingNewRooms();
|
||||||
|
try {
|
||||||
|
const virtualRoomId = await ensureDMExists(MatrixClientPeg.get(), virtualUser);
|
||||||
|
MatrixClientPeg.get().setRoomAccountData(virtualRoomId, VIRTUAL_ROOM_EVENT_TYPE, {
|
||||||
|
native_room: roomId,
|
||||||
|
});
|
||||||
|
this.virtualRoomIdCache.add(virtualRoomId);
|
||||||
|
|
||||||
export async function getOrCreateVirtualRoomForRoom(roomId: string):Promise<string> {
|
return virtualRoomId;
|
||||||
const user = DMRoomMap.shared().getUserIdForRoomId(roomId);
|
} finally {
|
||||||
if (!user) return null;
|
RoomListStore.instance.stopHoldingNewRooms();
|
||||||
return getOrCreateVirtualRoomForUser(user);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function roomForVirtualRoom(roomId: string):string {
|
public nativeRoomForVirtualRoom(roomId: string):string {
|
||||||
const virtualUser = DMRoomMap.shared().getUserIdForRoomId(roomId);
|
const virtualRoom = MatrixClientPeg.get().getRoom(roomId);
|
||||||
if (!virtualUser) return null;
|
if (!virtualRoom) return null;
|
||||||
const realUser = virtualUserToUser(virtualUser);
|
const virtualRoomEvent = virtualRoom.getAccountData(VIRTUAL_ROOM_EVENT_TYPE);
|
||||||
const room = findDMForUser(MatrixClientPeg.get(), realUser);
|
if (!virtualRoomEvent || !virtualRoomEvent.getContent()) return null;
|
||||||
if (room) {
|
return virtualRoomEvent.getContent()['native_room'] || null;
|
||||||
return room.roomId;
|
}
|
||||||
} else {
|
|
||||||
return null;
|
public isVirtualRoom(roomId: string):boolean {
|
||||||
|
if (this.nativeRoomForVirtualRoom(roomId)) return true;
|
||||||
|
|
||||||
|
return this.virtualRoomIdCache.has(roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async onNewInvitedRoom(invitedRoom: Room) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isVirtualRoom(roomId: string):boolean {
|
|
||||||
const virtualUser = DMRoomMap.shared().getUserIdForRoomId(roomId);
|
|
||||||
if (!virtualUser) return null;
|
|
||||||
const realUser = virtualUserToUser(virtualUser);
|
|
||||||
return Boolean(realUser);
|
|
||||||
}
|
|
||||||
|
|
|
@ -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"),
|
||||||
|
|
|
@ -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",
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
|
||||||
import { NameFilterCondition } from "./filters/NameFilterCondition";
|
import { NameFilterCondition } from "./filters/NameFilterCondition";
|
||||||
import { RoomNotificationStateStore } from "../notifications/RoomNotificationStateStore";
|
import { RoomNotificationStateStore } from "../notifications/RoomNotificationStateStore";
|
||||||
import { VisibilityProvider } from "./filters/VisibilityProvider";
|
import { VisibilityProvider } from "./filters/VisibilityProvider";
|
||||||
|
import VoipUserMapper from "../../VoipUserMapper";
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
tagsEnabled?: boolean;
|
tagsEnabled?: boolean;
|
||||||
|
@ -63,6 +64,9 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
}
|
}
|
||||||
this.emit(LISTS_UPDATE_EVENT);
|
this.emit(LISTS_UPDATE_EVENT);
|
||||||
});
|
});
|
||||||
|
// When new rooms arrive, we may hold them here until we have enough info to know whether we should before display them.
|
||||||
|
private roomHoldingPen: Room[] = [];
|
||||||
|
private holdNewRooms = false;
|
||||||
|
|
||||||
private readonly watchedSettings = [
|
private readonly watchedSettings = [
|
||||||
'feature_custom_tags',
|
'feature_custom_tags',
|
||||||
|
@ -126,6 +130,24 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
this.updateFn.trigger();
|
this.updateFn.trigger();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// After calling this, any new rooms that appear are not displayed until stopHoldingNewRooms()
|
||||||
|
// is called. Be sure to always call this in a try/finally block to ensure stopHoldingNewRooms
|
||||||
|
// is called afterwards.
|
||||||
|
public startHoldingNewRooms() {
|
||||||
|
console.log("hold-new-rooms mode enabled.");
|
||||||
|
this.holdNewRooms = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public stopHoldingNewRooms() {
|
||||||
|
console.log("hold-new-rooms mode disabled: processing " + this.roomHoldingPen.length + " held rooms");
|
||||||
|
this.holdNewRooms = false;
|
||||||
|
for (const heldRoom of this.roomHoldingPen) {
|
||||||
|
console.log("Processing held room: " + heldRoom.roomId);
|
||||||
|
this.handleRoomUpdate(heldRoom, RoomUpdateCause.NewRoom);
|
||||||
|
}
|
||||||
|
this.roomHoldingPen = [];
|
||||||
|
}
|
||||||
|
|
||||||
private checkLoggingEnabled() {
|
private checkLoggingEnabled() {
|
||||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||||
console.warn("Advanced room list logging is enabled");
|
console.warn("Advanced room list logging is enabled");
|
||||||
|
@ -398,6 +420,21 @@ 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) {
|
||||||
|
if (this.holdNewRooms) {
|
||||||
|
console.log("Room updates are held: putting room " + room.roomId + " into the holding pen");
|
||||||
|
this.roomHoldingPen.push(room);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// we call straight out to VoipUserMapper here which is a bit of a hack: probably this
|
||||||
|
// should be calling the visibility provider which in turn farms out to various visibility
|
||||||
|
// providers? Anyway, 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.
|
||||||
|
await VoipUserMapper.sharedInstance().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;
|
||||||
|
@ -35,7 +36,10 @@ export class VisibilityProvider {
|
||||||
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.roomId)
|
||||||
|
) {
|
||||||
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…
Add table
Add a link
Reference in a new issue