Make CallHandler
more EventEmitter
y (#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:
parent
e3187ed15c
commit
cbb34d8ac7
23 changed files with 300 additions and 511 deletions
|
@ -17,43 +17,6 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
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 React from 'react';
|
||||||
|
|
||||||
import { MatrixClientPeg } from './MatrixClientPeg';
|
import { MatrixClientPeg } from './MatrixClientPeg';
|
||||||
|
@ -65,7 +28,6 @@ import SettingsStore from './settings/SettingsStore';
|
||||||
import { Jitsi } from "./widgets/Jitsi";
|
import { Jitsi } from "./widgets/Jitsi";
|
||||||
import { WidgetType } from "./widgets/WidgetType";
|
import { WidgetType } from "./widgets/WidgetType";
|
||||||
import { SettingLevel } from "./settings/SettingLevel";
|
import { SettingLevel } from "./settings/SettingLevel";
|
||||||
import { ActionPayload } from "./dispatcher/payloads";
|
|
||||||
import { base32 } from "rfc4648";
|
import { base32 } from "rfc4648";
|
||||||
|
|
||||||
import QuestionDialog from "./components/views/dialogs/QuestionDialog";
|
import QuestionDialog from "./components/views/dialogs/QuestionDialog";
|
||||||
|
@ -133,24 +95,24 @@ interface ThirdpartyLookupResponse {
|
||||||
fields: ThirdpartyLookupResponseFields;
|
fields: ThirdpartyLookupResponseFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PlaceCallType {
|
|
||||||
Voice = 'voice',
|
|
||||||
Video = 'video',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum CallHandlerEvent {
|
export enum CallHandlerEvent {
|
||||||
CallsChanged = "calls_changed",
|
CallsChanged = "calls_changed",
|
||||||
CallChangeRoom = "call_change_room",
|
CallChangeRoom = "call_change_room",
|
||||||
SilencedCallsChanged = "silenced_calls_changed",
|
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 {
|
export default class CallHandler extends EventEmitter {
|
||||||
private calls = new Map<string, MatrixCall>(); // roomId -> call
|
private calls = new Map<string, MatrixCall>(); // roomId -> call
|
||||||
// Calls started as an attended transfer, ie. with the intention of transferring another
|
// Calls started as an attended transfer, ie. with the intention of transferring another
|
||||||
// call with a different party to this one.
|
// call with a different party to this one.
|
||||||
private transferees = new Map<string, MatrixCall>(); // callId (target) -> call (transferee)
|
private transferees = new Map<string, MatrixCall>(); // callId (target) -> call (transferee)
|
||||||
private audioPromises = new Map<AudioID, Promise<void>>();
|
private audioPromises = new Map<AudioID, Promise<void>>();
|
||||||
private dispatcherRef: string = null;
|
|
||||||
private supportsPstnProtocol = null;
|
private supportsPstnProtocol = null;
|
||||||
private pstnSupportPrefixed = null; // True if the server only support the prefixed pstn protocol
|
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 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
|
private silencedCalls = new Set<string>(); // callIds
|
||||||
|
|
||||||
static sharedInstance() {
|
public static get instance() {
|
||||||
if (!window.mxCallHandler) {
|
if (!window.mxCallHandler) {
|
||||||
window.mxCallHandler = new CallHandler();
|
window.mxCallHandler = new CallHandler();
|
||||||
}
|
}
|
||||||
|
@ -194,8 +156,7 @@ export default class CallHandler extends EventEmitter {
|
||||||
return VoipUserMapper.sharedInstance().nativeRoomForVirtualRoom(call.roomId) || call.roomId;
|
return VoipUserMapper.sharedInstance().nativeRoomForVirtualRoom(call.roomId) || call.roomId;
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
public start(): void {
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
|
||||||
// add empty handlers for media actions, otherwise the media keys
|
// add empty handlers for media actions, otherwise the media keys
|
||||||
// end up causing the audio elements with our ring/ringback etc
|
// end up causing the audio elements with our ring/ringback etc
|
||||||
// audio clips in to play.
|
// audio clips in to play.
|
||||||
|
@ -215,18 +176,14 @@ export default class CallHandler extends EventEmitter {
|
||||||
this.checkProtocols(CHECK_PROTOCOLS_ATTEMPTS);
|
this.checkProtocols(CHECK_PROTOCOLS_ATTEMPTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
public stop(): void {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
if (cli) {
|
if (cli) {
|
||||||
cli.removeListener('Call.incoming', this.onCallIncoming);
|
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.silencedCalls.add(callId);
|
||||||
this.emit(CallHandlerEvent.SilencedCallsChanged, this.silencedCalls);
|
this.emit(CallHandlerEvent.SilencedCallsChanged, this.silencedCalls);
|
||||||
|
|
||||||
|
@ -235,7 +192,7 @@ export default class CallHandler extends EventEmitter {
|
||||||
this.pause(AudioID.Ring);
|
this.pause(AudioID.Ring);
|
||||||
}
|
}
|
||||||
|
|
||||||
public unSilenceCall(callId: string) {
|
public unSilenceCall(callId: string): void {
|
||||||
this.silencedCalls.delete(callId);
|
this.silencedCalls.delete(callId);
|
||||||
this.emit(CallHandlerEvent.SilencedCallsChanged, this.silencedCalls);
|
this.emit(CallHandlerEvent.SilencedCallsChanged, this.silencedCalls);
|
||||||
this.play(AudioID.Ring);
|
this.play(AudioID.Ring);
|
||||||
|
@ -261,7 +218,7 @@ export default class CallHandler extends EventEmitter {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async checkProtocols(maxTries) {
|
private async checkProtocols(maxTries: number): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const protocols = await MatrixClientPeg.get().getThirdpartyProtocols();
|
const protocols = await MatrixClientPeg.get().getThirdpartyProtocols();
|
||||||
|
|
||||||
|
@ -296,11 +253,11 @@ export default class CallHandler extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSupportsPstnProtocol() {
|
public getSupportsPstnProtocol(): boolean {
|
||||||
return this.supportsPstnProtocol;
|
return this.supportsPstnProtocol;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSupportsVirtualRooms() {
|
public getSupportsVirtualRooms(): boolean {
|
||||||
return this.supportsSipNativeVirtual;
|
return this.supportsSipNativeVirtual;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,14 +285,32 @@ export default class CallHandler extends EventEmitter {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onCallIncoming = (call) => {
|
private onCallIncoming = (call: MatrixCall): void => {
|
||||||
// we dispatch this synchronously to make sure that the event
|
// if the runtime env doesn't do VoIP, stop here.
|
||||||
// handlers on the call are set up immediately (so that if
|
if (!MatrixClientPeg.get().supportsVoip()) {
|
||||||
// we get an immediate hangup, we don't get a stuck call)
|
return;
|
||||||
dis.dispatch({
|
}
|
||||||
action: 'incoming_call',
|
|
||||||
call: call,
|
const mappedRoomId = CallHandler.instance.roomIdForCall(call);
|
||||||
}, true);
|
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 {
|
public getCallById(callId: string): MatrixCall {
|
||||||
|
@ -345,11 +320,11 @@ export default class CallHandler extends EventEmitter {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCallForRoom(roomId: string): MatrixCall {
|
public getCallForRoom(roomId: string): MatrixCall | null {
|
||||||
return this.calls.get(roomId) || null;
|
return this.calls.get(roomId) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAnyActiveCall() {
|
public getAnyActiveCall(): MatrixCall | null {
|
||||||
for (const call of this.calls.values()) {
|
for (const call of this.calls.values()) {
|
||||||
if (call.state !== CallState.Ended) {
|
if (call.state !== CallState.Ended) {
|
||||||
return call;
|
return call;
|
||||||
|
@ -358,7 +333,7 @@ export default class CallHandler extends EventEmitter {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllActiveCalls() {
|
public getAllActiveCalls(): MatrixCall[] {
|
||||||
const activeCalls = [];
|
const activeCalls = [];
|
||||||
|
|
||||||
for (const call of this.calls.values()) {
|
for (const call of this.calls.values()) {
|
||||||
|
@ -369,7 +344,7 @@ export default class CallHandler extends EventEmitter {
|
||||||
return activeCalls;
|
return activeCalls;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllActiveCallsNotInRoom(notInThisRoomId) {
|
public getAllActiveCallsNotInRoom(notInThisRoomId: string): MatrixCall[] {
|
||||||
const callsNotInThatRoom = [];
|
const callsNotInThatRoom = [];
|
||||||
|
|
||||||
for (const [roomId, call] of this.calls.entries()) {
|
for (const [roomId, call] of this.calls.entries()) {
|
||||||
|
@ -390,11 +365,11 @@ export default class CallHandler extends EventEmitter {
|
||||||
return this.getAllActiveCallsNotInRoom(roomId);
|
return this.getAllActiveCallsNotInRoom(roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTransfereeForCallId(callId: string): MatrixCall {
|
public getTransfereeForCallId(callId: string): MatrixCall {
|
||||||
return this.transferees[callId];
|
return this.transferees[callId];
|
||||||
}
|
}
|
||||||
|
|
||||||
play(audioId: AudioID) {
|
public play(audioId: AudioID): void {
|
||||||
// TODO: Attach an invisible element for this instead
|
// TODO: Attach an invisible element for this instead
|
||||||
// which listens?
|
// which listens?
|
||||||
const audio = document.getElementById(audioId) as HTMLMediaElement;
|
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
|
// TODO: Attach an invisible element for this instead
|
||||||
// which listens?
|
// which listens?
|
||||||
const audio = document.getElementById(audioId) as HTMLMediaElement;
|
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
|
// 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
|
// 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.
|
// 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;
|
return callForThisRoom && call.callId === callForThisRoom.callId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private setCallListeners(call: MatrixCall) {
|
private setCallListeners(call: MatrixCall): void {
|
||||||
let mappedRoomId = this.roomIdForCall(call);
|
let mappedRoomId = this.roomIdForCall(call);
|
||||||
|
|
||||||
call.on(CallEvent.Error, (err: CallError) => {
|
call.on(CallEvent.Error, (err: CallError) => {
|
||||||
|
@ -542,6 +517,11 @@ export default class CallHandler extends EventEmitter {
|
||||||
|
|
||||||
const mappedRoomId = this.roomIdForCall(call);
|
const mappedRoomId = this.roomIdForCall(call);
|
||||||
this.setCallState(call, newState);
|
this.setCallState(call, newState);
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'call_state',
|
||||||
|
room_id: mappedRoomId,
|
||||||
|
state: newState,
|
||||||
|
});
|
||||||
|
|
||||||
switch (oldState) {
|
switch (oldState) {
|
||||||
case CallState.Ringing:
|
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();
|
const stats = await call.getCurrentCallStats();
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`Call completed. Call ID: ${call.callId}, virtual room ID: ${call.roomId}, ` +
|
`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) {
|
private setCallState(call: MatrixCall, status: CallState): void {
|
||||||
const mappedRoomId = CallHandler.sharedInstance().roomIdForCall(call);
|
const mappedRoomId = CallHandler.instance.roomIdForCall(call);
|
||||||
|
|
||||||
logger.log(
|
logger.log(
|
||||||
`Call state in ${mappedRoomId} changed to ${status}`,
|
`Call state in ${mappedRoomId} changed to ${status}`,
|
||||||
|
@ -683,20 +663,16 @@ export default class CallHandler extends EventEmitter {
|
||||||
ToastStore.sharedInstance().dismissToast(toastKey);
|
ToastStore.sharedInstance().dismissToast(toastKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
dis.dispatch({
|
this.emit(CallHandlerEvent.CallState, mappedRoomId, status);
|
||||||
action: 'call_state',
|
|
||||||
room_id: mappedRoomId,
|
|
||||||
state: status,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private removeCallForRoom(roomId: string) {
|
private removeCallForRoom(roomId: string): void {
|
||||||
logger.log("Removing call for room ", roomId);
|
logger.log("Removing call for room ", roomId);
|
||||||
this.calls.delete(roomId);
|
this.calls.delete(roomId);
|
||||||
this.emit(CallHandlerEvent.CallsChanged, this.calls);
|
this.emit(CallHandlerEvent.CallsChanged, this.calls);
|
||||||
}
|
}
|
||||||
|
|
||||||
private showICEFallbackPrompt() {
|
private showICEFallbackPrompt(): void {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const code = sub => <code>{ sub }</code>;
|
const code = sub => <code>{ sub }</code>;
|
||||||
Modal.createTrackedDialog('No TURN servers', '', QuestionDialog, {
|
Modal.createTrackedDialog('No TURN servers', '', QuestionDialog, {
|
||||||
|
@ -725,7 +701,7 @@ export default class CallHandler extends EventEmitter {
|
||||||
}, null, true);
|
}, null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private showMediaCaptureError(call: MatrixCall) {
|
private showMediaCaptureError(call: MatrixCall): void {
|
||||||
let title;
|
let title;
|
||||||
let description;
|
let description;
|
||||||
|
|
||||||
|
@ -754,9 +730,9 @@ export default class CallHandler extends EventEmitter {
|
||||||
}, null, true);
|
}, 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);
|
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;
|
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);
|
||||||
|
@ -782,7 +758,7 @@ export default class CallHandler extends EventEmitter {
|
||||||
|
|
||||||
this.setActiveCallRoomId(roomId);
|
this.setActiveCallRoomId(roomId);
|
||||||
|
|
||||||
if (type === PlaceCallType.Voice) {
|
if (type === CallType.Voice) {
|
||||||
call.placeVoiceCall();
|
call.placeVoiceCall();
|
||||||
} else if (type === 'video') {
|
} else if (type === 'video') {
|
||||||
call.placeVideoCall();
|
call.placeVideoCall();
|
||||||
|
@ -791,166 +767,102 @@ export default class CallHandler extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private onAction = (payload: ActionPayload) => {
|
public placeCall(roomId: string, type?: CallType, transferee?: MatrixCall): void {
|
||||||
switch (payload.action) {
|
// We might be using managed hybrid widgets
|
||||||
case 'place_call':
|
if (isManagedHybridWidgetEnabled()) {
|
||||||
{
|
addManagedHybridWidget(roomId);
|
||||||
// We might be using managed hybrid widgets
|
return;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
// 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 {
|
private stopRingingIfPossible(callId: string): void {
|
||||||
this.silencedCalls.delete(callId);
|
this.silencedCalls.delete(callId);
|
||||||
|
@ -958,7 +870,7 @@ export default class CallHandler extends EventEmitter {
|
||||||
this.pause(AudioID.Ring);
|
this.pause(AudioID.Ring);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async dialNumber(number: string) {
|
public async dialNumber(number: string): Promise<void> {
|
||||||
const results = await this.pstnLookup(number);
|
const results = await this.pstnLookup(number);
|
||||||
if (!results || results.length === 0 || !results[0].userid) {
|
if (!results || results.length === 0 || !results[0].userid) {
|
||||||
Modal.createTrackedDialog('', '', ErrorDialog, {
|
Modal.createTrackedDialog('', '', ErrorDialog, {
|
||||||
|
@ -988,10 +900,12 @@ export default class CallHandler extends EventEmitter {
|
||||||
room_id: roomId,
|
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);
|
const results = await this.pstnLookup(destination);
|
||||||
if (!results || results.length === 0 || !results[0].userid) {
|
if (!results || results.length === 0 || !results[0].userid) {
|
||||||
Modal.createTrackedDialog('', '', ErrorDialog, {
|
Modal.createTrackedDialog('', '', ErrorDialog, {
|
||||||
|
@ -1004,16 +918,13 @@ export default class CallHandler extends EventEmitter {
|
||||||
await this.startTransferToMatrixID(call, results[0].userid, consultFirst);
|
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) {
|
if (consultFirst) {
|
||||||
const dmRoomId = await ensureDMExists(MatrixClientPeg.get(), destination);
|
const dmRoomId = await ensureDMExists(MatrixClientPeg.get(), destination);
|
||||||
|
|
||||||
dis.dispatch({
|
this.placeCall(dmRoomId, call.type, call);
|
||||||
action: 'place_call',
|
|
||||||
type: call.type,
|
|
||||||
room_id: dmRoomId,
|
|
||||||
transferee: call,
|
|
||||||
});
|
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: Action.ViewRoom,
|
action: Action.ViewRoom,
|
||||||
room_id: dmRoomId,
|
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");
|
logger.info("Setting call in room " + activeCallRoomId + " active");
|
||||||
|
|
||||||
for (const [roomId, call] of this.calls.entries()) {
|
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
|
* @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()) {
|
for (const call of this.calls.values()) {
|
||||||
if (call.state === CallState.Ended) continue;
|
if (call.state === CallState.Ended) continue;
|
||||||
if (!call.isRemoteOnHold()) return true;
|
if (!call.isRemoteOnHold()) return true;
|
||||||
|
@ -1060,7 +971,11 @@ export default class CallHandler extends EventEmitter {
|
||||||
return false;
|
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({
|
dis.dispatch({
|
||||||
action: 'appsDrawer',
|
action: 'appsDrawer',
|
||||||
show: true,
|
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, {
|
Modal.createTrackedDialog('Confirm Jitsi Terminate', '', QuestionDialog, {
|
||||||
hasCancelButton: true,
|
hasCancelButton: true,
|
||||||
title: _t("End conference"),
|
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);
|
const roomInfo = WidgetStore.instance.getRoom(roomId);
|
||||||
if (!roomInfo) return; // "should never happen" clauses go here
|
if (!roomInfo) return; // "should never happen" clauses go here
|
||||||
|
|
||||||
|
|
|
@ -794,7 +794,7 @@ async function startMatrixClient(startSyncing = true): Promise<void> {
|
||||||
DMRoomMap.makeShared().start();
|
DMRoomMap.makeShared().start();
|
||||||
IntegrationManagers.sharedInstance().startWatching();
|
IntegrationManagers.sharedInstance().startWatching();
|
||||||
ActiveWidgetStore.instance.start();
|
ActiveWidgetStore.instance.start();
|
||||||
CallHandler.sharedInstance().start();
|
CallHandler.instance.start();
|
||||||
|
|
||||||
// Start Mjolnir even though we haven't checked the feature flag yet. Starting
|
// Start Mjolnir even though we haven't checked the feature flag yet. Starting
|
||||||
// the thing just wastes CPU cycles, but should result in no actual functionality
|
// the thing just wastes CPU cycles, but should result in no actual functionality
|
||||||
|
@ -897,7 +897,7 @@ async function clearStorage(opts?: { deleteEverything?: boolean }): Promise<void
|
||||||
*/
|
*/
|
||||||
export function stopMatrixClient(unsetClient = true): void {
|
export function stopMatrixClient(unsetClient = true): void {
|
||||||
Notifier.stop();
|
Notifier.stop();
|
||||||
CallHandler.sharedInstance().stop();
|
CallHandler.instance.stop();
|
||||||
UserActivity.sharedInstance().stop();
|
UserActivity.sharedInstance().stop();
|
||||||
TypingStore.sharedInstance().reset();
|
TypingStore.sharedInstance().reset();
|
||||||
Presence.stop();
|
Presence.stop();
|
||||||
|
|
|
@ -1037,7 +1037,7 @@ export const Commands = [
|
||||||
|
|
||||||
return success((async () => {
|
return success((async () => {
|
||||||
if (isPhoneNumber) {
|
if (isPhoneNumber) {
|
||||||
const results = await CallHandler.sharedInstance().pstnLookup(this.state.value);
|
const results = await CallHandler.instance.pstnLookup(this.state.value);
|
||||||
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");
|
||||||
}
|
}
|
||||||
|
@ -1089,7 +1089,7 @@ export const Commands = [
|
||||||
description: _td("Places the call in the current room on hold"),
|
description: _td("Places the call in the current room on hold"),
|
||||||
category: CommandCategories.other,
|
category: CommandCategories.other,
|
||||||
runFn: function(roomId, args) {
|
runFn: function(roomId, args) {
|
||||||
const call = CallHandler.sharedInstance().getCallForRoom(roomId);
|
const call = CallHandler.instance.getCallForRoom(roomId);
|
||||||
if (!call) {
|
if (!call) {
|
||||||
return reject("No active call in this room");
|
return reject("No active call in this room");
|
||||||
}
|
}
|
||||||
|
@ -1103,7 +1103,7 @@ export const Commands = [
|
||||||
description: _td("Takes the call in the current room off hold"),
|
description: _td("Takes the call in the current room off hold"),
|
||||||
category: CommandCategories.other,
|
category: CommandCategories.other,
|
||||||
runFn: function(roomId, args) {
|
runFn: function(roomId, args) {
|
||||||
const call = CallHandler.sharedInstance().getCallForRoom(roomId);
|
const call = CallHandler.instance.getCallForRoom(roomId);
|
||||||
if (!call) {
|
if (!call) {
|
||||||
return reject("No active call in this room");
|
return reject("No active call in this room");
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ export default class VoipUserMapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async userToVirtualUser(userId: string): Promise<string> {
|
private async userToVirtualUser(userId: string): Promise<string> {
|
||||||
const results = await CallHandler.sharedInstance().sipVirtualLookup(userId);
|
const results = await CallHandler.instance.sipVirtualLookup(userId);
|
||||||
if (results.length === 0 || !results[0].fields.lookup_success) return null;
|
if (results.length === 0 || !results[0].fields.lookup_success) return null;
|
||||||
return results[0].userid;
|
return results[0].userid;
|
||||||
}
|
}
|
||||||
|
@ -97,11 +97,11 @@ export default class VoipUserMapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onNewInvitedRoom(invitedRoom: Room): Promise<void> {
|
public async onNewInvitedRoom(invitedRoom: Room): Promise<void> {
|
||||||
if (!CallHandler.sharedInstance().getSupportsVirtualRooms()) return;
|
if (!CallHandler.instance.getSupportsVirtualRooms()) return;
|
||||||
|
|
||||||
const inviterId = invitedRoom.getDMInviter();
|
const inviterId = invitedRoom.getDMInviter();
|
||||||
logger.log(`Checking virtual-ness of room ID ${invitedRoom.roomId}, invited by ${inviterId}`);
|
logger.log(`Checking virtual-ness of room ID ${invitedRoom.roomId}, invited by ${inviterId}`);
|
||||||
const result = await CallHandler.sharedInstance().sipNativeLookup(inviterId);
|
const result = await CallHandler.instance.sipNativeLookup(inviterId);
|
||||||
if (result.length === 0) {
|
if (result.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,6 @@ import { CallEvent, CallState, CallType, MatrixCall } from "matrix-js-sdk/src/we
|
||||||
import CallHandler, { CallHandlerEvent } from '../../CallHandler';
|
import CallHandler, { CallHandlerEvent } from '../../CallHandler';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||||
import defaultDispatcher from "../../dispatcher/dispatcher";
|
|
||||||
|
|
||||||
export enum CallEventGrouperEvent {
|
export enum CallEventGrouperEvent {
|
||||||
StateChanged = "state_changed",
|
StateChanged = "state_changed",
|
||||||
|
@ -52,8 +51,8 @@ export default class CallEventGrouper extends EventEmitter {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
CallHandler.sharedInstance().addListener(CallHandlerEvent.CallsChanged, this.setCall);
|
CallHandler.instance.addListener(CallHandlerEvent.CallsChanged, this.setCall);
|
||||||
CallHandler.sharedInstance().addListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged);
|
CallHandler.instance.addListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
private get invite(): MatrixEvent {
|
private get invite(): MatrixEvent {
|
||||||
|
@ -114,7 +113,7 @@ export default class CallEventGrouper extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private onSilencedCallsChanged = () => {
|
private onSilencedCallsChanged = () => {
|
||||||
const newState = CallHandler.sharedInstance().isCallSilenced(this.callId);
|
const newState = CallHandler.instance.isCallSilenced(this.callId);
|
||||||
this.emit(CallEventGrouperEvent.SilencedChanged, newState);
|
this.emit(CallEventGrouperEvent.SilencedChanged, newState);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -122,33 +121,23 @@ export default class CallEventGrouper extends EventEmitter {
|
||||||
this.emit(CallEventGrouperEvent.LengthChanged, length);
|
this.emit(CallEventGrouperEvent.LengthChanged, length);
|
||||||
};
|
};
|
||||||
|
|
||||||
public answerCall = () => {
|
public answerCall = (): void => {
|
||||||
defaultDispatcher.dispatch({
|
CallHandler.instance.answerCall(this.roomId);
|
||||||
action: 'answer',
|
|
||||||
room_id: this.roomId,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public rejectCall = () => {
|
public rejectCall = (): void => {
|
||||||
defaultDispatcher.dispatch({
|
CallHandler.instance.hangupOrReject(this.roomId, true);
|
||||||
action: 'reject',
|
|
||||||
room_id: this.roomId,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public callBack = () => {
|
public callBack = (): void => {
|
||||||
defaultDispatcher.dispatch({
|
CallHandler.instance.placeCall(this.roomId, this.isVoice ? CallType.Voice : CallType.Video);
|
||||||
action: 'place_call',
|
|
||||||
type: this.isVoice ? CallType.Voice : CallType.Video,
|
|
||||||
room_id: this.roomId,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public toggleSilenced = () => {
|
public toggleSilenced = () => {
|
||||||
const silenced = CallHandler.sharedInstance().isCallSilenced(this.callId);
|
const silenced = CallHandler.instance.isCallSilenced(this.callId);
|
||||||
silenced ?
|
silenced ?
|
||||||
CallHandler.sharedInstance().unSilenceCall(this.callId) :
|
CallHandler.instance.unSilenceCall(this.callId) :
|
||||||
CallHandler.sharedInstance().silenceCall(this.callId);
|
CallHandler.instance.silenceCall(this.callId);
|
||||||
};
|
};
|
||||||
|
|
||||||
private setCallListeners() {
|
private setCallListeners() {
|
||||||
|
@ -174,7 +163,7 @@ export default class CallEventGrouper extends EventEmitter {
|
||||||
private setCall = () => {
|
private setCall = () => {
|
||||||
if (this.call) return;
|
if (this.call) return;
|
||||||
|
|
||||||
this.call = CallHandler.sharedInstance().getCallById(this.callId);
|
this.call = CallHandler.instance.getCallById(this.callId);
|
||||||
this.setCallListeners();
|
this.setCallListeners();
|
||||||
this.setState();
|
this.setState();
|
||||||
};
|
};
|
||||||
|
|
|
@ -340,7 +340,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
// If we have dialer support, show a button to bring up the dial pad
|
// If we have dialer support, show a button to bring up the dial pad
|
||||||
// to start a new call
|
// to start a new call
|
||||||
if (CallHandler.sharedInstance().getSupportsPstnProtocol()) {
|
if (CallHandler.instance.getSupportsPstnProtocol()) {
|
||||||
dialPadButton =
|
dialPadButton =
|
||||||
<AccessibleTooltipButton
|
<AccessibleTooltipButton
|
||||||
className={classNames("mx_LeftPanel_dialPadButton", {})}
|
className={classNames("mx_LeftPanel_dialPadButton", {})}
|
||||||
|
|
|
@ -53,7 +53,7 @@ import { getKeyBindingsManager, NavigationAction, RoomAction } from '../../KeyBi
|
||||||
import { IOpts } from "../../createRoom";
|
import { IOpts } from "../../createRoom";
|
||||||
import SpacePanel from "../views/spaces/SpacePanel";
|
import SpacePanel from "../views/spaces/SpacePanel";
|
||||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||||
import CallHandler from '../../CallHandler';
|
import CallHandler, { CallHandlerEvent } from '../../CallHandler';
|
||||||
import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
||||||
import AudioFeedArrayForCall from '../views/voip/AudioFeedArrayForCall';
|
import AudioFeedArrayForCall from '../views/voip/AudioFeedArrayForCall';
|
||||||
import { OwnProfileStore } from '../../stores/OwnProfileStore';
|
import { OwnProfileStore } from '../../stores/OwnProfileStore';
|
||||||
|
@ -164,7 +164,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
// use compact timeline view
|
// use compact timeline view
|
||||||
useCompactLayout: SettingsStore.getValue('useCompactLayout'),
|
useCompactLayout: SettingsStore.getValue('useCompactLayout'),
|
||||||
usageLimitDismissed: false,
|
usageLimitDismissed: false,
|
||||||
activeCalls: CallHandler.sharedInstance().getAllActiveCalls(),
|
activeCalls: CallHandler.instance.getAllActiveCalls(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// stash the MatrixClient in case we log out before we are unmounted
|
// stash the MatrixClient in case we log out before we are unmounted
|
||||||
|
@ -181,7 +181,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
document.addEventListener('keydown', this.onNativeKeyDown, false);
|
document.addEventListener('keydown', this.onNativeKeyDown, false);
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
CallHandler.instance.addListener(CallHandlerEvent.CallState, this.onCallState);
|
||||||
|
|
||||||
this.updateServerNoticeEvents();
|
this.updateServerNoticeEvents();
|
||||||
|
|
||||||
|
@ -212,6 +212,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
document.removeEventListener('keydown', this.onNativeKeyDown, false);
|
document.removeEventListener('keydown', this.onNativeKeyDown, false);
|
||||||
|
CallHandler.instance.removeListener(CallHandlerEvent.CallState, this.onCallState);
|
||||||
dis.unregister(this.dispatcherRef);
|
dis.unregister(this.dispatcherRef);
|
||||||
this._matrixClient.removeListener("accountData", this.onAccountData);
|
this._matrixClient.removeListener("accountData", this.onAccountData);
|
||||||
this._matrixClient.removeListener("sync", this.onSync);
|
this._matrixClient.removeListener("sync", this.onSync);
|
||||||
|
@ -222,6 +223,12 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
this.resizer.detach();
|
this.resizer.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onCallState = (): void => {
|
||||||
|
const activeCalls = CallHandler.instance.getAllActiveCalls();
|
||||||
|
if (activeCalls === this.state.activeCalls) return;
|
||||||
|
this.setState({ activeCalls });
|
||||||
|
};
|
||||||
|
|
||||||
private refreshBackgroundImage = async (): Promise<void> => {
|
private refreshBackgroundImage = async (): Promise<void> => {
|
||||||
let backgroundImage = SettingsStore.getValue("RoomList.backgroundImage");
|
let backgroundImage = SettingsStore.getValue("RoomList.backgroundImage");
|
||||||
if (backgroundImage) {
|
if (backgroundImage) {
|
||||||
|
@ -233,18 +240,6 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
this.setState({ backgroundImage });
|
this.setState({ backgroundImage });
|
||||||
};
|
};
|
||||||
|
|
||||||
private onAction = (payload): void => {
|
|
||||||
switch (payload.action) {
|
|
||||||
case 'call_state': {
|
|
||||||
const activeCalls = CallHandler.sharedInstance().getAllActiveCalls();
|
|
||||||
if (activeCalls !== this.state.activeCalls) {
|
|
||||||
this.setState({ activeCalls });
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public canResetTimelineInRoom = (roomId: string) => {
|
public canResetTimelineInRoom = (roomId: string) => {
|
||||||
if (!this._roomView.current) {
|
if (!this._roomView.current) {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -108,6 +108,7 @@ import { makeRoomPermalink } from "../../utils/permalinks/Permalinks";
|
||||||
import { copyPlaintext } from "../../utils/strings";
|
import { copyPlaintext } from "../../utils/strings";
|
||||||
import { PosthogAnalytics } from '../../PosthogAnalytics';
|
import { PosthogAnalytics } from '../../PosthogAnalytics';
|
||||||
import { initSentry } from "../../sentry";
|
import { initSentry } from "../../sentry";
|
||||||
|
import CallHandler from "../../CallHandler";
|
||||||
|
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { showSpaceInvite } from "../../utils/space";
|
import { showSpaceInvite } from "../../utils/space";
|
||||||
|
@ -604,7 +605,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'logout':
|
case 'logout':
|
||||||
dis.dispatch({ action: "hangup_all" });
|
CallHandler.instance.hangupAllCalls();
|
||||||
Lifecycle.logout();
|
Lifecycle.logout();
|
||||||
break;
|
break;
|
||||||
case 'require_registration':
|
case 'require_registration':
|
||||||
|
|
|
@ -34,7 +34,7 @@ import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks';
|
||||||
import ResizeNotifier from '../../utils/ResizeNotifier';
|
import ResizeNotifier from '../../utils/ResizeNotifier';
|
||||||
import ContentMessages from '../../ContentMessages';
|
import ContentMessages from '../../ContentMessages';
|
||||||
import Modal from '../../Modal';
|
import Modal from '../../Modal';
|
||||||
import CallHandler, { PlaceCallType } from '../../CallHandler';
|
import CallHandler, { CallHandlerEvent } from '../../CallHandler';
|
||||||
import dis from '../../dispatcher/dispatcher';
|
import dis from '../../dispatcher/dispatcher';
|
||||||
import * as Rooms from '../../Rooms';
|
import * as Rooms from '../../Rooms';
|
||||||
import eventSearch, { searchPagination } from '../../Searching';
|
import eventSearch, { searchPagination } from '../../Searching';
|
||||||
|
@ -66,7 +66,7 @@ import { IOOBData, IThreepidInvite } from "../../stores/ThreepidInviteStore";
|
||||||
import EffectsOverlay from "../views/elements/EffectsOverlay";
|
import EffectsOverlay from "../views/elements/EffectsOverlay";
|
||||||
import { containsEmoji } from '../../effects/utils';
|
import { containsEmoji } from '../../effects/utils';
|
||||||
import { CHAT_EFFECTS } from '../../effects';
|
import { CHAT_EFFECTS } from '../../effects';
|
||||||
import { CallState, MatrixCall } from "matrix-js-sdk/src/webrtc/call";
|
import { CallState, CallType, MatrixCall } from "matrix-js-sdk/src/webrtc/call";
|
||||||
import WidgetStore from "../../stores/WidgetStore";
|
import WidgetStore from "../../stores/WidgetStore";
|
||||||
import { UPDATE_EVENT } from "../../stores/AsyncStore";
|
import { UPDATE_EVENT } from "../../stores/AsyncStore";
|
||||||
import Notifier from "../../Notifier";
|
import Notifier from "../../Notifier";
|
||||||
|
@ -669,6 +669,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
|
CallHandler.instance.addListener(CallHandlerEvent.CallState, this.onCallState);
|
||||||
if (this.roomView.current) {
|
if (this.roomView.current) {
|
||||||
const roomView = this.roomView.current;
|
const roomView = this.roomView.current;
|
||||||
if (!roomView.ondrop) {
|
if (!roomView.ondrop) {
|
||||||
|
@ -699,6 +700,8 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
// (We could use isMounted, but facebook have deprecated that.)
|
// (We could use isMounted, but facebook have deprecated that.)
|
||||||
this.unmounted = true;
|
this.unmounted = true;
|
||||||
|
|
||||||
|
CallHandler.instance.removeListener(CallHandlerEvent.CallState, this.onCallState);
|
||||||
|
|
||||||
// update the scroll map before we get unmounted
|
// update the scroll map before we get unmounted
|
||||||
if (this.state.roomId) {
|
if (this.state.roomId) {
|
||||||
RoomScrollStateStore.setScrollState(this.state.roomId, this.getScrollState());
|
RoomScrollStateStore.setScrollState(this.state.roomId, this.getScrollState());
|
||||||
|
@ -822,6 +825,15 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private onCallState = (roomId: string): void => {
|
||||||
|
// don't filter out payloads for room IDs other than props.room because
|
||||||
|
// we may be interested in the conf 1:1 room
|
||||||
|
|
||||||
|
if (!roomId) return;
|
||||||
|
const call = this.getCallForRoom();
|
||||||
|
this.setState({ callState: call ? call.state : null });
|
||||||
|
};
|
||||||
|
|
||||||
private onAction = payload => {
|
private onAction = payload => {
|
||||||
switch (payload.action) {
|
switch (payload.action) {
|
||||||
case 'message_sent':
|
case 'message_sent':
|
||||||
|
@ -843,21 +855,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
case Action.UploadCanceled:
|
case Action.UploadCanceled:
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
break;
|
break;
|
||||||
case 'call_state': {
|
|
||||||
// don't filter out payloads for room IDs other than props.room because
|
|
||||||
// we may be interested in the conf 1:1 room
|
|
||||||
|
|
||||||
if (!payload.room_id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const call = this.getCallForRoom();
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
callState: call ? call.state : null,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'appsDrawer':
|
case 'appsDrawer':
|
||||||
this.setState({
|
this.setState({
|
||||||
showApps: payload.show,
|
showApps: payload.show,
|
||||||
|
@ -1531,12 +1528,8 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
private onCallPlaced = (type: PlaceCallType) => {
|
private onCallPlaced = (type: CallType): void => {
|
||||||
dis.dispatch({
|
CallHandler.instance.placeCall(this.state.room?.roomId, type);
|
||||||
action: 'place_call',
|
|
||||||
type: type,
|
|
||||||
room_id: this.state.room.roomId,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private onAppsClick = () => {
|
private onAppsClick = () => {
|
||||||
|
@ -1748,7 +1741,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
if (!this.state.room) {
|
if (!this.state.room) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return CallHandler.sharedInstance().getCallForRoom(this.state.room.roomId);
|
return CallHandler.instance.getCallForRoom(this.state.room.roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// this has to be a proper method rather than an unnamed function,
|
// this has to be a proper method rather than an unnamed function,
|
||||||
|
|
|
@ -45,7 +45,7 @@ export default class CallContextMenu extends React.Component<IProps> {
|
||||||
};
|
};
|
||||||
|
|
||||||
onUnholdClick = () => {
|
onUnholdClick = () => {
|
||||||
CallHandler.sharedInstance().setActiveCallRoomId(this.props.call.roomId);
|
CallHandler.instance.setActiveCallRoomId(this.props.call.roomId);
|
||||||
|
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
};
|
};
|
||||||
|
|
|
@ -63,7 +63,6 @@ import { copyPlaintext, selectText } from "../../../utils/strings";
|
||||||
import * as ContextMenu from "../../structures/ContextMenu";
|
import * as ContextMenu from "../../structures/ContextMenu";
|
||||||
import { toRightOf } from "../../structures/ContextMenu";
|
import { toRightOf } from "../../structures/ContextMenu";
|
||||||
import GenericTextContextMenu from "../context_menus/GenericTextContextMenu";
|
import GenericTextContextMenu from "../context_menus/GenericTextContextMenu";
|
||||||
import { TransferCallPayload } from '../../../dispatcher/payloads/TransferCallPayload';
|
|
||||||
import Field from '../elements/Field';
|
import Field from '../elements/Field';
|
||||||
import TabbedView, { Tab, TabLocation } from '../../structures/TabbedView';
|
import TabbedView, { Tab, TabLocation } from '../../structures/TabbedView';
|
||||||
import Dialpad from '../voip/DialPad';
|
import Dialpad from '../voip/DialPad';
|
||||||
|
@ -72,6 +71,7 @@ import Spinner from "../elements/Spinner";
|
||||||
import BaseDialog from "./BaseDialog";
|
import BaseDialog from "./BaseDialog";
|
||||||
import DialPadBackspaceButton from "../elements/DialPadBackspaceButton";
|
import DialPadBackspaceButton from "../elements/DialPadBackspaceButton";
|
||||||
import SpaceStore from "../../../stores/spaces/SpaceStore";
|
import SpaceStore from "../../../stores/spaces/SpaceStore";
|
||||||
|
import CallHandler from "../../../CallHandler";
|
||||||
|
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
|
@ -806,19 +806,17 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dis.dispatch({
|
CallHandler.instance.startTransferToMatrixID(
|
||||||
action: Action.TransferCallToMatrixID,
|
this.props.call,
|
||||||
call: this.props.call,
|
targetIds[0],
|
||||||
destination: targetIds[0],
|
this.state.consultFirst,
|
||||||
consultFirst: this.state.consultFirst,
|
);
|
||||||
} as TransferCallPayload);
|
|
||||||
} else {
|
} else {
|
||||||
dis.dispatch({
|
CallHandler.instance.startTransferToPhoneNumber(
|
||||||
action: Action.TransferCallToPhoneNumber,
|
this.props.call,
|
||||||
call: this.props.call,
|
this.state.dialPadValue,
|
||||||
destination: this.state.dialPadValue,
|
this.state.consultFirst,
|
||||||
consultFirst: this.state.consultFirst,
|
);
|
||||||
} as TransferCallPayload);
|
|
||||||
}
|
}
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
};
|
};
|
||||||
|
|
|
@ -38,6 +38,7 @@ import { MatrixCapabilities } from "matrix-widget-api";
|
||||||
import RoomWidgetContextMenu from "../context_menus/WidgetContextMenu";
|
import RoomWidgetContextMenu from "../context_menus/WidgetContextMenu";
|
||||||
import WidgetAvatar from "../avatars/WidgetAvatar";
|
import WidgetAvatar from "../avatars/WidgetAvatar";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
import CallHandler from '../../../CallHandler';
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { IApp } from "../../../stores/WidgetStore";
|
import { IApp } from "../../../stores/WidgetStore";
|
||||||
import { WidgetLayoutStore, Container } from "../../../stores/widgets/WidgetLayoutStore";
|
import { WidgetLayoutStore, Container } from "../../../stores/widgets/WidgetLayoutStore";
|
||||||
|
@ -290,7 +291,7 @@ export default class AppTile extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (WidgetType.JITSI.matches(this.props.app.type)) {
|
if (WidgetType.JITSI.matches(this.props.app.type)) {
|
||||||
dis.dispatch({ action: 'hangup_conference' });
|
CallHandler.instance.hangupCallApp(this.props.room.roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the widget from the persisted store for good measure.
|
// Delete the widget from the persisted store for good measure.
|
||||||
|
|
|
@ -27,7 +27,6 @@ import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
|
||||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||||
import RoomTopic from "../elements/RoomTopic";
|
import RoomTopic from "../elements/RoomTopic";
|
||||||
import RoomName from "../elements/RoomName";
|
import RoomName from "../elements/RoomName";
|
||||||
import { PlaceCallType } from "../../../CallHandler";
|
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import InfoDialog from "../dialogs/InfoDialog";
|
import InfoDialog from "../dialogs/InfoDialog";
|
||||||
|
@ -36,6 +35,7 @@ import { MatrixEvent, Room, RoomState } from 'matrix-js-sdk/src';
|
||||||
import { E2EStatus } from '../../../utils/ShieldUtils';
|
import { E2EStatus } from '../../../utils/ShieldUtils';
|
||||||
import { IOOBData } from '../../../stores/ThreepidInviteStore';
|
import { IOOBData } from '../../../stores/ThreepidInviteStore';
|
||||||
import { SearchScope } from './SearchBar';
|
import { SearchScope } from './SearchBar';
|
||||||
|
import { CallType } from "matrix-js-sdk/src/webrtc/call";
|
||||||
import { ContextMenuTooltipButton } from '../../structures/ContextMenu';
|
import { ContextMenuTooltipButton } from '../../structures/ContextMenu';
|
||||||
import RoomContextMenu from "../context_menus/RoomContextMenu";
|
import RoomContextMenu from "../context_menus/RoomContextMenu";
|
||||||
import { contextMenuBelow } from './RoomTile';
|
import { contextMenuBelow } from './RoomTile';
|
||||||
|
@ -55,7 +55,7 @@ interface IProps {
|
||||||
inRoom: boolean;
|
inRoom: boolean;
|
||||||
onSearchClick: () => void;
|
onSearchClick: () => void;
|
||||||
onForgetClick: () => void;
|
onForgetClick: () => void;
|
||||||
onCallPlaced: (type: PlaceCallType) => void;
|
onCallPlaced: (type: CallType) => void;
|
||||||
onAppsClick: () => void;
|
onAppsClick: () => void;
|
||||||
e2eStatus: E2EStatus;
|
e2eStatus: E2EStatus;
|
||||||
appsShown: boolean;
|
appsShown: boolean;
|
||||||
|
@ -213,14 +213,14 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
||||||
if (this.props.inRoom && SettingsStore.getValue("showCallButtonsInComposer")) {
|
if (this.props.inRoom && SettingsStore.getValue("showCallButtonsInComposer")) {
|
||||||
const voiceCallButton = <AccessibleTooltipButton
|
const voiceCallButton = <AccessibleTooltipButton
|
||||||
className="mx_RoomHeader_button mx_RoomHeader_voiceCallButton"
|
className="mx_RoomHeader_button mx_RoomHeader_voiceCallButton"
|
||||||
onClick={() => this.props.onCallPlaced(PlaceCallType.Voice)}
|
onClick={() => this.props.onCallPlaced(CallType.Voice)}
|
||||||
title={_t("Voice call")}
|
title={_t("Voice call")}
|
||||||
key="voice"
|
key="voice"
|
||||||
/>;
|
/>;
|
||||||
const videoCallButton = <AccessibleTooltipButton
|
const videoCallButton = <AccessibleTooltipButton
|
||||||
className="mx_RoomHeader_button mx_RoomHeader_videoCallButton"
|
className="mx_RoomHeader_button mx_RoomHeader_videoCallButton"
|
||||||
onClick={(ev: React.MouseEvent<Element>) => ev.shiftKey ?
|
onClick={(ev: React.MouseEvent<Element>) => ev.shiftKey ?
|
||||||
this.displayInfoDialogAboutScreensharing() : this.props.onCallPlaced(PlaceCallType.Video)}
|
this.displayInfoDialogAboutScreensharing() : this.props.onCallPlaced(CallType.Voice)}
|
||||||
title={_t("Video call")}
|
title={_t("Video call")}
|
||||||
key="video"
|
key="video"
|
||||||
/>;
|
/>;
|
||||||
|
|
|
@ -20,8 +20,6 @@ import React from 'react';
|
||||||
import CallView from "./CallView";
|
import CallView from "./CallView";
|
||||||
import RoomViewStore from '../../../stores/RoomViewStore';
|
import RoomViewStore from '../../../stores/RoomViewStore';
|
||||||
import CallHandler, { CallHandlerEvent } from '../../../CallHandler';
|
import CallHandler, { CallHandlerEvent } from '../../../CallHandler';
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
|
||||||
import { ActionPayload } from '../../../dispatcher/payloads';
|
|
||||||
import PersistentApp from "../elements/PersistentApp";
|
import PersistentApp from "../elements/PersistentApp";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import { CallEvent, CallState, MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
import { CallEvent, CallState, MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
||||||
|
@ -29,6 +27,8 @@ import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import { EventSubscription } from 'fbemitter';
|
import { EventSubscription } from 'fbemitter';
|
||||||
import PictureInPictureDragger from './PictureInPictureDragger';
|
import PictureInPictureDragger from './PictureInPictureDragger';
|
||||||
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
|
import { Action } from "../../../dispatcher/actions";
|
||||||
|
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { WidgetLayoutStore } from '../../../stores/widgets/WidgetLayoutStore';
|
import { WidgetLayoutStore } from '../../../stores/widgets/WidgetLayoutStore';
|
||||||
|
@ -61,7 +61,7 @@ interface IState {
|
||||||
// The primary will be the one not on hold, or an arbitrary one
|
// The primary will be the one not on hold, or an arbitrary one
|
||||||
// if they're all on hold)
|
// if they're all on hold)
|
||||||
function getPrimarySecondaryCallsForPip(roomId: string): [MatrixCall, MatrixCall[]] {
|
function getPrimarySecondaryCallsForPip(roomId: string): [MatrixCall, MatrixCall[]] {
|
||||||
const calls = CallHandler.sharedInstance().getAllActiveCallsForPip(roomId);
|
const calls = CallHandler.instance.getAllActiveCallsForPip(roomId);
|
||||||
|
|
||||||
let primary: MatrixCall = null;
|
let primary: MatrixCall = null;
|
||||||
let secondaries: MatrixCall[] = [];
|
let secondaries: MatrixCall[] = [];
|
||||||
|
@ -114,9 +114,9 @@ export default class CallPreview extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidMount() {
|
public componentDidMount() {
|
||||||
CallHandler.sharedInstance().addListener(CallHandlerEvent.CallChangeRoom, this.updateCalls);
|
CallHandler.instance.addListener(CallHandlerEvent.CallChangeRoom, this.updateCalls);
|
||||||
|
CallHandler.instance.addListener(CallHandlerEvent.CallState, this.updateCalls);
|
||||||
this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate);
|
this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate);
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
|
||||||
MatrixClientPeg.get().on(CallEvent.RemoteHoldUnhold, this.onCallRemoteHold);
|
MatrixClientPeg.get().on(CallEvent.RemoteHoldUnhold, this.onCallRemoteHold);
|
||||||
const room = MatrixClientPeg.get()?.getRoom(this.state.roomId);
|
const room = MatrixClientPeg.get()?.getRoom(this.state.roomId);
|
||||||
if (room) {
|
if (room) {
|
||||||
|
@ -125,12 +125,12 @@ export default class CallPreview extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
CallHandler.sharedInstance().removeListener(CallHandlerEvent.CallChangeRoom, this.updateCalls);
|
CallHandler.instance.removeListener(CallHandlerEvent.CallChangeRoom, this.updateCalls);
|
||||||
|
CallHandler.instance.removeListener(CallHandlerEvent.CallState, this.updateCalls);
|
||||||
MatrixClientPeg.get().removeListener(CallEvent.RemoteHoldUnhold, this.onCallRemoteHold);
|
MatrixClientPeg.get().removeListener(CallEvent.RemoteHoldUnhold, this.onCallRemoteHold);
|
||||||
if (this.roomStoreToken) {
|
if (this.roomStoreToken) {
|
||||||
this.roomStoreToken.remove();
|
this.roomStoreToken.remove();
|
||||||
}
|
}
|
||||||
dis.unregister(this.dispatcherRef);
|
|
||||||
SettingsStore.unwatchSetting(this.settingsWatcherRef);
|
SettingsStore.unwatchSetting(this.settingsWatcherRef);
|
||||||
const room = MatrixClientPeg.get().getRoom(this.state.roomId);
|
const room = MatrixClientPeg.get().getRoom(this.state.roomId);
|
||||||
WidgetLayoutStore.instance.off(WidgetLayoutStore.emissionForRoom(room), this.updateCalls);
|
WidgetLayoutStore.instance.off(WidgetLayoutStore.emissionForRoom(room), this.updateCalls);
|
||||||
|
@ -160,19 +160,7 @@ export default class CallPreview extends React.Component<IProps, IState> {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private onAction = (payload: ActionPayload) => {
|
private updateCalls = (): void => {
|
||||||
switch (payload.action) {
|
|
||||||
case 'call_state': {
|
|
||||||
// listen for call state changes to prod the render method, which
|
|
||||||
// may hide the global CallView if the call it is tracking is dead
|
|
||||||
|
|
||||||
this.updateCalls();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private updateCalls = () => {
|
|
||||||
if (!this.state.roomId) return;
|
if (!this.state.roomId) return;
|
||||||
const [primaryCall, secondaryCalls] = getPrimarySecondaryCallsForPip(this.state.roomId);
|
const [primaryCall, secondaryCalls] = getPrimarySecondaryCallsForPip(this.state.roomId);
|
||||||
|
|
||||||
|
@ -194,7 +182,7 @@ export default class CallPreview extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
private onDoubleClick = (): void => {
|
private onDoubleClick = (): void => {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: "view_room",
|
action: Action.ViewRoom,
|
||||||
room_id: this.state.primaryCall.roomId,
|
room_id: this.state.primaryCall.roomId,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -328,20 +328,17 @@ export default class CallView extends React.Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
private onCallResumeClick = (): void => {
|
private onCallResumeClick = (): void => {
|
||||||
const userFacingRoomId = CallHandler.sharedInstance().roomIdForCall(this.props.call);
|
const userFacingRoomId = CallHandler.instance.roomIdForCall(this.props.call);
|
||||||
CallHandler.sharedInstance().setActiveCallRoomId(userFacingRoomId);
|
CallHandler.instance.setActiveCallRoomId(userFacingRoomId);
|
||||||
};
|
};
|
||||||
|
|
||||||
private onTransferClick = (): void => {
|
private onTransferClick = (): void => {
|
||||||
const transfereeCall = CallHandler.sharedInstance().getTransfereeForCallId(this.props.call.callId);
|
const transfereeCall = CallHandler.instance.getTransfereeForCallId(this.props.call.callId);
|
||||||
this.props.call.transferToCall(transfereeCall);
|
this.props.call.transferToCall(transfereeCall);
|
||||||
};
|
};
|
||||||
|
|
||||||
private onHangupClick = (): void => {
|
private onHangupClick = (): void => {
|
||||||
dis.dispatch({
|
CallHandler.instance.hangupOrReject(CallHandler.instance.roomIdForCall(this.props.call));
|
||||||
action: 'hangup',
|
|
||||||
room_id: CallHandler.sharedInstance().roomIdForCall(this.props.call),
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private onToggleSidebar = (): void => {
|
private onToggleSidebar = (): void => {
|
||||||
|
@ -404,12 +401,12 @@ export default class CallView extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const callRoomId = CallHandler.sharedInstance().roomIdForCall(this.props.call);
|
const callRoomId = CallHandler.instance.roomIdForCall(this.props.call);
|
||||||
const secondaryCallRoomId = CallHandler.sharedInstance().roomIdForCall(this.props.secondaryCall);
|
const secondaryCallRoomId = CallHandler.instance.roomIdForCall(this.props.secondaryCall);
|
||||||
const callRoom = client.getRoom(callRoomId);
|
const callRoom = client.getRoom(callRoomId);
|
||||||
const secCallRoom = this.props.secondaryCall ? client.getRoom(secondaryCallRoomId) : null;
|
const secCallRoom = this.props.secondaryCall ? client.getRoom(secondaryCallRoomId) : null;
|
||||||
const avatarSize = this.props.pipMode ? 76 : 160;
|
const avatarSize = this.props.pipMode ? 76 : 160;
|
||||||
const transfereeCall = CallHandler.sharedInstance().getTransfereeForCallId(this.props.call.callId);
|
const transfereeCall = CallHandler.instance.getTransfereeForCallId(this.props.call.callId);
|
||||||
const isOnHold = this.state.isLocalOnHold || this.state.isRemoteOnHold;
|
const isOnHold = this.state.isLocalOnHold || this.state.isRemoteOnHold;
|
||||||
const isScreensharing = this.props.call.isScreensharing();
|
const isScreensharing = this.props.call.isScreensharing();
|
||||||
const sidebarShown = this.state.sidebarShown;
|
const sidebarShown = this.state.sidebarShown;
|
||||||
|
@ -423,12 +420,12 @@ export default class CallView extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
if (transfereeCall) {
|
if (transfereeCall) {
|
||||||
const transferTargetRoom = MatrixClientPeg.get().getRoom(
|
const transferTargetRoom = MatrixClientPeg.get().getRoom(
|
||||||
CallHandler.sharedInstance().roomIdForCall(this.props.call),
|
CallHandler.instance.roomIdForCall(this.props.call),
|
||||||
);
|
);
|
||||||
const transferTargetName = transferTargetRoom ? transferTargetRoom.name : _t("unknown person");
|
const transferTargetName = transferTargetRoom ? transferTargetRoom.name : _t("unknown person");
|
||||||
|
|
||||||
const transfereeRoom = MatrixClientPeg.get().getRoom(
|
const transfereeRoom = MatrixClientPeg.get().getRoom(
|
||||||
CallHandler.sharedInstance().roomIdForCall(transfereeCall),
|
CallHandler.instance.roomIdForCall(transfereeCall),
|
||||||
);
|
);
|
||||||
const transfereeName = transfereeRoom ? transfereeRoom.name : _t("unknown person");
|
const transfereeName = transfereeRoom ? transfereeRoom.name : _t("unknown person");
|
||||||
|
|
||||||
|
@ -449,7 +446,7 @@ export default class CallView extends React.Component<IProps, IState> {
|
||||||
} else if (isOnHold) {
|
} else if (isOnHold) {
|
||||||
let onHoldText = null;
|
let onHoldText = null;
|
||||||
if (this.state.isRemoteOnHold) {
|
if (this.state.isRemoteOnHold) {
|
||||||
const holdString = CallHandler.sharedInstance().hasAnyUnheldCall() ?
|
const holdString = CallHandler.instance.hasAnyUnheldCall() ?
|
||||||
_td("You held the call <a>Switch</a>") : _td("You held the call <a>Resume</a>");
|
_td("You held the call <a>Switch</a>") : _td("You held the call <a>Resume</a>");
|
||||||
onHoldText = _t(holdString, {}, {
|
onHoldText = _t(holdString, {}, {
|
||||||
a: sub => <AccessibleButton kind="link" onClick={this.onCallResumeClick}>
|
a: sub => <AccessibleButton kind="link" onClick={this.onCallResumeClick}>
|
||||||
|
|
|
@ -18,7 +18,6 @@ import { CallState, MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import CallHandler, { CallHandlerEvent } from '../../../CallHandler';
|
import CallHandler, { CallHandlerEvent } from '../../../CallHandler';
|
||||||
import CallView from './CallView';
|
import CallView from './CallView';
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
|
||||||
import { Resizable } from "re-resizable";
|
import { Resizable } from "re-resizable";
|
||||||
import ResizeNotifier from "../../../utils/ResizeNotifier";
|
import ResizeNotifier from "../../../utils/ResizeNotifier";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
@ -52,24 +51,15 @@ export default class CallViewForRoom extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidMount() {
|
public componentDidMount() {
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
CallHandler.instance.addListener(CallHandlerEvent.CallState, this.updateCall);
|
||||||
CallHandler.sharedInstance().addListener(CallHandlerEvent.CallChangeRoom, this.updateCall);
|
CallHandler.instance.addListener(CallHandlerEvent.CallChangeRoom, this.updateCall);
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
dis.unregister(this.dispatcherRef);
|
CallHandler.instance.removeListener(CallHandlerEvent.CallState, this.updateCall);
|
||||||
CallHandler.sharedInstance().removeListener(CallHandlerEvent.CallChangeRoom, this.updateCall);
|
CallHandler.instance.removeListener(CallHandlerEvent.CallChangeRoom, this.updateCall);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onAction = (payload) => {
|
|
||||||
switch (payload.action) {
|
|
||||||
case 'call_state': {
|
|
||||||
this.updateCall();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private updateCall = () => {
|
private updateCall = () => {
|
||||||
const newCall = this.getCall();
|
const newCall = this.getCall();
|
||||||
if (newCall !== this.state.call) {
|
if (newCall !== this.state.call) {
|
||||||
|
@ -78,7 +68,7 @@ export default class CallViewForRoom extends React.Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
private getCall(): MatrixCall {
|
private getCall(): MatrixCall {
|
||||||
const call = CallHandler.sharedInstance().getCallForRoom(this.props.roomId);
|
const call = CallHandler.instance.getCallForRoom(this.props.roomId);
|
||||||
|
|
||||||
if (call && [CallState.Ended, CallState.Ringing].includes(call.state)) return null;
|
if (call && [CallState.Ended, CallState.Ringing].includes(call.state)) return null;
|
||||||
return call;
|
return call;
|
||||||
|
|
|
@ -19,11 +19,9 @@ import { createRef } from "react";
|
||||||
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
|
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
|
||||||
import Field from "../elements/Field";
|
import Field from "../elements/Field";
|
||||||
import DialPad from './DialPad';
|
import DialPad from './DialPad';
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import { DialNumberPayload } from "../../../dispatcher/payloads/DialNumberPayload";
|
|
||||||
import { Action } from "../../../dispatcher/actions";
|
|
||||||
import DialPadBackspaceButton from "../elements/DialPadBackspaceButton";
|
import DialPadBackspaceButton from "../elements/DialPadBackspaceButton";
|
||||||
|
import CallHandler from "../../../CallHandler";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
onFinished: (boolean) => void;
|
onFinished: (boolean) => void;
|
||||||
|
@ -81,12 +79,7 @@ export default class DialpadModal extends React.PureComponent<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
onDialPress = async () => {
|
onDialPress = async () => {
|
||||||
const payload: DialNumberPayload = {
|
CallHandler.instance.dialNumber(this.state.value);
|
||||||
action: Action.DialNumber,
|
|
||||||
number: this.state.value,
|
|
||||||
};
|
|
||||||
dis.dispatch(payload);
|
|
||||||
|
|
||||||
this.props.onFinished(true);
|
this.props.onFinished(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -123,18 +123,6 @@ export enum Action {
|
||||||
*/
|
*/
|
||||||
DialNumber = "dial_number",
|
DialNumber = "dial_number",
|
||||||
|
|
||||||
/**
|
|
||||||
* Start a call transfer to a Matrix ID
|
|
||||||
* payload: TransferCallPayload
|
|
||||||
*/
|
|
||||||
TransferCallToMatrixID = "transfer_call_to_matrix_id",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start a call transfer to a phone number
|
|
||||||
* payload: TransferCallPayload
|
|
||||||
*/
|
|
||||||
TransferCallToPhoneNumber = "transfer_call_to_phone_number",
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fired when CallHandler has checked for PSTN protocol support
|
* Fired when CallHandler has checked for PSTN protocol support
|
||||||
* payload: none
|
* payload: none
|
||||||
|
|
|
@ -1,23 +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 { ActionPayload } from "../payloads";
|
|
||||||
import { Action } from "../actions";
|
|
||||||
|
|
||||||
export interface DialNumberPayload extends ActionPayload {
|
|
||||||
action: Action.DialNumber;
|
|
||||||
number: string;
|
|
||||||
}
|
|
|
@ -1,33 +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 { ActionPayload } from "../payloads";
|
|
||||||
import { Action } from "../actions";
|
|
||||||
import { MatrixCall } from "matrix-js-sdk/src/webrtc/call";
|
|
||||||
|
|
||||||
export interface TransferCallPayload extends ActionPayload {
|
|
||||||
action: Action.TransferCallToMatrixID | Action.TransferCallToPhoneNumber;
|
|
||||||
// The call to transfer
|
|
||||||
call: MatrixCall;
|
|
||||||
// Where to transfer the call. A Matrix ID if action == TransferCallToMatrixID
|
|
||||||
// and a phone number if action == TransferCallToPhoneNumber
|
|
||||||
destination: string;
|
|
||||||
// If true, puts the current call on hold and dials the transfer target, giving
|
|
||||||
// the user a button to complete the transfer when ready.
|
|
||||||
// If false, ends the call immediately and sends the user to the transfer
|
|
||||||
// destination
|
|
||||||
consultFirst: boolean;
|
|
||||||
}
|
|
|
@ -43,7 +43,7 @@ export class VisibilityProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
CallHandler.sharedInstance().getSupportsVirtualRooms() &&
|
CallHandler.instance.getSupportsVirtualRooms() &&
|
||||||
VoipUserMapper.sharedInstance().isVirtualRoom(room)
|
VoipUserMapper.sharedInstance().isVirtualRoom(room)
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -22,7 +22,6 @@ import { CallType, MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { replaceableComponent } from '../utils/replaceableComponent';
|
import { replaceableComponent } from '../utils/replaceableComponent';
|
||||||
import CallHandler, { CallHandlerEvent } from '../CallHandler';
|
import CallHandler, { CallHandlerEvent } from '../CallHandler';
|
||||||
import dis from '../dispatcher/dispatcher';
|
|
||||||
import { MatrixClientPeg } from '../MatrixClientPeg';
|
import { MatrixClientPeg } from '../MatrixClientPeg';
|
||||||
import { _t } from '../languageHandler';
|
import { _t } from '../languageHandler';
|
||||||
import RoomAvatar from '../components/views/avatars/RoomAvatar';
|
import RoomAvatar from '../components/views/avatars/RoomAvatar';
|
||||||
|
@ -45,49 +44,43 @@ export default class IncomingCallToast extends React.Component<IProps, IState> {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
silenced: CallHandler.sharedInstance().isCallSilenced(this.props.call.callId),
|
silenced: CallHandler.instance.isCallSilenced(this.props.call.callId),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidMount = (): void => {
|
public componentDidMount = (): void => {
|
||||||
CallHandler.sharedInstance().addListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged);
|
CallHandler.instance.addListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged);
|
||||||
};
|
};
|
||||||
|
|
||||||
public componentWillUnmount(): void {
|
public componentWillUnmount(): void {
|
||||||
CallHandler.sharedInstance().removeListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged);
|
CallHandler.instance.removeListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onSilencedCallsChanged = (): void => {
|
private onSilencedCallsChanged = (): void => {
|
||||||
this.setState({ silenced: CallHandler.sharedInstance().isCallSilenced(this.props.call.callId) });
|
this.setState({ silenced: CallHandler.instance.isCallSilenced(this.props.call.callId) });
|
||||||
};
|
};
|
||||||
|
|
||||||
private onAnswerClick = (e: React.MouseEvent): void => {
|
private onAnswerClick = (e: React.MouseEvent): void => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
dis.dispatch({
|
CallHandler.instance.answerCall(CallHandler.instance.roomIdForCall(this.props.call));
|
||||||
action: 'answer',
|
|
||||||
room_id: CallHandler.sharedInstance().roomIdForCall(this.props.call),
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private onRejectClick= (e: React.MouseEvent): void => {
|
private onRejectClick= (e: React.MouseEvent): void => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
dis.dispatch({
|
CallHandler.instance.hangupOrReject(CallHandler.instance.roomIdForCall(this.props.call), true);
|
||||||
action: 'reject',
|
|
||||||
room_id: CallHandler.sharedInstance().roomIdForCall(this.props.call),
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private onSilenceClick = (e: React.MouseEvent): void => {
|
private onSilenceClick = (e: React.MouseEvent): void => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const callId = this.props.call.callId;
|
const callId = this.props.call.callId;
|
||||||
this.state.silenced ?
|
this.state.silenced ?
|
||||||
CallHandler.sharedInstance().unSilenceCall(callId) :
|
CallHandler.instance.unSilenceCall(callId) :
|
||||||
CallHandler.sharedInstance().silenceCall(callId);
|
CallHandler.instance.silenceCall(callId);
|
||||||
};
|
};
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const call = this.props.call;
|
const call = this.props.call;
|
||||||
const room = MatrixClientPeg.get().getRoom(CallHandler.sharedInstance().roomIdForCall(call));
|
const room = MatrixClientPeg.get().getRoom(CallHandler.instance.roomIdForCall(call));
|
||||||
const isVoice = call.type === CallType.Voice;
|
const isVoice = call.type === CallType.Voice;
|
||||||
|
|
||||||
const contentClass = classNames("mx_IncomingCallToast_content", {
|
const contentClass = classNames("mx_IncomingCallToast_content", {
|
||||||
|
|
|
@ -16,16 +16,16 @@ limitations under the License.
|
||||||
|
|
||||||
import './skinned-sdk';
|
import './skinned-sdk';
|
||||||
|
|
||||||
import CallHandler, { PlaceCallType, CallHandlerEvent } from '../src/CallHandler';
|
import CallHandler, { CallHandlerEvent } from '../src/CallHandler';
|
||||||
import { stubClient, mkStubRoom } from './test-utils';
|
import { stubClient, mkStubRoom } from './test-utils';
|
||||||
import { MatrixClientPeg } from '../src/MatrixClientPeg';
|
import { MatrixClientPeg } from '../src/MatrixClientPeg';
|
||||||
import dis from '../src/dispatcher/dispatcher';
|
import dis from '../src/dispatcher/dispatcher';
|
||||||
import { CallEvent, CallState } from 'matrix-js-sdk/src/webrtc/call';
|
import { CallEvent, CallState, CallType } from 'matrix-js-sdk/src/webrtc/call';
|
||||||
import DMRoomMap from '../src/utils/DMRoomMap';
|
import DMRoomMap from '../src/utils/DMRoomMap';
|
||||||
import EventEmitter from 'events';
|
import EventEmitter from 'events';
|
||||||
import SdkConfig from '../src/SdkConfig';
|
import SdkConfig from '../src/SdkConfig';
|
||||||
import { ActionPayload } from '../src/dispatcher/payloads';
|
import { ActionPayload } from '../src/dispatcher/payloads';
|
||||||
import { Action } from '../src/dispatcher/actions';
|
import { Action } from "../src/dispatcher/actions";
|
||||||
|
|
||||||
const REAL_ROOM_ID = '$room1:example.org';
|
const REAL_ROOM_ID = '$room1:example.org';
|
||||||
const MAPPED_ROOM_ID = '$room2:example.org';
|
const MAPPED_ROOM_ID = '$room2:example.org';
|
||||||
|
@ -88,6 +88,14 @@ function untilDispatch(waitForAction: string): Promise<ActionPayload> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function untilCallHandlerEvent(callHandler: CallHandler, event: CallHandlerEvent): Promise<void> {
|
||||||
|
return new Promise<void>((resolve) => {
|
||||||
|
callHandler.addListener(event, () => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
describe('CallHandler', () => {
|
describe('CallHandler', () => {
|
||||||
let dmRoomMap;
|
let dmRoomMap;
|
||||||
let callHandler;
|
let callHandler;
|
||||||
|
@ -173,10 +181,7 @@ describe('CallHandler', () => {
|
||||||
},
|
},
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
dis.dispatch({
|
await callHandler.dialNumber('01818118181');
|
||||||
action: Action.DialNumber,
|
|
||||||
number: '01818118181',
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
const viewRoomPayload = await untilDispatch(Action.ViewRoom);
|
const viewRoomPayload = await untilDispatch(Action.ViewRoom);
|
||||||
expect(viewRoomPayload.room_id).toEqual(MAPPED_ROOM_ID);
|
expect(viewRoomPayload.room_id).toEqual(MAPPED_ROOM_ID);
|
||||||
|
@ -186,14 +191,9 @@ describe('CallHandler', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should move calls between rooms when remote asserted identity changes', async () => {
|
it('should move calls between rooms when remote asserted identity changes', async () => {
|
||||||
dis.dispatch({
|
callHandler.placeCall(REAL_ROOM_ID, CallType.Voice);
|
||||||
action: 'place_call',
|
|
||||||
type: PlaceCallType.Voice,
|
|
||||||
room_id: REAL_ROOM_ID,
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
// wait for the call to be set up
|
await untilCallHandlerEvent(callHandler, CallHandlerEvent.CallState);
|
||||||
await untilDispatch('call_state');
|
|
||||||
|
|
||||||
// should start off in the actual room ID it's in at the protocol level
|
// should start off in the actual room ID it's in at the protocol level
|
||||||
expect(callHandler.getCallForRoom(REAL_ROOM_ID)).toBe(fakeCall);
|
expect(callHandler.getCallForRoom(REAL_ROOM_ID)).toBe(fakeCall);
|
||||||
|
|
Loading…
Reference in a new issue