| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331 | /* @flow */
import { getLogger } from 'jitsi-meet-logger';
import * as JitsiMeetConferenceEvents from '../../ConferenceEvents';
import {
    JitsiConferenceEvents
} from '../../react/features/base/lib-jitsi-meet';
import {
    openRemoteControlAuthorizationDialog
} from '../../react/features/remote-control';
import {
    DISCO_REMOTE_CONTROL_FEATURE,
    EVENTS,
    PERMISSIONS_ACTIONS,
    REMOTE_CONTROL_MESSAGE_NAME,
    REQUESTS
} from '../../service/remotecontrol/Constants';
import * as RemoteControlEvents
    from '../../service/remotecontrol/RemoteControlEvents';
import { Transport, PostMessageTransportBackend } from '../transport';
import RemoteControlParticipant from './RemoteControlParticipant';
declare var APP: Object;
declare var config: Object;
declare var interfaceConfig: Object;
const logger = getLogger(__filename);
/**
 * The transport instance used for communication with external apps.
 *
 * @type {Transport}
 */
const transport = new Transport({
    backend: new PostMessageTransportBackend({
        postisOptions: { scope: 'jitsi-remote-control' }
    })
});
/**
 * This class represents the receiver party for a remote controller session.
 * It handles "remote-control-event" events and sends them to the
 * API module. From there the events can be received from wrapper application
 * and executed.
 */
export default class Receiver extends RemoteControlParticipant {
    _controller: ?string;
    _enabled: boolean;
    _hangupListener: Function;
    _remoteControlEventsListener: Function;
    _userLeftListener: Function;
    /**
     * Creates new instance.
     */
    constructor() {
        super();
        this._controller = null;
        this._remoteControlEventsListener
            = this._onRemoteControlMessage.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 => {
            if (event.name === REMOTE_CONTROL_MESSAGE_NAME) {
                this._onRemoteControlAPIEvent(event);
                return true;
            }
            return false;
        });
    }
    /**
     * Enables / Disables the remote control.
     *
     * @param {boolean} enabled - The new state.
     * @returns {void}
     */
    _enable(enabled: boolean) {
        if (this._enabled === enabled) {
            return;
        }
        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(
                JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
                this._remoteControlEventsListener);
            APP.conference.addListener(JitsiMeetConferenceEvents.BEFORE_HANGUP,
                this._hangupListener);
        } else {
            logger.log('Remote control receiver disabled.');
            this._stop(true);
            APP.connection.removeFeature(DISCO_REMOTE_CONTROL_FEATURE);
            APP.conference.removeConferenceListener(
                JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
                this._remoteControlEventsListener);
            APP.conference.removeListener(
                JitsiMeetConferenceEvents.BEFORE_HANGUP,
                this._hangupListener);
        }
    }
    /**
     * Removes the listener for JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED
     * events. Sends stop message to the wrapper application. Optionally
     * displays dialog for informing the user that remote control session
     * ended.
     *
     * @param {boolean} [dontNotify] - If true - a notification about stopping
     * the remote control won't be displayed.
     * @returns {void}
     */
    _stop(dontNotify: boolean = false) {
        if (!this._controller) {
            return;
        }
        logger.log('Remote control receiver stop.');
        this._controller = null;
        APP.conference.removeConferenceListener(
            JitsiConferenceEvents.USER_LEFT,
            this._userLeftListener);
        transport.sendEvent({
            name: REMOTE_CONTROL_MESSAGE_NAME,
            type: EVENTS.stop
        });
        this.emit(RemoteControlEvents.ACTIVE_CHANGED, false);
        if (!dontNotify) {
            APP.UI.messageHandler.notify(
                'dialog.remoteControlTitle',
                'dialog.remoteControlStopMessage'
            );
        }
    }
    /**
     * Calls this._stop() and sends stop message to the controller participant.
     *
     * @returns {void}
     */
    stop() {
        if (!this._controller) {
            return;
        }
        this.sendRemoteControlEndpointMessage(this._controller, {
            type: EVENTS.stop
        });
        this._stop();
    }
    /**
     * Listens for data channel EndpointMessage. Handles only remote control
     * messages. Sends the remote control messages to the external app that
     * will execute them.
     *
     * @param {JitsiParticipant} participant - The controller participant.
     * @param {Object} message - EndpointMessage from the data channels.
     * @param {string} message.name - The function processes only messages with
     * name REMOTE_CONTROL_MESSAGE_NAME.
     * @returns {void}
     */
    _onRemoteControlMessage(participant: Object, message: Object) {
        if (message.name !== REMOTE_CONTROL_MESSAGE_NAME) {
            return;
        }
        if (this._enabled) {
            if (this._controller === null
                    && message.type === EVENTS.permissions
                    && message.action === PERMISSIONS_ACTIONS.request) {
                const userId = participant.getId();
                this.emit(RemoteControlEvents.ACTIVE_CHANGED, true);
                APP.store.dispatch(
                    openRemoteControlAuthorizationDialog(userId));
            } else if (this._controller === participant.getId()) {
                if (message.type === EVENTS.stop) {
                    this._stop();
                } else { // forward the message
                    transport.sendEvent(message);
                }
            } // else ignore
        } else {
            logger.log('Remote control message is ignored because remote '
                + 'control is disabled', message);
        }
    }
    /**
     * 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.emit(RemoteControlEvents.ACTIVE_CHANGED, false);
        this.sendRemoteControlEndpointMessage(userId, {
            type: EVENTS.permissions,
            action: PERMISSIONS_ACTIONS.deny
        });
    }
    /**
     * 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}
     */
    grant(userId: string) {
        APP.conference.addConferenceListener(JitsiConferenceEvents.USER_LEFT,
            this._userLeftListener);
        this._controller = userId;
        logger.log(`Remote control permissions granted to: ${userId}`);
        let promise;
        if (APP.conference.isSharingScreen
                && APP.conference.getDesktopSharingSourceType() === 'screen') {
            promise = this._sendStartRequest();
        } else {
            promise = APP.conference.toggleScreenSharing(
                true,
                {
                    desktopSharingSources: [ 'screen' ]
                })
                .then(() => this._sendStartRequest());
        }
        promise
            .then(() =>
                this.sendRemoteControlEndpointMessage(userId, {
                    type: EVENTS.permissions,
                    action: PERMISSIONS_ACTIONS.grant
                })
            )
            .catch(error => {
                logger.error(error);
                this.sendRemoteControlEndpointMessage(userId, {
                    type: EVENTS.permissions,
                    action: PERMISSIONS_ACTIONS.error
                });
                APP.UI.messageHandler.notify(
                    'dialog.remoteControlTitle',
                    'dialog.startRemoteControlErrorMessage'
                );
                this._stop(true);
            });
    }
    /**
     * Sends remote control start request.
     *
     * @returns {Promise}
     */
    _sendStartRequest() {
        return transport.sendRequest({
            name: REMOTE_CONTROL_MESSAGE_NAME,
            type: REQUESTS.start,
            sourceId: APP.conference.getDesktopSharingSourceId()
        });
    }
    /**
     * Handles remote control events from the external app. Currently only
     * events with type EVENTS.supported and EVENTS.stop are
     * supported.
     *
     * @param {RemoteControlEvent} event - The remote control event.
     * @returns {void}
     */
    _onRemoteControlAPIEvent(event: Object) {
        switch (event.type) {
        case EVENTS.supported:
            this._onRemoteControlSupported();
            break;
        case EVENTS.stop:
            this.stop();
            break;
        }
    }
    /**
     * Handles events for support for executing remote control events into
     * the wrapper application.
     *
     * @returns {void}
     */
    _onRemoteControlSupported() {
        logger.log('Remote Control supported.');
        if (config.disableRemoteControl) {
            logger.log('Remote Control disabled.');
        } else {
            this._enable(true);
        }
    }
    /**
     * Calls the stop method if the other side have left.
     *
     * @param {string} id - The user id for the participant that have left.
     * @returns {void}
     */
    _onUserLeft(id: string) {
        if (this._controller === id) {
            this._stop();
        }
    }
    /**
     * Handles hangup events. Disables the receiver.
     *
     * @returns {void}
     */
    _onHangup() {
        this._enable(false);
    }
}
 |