Token based features (#3075)
* Adds an option to disable features based on token data.
Reverts changes from b84e910086
, removes disableDesktopSharing option and an interface_config option.
* Disable recording button based on token features data.
Hide recording if local participant isGuest and roles based on token.
When enableUserRolesBasedOnToken is enabled we were not hiding the record button for guests.
* Adds filtering of jibri iqs and rayo based on features.
Moves feature checking in separate utility function.
Renames utility method.
* Adds a footer text when outbound-call is not feature enabled.
* Fixes comments.
This commit is contained in:
parent
0cf585860b
commit
ac834326e7
|
@ -484,8 +484,7 @@ export default {
|
|||
|
||||
/**
|
||||
* Indicates if the desktop sharing functionality has been enabled.
|
||||
* It takes into consideration {@link isDesktopSharingDisabledByConfig}
|
||||
* as well as the status returned by
|
||||
* It takes into consideration the status returned by
|
||||
* {@link JitsiMeetJS.isDesktopSharingEnabled()}. The latter can be false
|
||||
* either if the desktop sharing is not supported by the current browser
|
||||
* or if it was disabled through lib-jitsi-meet specific options (check
|
||||
|
@ -493,19 +492,6 @@ export default {
|
|||
*/
|
||||
isDesktopSharingEnabled: false,
|
||||
|
||||
/**
|
||||
* Set to <tt>true</tt> if the desktop sharing functionality has been
|
||||
* explicitly disabled in the config.
|
||||
*/
|
||||
isDesktopSharingDisabledByConfig: false,
|
||||
|
||||
/**
|
||||
* The text displayed when the desktop sharing button is disabled through
|
||||
* the config. The value is set through
|
||||
* {@link interfaceConfig.DESKTOP_SHARING_BUTTON_DISABLED_TOOLTIP}.
|
||||
*/
|
||||
desktopSharingDisabledTooltip: null,
|
||||
|
||||
/**
|
||||
* The local audio track (if any).
|
||||
* FIXME tracks from redux store should be the single source of truth
|
||||
|
@ -720,13 +706,8 @@ export default {
|
|||
APP.connection = connection = con;
|
||||
|
||||
// Desktop sharing related stuff:
|
||||
this.isDesktopSharingDisabledByConfig
|
||||
= config.disableDesktopSharing;
|
||||
this.isDesktopSharingEnabled
|
||||
= !this.isDesktopSharingDisabledByConfig
|
||||
&& JitsiMeetJS.isDesktopSharingEnabled();
|
||||
this.desktopSharingDisabledTooltip
|
||||
= interfaceConfig.DESKTOP_SHARING_BUTTON_DISABLED_TOOLTIP;
|
||||
= JitsiMeetJS.isDesktopSharingEnabled();
|
||||
eventEmitter.emit(
|
||||
JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
|
||||
this.isDesktopSharingEnabled);
|
||||
|
@ -1909,6 +1890,14 @@ export default {
|
|||
JitsiConferenceEvents.PARTICIPANT_PROPERTY_CHANGED,
|
||||
(participant, name, oldValue, newValue) => {
|
||||
switch (name) {
|
||||
case 'features_screen-sharing': {
|
||||
APP.store.dispatch(participantUpdated({
|
||||
conference: room,
|
||||
id: participant.getId(),
|
||||
features: { 'screen-sharing': true }
|
||||
}));
|
||||
break;
|
||||
}
|
||||
case 'raisedHand':
|
||||
APP.store.dispatch(participantUpdated({
|
||||
conference: room,
|
||||
|
|
|
@ -142,9 +142,6 @@ var config = {
|
|||
|
||||
// Desktop sharing
|
||||
|
||||
// Enable / disable desktop sharing
|
||||
// disableDesktopSharing: false,
|
||||
|
||||
// The ID of the jidesha extension for Chrome.
|
||||
desktopSharingChromeExtId: null,
|
||||
|
||||
|
@ -248,6 +245,9 @@ var config = {
|
|||
// edit their profile.
|
||||
enableUserRolesBasedOnToken: false,
|
||||
|
||||
// Whether or not some features are checked based on token.
|
||||
// enableFeaturesBasedOnToken: false,
|
||||
|
||||
// Message to show the users. Example: 'The service will be down for
|
||||
// maintenance at 01:00 AM GMT,
|
||||
// noticeMessage: '',
|
||||
|
|
|
@ -95,7 +95,13 @@
|
|||
}
|
||||
|
||||
i.disabled {
|
||||
cursor: initial
|
||||
cursor: initial;
|
||||
color: #3b475c;
|
||||
}
|
||||
|
||||
.disabled i {
|
||||
cursor: initial;
|
||||
color: #3b475c;
|
||||
}
|
||||
|
||||
i.disabled:hover {
|
||||
|
@ -135,6 +141,10 @@
|
|||
&.unclickable:hover {
|
||||
background: inherit;
|
||||
}
|
||||
&.disabled {
|
||||
cursor: initial;
|
||||
color: #3b475c;
|
||||
}
|
||||
}
|
||||
|
||||
.beta-tag {
|
||||
|
|
|
@ -23,6 +23,21 @@
|
|||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-text-wrap {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.footer-telephone-icon {
|
||||
display: flex;
|
||||
transform: scaleX(-1);
|
||||
padding-left: 10px;
|
||||
|
||||
i {
|
||||
line-height: 20px;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,12 +5,6 @@ var interfaceConfig = {
|
|||
// methods allowing to use variables both in css and js.
|
||||
DEFAULT_BACKGROUND: '#474747',
|
||||
|
||||
/**
|
||||
* In case the desktop sharing is disabled through the config the button
|
||||
* will not be hidden, but displayed as disabled with this text us as
|
||||
* a tooltip.
|
||||
*/
|
||||
DESKTOP_SHARING_BUTTON_DISABLED_TOOLTIP: null,
|
||||
INITIAL_TOOLBAR_TIMEOUT: 20000,
|
||||
TOOLBAR_TIMEOUT: 4000,
|
||||
TOOLBAR_ALWAYS_VISIBLE: false,
|
||||
|
|
|
@ -361,6 +361,10 @@
|
|||
"muteParticipantTitle": "Mute this member?",
|
||||
"muteParticipantBody": "You won't be able to unmute them, but they can unmute themselves at any time.",
|
||||
"muteParticipantButton": "Mute",
|
||||
"liveStreamingDisabledTooltip": "Start live stream disabled.",
|
||||
"liveStreamingDisabledForGuestTooltip": "Guests can't start live streaming.",
|
||||
"recordingDisabledTooltip": "Start recording disabled.",
|
||||
"recordingDisabledForGuestTooltip": "Guests can't start recordings.",
|
||||
"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!",
|
||||
|
@ -371,6 +375,8 @@
|
|||
"remoteControlStopMessage": "The remote control session ended!",
|
||||
"close": "Close",
|
||||
"shareYourScreen": "Share your screen",
|
||||
"shareYourScreenDisabled": "Screen sharing disabled.",
|
||||
"shareYourScreenDisabledForGuest": "Guests can't screen share.",
|
||||
"yourEntireScreen": "Your entire screen",
|
||||
"applicationWindow": "Application window"
|
||||
},
|
||||
|
@ -521,6 +527,7 @@
|
|||
"countryNotSupported": "We do not support this destination yet.",
|
||||
"countryReminder": "Calling outside the US? Please make sure you start with the country code!",
|
||||
"disabled": "You can't invite people.",
|
||||
"footerText": "Dialing out is disabled.",
|
||||
"invite": "Invite",
|
||||
"loading": "Searching for people and phone numbers",
|
||||
"loadingNumber": "Validating phone number",
|
||||
|
|
|
@ -188,8 +188,17 @@ function _reportError(msg, err) {
|
|||
*/
|
||||
export function sendLocalParticipant(
|
||||
stateful: Function | Object,
|
||||
conference: { sendCommand: Function, setDisplayName: Function }) {
|
||||
const { avatarID, avatarURL, email, name } = getLocalParticipant(stateful);
|
||||
conference: {
|
||||
sendCommand: Function,
|
||||
setDisplayName: Function,
|
||||
setLocalParticipantProperty: Function }) {
|
||||
const {
|
||||
avatarID,
|
||||
avatarURL,
|
||||
email,
|
||||
features,
|
||||
name
|
||||
} = getLocalParticipant(stateful);
|
||||
|
||||
avatarID && conference.sendCommand(AVATAR_ID_COMMAND, {
|
||||
value: avatarID
|
||||
|
@ -200,5 +209,10 @@ export function sendLocalParticipant(
|
|||
email && conference.sendCommand(EMAIL_COMMAND, {
|
||||
value: email
|
||||
});
|
||||
|
||||
if (features && features['screen-sharing'] === 'true') {
|
||||
conference.setLocalParticipantProperty('features_screen-sharing', true);
|
||||
}
|
||||
|
||||
conference.setDisplayName(name);
|
||||
}
|
||||
|
|
|
@ -89,8 +89,6 @@ const WHITELISTED_KEYS = [
|
|||
'disableAGC',
|
||||
'disableAP',
|
||||
'disableAudioLevels',
|
||||
'disableDesktopSharing',
|
||||
'disableDesktopSharing',
|
||||
'disableH264',
|
||||
'disableHPF',
|
||||
'disableNS',
|
||||
|
@ -106,7 +104,6 @@ const WHITELISTED_KEYS = [
|
|||
'enableStatsID',
|
||||
'enableTalkWhileMuted',
|
||||
'enableTcc',
|
||||
'enableUserRolesBasedOnToken',
|
||||
'etherpad_base',
|
||||
'failICE',
|
||||
'fileRecordingsEnabled',
|
||||
|
|
|
@ -138,7 +138,7 @@ function _maybeSetCalleeInfoVisible({ dispatch, getState }, next, action) {
|
|||
*/
|
||||
function _overwriteLocalParticipant(
|
||||
{ dispatch, getState },
|
||||
{ avatarURL, email, name }) {
|
||||
{ avatarURL, email, name, features }) {
|
||||
let localParticipant;
|
||||
|
||||
if ((avatarURL || email || name)
|
||||
|
@ -157,6 +157,9 @@ function _overwriteLocalParticipant(
|
|||
if (name) {
|
||||
newProperties.name = name;
|
||||
}
|
||||
if (features) {
|
||||
newProperties.features = features;
|
||||
}
|
||||
dispatch(participantUpdated(newProperties));
|
||||
}
|
||||
}
|
||||
|
@ -229,7 +232,9 @@ function _setJWT(store, next, action) {
|
|||
action.server = context.server;
|
||||
action.user = user;
|
||||
|
||||
user && _overwriteLocalParticipant(store, user);
|
||||
user && _overwriteLocalParticipant(
|
||||
store, { ...user,
|
||||
features: context.features });
|
||||
}
|
||||
}
|
||||
} else if (typeof APP === 'undefined') {
|
||||
|
@ -281,6 +286,8 @@ function _undoOverwriteLocalParticipant(
|
|||
if (name === localParticipant.name) {
|
||||
newProperties.name = undefined;
|
||||
}
|
||||
newProperties.features = undefined;
|
||||
|
||||
dispatch(participantUpdated(newProperties));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,12 @@ class MultiSelectAutocomplete extends Component {
|
|||
*/
|
||||
defaultValue: PropTypes.array,
|
||||
|
||||
/**
|
||||
* Optional footer to show as a last element in the results.
|
||||
* Should be of type {content: <some content>}
|
||||
*/
|
||||
footer: PropTypes.object,
|
||||
|
||||
/**
|
||||
* Indicates if the component is disabled.
|
||||
*/
|
||||
|
@ -151,6 +157,7 @@ class MultiSelectAutocomplete extends Component {
|
|||
<div>
|
||||
<MultiSelectStateless
|
||||
filterValue = { this.state.filterValue }
|
||||
footer = { this.props.footer }
|
||||
icon = { null }
|
||||
isDisabled = { isDisabled }
|
||||
isLoading = { this.state.loading }
|
||||
|
|
|
@ -8,7 +8,8 @@ import { connect } from 'react-redux';
|
|||
|
||||
import { createInviteDialogEvent, sendAnalytics } from '../../analytics';
|
||||
import { Dialog, hideDialog } from '../../base/dialog';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { translate, translateToHTML } from '../../base/i18n';
|
||||
import { getLocalParticipant } from '../../base/participants';
|
||||
import { MultiSelectAutocomplete } from '../../base/react';
|
||||
|
||||
import { invite } from '../actions';
|
||||
|
@ -39,6 +40,12 @@ class AddPeopleDialog extends Component<*, *> {
|
|||
*/
|
||||
_dialOutAuthUrl: PropTypes.string,
|
||||
|
||||
/**
|
||||
* Whether to show a footer text after the search results
|
||||
* as a last element.
|
||||
*/
|
||||
_footerTextEnabled: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* The JWT token.
|
||||
*/
|
||||
|
@ -168,11 +175,15 @@ class AddPeopleDialog extends Component<*, *> {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { addPeopleEnabled, dialOutEnabled, t } = this.props;
|
||||
const { _footerTextEnabled,
|
||||
addPeopleEnabled,
|
||||
dialOutEnabled,
|
||||
t } = this.props;
|
||||
let isMultiSelectDisabled = this.state.addToCallInProgress || false;
|
||||
let placeholder;
|
||||
let loadingMessage;
|
||||
let noMatches;
|
||||
let footerText;
|
||||
|
||||
if (addPeopleEnabled && dialOutEnabled) {
|
||||
loadingMessage = 'addPeople.loading';
|
||||
|
@ -192,6 +203,19 @@ class AddPeopleDialog extends Component<*, *> {
|
|||
placeholder = 'addPeople.disabled';
|
||||
}
|
||||
|
||||
if (_footerTextEnabled) {
|
||||
footerText = {
|
||||
content: <div className = 'footer-text-wrap'>
|
||||
<div>
|
||||
<span className = 'footer-telephone-icon'>
|
||||
<i className = 'icon-telephone' />
|
||||
</span>
|
||||
</div>
|
||||
{ translateToHTML(t, 'addPeople.footerText') }
|
||||
</div>
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
okDisabled = { this._isAddDisabled() }
|
||||
|
@ -202,6 +226,7 @@ class AddPeopleDialog extends Component<*, *> {
|
|||
<div className = 'add-people-form-wrap'>
|
||||
{ this._renderErrorMessage() }
|
||||
<MultiSelectAutocomplete
|
||||
footer = { footerText }
|
||||
isDisabled = { isMultiSelectDisabled }
|
||||
loadingMessage = { t(loadingMessage) }
|
||||
noMatchesFound = { t(noMatches) }
|
||||
|
@ -525,12 +550,23 @@ class AddPeopleDialog extends Component<*, *> {
|
|||
function _mapStateToProps(state) {
|
||||
const {
|
||||
dialOutAuthUrl,
|
||||
enableFeaturesBasedOnToken,
|
||||
peopleSearchQueryTypes,
|
||||
peopleSearchUrl
|
||||
} = state['features/base/config'];
|
||||
let footerTextEnabled = false;
|
||||
|
||||
if (enableFeaturesBasedOnToken) {
|
||||
const { features = {} } = getLocalParticipant(state);
|
||||
|
||||
if (String(features['outbound-call']) !== 'true') {
|
||||
footerTextEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
_dialOutAuthUrl: dialOutAuthUrl,
|
||||
_footerTextEnabled: footerTextEnabled,
|
||||
_jwt: state['features/base/jwt'].jwt,
|
||||
_peopleSearchQueryTypes: peopleSearchQueryTypes,
|
||||
_peopleSearchUrl: peopleSearchUrl
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import Tooltip from '@atlaskit/tooltip';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
|
@ -8,6 +9,16 @@ import React, { Component } from 'react';
|
|||
* @extends Component
|
||||
*/
|
||||
class OverflowMenuItem extends Component {
|
||||
/**
|
||||
* Default values for {@code OverflowMenuItem} component's properties.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static defaultProps = {
|
||||
tooltipPosition: 'left',
|
||||
disabled: false
|
||||
};
|
||||
|
||||
/**
|
||||
* {@code OverflowMenuItem} component's property types.
|
||||
*
|
||||
|
@ -20,6 +31,11 @@ class OverflowMenuItem extends Component {
|
|||
*/
|
||||
accessibilityLabel: PropTypes.string,
|
||||
|
||||
/**
|
||||
* Whether menu item is disabled or not.
|
||||
*/
|
||||
disabled: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* The icon class to use for displaying an icon before the link text.
|
||||
*/
|
||||
|
@ -33,7 +49,18 @@ class OverflowMenuItem extends Component {
|
|||
/**
|
||||
* The text to display in the {@code OverflowMenuItem}.
|
||||
*/
|
||||
text: PropTypes.string
|
||||
text: PropTypes.string,
|
||||
|
||||
/**
|
||||
* The text to display in the tooltip.
|
||||
*/
|
||||
tooltip: PropTypes.string,
|
||||
|
||||
/**
|
||||
* From which direction the tooltip should appear, relative to the
|
||||
* button.
|
||||
*/
|
||||
tooltipPosition: PropTypes.string
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -43,15 +70,25 @@ class OverflowMenuItem extends Component {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
let className = 'overflow-menu-item';
|
||||
|
||||
className += this.props.disabled ? ' disabled' : '';
|
||||
|
||||
return (
|
||||
<li
|
||||
aria-label = { this.props.accessibilityLabel }
|
||||
className = 'overflow-menu-item'
|
||||
onClick = { this.props.onClick }>
|
||||
className = { className }
|
||||
onClick = { this.props.disabled ? null : this.props.onClick }>
|
||||
<span className = 'overflow-menu-item-icon'>
|
||||
<i className = { this.props.icon } />
|
||||
</span>
|
||||
{ this.props.text }
|
||||
{ this.props.tooltip
|
||||
? <Tooltip
|
||||
content = { this.props.tooltip }
|
||||
position = { this.props.tooltipPosition }>
|
||||
<span>{ this.props.text }</span>
|
||||
</Tooltip>
|
||||
: this.props.text }
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,39 +1,66 @@
|
|||
// @flow
|
||||
|
||||
import Tooltip from '@atlaskit/tooltip';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link OverflowMenuLiveStreamingItem}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The callback to invoke when {@code OverflowMenuLiveStreamingItem} is
|
||||
* clicked.
|
||||
*/
|
||||
onClick: Function,
|
||||
|
||||
/**
|
||||
* The current live streaming session, if any.
|
||||
*/
|
||||
session: ?Object,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* React {@code Component} for starting or stopping a live streaming of the
|
||||
* current conference and displaying the current state of live streaming.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class OverflowMenuLiveStreamingItem extends Component<Props> {
|
||||
class OverflowMenuLiveStreamingItem extends Component {
|
||||
/**
|
||||
* Default values for {@code OverflowMenuLiveStreamingItem}
|
||||
* component's properties.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static defaultProps = {
|
||||
tooltipPosition: 'left',
|
||||
disabled: false
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link OverflowMenuLiveStreamingItem}.
|
||||
*/
|
||||
static propTypes = {
|
||||
|
||||
/**
|
||||
* Whether menu item is disabled or not.
|
||||
*/
|
||||
disabled: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* The callback to invoke when {@code OverflowMenuLiveStreamingItem} is
|
||||
* clicked.
|
||||
*/
|
||||
onClick: Function,
|
||||
|
||||
/**
|
||||
* The current live streaming session, if any.
|
||||
*/
|
||||
session: PropTypes.object,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function,
|
||||
|
||||
/**
|
||||
* The text to display in the tooltip.
|
||||
*/
|
||||
tooltip: PropTypes.string,
|
||||
|
||||
/**
|
||||
* From which direction the tooltip should appear, relative to the
|
||||
* button.
|
||||
*/
|
||||
tooltipPosition: PropTypes.string
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
|
@ -41,26 +68,43 @@ class OverflowMenuLiveStreamingItem extends Component<Props> {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { onClick, session, t } = this.props;
|
||||
const { disabled, onClick, session, t, tooltip, tooltipPosition }
|
||||
= this.props;
|
||||
|
||||
const translationKey = session
|
||||
? 'dialog.stopLiveStreaming'
|
||||
: 'dialog.startLiveStreaming';
|
||||
|
||||
return (
|
||||
<li
|
||||
aria-label = { t('dialog.accessibilityLabel.liveStreaming') }
|
||||
className = 'overflow-menu-item'
|
||||
onClick = { onClick }>
|
||||
<span className = 'overflow-menu-item-icon'>
|
||||
<i className = 'icon-public' />
|
||||
</span>
|
||||
let className = 'overflow-menu-item';
|
||||
|
||||
className += disabled ? ' disabled' : '';
|
||||
|
||||
const button = (// eslint-disable-line no-extra-parens
|
||||
<span>
|
||||
<span className = 'profile-text'>
|
||||
{ t(translationKey) }
|
||||
</span>
|
||||
<span className = 'beta-tag'>
|
||||
{ t('recording.beta') }
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<li
|
||||
aria-label = { t('dialog.accessibilityLabel.liveStreaming') }
|
||||
className = { className }
|
||||
onClick = { disabled ? null : onClick }>
|
||||
<span className = 'overflow-menu-item-icon'>
|
||||
<i className = 'icon-public' />
|
||||
</span>
|
||||
{ tooltip
|
||||
? <Tooltip
|
||||
content = { tooltip }
|
||||
position = { tooltipPosition }>
|
||||
{ button }
|
||||
</Tooltip>
|
||||
: button }
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -13,8 +13,9 @@ import { openDialog } from '../../../base/dialog';
|
|||
import { translate } from '../../../base/i18n';
|
||||
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
|
||||
import {
|
||||
PARTICIPANT_ROLE,
|
||||
getLocalParticipant,
|
||||
getParticipants,
|
||||
isLocalParticipantModerator,
|
||||
participantUpdated
|
||||
} from '../../../base/participants';
|
||||
import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks';
|
||||
|
@ -74,9 +75,10 @@ type Props = {
|
|||
_conference: Object,
|
||||
|
||||
/**
|
||||
* Whether or not desktopsharing was explicitly configured to be disabled.
|
||||
* The tooltip key to use when screensharing is disabled. Or undefined
|
||||
* if non to be shown and the button to be hidden.
|
||||
*/
|
||||
_desktopSharingDisabledByConfig: boolean,
|
||||
_desktopSharingDisabledTooltipKey: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not screensharing is initialized.
|
||||
|
@ -103,6 +105,12 @@ type Props = {
|
|||
*/
|
||||
_feedbackConfigured: boolean,
|
||||
|
||||
/**
|
||||
* The tooltip key to use when file recording is disabled. Or undefined
|
||||
* if non to be shown and the button to be hidden.
|
||||
*/
|
||||
_fileRecordingsDisabledTooltipKey: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the file recording feature is enabled for use.
|
||||
*/
|
||||
|
@ -129,6 +137,12 @@ type Props = {
|
|||
*/
|
||||
_isGuest: boolean,
|
||||
|
||||
/**
|
||||
* The tooltip key to use when live streaming is disabled. Or undefined
|
||||
* if non to be shown and the button to be hidden.
|
||||
*/
|
||||
_liveStreamingDisabledTooltipKey: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the live streaming feature is enabled for use.
|
||||
*/
|
||||
|
@ -925,17 +939,14 @@ class Toolbox extends Component<Props> {
|
|||
*/
|
||||
_renderDesktopSharingButton() {
|
||||
const {
|
||||
_desktopSharingDisabledByConfig,
|
||||
_desktopSharingEnabled,
|
||||
_desktopSharingDisabledTooltipKey,
|
||||
_screensharing,
|
||||
t
|
||||
} = this.props;
|
||||
|
||||
const disabledTooltipText
|
||||
= interfaceConfig.DESKTOP_SHARING_BUTTON_DISABLED_TOOLTIP;
|
||||
const showDisabledTooltip
|
||||
= disabledTooltipText && _desktopSharingDisabledByConfig;
|
||||
const visible = _desktopSharingEnabled || showDisabledTooltip;
|
||||
const visible
|
||||
= _desktopSharingEnabled || _desktopSharingDisabledTooltipKey;
|
||||
|
||||
if (!visible) {
|
||||
return null;
|
||||
|
@ -944,9 +955,9 @@ class Toolbox extends Component<Props> {
|
|||
const classNames = `icon-share-desktop ${
|
||||
_screensharing ? 'toggled' : ''} ${
|
||||
_desktopSharingEnabled ? '' : 'disabled'}`;
|
||||
const tooltip = showDisabledTooltip
|
||||
? disabledTooltipText
|
||||
: t('dialog.shareYourScreen');
|
||||
const tooltip = t(
|
||||
_desktopSharingEnabled
|
||||
? 'dialog.shareYourScreen' : _desktopSharingDisabledTooltipKey);
|
||||
|
||||
return (
|
||||
<ToolbarButton
|
||||
|
@ -969,9 +980,11 @@ class Toolbox extends Component<Props> {
|
|||
_editingDocument,
|
||||
_etherpadInitialized,
|
||||
_feedbackConfigured,
|
||||
_fileRecordingsDisabledTooltipKey,
|
||||
_fileRecordingsEnabled,
|
||||
_fullScreen,
|
||||
_isGuest,
|
||||
_liveStreamingDisabledTooltipKey,
|
||||
_liveStreamingEnabled,
|
||||
_liveStreamingSession,
|
||||
_sharingVideo,
|
||||
|
@ -1000,13 +1013,15 @@ class Toolbox extends Component<Props> {
|
|||
text = { _fullScreen
|
||||
? t('toolbar.exitFullScreen')
|
||||
: t('toolbar.enterFullScreen') } />,
|
||||
_liveStreamingEnabled
|
||||
(_liveStreamingEnabled || _liveStreamingDisabledTooltipKey)
|
||||
&& this._shouldShowButton('livestreaming')
|
||||
&& <OverflowMenuLiveStreamingItem
|
||||
disabled = { !_liveStreamingEnabled }
|
||||
key = 'livestreaming'
|
||||
onClick = { this._onToolbarToggleLiveStreaming }
|
||||
session = { _liveStreamingSession } />,
|
||||
_fileRecordingsEnabled
|
||||
session = { _liveStreamingSession }
|
||||
tooltip = { t(_liveStreamingDisabledTooltipKey) } />,
|
||||
(_fileRecordingsEnabled || _fileRecordingsDisabledTooltipKey)
|
||||
&& this._shouldShowButton('recording')
|
||||
&& this._renderRecordingButton(),
|
||||
this._shouldShowButton('sharedvideo')
|
||||
|
@ -1070,7 +1085,11 @@ class Toolbox extends Component<Props> {
|
|||
* @returns {ReactElement|null}
|
||||
*/
|
||||
_renderRecordingButton() {
|
||||
const { _fileRecordingSession, t } = this.props;
|
||||
const {
|
||||
_fileRecordingSession,
|
||||
_fileRecordingsDisabledTooltipKey,
|
||||
_fileRecordingsEnabled,
|
||||
t } = this.props;
|
||||
|
||||
const translationKey = _fileRecordingSession
|
||||
? 'dialog.stopRecording'
|
||||
|
@ -1080,10 +1099,12 @@ class Toolbox extends Component<Props> {
|
|||
<OverflowMenuItem
|
||||
accessibilityLabel =
|
||||
{ t('toolbar.accessibilityLabel.recording') }
|
||||
disabled = { !_fileRecordingsEnabled }
|
||||
icon = 'icon-camera-take-picture'
|
||||
key = 'recording'
|
||||
onClick = { this._onToolbarToggleRecording }
|
||||
text = { t(translationKey) } />
|
||||
text = { t(translationKey) }
|
||||
tooltip = { t(_fileRecordingsDisabledTooltipKey) } />
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1111,15 +1132,14 @@ class Toolbox extends Component<Props> {
|
|||
* @returns {{}}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const {
|
||||
conference,
|
||||
desktopSharingEnabled
|
||||
} = state['features/base/conference'];
|
||||
const { conference } = state['features/base/conference'];
|
||||
let { desktopSharingEnabled } = state['features/base/conference'];
|
||||
const {
|
||||
callStatsID,
|
||||
disableDesktopSharing,
|
||||
iAmRecorder
|
||||
} = state['features/base/config'];
|
||||
let {
|
||||
fileRecordingsEnabled,
|
||||
iAmRecorder,
|
||||
liveStreamingEnabled
|
||||
} = state['features/base/config'];
|
||||
const sharedVideoStatus = state['features/shared-video'].status;
|
||||
|
@ -1133,15 +1153,77 @@ function _mapStateToProps(state) {
|
|||
} = state['features/toolbox'];
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
const localVideo = getLocalVideoTrack(state['features/base/tracks']);
|
||||
const isModerator = localParticipant.role === PARTICIPANT_ROLE.MODERATOR;
|
||||
const addPeopleEnabled = isAddPeopleEnabled(state);
|
||||
const dialOutEnabled = isDialOutEnabled(state);
|
||||
|
||||
let desktopSharingDisabledTooltipKey;
|
||||
let fileRecordingsDisabledTooltipKey;
|
||||
let liveStreamingDisabledTooltipKey;
|
||||
|
||||
fileRecordingsEnabled
|
||||
= isLocalParticipantModerator(state) && fileRecordingsEnabled;
|
||||
liveStreamingEnabled
|
||||
= isLocalParticipantModerator(state) && liveStreamingEnabled;
|
||||
|
||||
if (state['features/base/config'].enableFeaturesBasedOnToken) {
|
||||
// we enable desktop sharing if any participant already have this
|
||||
// feature enabled
|
||||
desktopSharingEnabled = getParticipants(state)
|
||||
.find(({ features = {} }) =>
|
||||
String(features['screen-sharing']) === 'true') !== undefined;
|
||||
|
||||
// we want to show button and tooltip
|
||||
if (state['features/base/jwt'].isGuest) {
|
||||
desktopSharingDisabledTooltipKey
|
||||
= 'dialog.shareYourScreenDisabledForGuest';
|
||||
} else {
|
||||
desktopSharingDisabledTooltipKey
|
||||
= 'dialog.shareYourScreenDisabled';
|
||||
}
|
||||
|
||||
// we enable recording if the local participant have this
|
||||
// feature enabled
|
||||
const { features = {} } = localParticipant;
|
||||
const { isGuest } = state['features/base/jwt'];
|
||||
|
||||
fileRecordingsEnabled
|
||||
= fileRecordingsEnabled && String(features.recording) === 'true';
|
||||
|
||||
// if the feature is disabled on purpose, do no show it, no tooltip
|
||||
if (!fileRecordingsEnabled
|
||||
&& String(features.recording) !== 'disabled') {
|
||||
// button and tooltip
|
||||
if (isGuest) {
|
||||
fileRecordingsDisabledTooltipKey
|
||||
= 'dialog.recordingDisabledForGuestTooltip';
|
||||
} else {
|
||||
fileRecordingsDisabledTooltipKey
|
||||
= 'dialog.recordingDisabledTooltip';
|
||||
}
|
||||
}
|
||||
|
||||
liveStreamingEnabled
|
||||
= liveStreamingEnabled && String(features.livestreaming) === 'true';
|
||||
|
||||
// if the feature is disabled on purpose, do no show it, no tooltip
|
||||
if (!liveStreamingEnabled
|
||||
&& String(features.livestreaming) !== 'disabled') {
|
||||
// button and tooltip
|
||||
if (isGuest) {
|
||||
liveStreamingDisabledTooltipKey
|
||||
= 'dialog.liveStreamingDisabledForGuestTooltip';
|
||||
} else {
|
||||
liveStreamingDisabledTooltipKey
|
||||
= 'dialog.liveStreamingDisabledTooltip';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
_chatOpen: current === 'chat_container',
|
||||
_conference: conference,
|
||||
_desktopSharingEnabled: desktopSharingEnabled,
|
||||
_desktopSharingDisabledByConfig: disableDesktopSharing,
|
||||
_desktopSharingDisabledTooltipKey: desktopSharingDisabledTooltipKey,
|
||||
_dialog: Boolean(state['features/base/dialog'].component),
|
||||
_editingDocument: Boolean(state['features/etherpad'].editing),
|
||||
_etherpadInitialized: Boolean(state['features/etherpad'].initialized),
|
||||
|
@ -1149,11 +1231,13 @@ function _mapStateToProps(state) {
|
|||
_hideInviteButton:
|
||||
iAmRecorder || (!addPeopleEnabled && !dialOutEnabled),
|
||||
_isGuest: state['features/base/jwt'].isGuest,
|
||||
_fileRecordingsEnabled: isModerator && fileRecordingsEnabled,
|
||||
_fileRecordingsDisabledTooltipKey: fileRecordingsDisabledTooltipKey,
|
||||
_fileRecordingsEnabled: fileRecordingsEnabled,
|
||||
_fileRecordingSession:
|
||||
getActiveSession(state, JitsiRecordingConstants.mode.FILE),
|
||||
_fullScreen: fullScreen,
|
||||
_liveStreamingEnabled: isModerator && liveStreamingEnabled,
|
||||
_liveStreamingDisabledTooltipKey: liveStreamingDisabledTooltipKey,
|
||||
_liveStreamingEnabled: liveStreamingEnabled,
|
||||
_liveStreamingSession:
|
||||
getActiveSession(state, JitsiRecordingConstants.mode.STREAM),
|
||||
_localParticipantID: localParticipant.id,
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
local st = require "util.stanza";
|
||||
local is_feature_allowed = module:require "util".is_feature_allowed;
|
||||
|
||||
-- filters jibri iq in case of requested from jwt authenticated session that
|
||||
-- has features in the user context, but without feature for recording
|
||||
module:hook("pre-iq/full", function(event)
|
||||
local stanza = event.stanza;
|
||||
if stanza.name == "iq" then
|
||||
local jibri = stanza:get_child('jibri', 'http://jitsi.org/protocol/jibri');
|
||||
if jibri then
|
||||
local session = event.origin;
|
||||
local token = session.auth_token;
|
||||
|
||||
if jibri.attr.action == 'start'
|
||||
and (token == nil
|
||||
or not is_feature_allowed(session,
|
||||
(jibri.attr.recording_mode == 'file' and 'recording' or 'livestreaming'))
|
||||
) then
|
||||
module:log("info",
|
||||
"Filtering jibri start recording, stanza:%s", tostring(stanza));
|
||||
session.send(st.error_reply(stanza, "auth", "forbidden"));
|
||||
return true;
|
||||
end
|
||||
end
|
||||
end
|
||||
end);
|
|
@ -2,6 +2,7 @@ local st = require "util.stanza";
|
|||
|
||||
local token_util = module:require "token/util".new(module);
|
||||
local room_jid_match_rewrite = module:require "util".room_jid_match_rewrite;
|
||||
local is_feature_allowed = module:require "util".is_feature_allowed;
|
||||
|
||||
-- no token configuration but required
|
||||
if token_util == nil then
|
||||
|
@ -10,6 +11,8 @@ if token_util == nil then
|
|||
end
|
||||
|
||||
-- filters rayo iq in case of requested from not jwt authenticated sessions
|
||||
-- or if the session has features in user context and it doesn't mention
|
||||
-- feature "outbound-call" to be enabled
|
||||
module:hook("pre-iq/full", function(event)
|
||||
local stanza = event.stanza;
|
||||
if stanza.name == "iq" then
|
||||
|
@ -31,8 +34,11 @@ module:hook("pre-iq/full", function(event)
|
|||
|
||||
if token == nil
|
||||
or roomName == nil
|
||||
or not token_util:verify_room(
|
||||
session, room_jid_match_rewrite(roomName)) then
|
||||
or not token_util:verify_room(session, room_jid_match_rewrite(roomName))
|
||||
or not is_feature_allowed(session,
|
||||
(dial.attr.to == 'jitsi_meet_transcribe' and 'transcription'
|
||||
or 'outbound-call'))
|
||||
then
|
||||
module:log("info",
|
||||
"Filtering stanza dial, stanza:%s", tostring(stanza));
|
||||
session.send(st.error_reply(stanza, "auth", "forbidden"));
|
||||
|
|
|
@ -235,6 +235,7 @@ end
|
|||
-- session.jitsi_meet_domain - the domain name value from the token
|
||||
-- session.jitsi_meet_context_user - the user details from the token
|
||||
-- session.jitsi_meet_context_group - the group value from the token
|
||||
-- session.jitsi_meet_context_features - the features value from the token
|
||||
-- @param session the current session
|
||||
-- @return false and error
|
||||
function Util:process_and_verify_token(session)
|
||||
|
@ -285,6 +286,11 @@ function Util:process_and_verify_token(session)
|
|||
-- Binds any group details to the session
|
||||
session.jitsi_meet_context_group = claims["context"]["group"];
|
||||
end
|
||||
|
||||
if claims["context"]["features"] ~= nil then
|
||||
-- Binds any features details to the session
|
||||
session.jitsi_meet_context_features = claims["context"]["features"];
|
||||
end
|
||||
end
|
||||
return true;
|
||||
else
|
||||
|
|
|
@ -133,7 +133,22 @@ function update_presence_identity(
|
|||
"Presence with identity inserted %s", tostring(stanza))
|
||||
end
|
||||
|
||||
-- Utility function to check whether feature is present and enabled. Allow
|
||||
-- a feature if there are features present in the session(coming from
|
||||
-- the token) and the value of the feature is true.
|
||||
-- If features is not present in the token we skip feature detection and allow
|
||||
-- everything.
|
||||
function is_feature_allowed(session, feature)
|
||||
if (session.jitsi_meet_context_features == nil
|
||||
or session.jitsi_meet_context_features[feature] == "true") then
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
is_feature_allowed = is_feature_allowed;
|
||||
get_room_from_jid = get_room_from_jid;
|
||||
wrap_async_run = wrap_async_run;
|
||||
room_jid_match_rewrite = room_jid_match_rewrite;
|
||||
|
|
Loading…
Reference in New Issue