feat(toolbar-buttons) Hide buttons disabled through JWT (#12261)

This commit is contained in:
Horatiu Muresan 2022-10-04 16:02:49 +03:00 committed by GitHub
parent c35d1d8d4b
commit a2d0492007
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 68 additions and 41 deletions

View File

@ -1,17 +1,29 @@
/**
* The list of supported meeting features to enable/disable through jwt.
*/
export const MEET_FEATURES = [
'branding',
'calendar',
'callstats',
'livestreaming',
'lobby',
'moderation',
'outbound-call',
'recording',
'room',
'screen-sharing',
'sip-outbound-call',
'transcription'
];
export const MEET_FEATURES = {
BRANDING: 'branding',
CALENDAR: 'calendar',
CALLSTATS: 'callstats',
FLIP: 'flip',
INBOUND_CALL: 'inbound-call',
LIVESTREAMING: 'livestreaming',
LOBBY: 'lobby',
MODERATION: 'moderation',
OUTBOUND_CALL: 'outbound-call',
RECORDING: 'recording',
ROOM: 'room',
SCREEN_SHARING: 'screen-sharing',
SIP_INBOUND_CALL: 'sip-inbound-call',
SIP_OUTBOUND_CALL: 'sip-outbound-call',
TRANSCRIPTION: 'transcription'
};
/**
* A mapping between jwt features and toolbar buttons keys.
*/
export const FEATURES_TO_BUTTONS_MAPPING = {
'livestreaming': 'livestreaming',
'recording': 'recording',
'transcription': 'closedcaptions'
};

View File

@ -147,9 +147,10 @@ export function validateJwt(jwt: string) {
errors.push('- `context` object is missing from the payload');
} else if (context.features) {
const { features } = context;
const meetFeatures = Object.values(MEET_FEATURES);
Object.keys(features).forEach(feature => {
if (MEET_FEATURES.includes(feature)) {
if (meetFeatures.includes(feature)) {
const featureValue = features[feature];
// cannot use truthy or falsy because we need the exact value and type check.

View File

@ -6,19 +6,6 @@ export const STATUSES = {
BLOCKED: 'BLOCKED'
};
/**
* Service features for JaaS users.
*/
export const FEATURES = {
INBOUND_CALL: 'inbound-call',
OUTBOUND_CALL: 'outbound-call',
RECORDING: 'recording',
SIP_INBOUND_CALL: 'sip-inbound-call',
SIP_OUTBOUND_CALL: 'sip-outbound-call',
STREAMING: 'streaming',
TRANSCRIPTION: 'transcription'
};
/**
* URL for displaying JaaS upgrade options.
*/

View File

@ -1,13 +1,13 @@
// @flow
import { IconLiveStreaming } from '../../../base/icons';
import { MEET_FEATURES } from '../../../base/jwt/constants';
import { isJwtFeatureEnabled } from '../../../base/jwt/functions';
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
import { isLocalParticipantModerator } from '../../../base/participants';
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
import { isInBreakoutRoom } from '../../../breakout-rooms/functions';
import { maybeShowPremiumFeatureDialog } from '../../../jaas/actions';
import { FEATURES } from '../../../jaas/constants';
import { getActiveSession } from '../../functions';
import { getLiveStreaming } from './functions';
@ -85,7 +85,7 @@ export default class AbstractLiveStreamButton<P: Props> extends AbstractButton<P
async _handleClick() {
const { dispatch } = this.props;
const dialogShown = await dispatch(maybeShowPremiumFeatureDialog(FEATURES.RECORDING));
const dialogShown = await dispatch(maybeShowPremiumFeatureDialog(MEET_FEATURES.RECORDING));
if (!dialogShown) {
this._onHandleClick();

View File

@ -4,9 +4,9 @@ import { Component } from 'react';
import { getActiveSession, isHighlightMeetingMomentDisabled } from '../..';
import { openDialog } from '../../../base/dialog';
import { MEET_FEATURES } from '../../../base/jwt/constants';
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
import { maybeShowPremiumFeatureDialog } from '../../../jaas/actions';
import { FEATURES } from '../../../jaas/constants';
import {
NOTIFICATION_TIMEOUT_TYPE,
NOTIFICATION_TYPE,
@ -79,7 +79,7 @@ export default class AbstractHighlightButton<P: Props> extends Component<P> {
customActionNameKey: [ 'localRecording.start' ],
customActionHandler: [ async () => {
dispatch(hideNotification(PROMPT_RECORDING_NOTIFICATION_ID));
const dialogShown = await dispatch(maybeShowPremiumFeatureDialog(FEATURES.RECORDING));
const dialogShown = await dispatch(maybeShowPremiumFeatureDialog(MEET_FEATURES.RECORDING));
if (!dialogShown) {
dispatch(openDialog(StartRecordingDialog));

View File

@ -5,10 +5,10 @@ import {
sendAnalytics
} from '../../../analytics';
import { IconToggleRecording } from '../../../base/icons';
import { MEET_FEATURES } from '../../../base/jwt/constants';
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
import { maybeShowPremiumFeatureDialog } from '../../../jaas/actions';
import { FEATURES } from '../../../jaas/constants';
import { getActiveSession, getRecordButtonProps } from '../../functions';
import LocalRecordingManager from './LocalRecordingManager';
@ -91,7 +91,7 @@ export default class AbstractRecordButton<P: Props> extends AbstractButton<P, *>
'is_recording': _isRecordingRunning,
type: JitsiRecordingConstants.mode.FILE
}));
const dialogShown = await dispatch(maybeShowPremiumFeatureDialog(FEATURES.RECORDING));
const dialogShown = await dispatch(maybeShowPremiumFeatureDialog(MEET_FEATURES.RECORDING));
if (!dialogShown) {
this._onHandleClick();

View File

@ -9,6 +9,7 @@ import { StartRecordingDialog } from '../..';
import { openDialog } from '../../../../base/dialog';
import { translate } from '../../../../base/i18n/functions';
import { IconHighlight } from '../../../../base/icons/svg';
import { MEET_FEATURES } from '../../../../base/jwt/constants';
import Label from '../../../../base/label/components/web/Label';
import { connect } from '../../../../base/redux/functions';
// @ts-ignore
@ -16,7 +17,6 @@ import { Tooltip } from '../../../../base/tooltip';
import BaseTheme from '../../../../base/ui/components/BaseTheme.web';
// @ts-ignore
import { maybeShowPremiumFeatureDialog } from '../../../../jaas/actions';
import { FEATURES } from '../../../../jaas/constants';
import AbstractHighlightButton, {
type Props as AbstractProps,
_abstractMapStateToProps
@ -140,7 +140,7 @@ export class HighlightButton extends AbstractHighlightButton<Props, State> {
async _onOpenDialog() {
// @ts-ignore
const { dispatch } = this.props;
const dialogShown = await dispatch(maybeShowPremiumFeatureDialog(FEATURES.RECORDING));
const dialogShown = await dispatch(maybeShowPremiumFeatureDialog(MEET_FEATURES.RECORDING));
if (!dialogShown) {
dispatch(openDialog(StartRecordingDialog));

View File

@ -1,10 +1,10 @@
// @flow
import { createToolbarEvent, sendAnalytics } from '../../analytics';
import { MEET_FEATURES } from '../../base/jwt/constants';
import { isLocalParticipantModerator } from '../../base/participants';
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components';
import { maybeShowPremiumFeatureDialog } from '../../jaas/actions';
import { FEATURES } from '../../jaas/constants';
export type AbstractProps = AbstractButtonProps & {
@ -61,7 +61,7 @@ export class AbstractClosedCaptionButton
'requesting_subtitles': Boolean(_requestingSubtitles)
}));
const dialogShown = await dispatch(maybeShowPremiumFeatureDialog(FEATURES.RECORDING));
const dialogShown = await dispatch(maybeShowPremiumFeatureDialog(MEET_FEATURES.RECORDING));
if (!dialogShown) {
this._handleClickOpenLanguageSelector();

View File

@ -115,6 +115,7 @@ import {
import { NOTIFY_CLICK_MODE, NOT_APPLICABLE, THRESHOLDS } from '../../constants';
// @ts-ignore
import { isDesktopShareButtonDisabled, isToolboxVisible } from '../../functions';
import { getJwtDisabledButtons } from '../../functions.any';
// @ts-ignore
import DownloadButton from '../DownloadButton';
// @ts-ignore
@ -251,6 +252,11 @@ interface Props extends WithTranslation {
*/
_isVpaasMeeting: boolean;
/**
* The array of toolbar buttons disabled through jwt features.
*/
_jwtDisabledButons: string[];
/**
* The ID of the local participant.
*/
@ -1009,10 +1015,10 @@ class Toolbox extends Component<Props> {
_getVisibleButtons() {
const {
_clientWidth,
_toolbarButtons
_toolbarButtons,
_jwtDisabledButons
} = this.props;
const buttons = this._getAllButtons();
this._setButtonsNotifyClickMode(buttons);
@ -1027,7 +1033,9 @@ class Toolbox extends Component<Props> {
...order.map(key => buttons[key as keyof typeof buttons]),
...Object.values(buttons).filter((button, index) => !order.includes(keys[index]))
].filter(Boolean).filter(({ key, alias = NOT_APPLICABLE }) =>
isToolbarButtonEnabled(key, _toolbarButtons) || isToolbarButtonEnabled(alias, _toolbarButtons));
!_jwtDisabledButons.includes(key)
&& (isToolbarButtonEnabled(key, _toolbarButtons) || isToolbarButtonEnabled(alias, _toolbarButtons))
);
if (isHangupVisible) {
sliceIndex -= 1;
@ -1534,6 +1542,7 @@ function _mapStateToProps(state: IState, ownProps: Partial<Props>) {
_isIosMobile: isIosMobileBrowser(),
_isMobile: isMobileBrowser(),
_isVpaasMeeting: isVpaasMeeting(state),
_jwtDisabledButons: getJwtDisabledButtons(state),
_hasSalesforce: isSalesforceEnabled(state),
_hangupMenuVisible: hangupMenuVisible,
_localParticipantID: localParticipant?.id,

View File

@ -1,4 +1,6 @@
import { IState } from '../app/types';
import { FEATURES_TO_BUTTONS_MAPPING } from '../base/jwt/constants';
import { isJwtFeatureEnabled } from '../base/jwt/functions';
/**
* Indicates if the audio mute button is disabled or not.
@ -12,3 +14,19 @@ export function isAudioMuteButtonDisabled(state: IState) {
return Boolean(!available || startSilent || (muted && unmuteBlocked));
}
/**
* Returns the buttons corresponding to features disabled through jwt.
*
* @param {IState} state - The state from the Redux store.
* @returns {string[]} - The disabled by jwt buttons array.
*/
export function getJwtDisabledButtons(state: IState) {
return Object.keys(FEATURES_TO_BUTTONS_MAPPING).reduce((acc: string[], current: string) => {
if (!isJwtFeatureEnabled(state, current, true)) {
acc.push(current);
}
return acc;
}, []);
}