diff --git a/.eslintignore.errorfiles b/.eslintignore.errorfiles
index 42818244b3..e5eb9d70c8 100644
--- a/.eslintignore.errorfiles
+++ b/.eslintignore.errorfiles
@@ -4,7 +4,6 @@ src/autocomplete/AutocompleteProvider.js
src/autocomplete/Autocompleter.js
src/autocomplete/EmojiProvider.js
src/autocomplete/UserProvider.js
-src/CallHandler.js
src/component-index.js
src/components/structures/BottomLeftMenu.js
src/components/structures/CompatibilityPage.js
diff --git a/src/CallHandler.js b/src/CallHandler.js
index fd56d7f1b1..a65d82fe85 100644
--- a/src/CallHandler.js
+++ b/src/CallHandler.js
@@ -1,6 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
-Copyright 2017 New Vector Ltd
+Copyright 2017, 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -60,6 +60,7 @@ import { _t } from './languageHandler';
import Matrix from 'matrix-js-sdk';
import dis from './dispatcher';
import { showUnknownDeviceDialogForCalls } from './cryptodevices';
+import SettingsStore from "./settings/SettingsStore";
global.mxCalls = {
//room_id: MatrixCall
@@ -123,7 +124,7 @@ function _setCallListeners(call) {
description: _t(
"There are unknown devices in this room: "+
"if you proceed without verifying them, it will be "+
- "possible for someone to eavesdrop on your call."
+ "possible for someone to eavesdrop on your call.",
),
button: _t('Review Devices'),
onFinished: function(confirmed) {
@@ -246,66 +247,58 @@ function _onAction(payload) {
switch (payload.action) {
case 'place_call':
- if (module.exports.getAnyActiveCall()) {
- const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
- Modal.createTrackedDialog('Call Handler', 'Existing Call', ErrorDialog, {
- title: _t('Existing Call'),
- description: _t('You are already in a call.'),
- });
- return; // don't allow >1 call to be placed.
- }
+ {
+ if (module.exports.getAnyActiveCall()) {
+ const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+ Modal.createTrackedDialog('Call Handler', 'Existing Call', ErrorDialog, {
+ title: _t('Existing Call'),
+ description: _t('You are already in a call.'),
+ });
+ return; // don't allow >1 call to be placed.
+ }
- // if the runtime env doesn't do VoIP, whine.
- 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.'),
- });
- return;
- }
+ // if the runtime env doesn't do VoIP, whine.
+ 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.'),
+ });
+ return;
+ }
- var room = MatrixClientPeg.get().getRoom(payload.room_id);
- if (!room) {
- console.error("Room %s does not exist.", payload.room_id);
- return;
- }
+ const room = MatrixClientPeg.get().getRoom(payload.room_id);
+ if (!room) {
+ console.error("Room %s does not exist.", payload.room_id);
+ return;
+ }
- var members = room.getJoinedMembers();
- if (members.length <= 1) {
- const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
- 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) {
- console.log("Place %s call in %s", payload.type, payload.room_id);
- const call = Matrix.createNewMatrixCall(MatrixClientPeg.get(), payload.room_id);
- placeCall(call);
- } else { // > 2
- dis.dispatch({
- action: "place_conference_call",
- room_id: payload.room_id,
- type: payload.type,
- remote_element: payload.remote_element,
- local_element: payload.local_element,
- });
+ const members = room.getJoinedMembers();
+ if (members.length <= 1) {
+ const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+ 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) {
+ console.log("Place %s call in %s", payload.type, payload.room_id);
+ const call = Matrix.createNewMatrixCall(MatrixClientPeg.get(), payload.room_id);
+ placeCall(call);
+ } else { // > 2
+ dis.dispatch({
+ action: "place_conference_call",
+ room_id: payload.room_id,
+ type: payload.type,
+ remote_element: payload.remote_element,
+ local_element: payload.local_element,
+ });
+ }
}
break;
case 'place_conference_call':
console.log("Place conference call in %s", payload.room_id);
- 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 if (MatrixClientPeg.get().isRoomEncrypted(payload.room_id)) {
+
+ if (MatrixClientPeg.get().isRoomEncrypted(payload.room_id)) {
// 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
@@ -316,47 +309,75 @@ function _onAction(payload) {
Modal.createTrackedDialog('Call Handler', 'Conference calls unsupported e2e', ErrorDialog, {
description: _t('Conference calls are not supported in encrypted rooms'),
});
+ return;
+ }
+
+ if (SettingsStore.isFeatureEnabled('feature_jitsi')) {
+ _startCallApp(payload.room_id, payload.type);
} 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 : ''),
+ 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 : '')
+ ),
+ },
+ );
});
- });
- }
- },
- });
+ }
+ },
+ });
+ }
}
break;
case 'incoming_call':
- if (module.exports.getAnyActiveCall()) {
- // 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;
- }
+ {
+ if (module.exports.getAnyActiveCall()) {
+ // 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;
+ }
- // if the runtime env doesn't do VoIP, stop here.
- if (!MatrixClientPeg.get().supportsVoip()) {
- return;
- }
+ // if the runtime env doesn't do VoIP, stop here.
+ if (!MatrixClientPeg.get().supportsVoip()) {
+ return;
+ }
- var call = payload.call;
- _setCallListeners(call);
- _setCallState(call, call.roomId, "ringing");
+ const call = payload.call;
+ _setCallListeners(call);
+ _setCallState(call, call.roomId, "ringing");
+ }
break;
case 'hangup':
if (!calls[payload.room_id]) {
@@ -378,6 +399,71 @@ function _onAction(payload) {
break;
}
}
+
+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
+ // (but currently the only thing that needs encoding is the confId)
+ 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 jitsi widget state event'), (e) => console.error(e));
+}
+
// FIXME: Nasty way of making sure we only register
// with the dispatcher once
if (!global.mxCallHandler) {
diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js
index 8763ea3d7f..f0b7eaa1d7 100644
--- a/src/components/views/rooms/AppsDrawer.js
+++ b/src/components/views/rooms/AppsDrawer.js
@@ -94,15 +94,7 @@ module.exports = React.createClass({
const hideWidgetKey = this.props.room.roomId + '_hide_widget_drawer';
switch (action.action) {
case 'appsDrawer':
- // When opening the app drawer when there aren't any apps,
- // auto-launch the integrations manager to skip the awkward
- // click on "Add widget"
if (action.show) {
- const apps = this._getApps();
- if (apps.length === 0) {
- this._launchManageIntegrations();
- }
-
localStorage.removeItem(hideWidgetKey);
} else {
// Store hidden state of widget
diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js
index 28a90b375a..bac996e65c 100644
--- a/src/components/views/rooms/MessageComposer.js
+++ b/src/components/views/rooms/MessageComposer.js
@@ -159,54 +159,20 @@ export default class MessageComposer extends React.Component {
});
}
- // _startCallApp(isAudioConf) {
- // dis.dispatch({
- // action: 'appsDrawer',
- // show: true,
- // });
-
- // const appsStateEvents = this.props.room.currentState.getStateEvents('im.vector.modular.widgets', '');
- // let appsStateEvent = {};
- // if (appsStateEvents) {
- // appsStateEvent = appsStateEvents.getContent();
- // }
- // if (!appsStateEvent.videoConf) {
- // appsStateEvent.videoConf = {
- // type: 'jitsi',
- // // FIXME -- This should not be localhost
- // url: 'http://localhost:8000/jitsi.html',
- // data: {
- // confId: this.props.room.roomId.replace(/[^A-Za-z0-9]/g, '_') + Date.now(),
- // isAudioConf: isAudioConf,
- // },
- // };
- // MatrixClientPeg.get().sendStateEvent(
- // this.props.room.roomId,
- // 'im.vector.modular.widgets',
- // appsStateEvent,
- // '',
- // ).then(() => console.log('Sent state'), (e) => console.error(e));
- // }
- // }
-
onCallClick(ev) {
- // NOTE -- Will be replaced by Jitsi code (currently commented)
dis.dispatch({
action: 'place_call',
type: ev.shiftKey ? "screensharing" : "video",
room_id: this.props.room.roomId,
});
- // this._startCallApp(false);
}
onVoiceCallClick(ev) {
- // NOTE -- Will be replaced by Jitsi code (currently commented)
dis.dispatch({
action: 'place_call',
type: "voice",
room_id: this.props.room.roomId,
});
- // this._startCallApp(true);
}
onInputContentChanged(content: string, selection: {start: number, end: number}) {
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 6d39586c0a..da47140c96 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -33,8 +33,8 @@
"VoIP is unsupported": "VoIP is unsupported",
"You cannot place VoIP calls in this browser.": "You cannot place VoIP calls in this browser.",
"You cannot place a call with yourself.": "You cannot place a call with yourself.",
- "Conference calls are not supported in this client": "Conference calls are not supported in this client",
"Conference calls are not supported in encrypted rooms": "Conference calls are not supported in encrypted rooms",
+ "Conference calls are not supported in this client": "Conference calls are not supported in this client",
"Warning!": "Warning!",
"Conference calling is in development and may not be reliable.": "Conference calling is in development and may not be reliable.",
"Failed to set up conference call": "Failed to set up conference call",
@@ -114,19 +114,36 @@
"Room %(roomId)s not visible": "Room %(roomId)s not visible",
"Missing user_id in request": "Missing user_id in request",
"Usage": "Usage",
+ "Searches DuckDuckGo for results": "Searches DuckDuckGo for results",
"/ddg is not a command": "/ddg is not a command",
"To use it, just wait for autocomplete results to load and tab through them.": "To use it, just wait for autocomplete results to load and tab through them.",
+ "Changes your display nickname": "Changes your display nickname",
+ "Changes colour scheme of current room": "Changes colour scheme of current room",
+ "Sets the room topic": "Sets the room topic",
+ "Invites user with given id to current room": "Invites user with given id to current room",
+ "Joins room with given alias": "Joins room with given alias",
+ "Leave room": "Leave room",
"Unrecognised room alias:": "Unrecognised room alias:",
+ "Kicks user with given id": "Kicks user with given id",
+ "Bans user with given id": "Bans user with given id",
+ "Unbans user with given id": "Unbans user with given id",
+ "Ignores a user, hiding their messages from you": "Ignores a user, hiding their messages from you",
"Ignored user": "Ignored user",
"You are now ignoring %(userId)s": "You are now ignoring %(userId)s",
+ "Stops ignoring a user, showing their messages going forward": "Stops ignoring a user, showing their messages going forward",
"Unignored user": "Unignored user",
"You are no longer ignoring %(userId)s": "You are no longer ignoring %(userId)s",
+ "Define the power level of a user": "Define the power level of a user",
+ "Deops user with given id": "Deops user with given id",
+ "Opens the Developer Tools dialog": "Opens the Developer Tools dialog",
+ "Verifies a user, device, and pubkey tuple": "Verifies a user, device, and pubkey tuple",
"Unknown (user, device) pair:": "Unknown (user, device) pair:",
"Device already verified!": "Device already verified!",
"WARNING: Device already verified, but keys do NOT MATCH!": "WARNING: Device already verified, but keys do NOT MATCH!",
"WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!",
"Verified key": "Verified key",
"The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.",
+ "Displays action": "Displays action",
"Unrecognised command:": "Unrecognised command:",
"Reason": "Reason",
"%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.",
@@ -187,6 +204,7 @@
"Authentication check failed: incorrect password?": "Authentication check failed: incorrect password?",
"Failed to join room": "Failed to join room",
"Message Pinning": "Message Pinning",
+ "Jitsi Conference Calling": "Jitsi Conference Calling",
"Disable Emoji suggestions while typing": "Disable Emoji suggestions while typing",
"Use compact timeline layout": "Use compact timeline layout",
"Hide removed messages": "Hide removed messages",
@@ -497,7 +515,6 @@
"Muted Users": "Muted Users",
"Banned users": "Banned users",
"This room is not accessible by remote Matrix servers": "This room is not accessible by remote Matrix servers",
- "Leave room": "Leave room",
"Favourite": "Favourite",
"Tagged as: ": "Tagged as: ",
"To link to a room it must have an address.": "To link to a room it must have an address.",
@@ -748,8 +765,8 @@
"Matrix ID": "Matrix ID",
"Matrix Room ID": "Matrix Room ID",
"email address": "email address",
- "Try using one of the following valid address types: %(validTypesList)s.": "Try using one of the following valid address types: %(validTypesList)s.",
"You have entered an invalid address.": "You have entered an invalid address.",
+ "Try using one of the following valid address types: %(validTypesList)s.": "Try using one of the following valid address types: %(validTypesList)s.",
"Preparing to send logs": "Preparing to send logs",
"Logs sent": "Logs sent",
"Thank you!": "Thank you!",
@@ -1142,22 +1159,6 @@
"You need to enter a user name.": "You need to enter a user name.",
"An unknown error occurred.": "An unknown error occurred.",
"I already have an account": "I already have an account",
- "Displays action": "Displays action",
- "Bans user with given id": "Bans user with given id",
- "Unbans user with given id": "Unbans user with given id",
- "Define the power level of a user": "Define the power level of a user",
- "Deops user with given id": "Deops user with given id",
- "Invites user with given id to current room": "Invites user with given id to current room",
- "Joins room with given alias": "Joins room with given alias",
- "Sets the room topic": "Sets the room topic",
- "Kicks user with given id": "Kicks user with given id",
- "Changes your display nickname": "Changes your display nickname",
- "Searches DuckDuckGo for results": "Searches DuckDuckGo for results",
- "Changes colour scheme of current room": "Changes colour scheme of current room",
- "Verifies a user, device, and pubkey tuple": "Verifies a user, device, and pubkey tuple",
- "Ignores a user, hiding their messages from you": "Ignores a user, hiding their messages from you",
- "Stops ignoring a user, showing their messages going forward": "Stops ignoring a user, showing their messages going forward",
- "Opens the Developer Tools dialog": "Opens the Developer Tools dialog",
"Commands": "Commands",
"Results from DuckDuckGo": "Results from DuckDuckGo",
"Emoji": "Emoji",
diff --git a/src/settings/Settings.js b/src/settings/Settings.js
index 40bb2dd57e..9f35b0bfec 100644
--- a/src/settings/Settings.js
+++ b/src/settings/Settings.js
@@ -1,5 +1,6 @@
/*
Copyright 2017 Travis Ralston
+Copyright 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -82,6 +83,12 @@ export const SETTINGS = {
supportedLevels: LEVELS_FEATURE,
default: false,
},
+ "feature_jitsi": {
+ isFeature: true,
+ displayName: _td("Jitsi Conference Calling"),
+ supportedLevels: LEVELS_FEATURE,
+ default: false,
+ },
"MessageComposerInput.dontSuggestEmoji": {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
displayName: _td('Disable Emoji suggestions while typing'),