1
0
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:
Šimon Brandner
2021-11-30 19:09:13 +01:00
committed by GitHub
parent e3187ed15c
commit cbb34d8ac7
23 changed files with 300 additions and 511 deletions

View File

@@ -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