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'),