2015-09-17 17:23:38 +00:00
|
|
|
/*
|
2016-01-07 04:06:39 +00:00
|
|
|
Copyright 2015, 2016 OpenMarket Ltd
|
2018-06-15 14:12:32 +00:00
|
|
|
Copyright 2017, 2018 New Vector Ltd
|
2015-09-17 17:23:38 +00:00
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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>
|
|
|
|
* }
|
|
|
|
*/
|
|
|
|
|
2017-06-12 19:15:14 +00:00
|
|
|
import MatrixClientPeg from './MatrixClientPeg';
|
|
|
|
import PlatformPeg from './PlatformPeg';
|
|
|
|
import Modal from './Modal';
|
|
|
|
import sdk from './index';
|
2017-05-25 10:39:08 +00:00
|
|
|
import { _t } from './languageHandler';
|
2017-06-12 19:15:14 +00:00
|
|
|
import Matrix from 'matrix-js-sdk';
|
|
|
|
import dis from './dispatcher';
|
2017-11-15 17:21:04 +00:00
|
|
|
import { showUnknownDeviceDialogForCalls } from './cryptodevices';
|
2018-06-15 14:12:32 +00:00
|
|
|
import SettingsStore from "./settings/SettingsStore";
|
2015-09-17 17:23:38 +00:00
|
|
|
|
2015-09-30 17:21:25 +00:00
|
|
|
global.mxCalls = {
|
2015-09-17 17:23:38 +00:00
|
|
|
//room_id: MatrixCall
|
|
|
|
};
|
2017-10-11 16:56:17 +00:00
|
|
|
const calls = global.mxCalls;
|
|
|
|
let ConferenceHandler = null;
|
2015-09-17 17:23:38 +00:00
|
|
|
|
2017-10-11 16:56:17 +00:00
|
|
|
const audioPromises = {};
|
2016-08-31 15:38:37 +00:00
|
|
|
|
2015-09-17 17:23:38 +00:00
|
|
|
function play(audioId) {
|
|
|
|
// TODO: Attach an invisible element for this instead
|
|
|
|
// which listens?
|
2017-10-11 16:56:17 +00:00
|
|
|
const audio = document.getElementById(audioId);
|
2015-09-17 17:23:38 +00:00
|
|
|
if (audio) {
|
2016-08-31 15:38:37 +00:00
|
|
|
if (audioPromises[audioId]) {
|
|
|
|
audioPromises[audioId] = audioPromises[audioId].then(()=>{
|
|
|
|
audio.load();
|
2016-08-31 16:25:41 +00:00
|
|
|
return audio.play();
|
2016-08-31 15:38:37 +00:00
|
|
|
});
|
2017-10-11 16:56:17 +00:00
|
|
|
} else {
|
2016-08-31 15:38:37 +00:00
|
|
|
audioPromises[audioId] = audio.play();
|
|
|
|
}
|
2015-09-17 17:23:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function pause(audioId) {
|
|
|
|
// TODO: Attach an invisible element for this instead
|
|
|
|
// which listens?
|
2017-10-11 16:56:17 +00:00
|
|
|
const audio = document.getElementById(audioId);
|
2015-09-17 17:23:38 +00:00
|
|
|
if (audio) {
|
2016-08-31 15:38:37 +00:00
|
|
|
if (audioPromises[audioId]) {
|
|
|
|
audioPromises[audioId] = audioPromises[audioId].then(()=>audio.pause());
|
2017-10-11 16:56:17 +00:00
|
|
|
} else {
|
2016-08-31 15:38:37 +00:00
|
|
|
// pause doesn't actually return a promise, but might as well do this for symmetry with play();
|
|
|
|
audioPromises[audioId] = audio.pause();
|
|
|
|
}
|
2015-09-17 17:23:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-15 17:21:04 +00:00
|
|
|
function _reAttemptCall(call) {
|
|
|
|
if (call.direction === 'outbound') {
|
|
|
|
dis.dispatch({
|
|
|
|
action: 'place_call',
|
|
|
|
room_id: call.roomId,
|
|
|
|
type: call.type,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
call.answer();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-17 17:23:38 +00:00
|
|
|
function _setCallListeners(call) {
|
|
|
|
call.on("error", function(err) {
|
2017-02-22 16:12:37 +00:00
|
|
|
console.error("Call error: %s", err);
|
|
|
|
console.error(err.stack);
|
2017-11-15 10:49:29 +00:00
|
|
|
if (err.code === 'unknown_devices') {
|
|
|
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
|
|
|
|
|
|
|
Modal.createTrackedDialog('Call Failed', '', QuestionDialog, {
|
|
|
|
title: _t('Call Failed'),
|
|
|
|
description: _t(
|
|
|
|
"There are unknown devices in this room: "+
|
|
|
|
"if you proceed without verifying them, it will be "+
|
2017-11-15 17:21:04 +00:00
|
|
|
"possible for someone to eavesdrop on your call."
|
2017-11-15 10:49:29 +00:00
|
|
|
),
|
|
|
|
button: _t('Review Devices'),
|
|
|
|
onFinished: function(confirmed) {
|
|
|
|
if (confirmed) {
|
|
|
|
const room = MatrixClientPeg.get().getRoom(call.roomId);
|
2017-11-15 17:21:04 +00:00
|
|
|
showUnknownDeviceDialogForCalls(
|
|
|
|
MatrixClientPeg.get(),
|
|
|
|
room,
|
|
|
|
() => {
|
|
|
|
_reAttemptCall(call);
|
|
|
|
},
|
|
|
|
call.direction === 'outbound' ? _t("Call Anyway") : _t("Answer Anyway"),
|
2017-11-16 17:59:42 +00:00
|
|
|
call.direction === 'outbound' ? _t("Call") : _t("Answer"),
|
2017-11-15 17:21:04 +00:00
|
|
|
);
|
2017-11-15 10:49:29 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
2017-11-15 17:21:04 +00:00
|
|
|
} else {
|
|
|
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
|
|
|
|
|
|
Modal.createTrackedDialog('Call Failed', '', ErrorDialog, {
|
|
|
|
title: _t('Call Failed'),
|
|
|
|
description: err.message,
|
|
|
|
});
|
2017-11-15 10:49:29 +00:00
|
|
|
}
|
2017-02-22 16:12:37 +00:00
|
|
|
});
|
2015-09-17 17:23:38 +00:00
|
|
|
call.on("hangup", function() {
|
|
|
|
_setCallState(undefined, call.roomId, "ended");
|
|
|
|
});
|
|
|
|
// map web rtc states to dummy UI state
|
|
|
|
// ringing|ringback|connected|ended|busy|stop_ringback|stop_ringing
|
|
|
|
call.on("state", function(newState, oldState) {
|
|
|
|
if (newState === "ringing") {
|
|
|
|
_setCallState(call, call.roomId, "ringing");
|
|
|
|
pause("ringbackAudio");
|
2017-10-11 16:56:17 +00:00
|
|
|
} else if (newState === "invite_sent") {
|
2015-09-17 17:23:38 +00:00
|
|
|
_setCallState(call, call.roomId, "ringback");
|
|
|
|
play("ringbackAudio");
|
2017-10-11 16:56:17 +00:00
|
|
|
} else if (newState === "ended" && oldState === "connected") {
|
2015-09-30 15:50:46 +00:00
|
|
|
_setCallState(undefined, call.roomId, "ended");
|
2015-09-17 17:23:38 +00:00
|
|
|
pause("ringbackAudio");
|
|
|
|
play("callendAudio");
|
2017-10-11 16:56:17 +00:00
|
|
|
} else if (newState === "ended" && oldState === "invite_sent" &&
|
2015-09-17 17:23:38 +00:00
|
|
|
(call.hangupParty === "remote" ||
|
|
|
|
(call.hangupParty === "local" && call.hangupReason === "invite_timeout")
|
|
|
|
)) {
|
|
|
|
_setCallState(call, call.roomId, "busy");
|
|
|
|
pause("ringbackAudio");
|
|
|
|
play("busyAudio");
|
2017-10-11 16:56:17 +00:00
|
|
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
2017-07-27 16:19:18 +00:00
|
|
|
Modal.createTrackedDialog('Call Handler', 'Call Timeout', ErrorDialog, {
|
2017-05-23 14:16:31 +00:00
|
|
|
title: _t('Call Timeout'),
|
|
|
|
description: _t('The remote side failed to pick up') + '.',
|
2015-09-17 17:23:38 +00:00
|
|
|
});
|
2017-10-11 16:56:17 +00:00
|
|
|
} else if (oldState === "invite_sent") {
|
2015-09-17 17:23:38 +00:00
|
|
|
_setCallState(call, call.roomId, "stop_ringback");
|
|
|
|
pause("ringbackAudio");
|
2017-10-11 16:56:17 +00:00
|
|
|
} else if (oldState === "ringing") {
|
2015-09-17 17:23:38 +00:00
|
|
|
_setCallState(call, call.roomId, "stop_ringing");
|
|
|
|
pause("ringbackAudio");
|
2017-10-11 16:56:17 +00:00
|
|
|
} else if (newState === "connected") {
|
2015-09-17 17:23:38 +00:00
|
|
|
_setCallState(call, call.roomId, "connected");
|
|
|
|
pause("ringbackAudio");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function _setCallState(call, roomId, status) {
|
|
|
|
console.log(
|
2017-10-11 16:56:17 +00:00
|
|
|
"Call state in %s changed to %s (%s)", roomId, status, (call ? call.call_state : "-"),
|
2015-09-17 17:23:38 +00:00
|
|
|
);
|
|
|
|
calls[roomId] = call;
|
2015-12-17 02:49:09 +00:00
|
|
|
|
|
|
|
if (status === "ringing") {
|
2017-01-20 14:22:27 +00:00
|
|
|
play("ringAudio");
|
2017-10-11 16:56:17 +00:00
|
|
|
} else if (call && call.call_state === "ringing") {
|
2017-01-20 14:22:27 +00:00
|
|
|
pause("ringAudio");
|
2015-12-17 02:49:09 +00:00
|
|
|
}
|
|
|
|
|
2015-09-17 17:23:38 +00:00
|
|
|
if (call) {
|
|
|
|
call.call_state = status;
|
|
|
|
}
|
|
|
|
dis.dispatch({
|
|
|
|
action: 'call_state',
|
2017-05-22 17:33:14 +00:00
|
|
|
room_id: roomId,
|
|
|
|
state: status,
|
2015-09-17 17:23:38 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-09-30 17:21:25 +00:00
|
|
|
function _onAction(payload) {
|
2015-09-30 15:50:46 +00:00
|
|
|
function placeCall(newCall) {
|
|
|
|
_setCallListeners(newCall);
|
|
|
|
if (payload.type === 'voice') {
|
|
|
|
newCall.placeVoiceCall();
|
2017-10-11 16:56:17 +00:00
|
|
|
} else if (payload.type === 'video') {
|
2015-10-21 00:21:21 +00:00
|
|
|
newCall.placeVideoCall(
|
|
|
|
payload.remote_element,
|
2017-10-11 16:56:17 +00:00
|
|
|
payload.local_element,
|
2015-10-21 00:21:21 +00:00
|
|
|
);
|
2017-10-11 16:56:17 +00:00
|
|
|
} else if (payload.type === 'screensharing') {
|
2017-01-10 18:37:57 +00:00
|
|
|
const screenCapErrorString = PlatformPeg.get().screenCaptureErrorString();
|
|
|
|
if (screenCapErrorString) {
|
|
|
|
_setCallState(undefined, newCall.roomId, "ended");
|
|
|
|
console.log("Can't capture screen: " + screenCapErrorString);
|
2017-01-11 10:06:48 +00:00
|
|
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
2017-07-27 16:19:18 +00:00
|
|
|
Modal.createTrackedDialog('Call Handler', 'Unable to capture screen', ErrorDialog, {
|
2017-05-23 14:16:31 +00:00
|
|
|
title: _t('Unable to capture screen'),
|
|
|
|
description: screenCapErrorString,
|
2017-01-11 10:06:48 +00:00
|
|
|
});
|
2017-01-10 18:37:57 +00:00
|
|
|
return;
|
|
|
|
}
|
2015-10-20 15:45:26 +00:00
|
|
|
newCall.placeScreenSharingCall(
|
2015-09-30 15:50:46 +00:00
|
|
|
payload.remote_element,
|
2017-10-11 16:56:17 +00:00
|
|
|
payload.local_element,
|
2015-09-30 15:50:46 +00:00
|
|
|
);
|
2017-10-11 16:56:17 +00:00
|
|
|
} else {
|
2015-09-30 15:50:46 +00:00
|
|
|
console.error("Unknown conf call type: %s", payload.type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-17 17:23:38 +00:00
|
|
|
switch (payload.action) {
|
|
|
|
case 'place_call':
|
2015-09-30 15:50:46 +00:00
|
|
|
if (module.exports.getAnyActiveCall()) {
|
2016-08-02 13:04:20 +00:00
|
|
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
2017-07-27 16:19:18 +00:00
|
|
|
Modal.createTrackedDialog('Call Handler', 'Existing Call', ErrorDialog, {
|
2017-05-23 14:16:31 +00:00
|
|
|
title: _t('Existing Call'),
|
2017-06-01 14:18:06 +00:00
|
|
|
description: _t('You are already in a call.'),
|
2015-09-30 15:50:46 +00:00
|
|
|
});
|
2015-09-17 17:23:38 +00:00
|
|
|
return; // don't allow >1 call to be placed.
|
|
|
|
}
|
2015-11-02 17:39:00 +00:00
|
|
|
|
|
|
|
// if the runtime env doesn't do VoIP, whine.
|
|
|
|
if (!MatrixClientPeg.get().supportsVoip()) {
|
2016-08-02 13:04:20 +00:00
|
|
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
2017-07-27 16:19:18 +00:00
|
|
|
Modal.createTrackedDialog('Call Handler', 'VoIP is unsupported', ErrorDialog, {
|
2017-05-23 14:16:31 +00:00
|
|
|
title: _t('VoIP is unsupported'),
|
2017-06-01 14:18:06 +00:00
|
|
|
description: _t('You cannot place VoIP calls in this browser.'),
|
2015-11-02 17:39:00 +00:00
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-09-17 17:23:38 +00:00
|
|
|
var room = MatrixClientPeg.get().getRoom(payload.room_id);
|
|
|
|
if (!room) {
|
|
|
|
console.error("Room %s does not exist.", payload.room_id);
|
|
|
|
return;
|
|
|
|
}
|
2015-09-30 15:50:46 +00:00
|
|
|
|
2015-09-17 17:23:38 +00:00
|
|
|
var members = room.getJoinedMembers();
|
2015-09-30 15:50:46 +00:00
|
|
|
if (members.length <= 1) {
|
2016-08-02 13:04:20 +00:00
|
|
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
2017-07-27 16:19:18 +00:00
|
|
|
Modal.createTrackedDialog('Call Handler', 'Cannot place call with self', ErrorDialog, {
|
2017-06-01 14:18:06 +00:00
|
|
|
description: _t('You cannot place a call with yourself.'),
|
2015-09-17 17:23:38 +00:00
|
|
|
});
|
|
|
|
return;
|
2017-10-11 16:56:17 +00:00
|
|
|
} else if (members.length === 2) {
|
2015-09-30 15:50:46 +00:00
|
|
|
console.log("Place %s call in %s", payload.type, payload.room_id);
|
2017-11-17 14:54:44 +00:00
|
|
|
const call = Matrix.createNewMatrixCall(MatrixClientPeg.get(), payload.room_id);
|
2015-09-30 15:50:46 +00:00
|
|
|
placeCall(call);
|
2017-10-11 16:56:17 +00:00
|
|
|
} else { // > 2
|
2015-09-30 15:50:46 +00:00
|
|
|
dis.dispatch({
|
|
|
|
action: "place_conference_call",
|
|
|
|
room_id: payload.room_id,
|
|
|
|
type: payload.type,
|
|
|
|
remote_element: payload.remote_element,
|
2017-10-11 16:56:17 +00:00
|
|
|
local_element: payload.local_element,
|
2015-09-30 15:50:46 +00:00
|
|
|
});
|
2015-09-17 17:23:38 +00:00
|
|
|
}
|
2015-09-30 15:50:46 +00:00
|
|
|
break;
|
|
|
|
case 'place_conference_call':
|
|
|
|
console.log("Place conference call in %s", payload.room_id);
|
2018-06-15 14:12:32 +00:00
|
|
|
|
|
|
|
if (MatrixClientPeg.get().isRoomEncrypted(payload.room_id)) {
|
2016-11-11 15:45:29 +00:00
|
|
|
// Conference calls are implemented by sending the media to central
|
|
|
|
// server which combines the audio from all the participants together
|
|
|
|
// into a single stream. This is incompatible with end-to-end encryption
|
|
|
|
// because a central server would be decrypting the audio for each
|
|
|
|
// participant.
|
|
|
|
// Therefore we disable conference calling in E2E rooms.
|
|
|
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
2017-07-27 16:19:18 +00:00
|
|
|
Modal.createTrackedDialog('Call Handler', 'Conference calls unsupported e2e', ErrorDialog, {
|
2017-05-23 14:16:31 +00:00
|
|
|
description: _t('Conference calls are not supported in encrypted rooms'),
|
2016-11-11 15:45:29 +00:00
|
|
|
});
|
2018-06-15 14:12:32 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (SettingsStore.isFeatureEnabled('feature_jitsi')) {
|
|
|
|
_startCallApp(payload.room_id, payload.type);
|
2017-10-11 16:56:17 +00:00
|
|
|
} else {
|
2018-06-15 14:12:32 +00:00
|
|
|
if (!ConferenceHandler) {
|
|
|
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
|
|
Modal.createTrackedDialog('Call Handler', 'Conference call unsupported client', ErrorDialog, {
|
|
|
|
description: _t('Conference calls are not supported in this client'),
|
|
|
|
});
|
|
|
|
} else if (!MatrixClientPeg.get().supportsVoip()) {
|
|
|
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
|
|
Modal.createTrackedDialog('Call Handler', 'VoIP is unsupported', ErrorDialog, {
|
|
|
|
title: _t('VoIP is unsupported'),
|
|
|
|
description: _t('You cannot place VoIP calls in this browser.'),
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
|
|
|
Modal.createTrackedDialog('Call Handler', 'Conference calling in development', QuestionDialog, {
|
|
|
|
title: _t('Warning!'),
|
|
|
|
description: _t('Conference calling is in development and may not be reliable.'),
|
|
|
|
onFinished: (confirm)=>{
|
|
|
|
if (confirm) {
|
|
|
|
ConferenceHandler.createNewMatrixCall(
|
|
|
|
MatrixClientPeg.get(), payload.room_id,
|
|
|
|
).done(function(call) {
|
|
|
|
placeCall(call);
|
|
|
|
}, function(err) {
|
|
|
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
|
|
console.error("Conference call failed: " + err);
|
|
|
|
Modal.createTrackedDialog('Call Handler', 'Failed to set up conference call', ErrorDialog, {
|
|
|
|
title: _t('Failed to set up conference call'),
|
|
|
|
description: _t('Conference call failed.') + ' ' + ((err && err.message) ? err.message : ''),
|
|
|
|
});
|
2016-08-03 18:56:04 +00:00
|
|
|
});
|
2018-06-15 14:12:32 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
2015-09-17 17:23:38 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'incoming_call':
|
2015-09-30 15:50:46 +00:00
|
|
|
if (module.exports.getAnyActiveCall()) {
|
2016-08-15 15:17:35 +00:00
|
|
|
// ignore multiple incoming calls. in future, we may want a line-1/line-2 setup.
|
|
|
|
// we avoid rejecting with "busy" in case the user wants to answer it on a different device.
|
|
|
|
// in future we could signal a "local busy" as a warning to the caller.
|
|
|
|
// see https://github.com/vector-im/vector-web/issues/1964
|
|
|
|
return;
|
2015-09-17 17:23:38 +00:00
|
|
|
}
|
2015-11-02 17:39:00 +00:00
|
|
|
|
|
|
|
// if the runtime env doesn't do VoIP, stop here.
|
|
|
|
if (!MatrixClientPeg.get().supportsVoip()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-09-17 17:23:38 +00:00
|
|
|
var call = payload.call;
|
|
|
|
_setCallListeners(call);
|
|
|
|
_setCallState(call, call.roomId, "ringing");
|
|
|
|
break;
|
|
|
|
case 'hangup':
|
|
|
|
if (!calls[payload.room_id]) {
|
|
|
|
return; // no call to hangup
|
|
|
|
}
|
|
|
|
calls[payload.room_id].hangup();
|
|
|
|
_setCallState(null, payload.room_id, "ended");
|
|
|
|
break;
|
|
|
|
case 'answer':
|
|
|
|
if (!calls[payload.room_id]) {
|
|
|
|
return; // no call to answer
|
|
|
|
}
|
|
|
|
calls[payload.room_id].answer();
|
|
|
|
_setCallState(calls[payload.room_id], payload.room_id, "connected");
|
|
|
|
dis.dispatch({
|
|
|
|
action: "view_room",
|
2017-10-11 16:56:17 +00:00
|
|
|
room_id: payload.room_id,
|
2015-09-17 17:23:38 +00:00
|
|
|
});
|
|
|
|
break;
|
|
|
|
}
|
2015-09-30 17:21:25 +00:00
|
|
|
}
|
2018-06-15 14:12:32 +00:00
|
|
|
|
|
|
|
function _startCallApp(roomId, type) {
|
|
|
|
dis.dispatch({
|
|
|
|
action: 'appsDrawer',
|
|
|
|
show: true,
|
|
|
|
});
|
|
|
|
|
|
|
|
const room = MatrixClientPeg.get().getRoom(roomId);
|
|
|
|
if (!room) {
|
|
|
|
console.error("Attempted to start conference call widget in unknown room: " + roomId);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const appsStateEvents = room.currentState.getStateEvents('im.vector.modular.widgets');
|
|
|
|
const currentJitsiWidgets = appsStateEvents.filter((ev) => {
|
|
|
|
ev.getContent().type == 'jitsi';
|
|
|
|
});
|
|
|
|
if (currentJitsiWidgets.length > 0) {
|
|
|
|
console.warn(
|
|
|
|
"Refusing to start conference call widget in " + roomId +
|
|
|
|
" a conference call widget is already present",
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// This inherits its poor naming from the field of the same name that goes into
|
|
|
|
// the event. It's just a random string to make the Jitsi URLs unique.
|
|
|
|
const widgetSessionId = Math.random().toString(36).substring(2);
|
|
|
|
const confId = room.roomId.replace(/[^A-Za-z0-9]/g, '') + widgetSessionId;
|
|
|
|
// NB. we can't just encodeURICompoent all of these because the $ signs need to be there
|
|
|
|
const queryString = [
|
|
|
|
'confId='+encodeURIComponent(confId),
|
|
|
|
'isAudioConf='+(type === 'voice' ? 'true' : 'false'),
|
|
|
|
'displayName=$matrix_display_name',
|
|
|
|
'avatarUrl=$matrix_avatar_url',
|
|
|
|
'email=$matrix_user_id',
|
|
|
|
].join('&');
|
|
|
|
const widgetUrl = (
|
|
|
|
'https://scalar.vector.im/api/widgets' +
|
|
|
|
'/jitsi.html?' +
|
|
|
|
queryString
|
|
|
|
);
|
|
|
|
|
|
|
|
const jitsiEvent = {
|
|
|
|
type: 'jitsi',
|
|
|
|
url: widgetUrl,
|
|
|
|
data: {
|
|
|
|
widgetSessionId: widgetSessionId,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
const widgetId = (
|
|
|
|
'jitsi_' +
|
|
|
|
MatrixClientPeg.get().credentials.userId +
|
|
|
|
'_' +
|
|
|
|
Date.now()
|
|
|
|
);
|
|
|
|
MatrixClientPeg.get().sendStateEvent(
|
|
|
|
roomId,
|
|
|
|
'im.vector.modular.widgets',
|
|
|
|
jitsiEvent,
|
|
|
|
widgetId,
|
|
|
|
).then(() => console.log('Sent state'), (e) => console.error(e));
|
|
|
|
}
|
|
|
|
|
2015-09-30 17:21:25 +00:00
|
|
|
// FIXME: Nasty way of making sure we only register
|
|
|
|
// with the dispatcher once
|
|
|
|
if (!global.mxCallHandler) {
|
|
|
|
dis.register(_onAction);
|
|
|
|
}
|
2015-09-30 15:50:46 +00:00
|
|
|
|
2017-10-11 16:56:17 +00:00
|
|
|
const callHandler = {
|
2015-09-30 15:50:46 +00:00
|
|
|
getCallForRoom: function(roomId) {
|
2017-10-11 16:56:17 +00:00
|
|
|
let call = module.exports.getCall(roomId);
|
2015-10-01 08:42:58 +00:00
|
|
|
if (call) return call;
|
|
|
|
|
2015-11-30 18:11:04 +00:00
|
|
|
if (ConferenceHandler) {
|
2015-10-01 08:42:58 +00:00
|
|
|
call = ConferenceHandler.getConferenceCallForRoom(roomId);
|
|
|
|
}
|
|
|
|
if (call) return call;
|
|
|
|
|
|
|
|
return null;
|
2015-09-30 15:50:46 +00:00
|
|
|
},
|
|
|
|
|
2015-09-17 17:23:38 +00:00
|
|
|
getCall: function(roomId) {
|
|
|
|
return calls[roomId] || null;
|
2015-09-30 15:50:46 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
getAnyActiveCall: function() {
|
2017-10-11 16:56:17 +00:00
|
|
|
const roomsWithCalls = Object.keys(calls);
|
|
|
|
for (let i = 0; i < roomsWithCalls.length; i++) {
|
2015-09-30 15:50:46 +00:00
|
|
|
if (calls[roomsWithCalls[i]] &&
|
|
|
|
calls[roomsWithCalls[i]].call_state !== "ended") {
|
|
|
|
return calls[roomsWithCalls[i]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
2015-11-30 18:11:04 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
setConferenceHandler: function(confHandler) {
|
|
|
|
ConferenceHandler = confHandler;
|
2016-03-05 02:30:18 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
getConferenceHandler: function() {
|
|
|
|
return ConferenceHandler;
|
2017-10-11 16:56:17 +00:00
|
|
|
},
|
2015-09-17 17:23:38 +00:00
|
|
|
};
|
2015-09-30 17:21:25 +00:00
|
|
|
// Only things in here which actually need to be global are the
|
|
|
|
// calls list (done separately) and making sure we only register
|
|
|
|
// with the dispatcher once (which uses this mechanism but checks
|
|
|
|
// separately). This could be tidied up.
|
|
|
|
if (global.mxCallHandler === undefined) {
|
|
|
|
global.mxCallHandler = callHandler;
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = global.mxCallHandler;
|