Remote control - display the authorization dialog in meet (#1541)

* fix(react/participant): store display name in redux

* feat(remotecontrol): Add option to display the authorization dialog in meet

* feat(remotecontrol): Enable ESLint and Flow
This commit is contained in:
hristoterezov 2017-05-03 18:57:52 -05:00 committed by Paweł Domas
parent d694e8df86
commit d91340166d
15 changed files with 658 additions and 252 deletions

View File

@ -35,6 +35,11 @@ module.system=haste
experimental.strict_type_args=true
; FIXME: munge_underscores should be false but right now there are some errors
; if we change the value to false
; Treats class properties with underscore as private. Disabled because currently
; for us "_" can mean protected too.
; munge_underscores=false
munge_underscores=true
module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
@ -46,6 +51,7 @@ suppress_type=$FixMe
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(3[0-8]\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(3[0-8]\\|1[0-9]\\|[1-2][0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
suppress_comment=\\(.\\|\n\\)*\\$FlowDisableNextLine
unsafe.enable_getters_and_setters=true

View File

@ -9,7 +9,7 @@ node_modules/
# supersedes JSHint.
flow-typed/
modules/API/
modules/remotecontrol/RemoteControlParticipant.js
modules/remotecontrol/
modules/transport/
react/

View File

@ -1442,6 +1442,10 @@ export default {
room.on(ConferenceEvents.DISPLAY_NAME_CHANGED, (id, displayName) => {
const formattedDisplayName
= displayName.substr(0, MAX_DISPLAY_NAME_LENGTH);
APP.store.dispatch(participantUpdated({
id,
name: formattedDisplayName
}));
APP.API.notifyDisplayNameChanged(id, formattedDisplayName);
APP.UI.changeDisplayName(id, formattedDisplayName);
});
@ -2053,6 +2057,12 @@ export default {
return;
}
APP.store.dispatch(participantUpdated({
id: this.getMyUserId(),
local: true,
name: formattedNickname
}));
APP.settings.setDisplayName(formattedNickname);
if (room) {
room.setDisplayName(formattedNickname);

View File

@ -212,6 +212,7 @@
},
"dialog": {
"add": "Add",
"allow": "Allow",
"kickMessage": "Ouch! You have been kicked out of the meet!",
"popupError": "Your browser is blocking popup windows from this site. Please enable popups in your browser's security settings and try again.",
"passwordErrorTitle": "Password Error",
@ -330,7 +331,9 @@
"muteParticipantTitle": "Mute this participant?",
"muteParticipantBody": "You won't be able to unmute them, but they can unmute themselves at any time.",
"muteParticipantButton": "Mute",
"remoteControlTitle": "Remote Control",
"remoteControlTitle": "Remote desktop control",
"remoteControlRequestMessage": "Will you allow __user__ to remotely control your desktop?",
"remoteControlShareScreenWarning": "Note that if you press \"Allow\" you will share your screen!",
"remoteControlDeniedMessage": "__user__ rejected your remote control request!",
"remoteControlAllowedMessage": "__user__ accepted your remote control request!",
"remoteControlErrorMessage": "An error occurred while trying to request remote control permissions from __user__!",

View File

@ -0,0 +1,3 @@
module.exports = {
'extends': '../../react/.eslintrc.js'
};

View File

@ -1,20 +1,29 @@
/* global $, JitsiMeetJS, APP */
const logger = require("jitsi-meet-logger").getLogger(__filename);
import * as KeyCodes from "../keycode/keycode";
/* @flow */
import { getLogger } from 'jitsi-meet-logger';
import * as KeyCodes from '../keycode/keycode';
import {
EVENT_TYPES,
PERMISSIONS_ACTIONS,
REMOTE_CONTROL_EVENT_NAME
} from "../../service/remotecontrol/Constants";
import RemoteControlParticipant from "./RemoteControlParticipant";
import UIEvents from "../../service/UI/UIEvents";
} from '../../service/remotecontrol/Constants';
import UIEvents from '../../service/UI/UIEvents';
import RemoteControlParticipant from './RemoteControlParticipant';
declare var $: Function;
declare var APP: Object;
declare var JitsiMeetJS: Object;
const ConferenceEvents = JitsiMeetJS.events.conference;
const logger = getLogger(__filename);
/**
* Extract the keyboard key from the keyboard event.
* @param event {KeyboardEvent} the event.
* @returns {KEYS} the key that is pressed or undefined.
*
* @param {KeyboardEvent} event - The event.
* @returns {KEYS} The key that is pressed or undefined.
*/
function getKey(event) {
return KeyCodes.keyboardEventToKey(event);
@ -22,26 +31,28 @@ function getKey(event) {
/**
* Extract the modifiers from the keyboard event.
* @param event {KeyboardEvent} the event.
* @returns {Array} with possible values: "shift", "control", "alt", "command".
*
* @param {KeyboardEvent} event - The event.
* @returns {Array} With possible values: "shift", "control", "alt", "command".
*/
function getModifiers(event) {
let modifiers = [];
if(event.shiftKey) {
modifiers.push("shift");
const modifiers = [];
if (event.shiftKey) {
modifiers.push('shift');
}
if(event.ctrlKey) {
modifiers.push("control");
if (event.ctrlKey) {
modifiers.push('control');
}
if(event.altKey) {
modifiers.push("alt");
if (event.altKey) {
modifiers.push('alt');
}
if(event.metaKey) {
modifiers.push("command");
if (event.metaKey) {
modifiers.push('command');
}
return modifiers;
@ -53,14 +64,22 @@ function getModifiers(event) {
* party of the remote control session.
*/
export default class Controller extends RemoteControlParticipant {
_area: ?Object;
_controlledParticipant: string | null;
_isCollectingEvents: boolean;
_largeVideoChangedListener: Function;
_requestedParticipant: string | null;
_stopListener: Function;
_userLeftListener: Function;
/**
* Creates new instance.
*/
constructor() {
super();
this.isCollectingEvents = false;
this.controlledParticipant = null;
this.requestedParticipant = null;
this._isCollectingEvents = false;
this._controlledParticipant = null;
this._requestedParticipant = null;
this._stopListener = this._handleRemoteControlStoppedEvent.bind(this);
this._userLeftListener = this._onUserLeft.bind(this);
this._largeVideoChangedListener
@ -69,24 +88,28 @@ export default class Controller extends RemoteControlParticipant {
/**
* Requests permissions from the remote control receiver side.
* @param {string} userId the user id of the participant that will be
*
* @param {string} userId - The user id of the participant that will be
* requested.
* @param {JQuerySelector} eventCaptureArea the area that is going to be
* @param {JQuerySelector} eventCaptureArea - The area that is going to be
* used mouse and keyboard event capture.
* @returns {Promise<boolean>} - resolve values:
* true - accept
* false - deny
* null - the participant has left.
* @returns {Promise<boolean>} Resolve values - true(accept), false(deny),
* null(the participant has left).
*/
requestPermissions(userId, eventCaptureArea) {
if(!this.enabled) {
return Promise.reject(new Error("Remote control is disabled!"));
requestPermissions(userId: string, eventCaptureArea: Object) {
if (!this.enabled) {
return Promise.reject(new Error('Remote control is disabled!'));
}
this.area = eventCaptureArea;// $("#largeVideoWrapper")
logger.log("Requsting remote control permissions from: " + userId);
this._area = eventCaptureArea;// $("#largeVideoWrapper")
logger.log(`Requsting remote control permissions from: ${userId}`);
return new Promise((resolve, reject) => {
// eslint-disable-next-line prefer-const
let onUserLeft, permissionsReplyListener;
const clearRequest = () => {
this.requestedParticipant = null;
this._requestedParticipant = null;
APP.conference.removeConferenceListener(
ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
permissionsReplyListener);
@ -94,31 +117,35 @@ export default class Controller extends RemoteControlParticipant {
ConferenceEvents.USER_LEFT,
onUserLeft);
};
const permissionsReplyListener = (participant, event) => {
permissionsReplyListener = (participant, event) => {
let result = null;
try {
result = this._handleReply(participant, event);
} catch (e) {
clearRequest();
reject(e);
}
if(result !== null) {
if (result !== null) {
clearRequest();
resolve(result);
}
};
const onUserLeft = (id) => {
if(id === this.requestedParticipant) {
onUserLeft = id => {
if (id === this._requestedParticipant) {
clearRequest();
resolve(null);
}
};
APP.conference.addConferenceListener(
ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
permissionsReplyListener);
APP.conference.addConferenceListener(ConferenceEvents.USER_LEFT,
onUserLeft);
this.requestedParticipant = userId;
this._sendRemoteControlEvent(userId, {
this._requestedParticipant = userId;
this.sendRemoteControlEvent(userId, {
type: EVENT_TYPES.permissions,
action: PERMISSIONS_ACTIONS.request
}, e => {
@ -130,54 +157,58 @@ export default class Controller extends RemoteControlParticipant {
/**
* Handles the reply of the permissions request.
* @param {JitsiParticipant} participant the participant that has sent the
* reply
* @param {RemoteControlEvent} event the remote control event.
*
* @param {JitsiParticipant} participant - The participant that has sent the
* reply.
* @param {RemoteControlEvent} event - The remote control event.
* @returns {void}
*/
_handleReply(participant, event) {
_handleReply(participant: Object, event: Object) {
const userId = participant.getId();
if(this.enabled
if (this.enabled
&& event.name === REMOTE_CONTROL_EVENT_NAME
&& event.type === EVENT_TYPES.permissions
&& userId === this.requestedParticipant) {
if(event.action !== PERMISSIONS_ACTIONS.grant) {
this.area = null;
&& userId === this._requestedParticipant) {
if (event.action !== PERMISSIONS_ACTIONS.grant) {
this._area = undefined;
}
switch(event.action) {
case PERMISSIONS_ACTIONS.grant: {
this.controlledParticipant = userId;
logger.log("Remote control permissions granted to: "
+ userId);
this._start();
return true;
}
case PERMISSIONS_ACTIONS.deny:
return false;
case PERMISSIONS_ACTIONS.error:
throw new Error("Error occurred on receiver side");
default:
throw new Error("Unknown reply received!");
switch (event.action) {
case PERMISSIONS_ACTIONS.grant: {
this._controlledParticipant = userId;
logger.log('Remote control permissions granted to:', userId);
this._start();
return true;
}
case PERMISSIONS_ACTIONS.deny:
return false;
case PERMISSIONS_ACTIONS.error:
throw new Error('Error occurred on receiver side');
default:
throw new Error('Unknown reply received!');
}
} else {
//different message type or another user -> ignoring the message
// different message type or another user -> ignoring the message
return null;
}
}
/**
* Handles remote control stopped.
* @param {JitsiParticipant} participant the participant that has sent the
* event
* @param {Object} event EndpointMessage event from the data channels.
* @property {string} type property. The function process only events with
* name REMOTE_CONTROL_EVENT_NAME
* @property {RemoteControlEvent} event - the remote control event.
*
* @param {JitsiParticipant} participant - The participant that has sent the
* event.
* @param {Object} event - EndpointMessage event from the data channels.
* @property {string} type - The function process only events with
* name REMOTE_CONTROL_EVENT_NAME.
* @returns {void}
*/
_handleRemoteControlStoppedEvent(participant, event) {
if(this.enabled
_handleRemoteControlStoppedEvent(participant: Object, event: Object) {
if (this.enabled
&& event.name === REMOTE_CONTROL_EVENT_NAME
&& event.type === EVENT_TYPES.stop
&& participant.getId() === this.controlledParticipant) {
&& participant.getId() === this._controlledParticipant) {
this._stop();
}
}
@ -185,9 +216,11 @@ export default class Controller extends RemoteControlParticipant {
/**
* Starts processing the mouse and keyboard events. Sets conference
* listeners. Disables keyboard events.
*
* @returns {void}
*/
_start() {
logger.log("Starting remote control controller.");
logger.log('Starting remote control controller.');
APP.UI.addListener(UIEvents.LARGE_VIDEO_ID_CHANGED,
this._largeVideoChangedListener);
APP.conference.addConferenceListener(
@ -200,35 +233,53 @@ export default class Controller extends RemoteControlParticipant {
/**
* Disables the keyboatd shortcuts. Starts collecting remote control
* events.
* events. It can be used to resume an active remote control session wchich
* was paused with this.pause().
*
* It can be used to resume an active remote control session wchich was
* paused with this.pause().
* @returns {void}
*/
resume() {
if(!this.enabled || this.isCollectingEvents) {
if (!this.enabled || this._isCollectingEvents || !this._area) {
return;
}
logger.log("Resuming remote control controller.");
this.isCollectingEvents = true;
logger.log('Resuming remote control controller.');
this._isCollectingEvents = true;
APP.keyboardshortcut.enable(false);
this.area.mousemove(event => {
const position = this.area.position();
this._sendRemoteControlEvent(this.controlledParticipant, {
// $FlowDisableNextLine: we are sure that this._area is not null.
this._area.mousemove(event => {
// $FlowDisableNextLine: we are sure that this._area is not null.
const position = this._area.position();
this.sendRemoteControlEvent(this._controlledParticipant, {
type: EVENT_TYPES.mousemove,
x: (event.pageX - position.left)/this.area.width(),
y: (event.pageY - position.top)/this.area.height()
// $FlowDisableNextLine: we are sure that this._area is not null
x: (event.pageX - position.left) / this._area.width(),
// $FlowDisableNextLine: we are sure that this._area is not null
y: (event.pageY - position.top) / this._area.height()
});
});
this.area.mousedown(this._onMouseClickHandler.bind(this,
// $FlowDisableNextLine: we are sure that this._area is not null.
this._area.mousedown(this._onMouseClickHandler.bind(this,
EVENT_TYPES.mousedown));
this.area.mouseup(this._onMouseClickHandler.bind(this,
// $FlowDisableNextLine: we are sure that this._area is not null.
this._area.mouseup(this._onMouseClickHandler.bind(this,
EVENT_TYPES.mouseup));
this.area.dblclick(
// $FlowDisableNextLine: we are sure that this._area is not null.
this._area.dblclick(
this._onMouseClickHandler.bind(this, EVENT_TYPES.mousedblclick));
this.area.contextmenu(() => false);
this.area[0].onmousewheel = event => {
this._sendRemoteControlEvent(this.controlledParticipant, {
// $FlowDisableNextLine: we are sure that this._area is not null.
this._area.contextmenu(() => false);
// $FlowDisableNextLine: we are sure that this._area is not null.
this._area[0].onmousewheel = event => {
this.sendRemoteControlEvent(this._controlledParticipant, {
type: EVENT_TYPES.mousescroll,
x: event.deltaX,
y: event.deltaY
@ -243,12 +294,14 @@ export default class Controller extends RemoteControlParticipant {
* Stops processing the mouse and keyboard events. Removes added listeners.
* Enables the keyboard shortcuts. Displays dialog to notify the user that
* remote control session has ended.
*
* @returns {void}
*/
_stop() {
if(!this.controlledParticipant) {
if (!this._controlledParticipant) {
return;
}
logger.log("Stopping remote control controller.");
logger.log('Stopping remote control controller.');
APP.UI.removeListener(UIEvents.LARGE_VIDEO_ID_CHANGED,
this._largeVideoChangedListener);
APP.conference.removeConferenceListener(
@ -256,29 +309,28 @@ export default class Controller extends RemoteControlParticipant {
this._stopListener);
APP.conference.removeConferenceListener(ConferenceEvents.USER_LEFT,
this._userLeftListener);
this.controlledParticipant = null;
this._controlledParticipant = null;
this.pause();
this.area = null;
this._area = undefined;
APP.UI.messageHandler.openMessageDialog(
"dialog.remoteControlTitle",
"dialog.remoteControlStopMessage"
'dialog.remoteControlTitle',
'dialog.remoteControlStopMessage'
);
}
/**
* Executes this._stop() mehtod:
* Stops processing the mouse and keyboard events. Removes added listeners.
* Enables the keyboard shortcuts. Displays dialog to notify the user that
* remote control session has ended.
* Executes this._stop() mehtod which stops processing the mouse and
* keyboard events, removes added listeners, enables the keyboard shortcuts,
* displays dialog to notify the user that remote control session has ended.
* In addition sends stop message to the controlled participant.
*
* In addition:
* Sends stop message to the controlled participant.
* @returns {void}
*/
stop() {
if(!this.controlledParticipant) {
if (!this._controlledParticipant) {
return;
}
this._sendRemoteControlEvent(this.controlledParticipant, {
this.sendRemoteControlEvent(this._controlledParticipant, {
type: EVENT_TYPES.stop
});
this._stop();
@ -288,88 +340,112 @@ export default class Controller extends RemoteControlParticipant {
* Pauses the collecting of events and enables the keyboard shortcus. But
* it doesn't removes any other listeners. Basically the remote control
* session will be still active after this.pause(), but no events from the
* controller side will be captured and sent.
* controller side will be captured and sent. You can resume the collecting
* of the events with this.resume().
*
* You can resume the collecting of the events with this.resume().
* @returns {void}
*/
pause() {
if(!this.controlledParticipant) {
if (!this._controlledParticipant) {
return;
}
logger.log("Pausing remote control controller.");
this.isCollectingEvents = false;
logger.log('Pausing remote control controller.');
this._isCollectingEvents = false;
APP.keyboardshortcut.enable(true);
this.area.off( "mousemove" );
this.area.off( "mousedown" );
this.area.off( "mouseup" );
this.area.off( "contextmenu" );
this.area.off( "dblclick" );
$(window).off( "keydown");
$(window).off( "keyup");
this.area[0].onmousewheel = undefined;
// $FlowDisableNextLine: we are sure that this._area is not null.
this._area.off('mousemove');
// $FlowDisableNextLine: we are sure that this._area is not null.
this._area.off('mousedown');
// $FlowDisableNextLine: we are sure that this._area is not null.
this._area.off('mouseup');
// $FlowDisableNextLine: we are sure that this._area is not null.
this._area.off('contextmenu');
// $FlowDisableNextLine: we are sure that this._area is not null.
this._area.off('dblclick');
$(window).off('keydown');
$(window).off('keyup');
// $FlowDisableNextLine: we are sure that this._area is not null.
this._area[0].onmousewheel = undefined;
}
/**
* Handler for mouse click events.
* @param {String} type the type of event ("mousedown"/"mouseup")
* @param {Event} event the mouse event.
*
* @param {string} type - The type of event ("mousedown"/"mouseup").
* @param {Event} event - The mouse event.
* @returns {void}
*/
_onMouseClickHandler(type, event) {
this._sendRemoteControlEvent(this.controlledParticipant, {
type: type,
_onMouseClickHandler(type: string, event: Object) {
this.sendRemoteControlEvent(this._controlledParticipant, {
type,
button: event.which
});
}
/**
* Returns true if the remote control session is started.
*
* @returns {boolean}
*/
isStarted() {
return this.controlledParticipant !== null;
return this._controlledParticipant !== null;
}
/**
* Returns the id of the requested participant
* @returns {string} this.requestedParticipant.
* Returns the id of the requested participant.
*
* @returns {string} The id of the requested participant.
* NOTE: This id should be the result of JitsiParticipant.getId() call.
*/
getRequestedParticipant() {
return this.requestedParticipant;
return this._requestedParticipant;
}
/**
* Handler for key press events.
* @param {String} type the type of event ("keydown"/"keyup")
* @param {Event} event the key event.
*
* @param {string} type - The type of event ("keydown"/"keyup").
* @param {Event} event - The key event.
* @returns {void}
*/
_onKeyPessHandler(type, event) {
this._sendRemoteControlEvent(this.controlledParticipant, {
type: type,
_onKeyPessHandler(type: string, event: Object) {
this.sendRemoteControlEvent(this._controlledParticipant, {
type,
key: getKey(event),
modifiers: getModifiers(event),
modifiers: getModifiers(event)
});
}
/**
* Calls the stop method if the other side have left.
* @param {string} id - the user id for the participant that have left
*
* @param {string} id - The user id for the participant that have left.
* @returns {void}
*/
_onUserLeft(id) {
if(this.controlledParticipant === id) {
_onUserLeft(id: string) {
if (this._controlledParticipant === id) {
this._stop();
}
}
/**
* Handles changes of the participant displayed on the large video.
* @param {string} id - the user id for the participant that is displayed.
*
* @param {string} id - The user id for the participant that is displayed.
* @returns {void}
*/
_onLargeVideoIdChanged(id) {
if (!this.controlledParticipant) {
_onLargeVideoIdChanged(id: string) {
if (!this._controlledParticipant) {
return;
}
if(this.controlledParticipant == id) {
if (this._controlledParticipant === id) {
this.resume();
} else {
this.pause();

View File

@ -1,6 +1,11 @@
/* global APP, config, interfaceConfig, JitsiMeetJS */
/* @flow */
import { getLogger } from 'jitsi-meet-logger';
import * as JitsiMeetConferenceEvents from '../../ConferenceEvents';
import {
openRemoteControlAuthorizationDialog
} from '../../react/features/remote-control';
import {
DISCO_REMOTE_CONTROL_FEATURE,
EVENT_TYPES,
@ -11,8 +16,13 @@ import { getJitsiMeetTransport } from '../transport';
import RemoteControlParticipant from './RemoteControlParticipant';
declare var APP: Object;
declare var config: Object;
declare var interfaceConfig: Object;
declare var JitsiMeetJS: Object;
const ConferenceEvents = JitsiMeetJS.events.conference;
const logger = require("jitsi-meet-logger").getLogger(__filename);
const logger = getLogger(__filename);
/**
* The transport instance used for communication with external apps.
@ -28,17 +38,23 @@ const transport = getJitsiMeetTransport();
* and executed.
*/
export default class Receiver extends RemoteControlParticipant {
_controller: ?string;
_enabled: boolean;
_hangupListener: Function;
_remoteControlEventsListener: Function;
_userLeftListener: Function;
/**
* Creates new instance.
* @constructor
*/
constructor() {
super();
this.controller = null;
this._controller = null;
this._remoteControlEventsListener
= this._onRemoteControlEvent.bind(this);
this._userLeftListener = this._onUserLeft.bind(this);
this._hangupListener = this._onHangup.bind(this);
// We expect here that even if we receive the supported event earlier
// it will be cached and we'll receive it.
transport.on('event', event => {
@ -53,16 +69,19 @@ export default class Receiver extends RemoteControlParticipant {
}
/**
* Enables / Disables the remote control
* @param {boolean} enabled the new state.
* Enables / Disables the remote control.
*
* @param {boolean} enabled - The new state.
* @returns {void}
*/
_enable(enabled) {
if(this.enabled === enabled) {
_enable(enabled: boolean) {
if (this._enabled === enabled) {
return;
}
this.enabled = enabled;
if(enabled === true) {
logger.log("Remote control receiver enabled.");
this._enabled = enabled;
if (enabled === true) {
logger.log('Remote control receiver enabled.');
// Announce remote control support.
APP.connection.addFeature(DISCO_REMOTE_CONTROL_FEATURE, true);
APP.conference.addConferenceListener(
@ -71,7 +90,7 @@ export default class Receiver extends RemoteControlParticipant {
APP.conference.addListener(JitsiMeetConferenceEvents.BEFORE_HANGUP,
this._hangupListener);
} else {
logger.log("Remote control receiver disabled.");
logger.log('Remote control receiver disabled.');
this._stop(true);
APP.connection.removeFeature(DISCO_REMOTE_CONTROL_FEATURE);
APP.conference.removeConferenceListener(
@ -88,36 +107,43 @@ export default class Receiver extends RemoteControlParticipant {
* events. Sends stop message to the wrapper application. Optionally
* displays dialog for informing the user that remote control session
* ended.
* @param {boolean} dontShowDialog - if true the dialog won't be displayed.
*
* @param {boolean} [dontShowDialog] - If true the dialog won't be
* displayed.
* @returns {void}
*/
_stop(dontShowDialog = false) {
if(!this.controller) {
_stop(dontShowDialog: boolean = false) {
if (!this._controller) {
return;
}
logger.log("Remote control receiver stop.");
this.controller = null;
logger.log('Remote control receiver stop.');
this._controller = null;
APP.conference.removeConferenceListener(ConferenceEvents.USER_LEFT,
this._userLeftListener);
transport.sendEvent({
name: REMOTE_CONTROL_EVENT_NAME,
type: EVENT_TYPES.stop
});
if(!dontShowDialog) {
if (this.remoteControlExternalAuth) {
transport.sendEvent({
name: REMOTE_CONTROL_EVENT_NAME,
type: EVENT_TYPES.stop
});
}
if (!dontShowDialog) {
APP.UI.messageHandler.openMessageDialog(
"dialog.remoteControlTitle",
"dialog.remoteControlStopMessage"
'dialog.remoteControlTitle',
'dialog.remoteControlStopMessage'
);
}
}
/**
* Calls this._stop() and sends stop message to the controller participant
* Calls this._stop() and sends stop message to the controller participant.
*
* @returns {void}
*/
stop() {
if(!this.controller) {
if (!this._controller) {
return;
}
this._sendRemoteControlEvent(this.controller, {
this.sendRemoteControlEvent(this._controller, {
type: EVENT_TYPES.stop
});
this._stop();
@ -127,91 +153,145 @@ export default class Receiver extends RemoteControlParticipant {
* Listens for data channel EndpointMessage events. Handles only events of
* type remote control. Sends "remote-control-event" events to the API
* module.
* @param {JitsiParticipant} participant the controller participant
* @param {Object} event EndpointMessage event from the data channels.
* @property {string} type property. The function process only events with
* name REMOTE_CONTROL_EVENT_NAME
* @property {RemoteControlEvent} event - the remote control event.
*
* @param {JitsiParticipant} participant - The controller participant.
* @param {Object} event - EndpointMessage event from the data channels.
* @param {string} event.name - The function process only events with
* name REMOTE_CONTROL_EVENT_NAME.
* @returns {void}
*/
_onRemoteControlEvent(participant, event) {
_onRemoteControlEvent(participant: Object, event: Object) {
if (event.name !== REMOTE_CONTROL_EVENT_NAME) {
return;
}
const remoteControlEvent = Object.assign({}, event);
if (this.enabled) {
if (this.controller === null
if (this._enabled) {
if (this._controller === null
&& event.type === EVENT_TYPES.permissions
&& event.action === PERMISSIONS_ACTIONS.request) {
const userId = participant.getId();
if (!config.remoteControlExternalAuth) {
APP.store.dispatch(
openRemoteControlAuthorizationDialog(userId));
return;
}
// FIXME: Maybe use transport.sendRequest in this case???
remoteControlEvent.userId = participant.getId();
remoteControlEvent.userId = userId;
remoteControlEvent.userJID = participant.getJid();
remoteControlEvent.displayName = participant.getDisplayName()
|| interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME;
remoteControlEvent.screenSharing
= APP.conference.isSharingScreen;
} else if (this.controller !== participant.getId()) {
} else if (this._controller !== participant.getId()) {
return;
} else if (event.type === EVENT_TYPES.stop) {
this._stop();
return;
}
transport.sendEvent(remoteControlEvent);
} else {
logger.log("Remote control event is ignored because remote "
+ "control is disabled", event);
logger.log('Remote control event is ignored because remote '
+ 'control is disabled', event);
}
}
/**
* Handles remote control permission events.
* @param {String} userId the user id of the participant related to the
*
* @param {string} userId - The user id of the participant related to the
* event.
* @param {PERMISSIONS_ACTIONS} action the action related to the event.
* @param {PERMISSIONS_ACTIONS} action - The action related to the event.
* @returns {void}
*/
_onRemoteControlPermissionsEvent(userId, action) {
if (action === PERMISSIONS_ACTIONS.grant) {
APP.conference.addConferenceListener(ConferenceEvents.USER_LEFT,
this._userLeftListener);
this.controller = userId;
logger.log("Remote control permissions granted to: " + userId);
if(!APP.conference.isSharingScreen) {
APP.conference.toggleScreenSharing();
APP.conference.screenSharingPromise.then(() => {
if(APP.conference.isSharingScreen) {
this._sendRemoteControlEvent(userId, {
type: EVENT_TYPES.permissions,
action: action
});
} else {
this._sendRemoteControlEvent(userId, {
type: EVENT_TYPES.permissions,
action: PERMISSIONS_ACTIONS.error
});
}
}).catch(() => {
this._sendRemoteControlEvent(userId, {
type: EVENT_TYPES.permissions,
action: PERMISSIONS_ACTIONS.error
});
});
return;
}
_onRemoteControlPermissionsEvent(userId: string, action: string) {
switch (action) {
case PERMISSIONS_ACTIONS.grant:
this.grant(userId);
break;
case PERMISSIONS_ACTIONS.deny:
this.deny(userId);
break;
case PERMISSIONS_ACTIONS.error:
this.sendRemoteControlEvent(userId, {
type: EVENT_TYPES.permissions,
action
});
break;
default:
// Unknown action. Ignore.
}
this._sendRemoteControlEvent(userId, {
}
/**
* Denies remote control access for user associated with the passed user id.
*
* @param {string} userId - The id associated with the user who sent the
* request for remote control authorization.
* @returns {void}
*/
deny(userId: string) {
this.sendRemoteControlEvent(userId, {
type: EVENT_TYPES.permissions,
action
action: PERMISSIONS_ACTIONS.deny
});
}
/**
* Handles remote control events from the external app. Currently only
* events with type = EVENT_TYPES.supported or EVENT_TYPES.permissions
* @param {RemoteControlEvent} event the remote control event.
* Grants remote control access to user associated with the passed user id.
*
* @param {string} userId - The id associated with the user who sent the
* request for remote control authorization.
* @returns {void}
*/
_onRemoteControlAPIEvent(event) {
switch(event.type) {
grant(userId: string) {
APP.conference.addConferenceListener(ConferenceEvents.USER_LEFT,
this._userLeftListener);
this._controller = userId;
logger.log(`Remote control permissions granted to: ${userId}`);
if (APP.conference.isSharingScreen) {
this.sendRemoteControlEvent(userId, {
type: EVENT_TYPES.permissions,
action: PERMISSIONS_ACTIONS.grant
});
} else {
APP.conference.toggleScreenSharing();
APP.conference.screenSharingPromise.then(() => {
if (APP.conference.isSharingScreen) {
this.sendRemoteControlEvent(userId, {
type: EVENT_TYPES.permissions,
action: PERMISSIONS_ACTIONS.grant
});
} else {
this.sendRemoteControlEvent(userId, {
type: EVENT_TYPES.permissions,
action: PERMISSIONS_ACTIONS.error
});
}
}).catch(() => {
this.sendRemoteControlEvent(userId, {
type: EVENT_TYPES.permissions,
action: PERMISSIONS_ACTIONS.error
});
});
}
}
/**
* Handles remote control events from the external app. Currently only
* events with type = EVENT_TYPES.supported or EVENT_TYPES.permissions.
*
* @param {RemoteControlEvent} event - The remote control event.
* @returns {void}
*/
_onRemoteControlAPIEvent(event: Object) {
switch (event.type) {
case EVENT_TYPES.permissions:
this._onRemoteControlPermissionsEvent(event.userId, event.action);
break;
@ -224,11 +304,13 @@ export default class Receiver extends RemoteControlParticipant {
/**
* Handles events for support for executing remote control events into
* the wrapper application.
*
* @returns {void}
*/
_onRemoteControlSupported() {
logger.log("Remote Control supported.");
logger.log('Remote Control supported.');
if (config.disableRemoteControl) {
logger.log("Remote Control disabled.");
logger.log('Remote Control disabled.');
} else {
this._enable(true);
}
@ -236,16 +318,20 @@ export default class Receiver extends RemoteControlParticipant {
/**
* Calls the stop method if the other side have left.
* @param {string} id - the user id for the participant that have left
*
* @param {string} id - The user id for the participant that have left.
* @returns {void}
*/
_onUserLeft(id) {
if(this.controller === id) {
_onUserLeft(id: string) {
if (this._controller === id) {
this._stop();
}
}
/**
* Handles hangup events. Disables the receiver.
*
* @returns {void}
*/
_onHangup() {
this._enable(false);

View File

@ -1,4 +1,6 @@
/* global APP, config */
/* @flow */
import { getLogger } from 'jitsi-meet-logger';
import { DISCO_REMOTE_CONTROL_FEATURE }
from '../../service/remotecontrol/Constants';
@ -6,44 +8,53 @@ import { DISCO_REMOTE_CONTROL_FEATURE }
import Controller from './Controller';
import Receiver from './Receiver';
const logger = require('jitsi-meet-logger').getLogger(__filename);
const logger = getLogger(__filename);
declare var APP: Object;
declare var config: Object;
/**
* Implements the remote control functionality.
*/
class RemoteControl {
_initialized: boolean;
controller: Controller;
receiver: Receiver;
/**
* Constructs new instance. Creates controller and receiver properties.
* @constructor
*/
constructor() {
this.controller = new Controller();
this.initialized = false;
this._initialized = false;
}
/**
* Initializes the remote control - checks if the remote control should be
* enabled or not.
*
* @returns {void}
*/
init() {
if(config.disableRemoteControl
|| this.initialized
if (config.disableRemoteControl
|| this._initialized
|| !APP.conference.isDesktopSharingEnabled) {
return;
}
logger.log("Initializing remote control.");
this.initialized = true;
logger.log('Initializing remote control.');
this._initialized = true;
this.controller.enable(true);
this.receiver = new Receiver();
}
/**
* Checks whether the passed user supports remote control or not
* @param {JitsiParticipant} user the user to be tested
* @returns {Promise<boolean>} the promise will be resolved with true if
* Checks whether the passed user supports remote control or not.
*
* @param {JitsiParticipant} user - The user to be tested.
* @returns {Promise<boolean>} The promise will be resolved with true if
* the user supports remote control and with false if not.
*/
checkUserRemoteControlSupport(user) {
checkUserRemoteControlSupport(user: Object) {
return user.getFeatures().then(
features => features.has(DISCO_REMOTE_CONTROL_FEATURE),
() => false);

View File

@ -1,50 +1,70 @@
/* global APP */
/* @flow */
import { getLogger } from 'jitsi-meet-logger';
import {
REMOTE_CONTROL_EVENT_NAME
} from "../../service/remotecontrol/Constants";
} from '../../service/remotecontrol/Constants';
const logger = require("jitsi-meet-logger").getLogger(__filename);
const logger = getLogger(__filename);
declare var APP: Object;
/**
* Implements common logic for Receiver class and Controller class.
*/
export default class RemoteControlParticipant {
_enabled: boolean;
/**
* Creates new instance.
*/
constructor() {
this.enabled = false;
this._enabled = false;
}
/**
* Enables / Disables the remote control
* @param {boolean} enabled the new state.
* Enables / Disables the remote control.
*
* @param {boolean} enabled - The new state.
* @returns {void}
*/
enable(enabled) {
this.enabled = enabled;
enable(enabled: boolean) {
this._enabled = enabled;
}
/**
* Sends remote control event to other participant trough data channel.
* @param {RemoteControlEvent} event the remote control event.
* @param {Function} onDataChannelFail handler for data channel failure.
*
* @param {string} to - The participant who will receive the event.
* @param {RemoteControlEvent} event - The remote control event.
* @param {Function} onDataChannelFail - Handler for data channel failure.
* @returns {void}
*/
_sendRemoteControlEvent(to, event, onDataChannelFail = () => {}) {
if(!this.enabled || !to) {
sendRemoteControlEvent(
to: ?string,
event: Object,
onDataChannelFail: ?Function) {
if (!this._enabled || !to) {
logger.warn(
"Remote control: Skip sending remote control event. Params:",
'Remote control: Skip sending remote control event. Params:',
this.enable,
to);
return;
}
try{
try {
APP.conference.sendEndpointMessage(to, {
name: REMOTE_CONTROL_EVENT_NAME,
...event
});
} catch (e) {
logger.error(
"Failed to send EndpointMessage via the datachannels",
'Failed to send EndpointMessage via the datachannels',
e);
onDataChannelFail(e);
if (typeof onDataChannelFail === 'function') {
onDataChannelFail(e);
}
}
}
}

View File

@ -88,7 +88,8 @@ export class AbstractApp extends Component {
localParticipant = {
avatarID: APP.settings.getAvatarId(),
avatarURL: APP.settings.getAvatarUrl(),
email: APP.settings.getEmail()
email: APP.settings.getEmail(),
name: APP.settings.getDisplayName()
};
}
dispatch(localParticipantJoined(localParticipant));

View File

@ -24,7 +24,7 @@ export default class AbstractDialog extends Component {
cancelTitleKey: React.PropTypes.string,
/**
* Used to show hide the dialog on cancel.
* Used to show/hide the dialog on cancel.
*/
dispatch: React.PropTypes.func,

View File

@ -0,0 +1,21 @@
import { openDialog } from '../base/dialog';
import { RemoteControlAuthorizationDialog } from './components';
/**
* Signals that the remote control authorization dialog should be displayed.
*
* @param {string} participantId - The id of the participant who is requesting
* the authorization.
* @returns {{
* type: OPEN_DIALOG,
* component: {RemoteControlAuthorizationDialog},
* componentProps: {
* participantId: {string}
* }
* }}
* @public
*/
export function openRemoteControlAuthorizationDialog(participantId) {
return openDialog(RemoteControlAuthorizationDialog, { participantId });
}

View File

@ -0,0 +1,165 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {
Dialog,
hideDialog
} from '../../base/dialog';
import { translate } from '../../base/i18n';
import { getParticipantById } from '../../base/participants';
declare var APP: Object;
/**
* Implements a dialog for remote control authorization.
*/
class RemoteControlAuthorizationDialog extends Component {
/**
* RemoteControlAuthorizationDialog component's property types.
*
* @static
*/
static propTypes = {
/**
* The display name of the participant who is requesting authorization
* for remote desktop control session.
*
* @private
*/
_displayName: React.PropTypes.string,
/**
* Used to show/hide the dialog on cancel.
*/
dispatch: React.PropTypes.func,
/**
* The ID of the participant who is requesting authorization for remote
* desktop control session.
*
* @public
*/
participantId: React.PropTypes.string,
/**
* Invoked to obtain translated strings.
*/
t: React.PropTypes.func
}
/**
* Initializes a new RemoteControlAuthorizationDialog instance.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props) {
super(props);
this._onCancel = this._onCancel.bind(this);
this._onSubmit = this._onSubmit.bind(this);
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
*/
render() {
return (
<Dialog
okTitleKey = { 'dialog.allow' }
onCancel = { this._onCancel }
onSubmit = { this._onSubmit }
titleKey = 'dialog.remoteControlTitle'
width = 'small'>
{
this.props.t('dialog.remoteControlRequestMessage',
{ user: this.props._displayName })
}
{
this._getAdditionalMessage()
}
</Dialog>
);
}
/**
* Renders additional message text for the dialog.
*
* @private
* @returns {ReactElement}
*/
_getAdditionalMessage() {
// FIXME: Once we have this information in redux we should
// start getting it from there.
if (APP.conference.isSharingScreen) {
return null;
}
return (
<div>
<br />
{ this.props.t('dialog.remoteControlShareScreenWarning') }
</div>
);
}
/**
* Notifies the remote control module about the denial of the remote control
* request.
*
* @private
* @returns {boolean} Returns true to close the dialog.
*/
_onCancel() {
// FIXME: This should be action one day.
APP.remoteControl.receiver.deny(this.props.participantId);
return true;
}
/**
* Notifies the remote control module that the remote control request is
* accepted.
*
* @private
* @returns {boolean} Returns false to prevent closure because the dialog is
* closed manually to be sure that if the desktop picker dialog can be
* displayed (if this dialog is displayed when we try to display the desktop
* picker window, the action will be ignored).
*/
_onSubmit() {
this.props.dispatch(hideDialog());
// FIXME: This should be action one day.
APP.remoteControl.receiver.grant(this.props.participantId);
return false;
}
}
/**
* Maps (parts of) the Redux state to the RemoteControlAuthorizationDialog's
* props.
*
* @param {Object} state - The Redux state.
* @param {Object} ownProps - The React Component props passed to the associated
* (instance of) RemoteControlAuthorizationDialog.
* @private
* @returns {{
* _displayName: string
* }}
*/
function _mapStateToProps(state, ownProps) {
const { _displayName, participantId } = ownProps;
const participant = getParticipantById(
state['features/base/participants'], participantId);
return {
_displayName: participant ? participant.name : _displayName
};
}
export default translate(
connect(_mapStateToProps)(RemoteControlAuthorizationDialog));

View File

@ -0,0 +1,2 @@
export { default as RemoteControlAuthorizationDialog }
from './RemoteControlAuthorizationDialog';

View File

@ -0,0 +1,2 @@
export * from './actions';
export * from './components';