diff --git a/src/CallHandler.js b/src/CallHandler.js
index fd56d7f1b1..da764ec4b6 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
@@ -294,18 +295,8 @@ function _onAction(payload) {
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,28 +307,46 @@ 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':
@@ -378,6 +387,70 @@ 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
+ 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));
+}
+
// 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 41f67ad9d9..9af90d39d4 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",
@@ -42,10 +42,6 @@
"The file '%(fileName)s' failed to upload": "The file '%(fileName)s' failed to upload",
"The file '%(fileName)s' exceeds this home server's size limit for uploads": "The file '%(fileName)s' exceeds this home server's size limit for uploads",
"Upload Failed": "Upload Failed",
- "Failure to create room": "Failure to create room",
- "Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.",
- "Send anyway": "Send anyway",
- "Send": "Send",
"Sun": "Sun",
"Mon": "Mon",
"Tue": "Tue",
@@ -85,7 +81,6 @@
"Failed to invite users to community": "Failed to invite users to community",
"Failed to invite users to %(groupId)s": "Failed to invite users to %(groupId)s",
"Failed to add the following rooms to %(groupId)s:": "Failed to add the following rooms to %(groupId)s:",
- "Unnamed Room": "Unnamed Room",
"Riot does not have permission to send you notifications - please check your browser settings": "Riot does not have permission to send you notifications - please check your browser settings",
"Riot was not given permission to send notifications - please try again": "Riot was not given permission to send notifications - please try again",
"Unable to enable Notifications": "Unable to enable Notifications",
@@ -116,10 +111,8 @@
"You are not in this room.": "You are not in this room.",
"You do not have permission to do that in this room.": "You do not have permission to do that in this room.",
"Missing room_id in request": "Missing room_id in request",
- "Must be viewing a room": "Must be viewing a room",
"Room %(roomId)s not visible": "Room %(roomId)s not visible",
"Missing user_id in request": "Missing user_id in request",
- "Failed to lookup current room": "Failed to lookup current room",
"Usage": "Usage",
"/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.",
@@ -184,13 +177,18 @@
"%(names)s and %(count)s others are typing|other": "%(names)s and %(count)s others are typing",
"%(names)s and %(count)s others are typing|one": "%(names)s and one other is typing",
"%(names)s and %(lastPerson)s are typing": "%(names)s and %(lastPerson)s are typing",
+ "Failure to create room": "Failure to create room",
+ "Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.",
+ "Send anyway": "Send anyway",
+ "Send": "Send",
+ "Unnamed Room": "Unnamed Room",
"Your browser does not support the required cryptography extensions": "Your browser does not support the required cryptography extensions",
"Not a valid Riot keyfile": "Not a valid Riot keyfile",
"Authentication check failed: incorrect password?": "Authentication check failed: incorrect password?",
"Failed to join room": "Failed to join room",
"Message Replies": "Message Replies",
"Message Pinning": "Message Pinning",
- "Tag Panel": "Tag Panel",
+ "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",
@@ -297,29 +295,6 @@
"Off": "Off",
"On": "On",
"Noisy": "Noisy",
- "Invalid alias format": "Invalid alias format",
- "'%(alias)s' is not a valid format for an alias": "'%(alias)s' is not a valid format for an alias",
- "Invalid address format": "Invalid address format",
- "'%(alias)s' is not a valid format for an address": "'%(alias)s' is not a valid format for an address",
- "not specified": "not specified",
- "not set": "not set",
- "Remote addresses for this room:": "Remote addresses for this room:",
- "Addresses": "Addresses",
- "The main address for this room is": "The main address for this room is",
- "Local addresses for this room:": "Local addresses for this room:",
- "This room has no local addresses": "This room has no local addresses",
- "New address (e.g. #foo:%(localDomain)s)": "New address (e.g. #foo:%(localDomain)s)",
- "Invalid community ID": "Invalid community ID",
- "'%(groupId)s' is not a valid community ID": "'%(groupId)s' is not a valid community ID",
- "Flair": "Flair",
- "Showing flair for these communities:": "Showing flair for these communities:",
- "This room is not showing flair for any communities": "This room is not showing flair for any communities",
- "New community ID (e.g. +foo:%(localDomain)s)": "New community ID (e.g. +foo:%(localDomain)s)",
- "You have enabled URL previews by default.": "You have enabled URL previews by default.",
- "You have disabled URL previews by default.": "You have disabled URL previews by default.",
- "URL previews are enabled by default for participants in this room.": "URL previews are enabled by default for participants in this room.",
- "URL previews are disabled by default for participants in this room.": "URL previews are disabled by default for participants in this room.",
- "URL Previews": "URL Previews",
"Cannot add any more widgets": "Cannot add any more widgets",
"The maximum permitted number of widgets have already been added to this room.": "The maximum permitted number of widgets have already been added to this room.",
"Add a widget": "Add a widget",
@@ -419,11 +394,11 @@
"numbullet": "numbullet",
"Markdown is disabled": "Markdown is disabled",
"Markdown is enabled": "Markdown is enabled",
+ "Unpin Message": "Unpin Message",
+ "Jump to message": "Jump to message",
"No pinned messages.": "No pinned messages.",
"Loading...": "Loading...",
"Pinned Messages": "Pinned Messages",
- "Unpin Message": "Unpin Message",
- "Jump to message": "Jump to message",
"%(duration)ss": "%(duration)ss",
"%(duration)sm": "%(duration)sm",
"%(duration)sh": "%(duration)sh",
@@ -557,6 +532,29 @@
"Scroll to unread messages": "Scroll to unread messages",
"Jump to first unread message.": "Jump to first unread message.",
"Close": "Close",
+ "Invalid alias format": "Invalid alias format",
+ "'%(alias)s' is not a valid format for an alias": "'%(alias)s' is not a valid format for an alias",
+ "Invalid address format": "Invalid address format",
+ "'%(alias)s' is not a valid format for an address": "'%(alias)s' is not a valid format for an address",
+ "not specified": "not specified",
+ "not set": "not set",
+ "Remote addresses for this room:": "Remote addresses for this room:",
+ "Addresses": "Addresses",
+ "The main address for this room is": "The main address for this room is",
+ "Local addresses for this room:": "Local addresses for this room:",
+ "This room has no local addresses": "This room has no local addresses",
+ "New address (e.g. #foo:%(localDomain)s)": "New address (e.g. #foo:%(localDomain)s)",
+ "Invalid community ID": "Invalid community ID",
+ "'%(groupId)s' is not a valid community ID": "'%(groupId)s' is not a valid community ID",
+ "Flair": "Flair",
+ "Showing flair for these communities:": "Showing flair for these communities:",
+ "This room is not showing flair for any communities": "This room is not showing flair for any communities",
+ "New community ID (e.g. +foo:%(localDomain)s)": "New community ID (e.g. +foo:%(localDomain)s)",
+ "You have enabled URL previews by default.": "You have enabled URL previews by default.",
+ "You have disabled URL previews by default.": "You have disabled URL previews by default.",
+ "URL previews are enabled by default for participants in this room.": "URL previews are enabled by default for participants in this room.",
+ "URL previews are disabled by default for participants in this room.": "URL previews are disabled by default for participants in this room.",
+ "URL Previews": "URL Previews",
"Sunday": "Sunday",
"Monday": "Monday",
"Tuesday": "Tuesday",
@@ -876,6 +874,10 @@
"Public Chat": "Public Chat",
"Custom": "Custom",
"Alias (optional)": "Alias (optional)",
+ "Reject invitation": "Reject invitation",
+ "Are you sure you want to reject the invitation?": "Are you sure you want to reject the invitation?",
+ "Unable to reject invite": "Unable to reject invite",
+ "Reject": "Reject",
"You cannot delete this message. (%(code)s)": "You cannot delete this message. (%(code)s)",
"Resend": "Resend",
"Cancel Sending": "Cancel Sending",
@@ -895,7 +897,6 @@
"Mentions only": "Mentions only",
"Leave": "Leave",
"Forget": "Forget",
- "Reject": "Reject",
"Low Priority": "Low Priority",
"Direct Chat": "Direct Chat",
"View Community": "View Community",
@@ -930,7 +931,6 @@
"Failed to upload image": "Failed to upload image",
"Failed to update community": "Failed to update community",
"Unable to accept invite": "Unable to accept invite",
- "Unable to reject invite": "Unable to reject invite",
"Unable to join community": "Unable to join community",
"Leave Community": "Leave Community",
"Leave %(groupName)s?": "Leave %(groupName)s?",
@@ -956,8 +956,6 @@
"Failed to load %(groupId)s": "Failed to load %(groupId)s",
"Couldn't load home page": "Couldn't load home page",
"Login": "Login",
- "Reject invitation": "Reject invitation",
- "Are you sure you want to reject the invitation?": "Are you sure you want to reject the invitation?",
"Failed to reject invitation": "Failed to reject invitation",
"This room is not public. You will not be able to rejoin without an invite.": "This room is not public. You will not be able to rejoin without an invite.",
"Are you sure you want to leave the room '%(roomName)s'?": "Are you sure you want to leave the room '%(roomName)s'?",
diff --git a/src/settings/Settings.js b/src/settings/Settings.js
index 039bd78d79..1eb30f5cc1 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.
@@ -88,6 +89,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'),