You've already forked matrix-react-sdk
mirror of
https://github.com/matrix-org/matrix-react-sdk.git
synced 2025-11-10 09:22:25 +03:00
Make CallHandler more EventEmittery (#6704)
* sharedInstance() -> instance Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Use CallState event instead of dispatching Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Simplifie some code Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Use a method to start a call instead of the dispatcher Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Use a method instead of place_conference_call Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Make terminateCallApp() and hangupCallApp() public Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Use hangupAllCalls() instead of the dispatcher Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Make dialNumber(), startTransferToMatrixID() and startTransferToPhoneNumber() public instead of using the dispatcher Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Use answerCall() instead of using the dispatcher Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Use hangupOrReject() instead of the dispatcher Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Update docs Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Improve TS Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Dispatch call_state, see https://github.com/vector-im/element-web/pull/18823#issuecomment-917377277 Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Add missing import Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
@@ -17,43 +17,6 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Manages a list of all the currently active calls.
|
||||
*
|
||||
* This handler dispatches when voip calls are added/updated/removed from this list:
|
||||
* {
|
||||
* action: 'call_state'
|
||||
* room_id: <room ID of the call>
|
||||
* }
|
||||
*
|
||||
* To know the state of the call, this handler exposes a getter to
|
||||
* obtain the call for a room:
|
||||
* var call = CallHandler.getCall(roomId)
|
||||
* var state = call.call_state; // ringing|ringback|connected|ended|busy|stop_ringback|stop_ringing
|
||||
*
|
||||
* This handler listens for and handles the following actions:
|
||||
* {
|
||||
* action: 'place_call',
|
||||
* type: 'voice|video',
|
||||
* room_id: <room that the place call button was pressed in>
|
||||
* }
|
||||
*
|
||||
* {
|
||||
* action: 'incoming_call'
|
||||
* call: MatrixCall
|
||||
* }
|
||||
*
|
||||
* {
|
||||
* action: 'hangup'
|
||||
* room_id: <room that the hangup button was pressed in>
|
||||
* }
|
||||
*
|
||||
* {
|
||||
* action: 'answer'
|
||||
* room_id: <room that the answer button was pressed in>
|
||||
* }
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { MatrixClientPeg } from './MatrixClientPeg';
|
||||
@@ -65,7 +28,6 @@ import SettingsStore from './settings/SettingsStore';
|
||||
import { Jitsi } from "./widgets/Jitsi";
|
||||
import { WidgetType } from "./widgets/WidgetType";
|
||||
import { SettingLevel } from "./settings/SettingLevel";
|
||||
import { ActionPayload } from "./dispatcher/payloads";
|
||||
import { base32 } from "rfc4648";
|
||||
|
||||
import QuestionDialog from "./components/views/dialogs/QuestionDialog";
|
||||
@@ -133,24 +95,24 @@ interface ThirdpartyLookupResponse {
|
||||
fields: ThirdpartyLookupResponseFields;
|
||||
}
|
||||
|
||||
export enum PlaceCallType {
|
||||
Voice = 'voice',
|
||||
Video = 'video',
|
||||
}
|
||||
|
||||
export enum CallHandlerEvent {
|
||||
CallsChanged = "calls_changed",
|
||||
CallChangeRoom = "call_change_room",
|
||||
SilencedCallsChanged = "silenced_calls_changed",
|
||||
CallState = "call_state",
|
||||
}
|
||||
|
||||
/**
|
||||
* CallHandler manages all currently active calls. It should be used for
|
||||
* placing, answering, rejecting and hanging up calls. It also handles ringing,
|
||||
* PSTN support and other things.
|
||||
*/
|
||||
export default class CallHandler extends EventEmitter {
|
||||
private calls = new Map<string, MatrixCall>(); // roomId -> call
|
||||
// Calls started as an attended transfer, ie. with the intention of transferring another
|
||||
// call with a different party to this one.
|
||||
private transferees = new Map<string, MatrixCall>(); // callId (target) -> call (transferee)
|
||||
private audioPromises = new Map<AudioID, Promise<void>>();
|
||||
private dispatcherRef: string = 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
|
||||
@@ -166,7 +128,7 @@ export default class CallHandler extends EventEmitter {
|
||||
|
||||
private silencedCalls = new Set<string>(); // callIds
|
||||
|
||||
static sharedInstance() {
|
||||
public static get instance() {
|
||||
if (!window.mxCallHandler) {
|
||||
window.mxCallHandler = new CallHandler();
|
||||
}
|
||||
@@ -194,8 +156,7 @@ export default class CallHandler extends EventEmitter {
|
||||
return VoipUserMapper.sharedInstance().nativeRoomForVirtualRoom(call.roomId) || call.roomId;
|
||||
}
|
||||
|
||||
start() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
public start(): void {
|
||||
// add empty handlers for media actions, otherwise the media keys
|
||||
// end up causing the audio elements with our ring/ringback etc
|
||||
// audio clips in to play.
|
||||
@@ -215,18 +176,14 @@ export default class CallHandler extends EventEmitter {
|
||||
this.checkProtocols(CHECK_PROTOCOLS_ATTEMPTS);
|
||||
}
|
||||
|
||||
stop() {
|
||||
public stop(): void {
|
||||
const cli = MatrixClientPeg.get();
|
||||
if (cli) {
|
||||
cli.removeListener('Call.incoming', this.onCallIncoming);
|
||||
}
|
||||
if (this.dispatcherRef !== null) {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
this.dispatcherRef = null;
|
||||
}
|
||||
}
|
||||
|
||||
public silenceCall(callId: string) {
|
||||
public silenceCall(callId: string): void {
|
||||
this.silencedCalls.add(callId);
|
||||
this.emit(CallHandlerEvent.SilencedCallsChanged, this.silencedCalls);
|
||||
|
||||
@@ -235,7 +192,7 @@ export default class CallHandler extends EventEmitter {
|
||||
this.pause(AudioID.Ring);
|
||||
}
|
||||
|
||||
public unSilenceCall(callId: string) {
|
||||
public unSilenceCall(callId: string): void {
|
||||
this.silencedCalls.delete(callId);
|
||||
this.emit(CallHandlerEvent.SilencedCallsChanged, this.silencedCalls);
|
||||
this.play(AudioID.Ring);
|
||||
@@ -261,7 +218,7 @@ export default class CallHandler extends EventEmitter {
|
||||
return false;
|
||||
}
|
||||
|
||||
private async checkProtocols(maxTries) {
|
||||
private async checkProtocols(maxTries: number): Promise<void> {
|
||||
try {
|
||||
const protocols = await MatrixClientPeg.get().getThirdpartyProtocols();
|
||||
|
||||
@@ -296,11 +253,11 @@ export default class CallHandler extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
public getSupportsPstnProtocol() {
|
||||
public getSupportsPstnProtocol(): boolean {
|
||||
return this.supportsPstnProtocol;
|
||||
}
|
||||
|
||||
public getSupportsVirtualRooms() {
|
||||
public getSupportsVirtualRooms(): boolean {
|
||||
return this.supportsSipNativeVirtual;
|
||||
}
|
||||
|
||||
@@ -328,14 +285,32 @@ export default class CallHandler extends EventEmitter {
|
||||
);
|
||||
}
|
||||
|
||||
private onCallIncoming = (call) => {
|
||||
// we dispatch this synchronously to make sure that the event
|
||||
// handlers on the call are set up immediately (so that if
|
||||
// we get an immediate hangup, we don't get a stuck call)
|
||||
dis.dispatch({
|
||||
action: 'incoming_call',
|
||||
call: call,
|
||||
}, true);
|
||||
private onCallIncoming = (call: MatrixCall): void => {
|
||||
// if the runtime env doesn't do VoIP, stop here.
|
||||
if (!MatrixClientPeg.get().supportsVoip()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mappedRoomId = CallHandler.instance.roomIdForCall(call);
|
||||
if (this.getCallForRoom(mappedRoomId)) {
|
||||
logger.log(
|
||||
"Got incoming call for room " + mappedRoomId +
|
||||
" but there's already a call for this room: ignoring",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
Analytics.trackEvent('voip', 'receiveCall', 'type', call.type);
|
||||
this.addCallForRoom(mappedRoomId, call);
|
||||
this.setCallListeners(call);
|
||||
// Explicitly handle first state change
|
||||
this.onCallStateChanged(call.state, null, call);
|
||||
|
||||
// get ready to send encrypted events in the room, so if the user does answer
|
||||
// the call, we'll be ready to send. NB. This is the protocol-level room ID not
|
||||
// the mapped one: that's where we'll send the events.
|
||||
const cli = MatrixClientPeg.get();
|
||||
cli.prepareToEncrypt(cli.getRoom(call.roomId));
|
||||
};
|
||||
|
||||
public getCallById(callId: string): MatrixCall {
|
||||
@@ -345,11 +320,11 @@ export default class CallHandler extends EventEmitter {
|
||||
return null;
|
||||
}
|
||||
|
||||
getCallForRoom(roomId: string): MatrixCall {
|
||||
public getCallForRoom(roomId: string): MatrixCall | null {
|
||||
return this.calls.get(roomId) || null;
|
||||
}
|
||||
|
||||
getAnyActiveCall() {
|
||||
public getAnyActiveCall(): MatrixCall | null {
|
||||
for (const call of this.calls.values()) {
|
||||
if (call.state !== CallState.Ended) {
|
||||
return call;
|
||||
@@ -358,7 +333,7 @@ export default class CallHandler extends EventEmitter {
|
||||
return null;
|
||||
}
|
||||
|
||||
getAllActiveCalls() {
|
||||
public getAllActiveCalls(): MatrixCall[] {
|
||||
const activeCalls = [];
|
||||
|
||||
for (const call of this.calls.values()) {
|
||||
@@ -369,7 +344,7 @@ export default class CallHandler extends EventEmitter {
|
||||
return activeCalls;
|
||||
}
|
||||
|
||||
getAllActiveCallsNotInRoom(notInThisRoomId) {
|
||||
public getAllActiveCallsNotInRoom(notInThisRoomId: string): MatrixCall[] {
|
||||
const callsNotInThatRoom = [];
|
||||
|
||||
for (const [roomId, call] of this.calls.entries()) {
|
||||
@@ -390,11 +365,11 @@ export default class CallHandler extends EventEmitter {
|
||||
return this.getAllActiveCallsNotInRoom(roomId);
|
||||
}
|
||||
|
||||
getTransfereeForCallId(callId: string): MatrixCall {
|
||||
public getTransfereeForCallId(callId: string): MatrixCall {
|
||||
return this.transferees[callId];
|
||||
}
|
||||
|
||||
play(audioId: AudioID) {
|
||||
public play(audioId: AudioID): void {
|
||||
// TODO: Attach an invisible element for this instead
|
||||
// which listens?
|
||||
const audio = document.getElementById(audioId) as HTMLMediaElement;
|
||||
@@ -423,7 +398,7 @@ export default class CallHandler extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
pause(audioId: AudioID) {
|
||||
public pause(audioId: AudioID): void {
|
||||
// TODO: Attach an invisible element for this instead
|
||||
// which listens?
|
||||
const audio = document.getElementById(audioId) as HTMLMediaElement;
|
||||
@@ -437,7 +412,7 @@ export default class CallHandler extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
private matchesCallForThisRoom(call: MatrixCall) {
|
||||
private matchesCallForThisRoom(call: MatrixCall): boolean {
|
||||
// We don't allow placing more than one call per room, but that doesn't mean there
|
||||
// can't be more than one, eg. in a glare situation. This checks that the given call
|
||||
// is the call we consider 'the' call for its room.
|
||||
@@ -447,7 +422,7 @@ export default class CallHandler extends EventEmitter {
|
||||
return callForThisRoom && call.callId === callForThisRoom.callId;
|
||||
}
|
||||
|
||||
private setCallListeners(call: MatrixCall) {
|
||||
private setCallListeners(call: MatrixCall): void {
|
||||
let mappedRoomId = this.roomIdForCall(call);
|
||||
|
||||
call.on(CallEvent.Error, (err: CallError) => {
|
||||
@@ -542,6 +517,11 @@ export default class CallHandler extends EventEmitter {
|
||||
|
||||
const mappedRoomId = this.roomIdForCall(call);
|
||||
this.setCallState(call, newState);
|
||||
dis.dispatch({
|
||||
action: 'call_state',
|
||||
room_id: mappedRoomId,
|
||||
state: newState,
|
||||
});
|
||||
|
||||
switch (oldState) {
|
||||
case CallState.Ringing:
|
||||
@@ -620,7 +600,7 @@ export default class CallHandler extends EventEmitter {
|
||||
}
|
||||
};
|
||||
|
||||
private async logCallStats(call: MatrixCall, mappedRoomId: string) {
|
||||
private async logCallStats(call: MatrixCall, mappedRoomId: string): Promise<void> {
|
||||
const stats = await call.getCurrentCallStats();
|
||||
logger.debug(
|
||||
`Call completed. Call ID: ${call.callId}, virtual room ID: ${call.roomId}, ` +
|
||||
@@ -663,8 +643,8 @@ export default class CallHandler extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
private setCallState(call: MatrixCall, status: CallState) {
|
||||
const mappedRoomId = CallHandler.sharedInstance().roomIdForCall(call);
|
||||
private setCallState(call: MatrixCall, status: CallState): void {
|
||||
const mappedRoomId = CallHandler.instance.roomIdForCall(call);
|
||||
|
||||
logger.log(
|
||||
`Call state in ${mappedRoomId} changed to ${status}`,
|
||||
@@ -683,20 +663,16 @@ export default class CallHandler extends EventEmitter {
|
||||
ToastStore.sharedInstance().dismissToast(toastKey);
|
||||
}
|
||||
|
||||
dis.dispatch({
|
||||
action: 'call_state',
|
||||
room_id: mappedRoomId,
|
||||
state: status,
|
||||
});
|
||||
this.emit(CallHandlerEvent.CallState, mappedRoomId, status);
|
||||
}
|
||||
|
||||
private removeCallForRoom(roomId: string) {
|
||||
private removeCallForRoom(roomId: string): void {
|
||||
logger.log("Removing call for room ", roomId);
|
||||
this.calls.delete(roomId);
|
||||
this.emit(CallHandlerEvent.CallsChanged, this.calls);
|
||||
}
|
||||
|
||||
private showICEFallbackPrompt() {
|
||||
private showICEFallbackPrompt(): void {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const code = sub => <code>{ sub }</code>;
|
||||
Modal.createTrackedDialog('No TURN servers', '', QuestionDialog, {
|
||||
@@ -725,7 +701,7 @@ export default class CallHandler extends EventEmitter {
|
||||
}, null, true);
|
||||
}
|
||||
|
||||
private showMediaCaptureError(call: MatrixCall) {
|
||||
private showMediaCaptureError(call: MatrixCall): void {
|
||||
let title;
|
||||
let description;
|
||||
|
||||
@@ -754,9 +730,9 @@ export default class CallHandler extends EventEmitter {
|
||||
}, null, true);
|
||||
}
|
||||
|
||||
private async placeCall(roomId: string, type: PlaceCallType, transferee: MatrixCall) {
|
||||
private async placeMatrixCall(roomId: string, type: CallType, transferee?: MatrixCall): Promise<void> {
|
||||
Analytics.trackEvent('voip', 'placeCall', 'type', type);
|
||||
CountlyAnalytics.instance.trackStartCall(roomId, type === PlaceCallType.Video, false);
|
||||
CountlyAnalytics.instance.trackStartCall(roomId, type === CallType.Video, false);
|
||||
|
||||
const mappedRoomId = (await VoipUserMapper.sharedInstance().getOrCreateVirtualRoomForRoom(roomId)) || roomId;
|
||||
logger.debug("Mapped real room " + roomId + " to room ID " + mappedRoomId);
|
||||
@@ -782,7 +758,7 @@ export default class CallHandler extends EventEmitter {
|
||||
|
||||
this.setActiveCallRoomId(roomId);
|
||||
|
||||
if (type === PlaceCallType.Voice) {
|
||||
if (type === CallType.Voice) {
|
||||
call.placeVoiceCall();
|
||||
} else if (type === 'video') {
|
||||
call.placeVideoCall();
|
||||
@@ -791,166 +767,102 @@ export default class CallHandler extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
private onAction = (payload: ActionPayload) => {
|
||||
switch (payload.action) {
|
||||
case 'place_call':
|
||||
{
|
||||
// We might be using managed hybrid widgets
|
||||
if (isManagedHybridWidgetEnabled()) {
|
||||
addManagedHybridWidget(payload.room_id);
|
||||
return;
|
||||
}
|
||||
|
||||
// if the runtime env doesn't do VoIP, whine.
|
||||
if (!MatrixClientPeg.get().supportsVoip()) {
|
||||
Modal.createTrackedDialog('Call Handler', 'VoIP is unsupported', ErrorDialog, {
|
||||
title: _t('VoIP is unsupported'),
|
||||
description: _t('You cannot place VoIP calls in this browser.'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// don't allow > 2 calls to be placed.
|
||||
if (this.getAllActiveCalls().length > 1) {
|
||||
Modal.createTrackedDialog('Call Handler', 'Existing Call', ErrorDialog, {
|
||||
title: _t('Too Many Calls'),
|
||||
description: _t("You've reached the maximum number of simultaneous calls."),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const room = MatrixClientPeg.get().getRoom(payload.room_id);
|
||||
if (!room) {
|
||||
logger.error(`Room ${payload.room_id} does not exist.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// We leave the check for whether there's already a call in this room until later,
|
||||
// otherwise it can race.
|
||||
|
||||
const members = room.getJoinedMembers();
|
||||
if (members.length <= 1) {
|
||||
Modal.createTrackedDialog('Call Handler', 'Cannot place call with self', ErrorDialog, {
|
||||
description: _t('You cannot place a call with yourself.'),
|
||||
});
|
||||
return;
|
||||
} else if (members.length === 2) {
|
||||
logger.info(`Place ${payload.type} call in ${payload.room_id}`);
|
||||
|
||||
this.placeCall(payload.room_id, payload.type, payload.transferee);
|
||||
} else { // > 2
|
||||
dis.dispatch({
|
||||
action: "place_conference_call",
|
||||
room_id: payload.room_id,
|
||||
type: payload.type,
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'place_conference_call':
|
||||
logger.info("Place conference call in " + payload.room_id);
|
||||
Analytics.trackEvent('voip', 'placeConferenceCall');
|
||||
CountlyAnalytics.instance.trackStartCall(payload.room_id, payload.type === PlaceCallType.Video, true);
|
||||
this.startCallApp(payload.room_id, payload.type);
|
||||
break;
|
||||
case 'end_conference':
|
||||
logger.info("Terminating conference call in " + payload.room_id);
|
||||
this.terminateCallApp(payload.room_id);
|
||||
break;
|
||||
case 'hangup_conference':
|
||||
logger.info("Leaving conference call in "+ payload.room_id);
|
||||
this.hangupCallApp(payload.room_id);
|
||||
break;
|
||||
case 'incoming_call':
|
||||
{
|
||||
// if the runtime env doesn't do VoIP, stop here.
|
||||
if (!MatrixClientPeg.get().supportsVoip()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const call = payload.call as MatrixCall;
|
||||
|
||||
const mappedRoomId = CallHandler.sharedInstance().roomIdForCall(call);
|
||||
if (this.getCallForRoom(mappedRoomId)) {
|
||||
logger.log(
|
||||
"Got incoming call for room " + mappedRoomId +
|
||||
" but there's already a call for this room: ignoring",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
Analytics.trackEvent('voip', 'receiveCall', 'type', call.type);
|
||||
|
||||
this.addCallForRoom(mappedRoomId, call);
|
||||
this.setCallListeners(call);
|
||||
// Explicitly handle first state change
|
||||
this.onCallStateChanged(call.state, null, call);
|
||||
|
||||
// get ready to send encrypted events in the room, so if the user does answer
|
||||
// the call, we'll be ready to send. NB. This is the protocol-level room ID not
|
||||
// the mapped one: that's where we'll send the events.
|
||||
const cli = MatrixClientPeg.get();
|
||||
cli.prepareToEncrypt(cli.getRoom(call.roomId));
|
||||
}
|
||||
break;
|
||||
case 'hangup':
|
||||
case 'reject':
|
||||
this.stopRingingIfPossible(this.calls.get(payload.room_id).callId);
|
||||
|
||||
if (!this.calls.get(payload.room_id)) {
|
||||
return; // no call to hangup
|
||||
}
|
||||
if (payload.action === 'reject') {
|
||||
this.calls.get(payload.room_id).reject();
|
||||
} else {
|
||||
this.calls.get(payload.room_id).hangup(CallErrorCode.UserHangup, false);
|
||||
}
|
||||
// don't remove the call yet: let the hangup event handler do it (otherwise it will throw
|
||||
// the hangup event away)
|
||||
break;
|
||||
case 'hangup_all':
|
||||
this.stopRingingIfPossible(this.calls.get(payload.room_id).callId);
|
||||
|
||||
for (const call of this.calls.values()) {
|
||||
call.hangup(CallErrorCode.UserHangup, false);
|
||||
}
|
||||
break;
|
||||
case 'answer': {
|
||||
this.stopRingingIfPossible(this.calls.get(payload.room_id).callId);
|
||||
|
||||
if (!this.calls.has(payload.room_id)) {
|
||||
return; // no call to answer
|
||||
}
|
||||
|
||||
if (this.getAllActiveCalls().length > 1) {
|
||||
Modal.createTrackedDialog('Call Handler', 'Existing Call', ErrorDialog, {
|
||||
title: _t('Too Many Calls'),
|
||||
description: _t("You've reached the maximum number of simultaneous calls."),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const call = this.calls.get(payload.room_id);
|
||||
call.answer();
|
||||
this.setActiveCallRoomId(payload.room_id);
|
||||
CountlyAnalytics.instance.trackJoinCall(payload.room_id, call.type === CallType.Video, false);
|
||||
dis.dispatch({
|
||||
action: Action.ViewRoom,
|
||||
room_id: payload.room_id,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case Action.DialNumber:
|
||||
this.dialNumber(payload.number);
|
||||
break;
|
||||
case Action.TransferCallToMatrixID:
|
||||
this.startTransferToMatrixID(payload.call, payload.destination, payload.consultFirst);
|
||||
break;
|
||||
case Action.TransferCallToPhoneNumber:
|
||||
this.startTransferToPhoneNumber(payload.call, payload.destination, payload.consultFirst);
|
||||
break;
|
||||
public placeCall(roomId: string, type?: CallType, transferee?: MatrixCall): void {
|
||||
// We might be using managed hybrid widgets
|
||||
if (isManagedHybridWidgetEnabled()) {
|
||||
addManagedHybridWidget(roomId);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// if the runtime env doesn't do VoIP, whine.
|
||||
if (!MatrixClientPeg.get().supportsVoip()) {
|
||||
Modal.createTrackedDialog('Call Handler', 'VoIP is unsupported', ErrorDialog, {
|
||||
title: _t('VoIP is unsupported'),
|
||||
description: _t('You cannot place VoIP calls in this browser.'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// don't allow > 2 calls to be placed.
|
||||
if (this.getAllActiveCalls().length > 1) {
|
||||
Modal.createTrackedDialog('Call Handler', 'Existing Call', ErrorDialog, {
|
||||
title: _t('Too Many Calls'),
|
||||
description: _t("You've reached the maximum number of simultaneous calls."),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||
if (!room) {
|
||||
logger.error(`Room ${roomId} does not exist.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// We leave the check for whether there's already a call in this room until later,
|
||||
// otherwise it can race.
|
||||
|
||||
const members = room.getJoinedMembers();
|
||||
if (members.length <= 1) {
|
||||
Modal.createTrackedDialog('Call Handler', 'Cannot place call with self', ErrorDialog, {
|
||||
description: _t('You cannot place a call with yourself.'),
|
||||
});
|
||||
} else if (members.length === 2) {
|
||||
logger.info(`Place ${type} call in ${roomId}`);
|
||||
|
||||
this.placeMatrixCall(roomId, type, transferee);
|
||||
} else { // > 2
|
||||
this.placeJitsiCall(roomId, type);
|
||||
}
|
||||
}
|
||||
|
||||
public hangupAllCalls(): void {
|
||||
for (const call of this.calls.values()) {
|
||||
this.stopRingingIfPossible(call.callId);
|
||||
call.hangup(CallErrorCode.UserHangup, false);
|
||||
}
|
||||
}
|
||||
|
||||
public hangupOrReject(roomId: string, reject?: boolean): void {
|
||||
const call = this.calls.get(roomId);
|
||||
|
||||
// no call to hangup
|
||||
if (!call) return;
|
||||
|
||||
this.stopRingingIfPossible(call.callId);
|
||||
|
||||
if (reject) {
|
||||
call.reject();
|
||||
} else {
|
||||
call.hangup(CallErrorCode.UserHangup, false);
|
||||
}
|
||||
// don't remove the call yet: let the hangup event handler do it (otherwise it will throw
|
||||
// the hangup event away)
|
||||
}
|
||||
|
||||
public answerCall(roomId: string): void {
|
||||
const call = this.calls.get(roomId);
|
||||
|
||||
this.stopRingingIfPossible(call.callId);
|
||||
|
||||
// no call to answer
|
||||
if (!this.calls.has(roomId)) return;
|
||||
|
||||
if (this.getAllActiveCalls().length > 1) {
|
||||
Modal.createTrackedDialog('Call Handler', 'Existing Call', ErrorDialog, {
|
||||
title: _t('Too Many Calls'),
|
||||
description: _t("You've reached the maximum number of simultaneous calls."),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
call.answer();
|
||||
this.setActiveCallRoomId(roomId);
|
||||
CountlyAnalytics.instance.trackJoinCall(roomId, call.type === CallType.Video, false);
|
||||
dis.dispatch({
|
||||
action: Action.ViewRoom,
|
||||
room_id: roomId,
|
||||
});
|
||||
}
|
||||
|
||||
private stopRingingIfPossible(callId: string): void {
|
||||
this.silencedCalls.delete(callId);
|
||||
@@ -958,7 +870,7 @@ export default class CallHandler extends EventEmitter {
|
||||
this.pause(AudioID.Ring);
|
||||
}
|
||||
|
||||
private async dialNumber(number: string) {
|
||||
public async dialNumber(number: string): Promise<void> {
|
||||
const results = await this.pstnLookup(number);
|
||||
if (!results || results.length === 0 || !results[0].userid) {
|
||||
Modal.createTrackedDialog('', '', ErrorDialog, {
|
||||
@@ -988,10 +900,12 @@ export default class CallHandler extends EventEmitter {
|
||||
room_id: roomId,
|
||||
});
|
||||
|
||||
await this.placeCall(roomId, PlaceCallType.Voice, null);
|
||||
await this.placeMatrixCall(roomId, CallType.Voice, null);
|
||||
}
|
||||
|
||||
private async startTransferToPhoneNumber(call: MatrixCall, destination: string, consultFirst: boolean) {
|
||||
public async startTransferToPhoneNumber(
|
||||
call: MatrixCall, destination: string, consultFirst: boolean,
|
||||
): Promise<void> {
|
||||
const results = await this.pstnLookup(destination);
|
||||
if (!results || results.length === 0 || !results[0].userid) {
|
||||
Modal.createTrackedDialog('', '', ErrorDialog, {
|
||||
@@ -1004,16 +918,13 @@ export default class CallHandler extends EventEmitter {
|
||||
await this.startTransferToMatrixID(call, results[0].userid, consultFirst);
|
||||
}
|
||||
|
||||
private async startTransferToMatrixID(call: MatrixCall, destination: string, consultFirst: boolean) {
|
||||
public async startTransferToMatrixID(
|
||||
call: MatrixCall, destination: string, consultFirst: boolean,
|
||||
): Promise<void> {
|
||||
if (consultFirst) {
|
||||
const dmRoomId = await ensureDMExists(MatrixClientPeg.get(), destination);
|
||||
|
||||
dis.dispatch({
|
||||
action: 'place_call',
|
||||
type: call.type,
|
||||
room_id: dmRoomId,
|
||||
transferee: call,
|
||||
});
|
||||
this.placeCall(dmRoomId, call.type, call);
|
||||
dis.dispatch({
|
||||
action: Action.ViewRoom,
|
||||
room_id: dmRoomId,
|
||||
@@ -1033,7 +944,7 @@ export default class CallHandler extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
setActiveCallRoomId(activeCallRoomId: string) {
|
||||
public setActiveCallRoomId(activeCallRoomId: string): void {
|
||||
logger.info("Setting call in room " + activeCallRoomId + " active");
|
||||
|
||||
for (const [roomId, call] of this.calls.entries()) {
|
||||
@@ -1051,7 +962,7 @@ export default class CallHandler extends EventEmitter {
|
||||
/**
|
||||
* @returns true if we are currently in any call where we haven't put the remote party on hold
|
||||
*/
|
||||
hasAnyUnheldCall() {
|
||||
public hasAnyUnheldCall(): boolean {
|
||||
for (const call of this.calls.values()) {
|
||||
if (call.state === CallState.Ended) continue;
|
||||
if (!call.isRemoteOnHold()) return true;
|
||||
@@ -1060,7 +971,11 @@ export default class CallHandler extends EventEmitter {
|
||||
return false;
|
||||
}
|
||||
|
||||
private async startCallApp(roomId: string, type: string) {
|
||||
private async placeJitsiCall(roomId: string, type: string): Promise<void> {
|
||||
logger.info("Place conference call in " + roomId);
|
||||
Analytics.trackEvent('voip', 'placeConferenceCall');
|
||||
CountlyAnalytics.instance.trackStartCall(roomId, type === CallType.Video, true);
|
||||
|
||||
dis.dispatch({
|
||||
action: 'appsDrawer',
|
||||
show: true,
|
||||
@@ -1126,7 +1041,9 @@ export default class CallHandler extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
private terminateCallApp(roomId: string) {
|
||||
public terminateCallApp(roomId: string): void {
|
||||
logger.info("Terminating conference call in " + roomId);
|
||||
|
||||
Modal.createTrackedDialog('Confirm Jitsi Terminate', '', QuestionDialog, {
|
||||
hasCancelButton: true,
|
||||
title: _t("End conference"),
|
||||
@@ -1147,7 +1064,9 @@ export default class CallHandler extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
private hangupCallApp(roomId: string) {
|
||||
public hangupCallApp(roomId: string): void {
|
||||
logger.info("Leaving conference call in " + roomId);
|
||||
|
||||
const roomInfo = WidgetStore.instance.getRoom(roomId);
|
||||
if (!roomInfo) return; // "should never happen" clauses go here
|
||||
|
||||
|
||||
Reference in New Issue
Block a user