浏览代码

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.
master
Дамян Минков 7 年前
父节点
当前提交
ac834326e7
没有帐户链接到提交者的电子邮件

+ 10
- 21
conference.js 查看文件

484
 
484
 
485
     /**
485
     /**
486
      * Indicates if the desktop sharing functionality has been enabled.
486
      * Indicates if the desktop sharing functionality has been enabled.
487
-     * It takes into consideration {@link isDesktopSharingDisabledByConfig}
488
-     * as well as the status returned by
487
+     * It takes into consideration the status returned by
489
      * {@link JitsiMeetJS.isDesktopSharingEnabled()}. The latter can be false
488
      * {@link JitsiMeetJS.isDesktopSharingEnabled()}. The latter can be false
490
      * either if the desktop sharing is not supported by the current browser
489
      * either if the desktop sharing is not supported by the current browser
491
      * or if it was disabled through lib-jitsi-meet specific options (check
490
      * or if it was disabled through lib-jitsi-meet specific options (check
493
      */
492
      */
494
     isDesktopSharingEnabled: false,
493
     isDesktopSharingEnabled: false,
495
 
494
 
496
-    /**
497
-     * Set to <tt>true</tt> if the desktop sharing functionality has been
498
-     * explicitly disabled in the config.
499
-     */
500
-    isDesktopSharingDisabledByConfig: false,
501
-
502
-    /**
503
-     * The text displayed when the desktop sharing button is disabled through
504
-     * the config. The value is set through
505
-     * {@link interfaceConfig.DESKTOP_SHARING_BUTTON_DISABLED_TOOLTIP}.
506
-     */
507
-    desktopSharingDisabledTooltip: null,
508
-
509
     /**
495
     /**
510
      * The local audio track (if any).
496
      * The local audio track (if any).
511
      * FIXME tracks from redux store should be the single source of truth
497
      * FIXME tracks from redux store should be the single source of truth
720
                 APP.connection = connection = con;
706
                 APP.connection = connection = con;
721
 
707
 
722
                 // Desktop sharing related stuff:
708
                 // Desktop sharing related stuff:
723
-                this.isDesktopSharingDisabledByConfig
724
-                    = config.disableDesktopSharing;
725
                 this.isDesktopSharingEnabled
709
                 this.isDesktopSharingEnabled
726
-                    = !this.isDesktopSharingDisabledByConfig
727
-                        && JitsiMeetJS.isDesktopSharingEnabled();
728
-                this.desktopSharingDisabledTooltip
729
-                    = interfaceConfig.DESKTOP_SHARING_BUTTON_DISABLED_TOOLTIP;
710
+                    = JitsiMeetJS.isDesktopSharingEnabled();
730
                 eventEmitter.emit(
711
                 eventEmitter.emit(
731
                     JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
712
                     JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
732
                     this.isDesktopSharingEnabled);
713
                     this.isDesktopSharingEnabled);
1909
             JitsiConferenceEvents.PARTICIPANT_PROPERTY_CHANGED,
1890
             JitsiConferenceEvents.PARTICIPANT_PROPERTY_CHANGED,
1910
             (participant, name, oldValue, newValue) => {
1891
             (participant, name, oldValue, newValue) => {
1911
                 switch (name) {
1892
                 switch (name) {
1893
+                case 'features_screen-sharing': {
1894
+                    APP.store.dispatch(participantUpdated({
1895
+                        conference: room,
1896
+                        id: participant.getId(),
1897
+                        features: { 'screen-sharing': true }
1898
+                    }));
1899
+                    break;
1900
+                }
1912
                 case 'raisedHand':
1901
                 case 'raisedHand':
1913
                     APP.store.dispatch(participantUpdated({
1902
                     APP.store.dispatch(participantUpdated({
1914
                         conference: room,
1903
                         conference: room,

+ 3
- 3
config.js 查看文件

142
 
142
 
143
     // Desktop sharing
143
     // Desktop sharing
144
 
144
 
145
-    // Enable / disable desktop sharing
146
-    // disableDesktopSharing: false,
147
-
148
     // The ID of the jidesha extension for Chrome.
145
     // The ID of the jidesha extension for Chrome.
149
     desktopSharingChromeExtId: null,
146
     desktopSharingChromeExtId: null,
150
 
147
 
248
     // edit their profile.
245
     // edit their profile.
249
     enableUserRolesBasedOnToken: false,
246
     enableUserRolesBasedOnToken: false,
250
 
247
 
248
+    // Whether or not some features are checked based on token.
249
+    // enableFeaturesBasedOnToken: false,
250
+
251
     // Message to show the users. Example: 'The service will be down for
251
     // Message to show the users. Example: 'The service will be down for
252
     // maintenance at 01:00 AM GMT,
252
     // maintenance at 01:00 AM GMT,
253
     // noticeMessage: '',
253
     // noticeMessage: '',

+ 11
- 1
css/_toolbars.scss 查看文件

95
     }
95
     }
96
 
96
 
97
     i.disabled {
97
     i.disabled {
98
-        cursor: initial
98
+        cursor: initial;
99
+        color: #3b475c;
100
+    }
101
+
102
+    .disabled i {
103
+        cursor: initial;
104
+        color: #3b475c;
99
     }
105
     }
100
 
106
 
101
     i.disabled:hover {
107
     i.disabled:hover {
135
             &.unclickable:hover {
141
             &.unclickable:hover {
136
                 background: inherit;
142
                 background: inherit;
137
             }
143
             }
144
+            &.disabled {
145
+                cursor: initial;
146
+                color: #3b475c;
147
+            }
138
         }
148
         }
139
 
149
 
140
         .beta-tag {
150
         .beta-tag {

+ 15
- 0
css/modals/invite/_add-people.scss 查看文件

23
                 margin: auto;
23
                 margin: auto;
24
             }
24
             }
25
         }
25
         }
26
+
27
+        .footer-text-wrap {
28
+            display: flex;
29
+        }
30
+
31
+        .footer-telephone-icon {
32
+            display: flex;
33
+            transform: scaleX(-1);
34
+            padding-left: 10px;
35
+
36
+            i {
37
+                line-height: 20px;
38
+                margin: auto;
39
+            }
40
+        }
26
     }
41
     }
27
 }
42
 }
28
 
43
 

+ 0
- 6
interface_config.js 查看文件

5
     // methods allowing to use variables both in css and js.
5
     // methods allowing to use variables both in css and js.
6
     DEFAULT_BACKGROUND: '#474747',
6
     DEFAULT_BACKGROUND: '#474747',
7
 
7
 
8
-    /**
9
-     * In case the desktop sharing is disabled through the config the button
10
-     * will not be hidden, but displayed as disabled with this text us as
11
-     * a tooltip.
12
-     */
13
-    DESKTOP_SHARING_BUTTON_DISABLED_TOOLTIP: null,
14
     INITIAL_TOOLBAR_TIMEOUT: 20000,
8
     INITIAL_TOOLBAR_TIMEOUT: 20000,
15
     TOOLBAR_TIMEOUT: 4000,
9
     TOOLBAR_TIMEOUT: 4000,
16
     TOOLBAR_ALWAYS_VISIBLE: false,
10
     TOOLBAR_ALWAYS_VISIBLE: false,

+ 7
- 0
lang/main.json 查看文件

361
         "muteParticipantTitle": "Mute this member?",
361
         "muteParticipantTitle": "Mute this member?",
362
         "muteParticipantBody": "You won't be able to unmute them, but they can unmute themselves at any time.",
362
         "muteParticipantBody": "You won't be able to unmute them, but they can unmute themselves at any time.",
363
         "muteParticipantButton": "Mute",
363
         "muteParticipantButton": "Mute",
364
+        "liveStreamingDisabledTooltip": "Start live stream disabled.",
365
+        "liveStreamingDisabledForGuestTooltip": "Guests can't start live streaming.",
366
+        "recordingDisabledTooltip": "Start recording disabled.",
367
+        "recordingDisabledForGuestTooltip": "Guests can't start recordings.",
364
         "remoteControlTitle": "Remote desktop control",
368
         "remoteControlTitle": "Remote desktop control",
365
         "remoteControlRequestMessage": "Will you allow __user__ to remotely control your desktop?",
369
         "remoteControlRequestMessage": "Will you allow __user__ to remotely control your desktop?",
366
         "remoteControlShareScreenWarning": "Note that if you press \"Allow\" you will share your screen!",
370
         "remoteControlShareScreenWarning": "Note that if you press \"Allow\" you will share your screen!",
371
         "remoteControlStopMessage": "The remote control session ended!",
375
         "remoteControlStopMessage": "The remote control session ended!",
372
         "close": "Close",
376
         "close": "Close",
373
         "shareYourScreen": "Share your screen",
377
         "shareYourScreen": "Share your screen",
378
+        "shareYourScreenDisabled": "Screen sharing disabled.",
379
+        "shareYourScreenDisabledForGuest": "Guests can't screen share.",
374
         "yourEntireScreen": "Your entire screen",
380
         "yourEntireScreen": "Your entire screen",
375
         "applicationWindow": "Application window"
381
         "applicationWindow": "Application window"
376
     },
382
     },
521
         "countryNotSupported": "We do not support this destination yet.",
527
         "countryNotSupported": "We do not support this destination yet.",
522
         "countryReminder": "Calling outside the US? Please make sure you start with the country code!",
528
         "countryReminder": "Calling outside the US? Please make sure you start with the country code!",
523
         "disabled": "You can't invite people.",
529
         "disabled": "You can't invite people.",
530
+        "footerText": "Dialing out is disabled.",
524
         "invite": "Invite",
531
         "invite": "Invite",
525
         "loading": "Searching for people and phone numbers",
532
         "loading": "Searching for people and phone numbers",
526
         "loadingNumber": "Validating phone number",
533
         "loadingNumber": "Validating phone number",

+ 16
- 2
react/features/base/conference/functions.js 查看文件

188
  */
188
  */
189
 export function sendLocalParticipant(
189
 export function sendLocalParticipant(
190
         stateful: Function | Object,
190
         stateful: Function | Object,
191
-        conference: { sendCommand: Function, setDisplayName: Function }) {
192
-    const { avatarID, avatarURL, email, name } = getLocalParticipant(stateful);
191
+        conference: {
192
+            sendCommand: Function,
193
+            setDisplayName: Function,
194
+            setLocalParticipantProperty: Function }) {
195
+    const {
196
+        avatarID,
197
+        avatarURL,
198
+        email,
199
+        features,
200
+        name
201
+    } = getLocalParticipant(stateful);
193
 
202
 
194
     avatarID && conference.sendCommand(AVATAR_ID_COMMAND, {
203
     avatarID && conference.sendCommand(AVATAR_ID_COMMAND, {
195
         value: avatarID
204
         value: avatarID
200
     email && conference.sendCommand(EMAIL_COMMAND, {
209
     email && conference.sendCommand(EMAIL_COMMAND, {
201
         value: email
210
         value: email
202
     });
211
     });
212
+
213
+    if (features && features['screen-sharing'] === 'true') {
214
+        conference.setLocalParticipantProperty('features_screen-sharing', true);
215
+    }
216
+
203
     conference.setDisplayName(name);
217
     conference.setDisplayName(name);
204
 }
218
 }

+ 0
- 3
react/features/base/config/functions.js 查看文件

89
     'disableAGC',
89
     'disableAGC',
90
     'disableAP',
90
     'disableAP',
91
     'disableAudioLevels',
91
     'disableAudioLevels',
92
-    'disableDesktopSharing',
93
-    'disableDesktopSharing',
94
     'disableH264',
92
     'disableH264',
95
     'disableHPF',
93
     'disableHPF',
96
     'disableNS',
94
     'disableNS',
106
     'enableStatsID',
104
     'enableStatsID',
107
     'enableTalkWhileMuted',
105
     'enableTalkWhileMuted',
108
     'enableTcc',
106
     'enableTcc',
109
-    'enableUserRolesBasedOnToken',
110
     'etherpad_base',
107
     'etherpad_base',
111
     'failICE',
108
     'failICE',
112
     'fileRecordingsEnabled',
109
     'fileRecordingsEnabled',

+ 9
- 2
react/features/base/jwt/middleware.js 查看文件

138
  */
138
  */
139
 function _overwriteLocalParticipant(
139
 function _overwriteLocalParticipant(
140
         { dispatch, getState },
140
         { dispatch, getState },
141
-        { avatarURL, email, name }) {
141
+        { avatarURL, email, name, features }) {
142
     let localParticipant;
142
     let localParticipant;
143
 
143
 
144
     if ((avatarURL || email || name)
144
     if ((avatarURL || email || name)
157
         if (name) {
157
         if (name) {
158
             newProperties.name = name;
158
             newProperties.name = name;
159
         }
159
         }
160
+        if (features) {
161
+            newProperties.features = features;
162
+        }
160
         dispatch(participantUpdated(newProperties));
163
         dispatch(participantUpdated(newProperties));
161
     }
164
     }
162
 }
165
 }
229
                     action.server = context.server;
232
                     action.server = context.server;
230
                     action.user = user;
233
                     action.user = user;
231
 
234
 
232
-                    user && _overwriteLocalParticipant(store, user);
235
+                    user && _overwriteLocalParticipant(
236
+                        store, { ...user,
237
+                            features: context.features });
233
                 }
238
                 }
234
             }
239
             }
235
         } else if (typeof APP === 'undefined') {
240
         } else if (typeof APP === 'undefined') {
281
         if (name === localParticipant.name) {
286
         if (name === localParticipant.name) {
282
             newProperties.name = undefined;
287
             newProperties.name = undefined;
283
         }
288
         }
289
+        newProperties.features = undefined;
290
+
284
         dispatch(participantUpdated(newProperties));
291
         dispatch(participantUpdated(newProperties));
285
     }
292
     }
286
 }
293
 }

+ 7
- 0
react/features/base/react/components/web/MultiSelectAutocomplete.js 查看文件

24
          */
24
          */
25
         defaultValue: PropTypes.array,
25
         defaultValue: PropTypes.array,
26
 
26
 
27
+        /**
28
+         * Optional footer to show as a last element in the results.
29
+         * Should be of type {content: <some content>}
30
+         */
31
+        footer: PropTypes.object,
32
+
27
         /**
33
         /**
28
          * Indicates if the component is disabled.
34
          * Indicates if the component is disabled.
29
          */
35
          */
151
             <div>
157
             <div>
152
                 <MultiSelectStateless
158
                 <MultiSelectStateless
153
                     filterValue = { this.state.filterValue }
159
                     filterValue = { this.state.filterValue }
160
+                    footer = { this.props.footer }
154
                     icon = { null }
161
                     icon = { null }
155
                     isDisabled = { isDisabled }
162
                     isDisabled = { isDisabled }
156
                     isLoading = { this.state.loading }
163
                     isLoading = { this.state.loading }

+ 38
- 2
react/features/invite/components/AddPeopleDialog.web.js 查看文件

8
 
8
 
9
 import { createInviteDialogEvent, sendAnalytics } from '../../analytics';
9
 import { createInviteDialogEvent, sendAnalytics } from '../../analytics';
10
 import { Dialog, hideDialog } from '../../base/dialog';
10
 import { Dialog, hideDialog } from '../../base/dialog';
11
-import { translate } from '../../base/i18n';
11
+import { translate, translateToHTML } from '../../base/i18n';
12
+import { getLocalParticipant } from '../../base/participants';
12
 import { MultiSelectAutocomplete } from '../../base/react';
13
 import { MultiSelectAutocomplete } from '../../base/react';
13
 
14
 
14
 import { invite } from '../actions';
15
 import { invite } from '../actions';
39
          */
40
          */
40
         _dialOutAuthUrl: PropTypes.string,
41
         _dialOutAuthUrl: PropTypes.string,
41
 
42
 
43
+        /**
44
+         * Whether to show a footer text after the search results
45
+         * as a last element.
46
+         */
47
+        _footerTextEnabled: PropTypes.bool,
48
+
42
         /**
49
         /**
43
          * The JWT token.
50
          * The JWT token.
44
          */
51
          */
168
      * @returns {ReactElement}
175
      * @returns {ReactElement}
169
      */
176
      */
170
     render() {
177
     render() {
171
-        const { addPeopleEnabled, dialOutEnabled, t } = this.props;
178
+        const { _footerTextEnabled,
179
+            addPeopleEnabled,
180
+            dialOutEnabled,
181
+            t } = this.props;
172
         let isMultiSelectDisabled = this.state.addToCallInProgress || false;
182
         let isMultiSelectDisabled = this.state.addToCallInProgress || false;
173
         let placeholder;
183
         let placeholder;
174
         let loadingMessage;
184
         let loadingMessage;
175
         let noMatches;
185
         let noMatches;
186
+        let footerText;
176
 
187
 
177
         if (addPeopleEnabled && dialOutEnabled) {
188
         if (addPeopleEnabled && dialOutEnabled) {
178
             loadingMessage = 'addPeople.loading';
189
             loadingMessage = 'addPeople.loading';
192
             placeholder = 'addPeople.disabled';
203
             placeholder = 'addPeople.disabled';
193
         }
204
         }
194
 
205
 
206
+        if (_footerTextEnabled) {
207
+            footerText = {
208
+                content: <div className = 'footer-text-wrap'>
209
+                    <div>
210
+                        <span className = 'footer-telephone-icon'>
211
+                            <i className = 'icon-telephone' />
212
+                        </span>
213
+                    </div>
214
+                    { translateToHTML(t, 'addPeople.footerText') }
215
+                </div>
216
+            };
217
+        }
218
+
195
         return (
219
         return (
196
             <Dialog
220
             <Dialog
197
                 okDisabled = { this._isAddDisabled() }
221
                 okDisabled = { this._isAddDisabled() }
202
                 <div className = 'add-people-form-wrap'>
226
                 <div className = 'add-people-form-wrap'>
203
                     { this._renderErrorMessage() }
227
                     { this._renderErrorMessage() }
204
                     <MultiSelectAutocomplete
228
                     <MultiSelectAutocomplete
229
+                        footer = { footerText }
205
                         isDisabled = { isMultiSelectDisabled }
230
                         isDisabled = { isMultiSelectDisabled }
206
                         loadingMessage = { t(loadingMessage) }
231
                         loadingMessage = { t(loadingMessage) }
207
                         noMatchesFound = { t(noMatches) }
232
                         noMatchesFound = { t(noMatches) }
525
 function _mapStateToProps(state) {
550
 function _mapStateToProps(state) {
526
     const {
551
     const {
527
         dialOutAuthUrl,
552
         dialOutAuthUrl,
553
+        enableFeaturesBasedOnToken,
528
         peopleSearchQueryTypes,
554
         peopleSearchQueryTypes,
529
         peopleSearchUrl
555
         peopleSearchUrl
530
     } = state['features/base/config'];
556
     } = state['features/base/config'];
557
+    let footerTextEnabled = false;
558
+
559
+    if (enableFeaturesBasedOnToken) {
560
+        const { features = {} } = getLocalParticipant(state);
561
+
562
+        if (String(features['outbound-call']) !== 'true') {
563
+            footerTextEnabled = true;
564
+        }
565
+    }
531
 
566
 
532
     return {
567
     return {
533
         _dialOutAuthUrl: dialOutAuthUrl,
568
         _dialOutAuthUrl: dialOutAuthUrl,
569
+        _footerTextEnabled: footerTextEnabled,
534
         _jwt: state['features/base/jwt'].jwt,
570
         _jwt: state['features/base/jwt'].jwt,
535
         _peopleSearchQueryTypes: peopleSearchQueryTypes,
571
         _peopleSearchQueryTypes: peopleSearchQueryTypes,
536
         _peopleSearchUrl: peopleSearchUrl
572
         _peopleSearchUrl: peopleSearchUrl

+ 41
- 4
react/features/toolbox/components/web/OverflowMenuItem.js 查看文件

1
+import Tooltip from '@atlaskit/tooltip';
1
 import PropTypes from 'prop-types';
2
 import PropTypes from 'prop-types';
2
 import React, { Component } from 'react';
3
 import React, { Component } from 'react';
3
 
4
 
8
  * @extends Component
9
  * @extends Component
9
  */
10
  */
10
 class OverflowMenuItem extends Component {
11
 class OverflowMenuItem extends Component {
12
+    /**
13
+     * Default values for {@code OverflowMenuItem} component's properties.
14
+     *
15
+     * @static
16
+     */
17
+    static defaultProps = {
18
+        tooltipPosition: 'left',
19
+        disabled: false
20
+    };
21
+
11
     /**
22
     /**
12
      * {@code OverflowMenuItem} component's property types.
23
      * {@code OverflowMenuItem} component's property types.
13
      *
24
      *
20
          */
31
          */
21
         accessibilityLabel: PropTypes.string,
32
         accessibilityLabel: PropTypes.string,
22
 
33
 
34
+        /**
35
+         * Whether menu item is disabled or not.
36
+         */
37
+        disabled: PropTypes.bool,
38
+
23
         /**
39
         /**
24
          * The icon class to use for displaying an icon before the link text.
40
          * The icon class to use for displaying an icon before the link text.
25
          */
41
          */
33
         /**
49
         /**
34
          * The text to display in the {@code OverflowMenuItem}.
50
          * The text to display in the {@code OverflowMenuItem}.
35
          */
51
          */
36
-        text: PropTypes.string
52
+        text: PropTypes.string,
53
+
54
+        /**
55
+         * The text to display in the tooltip.
56
+         */
57
+        tooltip: PropTypes.string,
58
+
59
+        /**
60
+         * From which direction the tooltip should appear, relative to the
61
+         * button.
62
+         */
63
+        tooltipPosition: PropTypes.string
37
     };
64
     };
38
 
65
 
39
     /**
66
     /**
43
      * @returns {ReactElement}
70
      * @returns {ReactElement}
44
      */
71
      */
45
     render() {
72
     render() {
73
+        let className = 'overflow-menu-item';
74
+
75
+        className += this.props.disabled ? ' disabled' : '';
76
+
46
         return (
77
         return (
47
             <li
78
             <li
48
                 aria-label = { this.props.accessibilityLabel }
79
                 aria-label = { this.props.accessibilityLabel }
49
-                className = 'overflow-menu-item'
50
-                onClick = { this.props.onClick }>
80
+                className = { className }
81
+                onClick = { this.props.disabled ? null : this.props.onClick }>
51
                 <span className = 'overflow-menu-item-icon'>
82
                 <span className = 'overflow-menu-item-icon'>
52
                     <i className = { this.props.icon } />
83
                     <i className = { this.props.icon } />
53
                 </span>
84
                 </span>
54
-                { this.props.text }
85
+                { this.props.tooltip
86
+                    ? <Tooltip
87
+                        content = { this.props.tooltip }
88
+                        position = { this.props.tooltipPosition }>
89
+                        <span>{ this.props.text }</span>
90
+                    </Tooltip>
91
+                    : this.props.text }
55
             </li>
92
             </li>
56
         );
93
         );
57
     }
94
     }

+ 76
- 32
react/features/toolbox/components/web/OverflowMenuLiveStreamingItem.js 查看文件

1
-// @flow
2
-
1
+import Tooltip from '@atlaskit/tooltip';
2
+import PropTypes from 'prop-types';
3
 import React, { Component } from 'react';
3
 import React, { Component } from 'react';
4
 
4
 
5
 import { translate } from '../../../base/i18n';
5
 import { translate } from '../../../base/i18n';
6
 
6
 
7
 /**
7
 /**
8
- * The type of the React {@code Component} props of
9
- * {@link OverflowMenuLiveStreamingItem}.
8
+ * React {@code Component} for starting or stopping a live streaming of the
9
+ * current conference and displaying the current state of live streaming.
10
+ *
11
+ * @extends Component
10
  */
12
  */
11
-type Props = {
12
-
13
+class OverflowMenuLiveStreamingItem extends Component {
13
     /**
14
     /**
14
-     * The callback to invoke when {@code OverflowMenuLiveStreamingItem} is
15
-     * clicked.
15
+     * Default values for {@code OverflowMenuLiveStreamingItem}
16
+     * component's properties.
17
+     *
18
+     * @static
16
      */
19
      */
17
-    onClick: Function,
20
+    static defaultProps = {
21
+        tooltipPosition: 'left',
22
+        disabled: false
23
+    };
18
 
24
 
19
     /**
25
     /**
20
-     * The current live streaming session, if any.
26
+     * The type of the React {@code Component} props of
27
+     * {@link OverflowMenuLiveStreamingItem}.
21
      */
28
      */
22
-    session: ?Object,
29
+    static propTypes = {
23
 
30
 
24
-    /**
25
-     * Invoked to obtain translated strings.
26
-     */
27
-    t: Function
28
-};
31
+        /**
32
+         * Whether menu item is disabled or not.
33
+         */
34
+        disabled: PropTypes.bool,
35
+
36
+        /**
37
+         * The callback to invoke when {@code OverflowMenuLiveStreamingItem} is
38
+         * clicked.
39
+         */
40
+        onClick: Function,
41
+
42
+        /**
43
+         * The current live streaming session, if any.
44
+         */
45
+        session: PropTypes.object,
46
+
47
+        /**
48
+         * Invoked to obtain translated strings.
49
+         */
50
+        t: Function,
51
+
52
+        /**
53
+         * The text to display in the tooltip.
54
+         */
55
+        tooltip: PropTypes.string,
56
+
57
+        /**
58
+         * From which direction the tooltip should appear, relative to the
59
+         * button.
60
+         */
61
+        tooltipPosition: PropTypes.string
62
+    };
29
 
63
 
30
-/**
31
- * React {@code Component} for starting or stopping a live streaming of the
32
- * current conference and displaying the current state of live streaming.
33
- *
34
- * @extends Component
35
- */
36
-class OverflowMenuLiveStreamingItem extends Component<Props> {
37
     /**
64
     /**
38
      * Implements React's {@link Component#render()}.
65
      * Implements React's {@link Component#render()}.
39
      *
66
      *
41
      * @returns {ReactElement}
68
      * @returns {ReactElement}
42
      */
69
      */
43
     render() {
70
     render() {
44
-        const { onClick, session, t } = this.props;
71
+        const { disabled, onClick, session, t, tooltip, tooltipPosition }
72
+            = this.props;
45
 
73
 
46
         const translationKey = session
74
         const translationKey = session
47
             ? 'dialog.stopLiveStreaming'
75
             ? 'dialog.stopLiveStreaming'
48
             : 'dialog.startLiveStreaming';
76
             : 'dialog.startLiveStreaming';
49
 
77
 
50
-        return (
51
-            <li
52
-                aria-label = { t('dialog.accessibilityLabel.liveStreaming') }
53
-                className = 'overflow-menu-item'
54
-                onClick = { onClick }>
55
-                <span className = 'overflow-menu-item-icon'>
56
-                    <i className = 'icon-public' />
57
-                </span>
78
+        let className = 'overflow-menu-item';
79
+
80
+        className += disabled ? ' disabled' : '';
81
+
82
+        const button = (// eslint-disable-line no-extra-parens
83
+            <span>
58
                 <span className = 'profile-text'>
84
                 <span className = 'profile-text'>
59
                     { t(translationKey) }
85
                     { t(translationKey) }
60
                 </span>
86
                 </span>
61
                 <span className = 'beta-tag'>
87
                 <span className = 'beta-tag'>
62
                     { t('recording.beta') }
88
                     { t('recording.beta') }
63
                 </span>
89
                 </span>
90
+            </span>
91
+        );
92
+
93
+        return (
94
+            <li
95
+                aria-label = { t('dialog.accessibilityLabel.liveStreaming') }
96
+                className = { className }
97
+                onClick = { disabled ? null : onClick }>
98
+                <span className = 'overflow-menu-item-icon'>
99
+                    <i className = 'icon-public' />
100
+                </span>
101
+                { tooltip
102
+                    ? <Tooltip
103
+                        content = { tooltip }
104
+                        position = { tooltipPosition }>
105
+                        { button }
106
+                    </Tooltip>
107
+                    : button }
64
             </li>
108
             </li>
65
         );
109
         );
66
     }
110
     }

+ 111
- 27
react/features/toolbox/components/web/Toolbox.js 查看文件

13
 import { translate } from '../../../base/i18n';
13
 import { translate } from '../../../base/i18n';
14
 import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
14
 import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
15
 import {
15
 import {
16
-    PARTICIPANT_ROLE,
17
     getLocalParticipant,
16
     getLocalParticipant,
17
+    getParticipants,
18
+    isLocalParticipantModerator,
18
     participantUpdated
19
     participantUpdated
19
 } from '../../../base/participants';
20
 } from '../../../base/participants';
20
 import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks';
21
 import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks';
74
     _conference: Object,
75
     _conference: Object,
75
 
76
 
76
     /**
77
     /**
77
-     * Whether or not desktopsharing was explicitly configured to be disabled.
78
+     * The tooltip key to use when screensharing is disabled. Or undefined
79
+     * if non to be shown and the button to be hidden.
78
      */
80
      */
79
-    _desktopSharingDisabledByConfig: boolean,
81
+    _desktopSharingDisabledTooltipKey: boolean,
80
 
82
 
81
     /**
83
     /**
82
      * Whether or not screensharing is initialized.
84
      * Whether or not screensharing is initialized.
103
      */
105
      */
104
     _feedbackConfigured: boolean,
106
     _feedbackConfigured: boolean,
105
 
107
 
108
+    /**
109
+     * The tooltip key to use when file recording is disabled. Or undefined
110
+     * if non to be shown and the button to be hidden.
111
+     */
112
+    _fileRecordingsDisabledTooltipKey: boolean,
113
+
106
     /**
114
     /**
107
      * Whether or not the file recording feature is enabled for use.
115
      * Whether or not the file recording feature is enabled for use.
108
      */
116
      */
129
      */
137
      */
130
     _isGuest: boolean,
138
     _isGuest: boolean,
131
 
139
 
140
+    /**
141
+     * The tooltip key to use when live streaming is disabled. Or undefined
142
+     * if non to be shown and the button to be hidden.
143
+     */
144
+    _liveStreamingDisabledTooltipKey: boolean,
145
+
132
     /**
146
     /**
133
      * Whether or not the live streaming feature is enabled for use.
147
      * Whether or not the live streaming feature is enabled for use.
134
      */
148
      */
925
      */
939
      */
926
     _renderDesktopSharingButton() {
940
     _renderDesktopSharingButton() {
927
         const {
941
         const {
928
-            _desktopSharingDisabledByConfig,
929
             _desktopSharingEnabled,
942
             _desktopSharingEnabled,
943
+            _desktopSharingDisabledTooltipKey,
930
             _screensharing,
944
             _screensharing,
931
             t
945
             t
932
         } = this.props;
946
         } = this.props;
933
 
947
 
934
-        const disabledTooltipText
935
-            = interfaceConfig.DESKTOP_SHARING_BUTTON_DISABLED_TOOLTIP;
936
-        const showDisabledTooltip
937
-            = disabledTooltipText && _desktopSharingDisabledByConfig;
938
-        const visible = _desktopSharingEnabled || showDisabledTooltip;
948
+        const visible
949
+            = _desktopSharingEnabled || _desktopSharingDisabledTooltipKey;
939
 
950
 
940
         if (!visible) {
951
         if (!visible) {
941
             return null;
952
             return null;
944
         const classNames = `icon-share-desktop ${
955
         const classNames = `icon-share-desktop ${
945
             _screensharing ? 'toggled' : ''} ${
956
             _screensharing ? 'toggled' : ''} ${
946
             _desktopSharingEnabled ? '' : 'disabled'}`;
957
             _desktopSharingEnabled ? '' : 'disabled'}`;
947
-        const tooltip = showDisabledTooltip
948
-            ? disabledTooltipText
949
-            : t('dialog.shareYourScreen');
958
+        const tooltip = t(
959
+            _desktopSharingEnabled
960
+                ? 'dialog.shareYourScreen' : _desktopSharingDisabledTooltipKey);
950
 
961
 
951
         return (
962
         return (
952
             <ToolbarButton
963
             <ToolbarButton
969
             _editingDocument,
980
             _editingDocument,
970
             _etherpadInitialized,
981
             _etherpadInitialized,
971
             _feedbackConfigured,
982
             _feedbackConfigured,
983
+            _fileRecordingsDisabledTooltipKey,
972
             _fileRecordingsEnabled,
984
             _fileRecordingsEnabled,
973
             _fullScreen,
985
             _fullScreen,
974
             _isGuest,
986
             _isGuest,
987
+            _liveStreamingDisabledTooltipKey,
975
             _liveStreamingEnabled,
988
             _liveStreamingEnabled,
976
             _liveStreamingSession,
989
             _liveStreamingSession,
977
             _sharingVideo,
990
             _sharingVideo,
1000
                     text = { _fullScreen
1013
                     text = { _fullScreen
1001
                         ? t('toolbar.exitFullScreen')
1014
                         ? t('toolbar.exitFullScreen')
1002
                         : t('toolbar.enterFullScreen') } />,
1015
                         : t('toolbar.enterFullScreen') } />,
1003
-            _liveStreamingEnabled
1016
+            (_liveStreamingEnabled || _liveStreamingDisabledTooltipKey)
1004
                 && this._shouldShowButton('livestreaming')
1017
                 && this._shouldShowButton('livestreaming')
1005
                 && <OverflowMenuLiveStreamingItem
1018
                 && <OverflowMenuLiveStreamingItem
1019
+                    disabled = { !_liveStreamingEnabled }
1006
                     key = 'livestreaming'
1020
                     key = 'livestreaming'
1007
                     onClick = { this._onToolbarToggleLiveStreaming }
1021
                     onClick = { this._onToolbarToggleLiveStreaming }
1008
-                    session = { _liveStreamingSession } />,
1009
-            _fileRecordingsEnabled
1022
+                    session = { _liveStreamingSession }
1023
+                    tooltip = { t(_liveStreamingDisabledTooltipKey) } />,
1024
+            (_fileRecordingsEnabled || _fileRecordingsDisabledTooltipKey)
1010
                 && this._shouldShowButton('recording')
1025
                 && this._shouldShowButton('recording')
1011
                 && this._renderRecordingButton(),
1026
                 && this._renderRecordingButton(),
1012
             this._shouldShowButton('sharedvideo')
1027
             this._shouldShowButton('sharedvideo')
1070
      * @returns {ReactElement|null}
1085
      * @returns {ReactElement|null}
1071
      */
1086
      */
1072
     _renderRecordingButton() {
1087
     _renderRecordingButton() {
1073
-        const { _fileRecordingSession, t } = this.props;
1088
+        const {
1089
+            _fileRecordingSession,
1090
+            _fileRecordingsDisabledTooltipKey,
1091
+            _fileRecordingsEnabled,
1092
+            t } = this.props;
1074
 
1093
 
1075
         const translationKey = _fileRecordingSession
1094
         const translationKey = _fileRecordingSession
1076
             ? 'dialog.stopRecording'
1095
             ? 'dialog.stopRecording'
1080
             <OverflowMenuItem
1099
             <OverflowMenuItem
1081
                 accessibilityLabel =
1100
                 accessibilityLabel =
1082
                     { t('toolbar.accessibilityLabel.recording') }
1101
                     { t('toolbar.accessibilityLabel.recording') }
1102
+                disabled = { !_fileRecordingsEnabled }
1083
                 icon = 'icon-camera-take-picture'
1103
                 icon = 'icon-camera-take-picture'
1084
                 key = 'recording'
1104
                 key = 'recording'
1085
                 onClick = { this._onToolbarToggleRecording }
1105
                 onClick = { this._onToolbarToggleRecording }
1086
-                text = { t(translationKey) } />
1106
+                text = { t(translationKey) }
1107
+                tooltip = { t(_fileRecordingsDisabledTooltipKey) } />
1087
         );
1108
         );
1088
     }
1109
     }
1089
 
1110
 
1111
  * @returns {{}}
1132
  * @returns {{}}
1112
  */
1133
  */
1113
 function _mapStateToProps(state) {
1134
 function _mapStateToProps(state) {
1114
-    const {
1115
-        conference,
1116
-        desktopSharingEnabled
1117
-    } = state['features/base/conference'];
1135
+    const { conference } = state['features/base/conference'];
1136
+    let { desktopSharingEnabled } = state['features/base/conference'];
1118
     const {
1137
     const {
1119
         callStatsID,
1138
         callStatsID,
1120
-        disableDesktopSharing,
1139
+        iAmRecorder
1140
+    } = state['features/base/config'];
1141
+    let {
1121
         fileRecordingsEnabled,
1142
         fileRecordingsEnabled,
1122
-        iAmRecorder,
1123
         liveStreamingEnabled
1143
         liveStreamingEnabled
1124
     } = state['features/base/config'];
1144
     } = state['features/base/config'];
1125
     const sharedVideoStatus = state['features/shared-video'].status;
1145
     const sharedVideoStatus = state['features/shared-video'].status;
1133
     } = state['features/toolbox'];
1153
     } = state['features/toolbox'];
1134
     const localParticipant = getLocalParticipant(state);
1154
     const localParticipant = getLocalParticipant(state);
1135
     const localVideo = getLocalVideoTrack(state['features/base/tracks']);
1155
     const localVideo = getLocalVideoTrack(state['features/base/tracks']);
1136
-    const isModerator = localParticipant.role === PARTICIPANT_ROLE.MODERATOR;
1137
     const addPeopleEnabled = isAddPeopleEnabled(state);
1156
     const addPeopleEnabled = isAddPeopleEnabled(state);
1138
     const dialOutEnabled = isDialOutEnabled(state);
1157
     const dialOutEnabled = isDialOutEnabled(state);
1139
 
1158
 
1159
+    let desktopSharingDisabledTooltipKey;
1160
+    let fileRecordingsDisabledTooltipKey;
1161
+    let liveStreamingDisabledTooltipKey;
1162
+
1163
+    fileRecordingsEnabled
1164
+        = isLocalParticipantModerator(state) && fileRecordingsEnabled;
1165
+    liveStreamingEnabled
1166
+        = isLocalParticipantModerator(state) && liveStreamingEnabled;
1167
+
1168
+    if (state['features/base/config'].enableFeaturesBasedOnToken) {
1169
+        // we enable desktop sharing if any participant already have this
1170
+        // feature enabled
1171
+        desktopSharingEnabled = getParticipants(state)
1172
+            .find(({ features = {} }) =>
1173
+                String(features['screen-sharing']) === 'true') !== undefined;
1174
+
1175
+        // we want to show button and tooltip
1176
+        if (state['features/base/jwt'].isGuest) {
1177
+            desktopSharingDisabledTooltipKey
1178
+                = 'dialog.shareYourScreenDisabledForGuest';
1179
+        } else {
1180
+            desktopSharingDisabledTooltipKey
1181
+                = 'dialog.shareYourScreenDisabled';
1182
+        }
1183
+
1184
+        // we enable recording if the local participant have this
1185
+        // feature enabled
1186
+        const { features = {} } = localParticipant;
1187
+        const { isGuest } = state['features/base/jwt'];
1188
+
1189
+        fileRecordingsEnabled
1190
+            = fileRecordingsEnabled && String(features.recording) === 'true';
1191
+
1192
+        // if the feature is disabled on purpose, do no show it, no tooltip
1193
+        if (!fileRecordingsEnabled
1194
+            && String(features.recording) !== 'disabled') {
1195
+            // button and tooltip
1196
+            if (isGuest) {
1197
+                fileRecordingsDisabledTooltipKey
1198
+                    = 'dialog.recordingDisabledForGuestTooltip';
1199
+            } else {
1200
+                fileRecordingsDisabledTooltipKey
1201
+                    = 'dialog.recordingDisabledTooltip';
1202
+            }
1203
+        }
1204
+
1205
+        liveStreamingEnabled
1206
+            = liveStreamingEnabled && String(features.livestreaming) === 'true';
1207
+
1208
+        // if the feature is disabled on purpose, do no show it, no tooltip
1209
+        if (!liveStreamingEnabled
1210
+            && String(features.livestreaming) !== 'disabled') {
1211
+            // button and tooltip
1212
+            if (isGuest) {
1213
+                liveStreamingDisabledTooltipKey
1214
+                    = 'dialog.liveStreamingDisabledForGuestTooltip';
1215
+            } else {
1216
+                liveStreamingDisabledTooltipKey
1217
+                    = 'dialog.liveStreamingDisabledTooltip';
1218
+            }
1219
+        }
1220
+    }
1221
+
1140
     return {
1222
     return {
1141
         _chatOpen: current === 'chat_container',
1223
         _chatOpen: current === 'chat_container',
1142
         _conference: conference,
1224
         _conference: conference,
1143
         _desktopSharingEnabled: desktopSharingEnabled,
1225
         _desktopSharingEnabled: desktopSharingEnabled,
1144
-        _desktopSharingDisabledByConfig: disableDesktopSharing,
1226
+        _desktopSharingDisabledTooltipKey: desktopSharingDisabledTooltipKey,
1145
         _dialog: Boolean(state['features/base/dialog'].component),
1227
         _dialog: Boolean(state['features/base/dialog'].component),
1146
         _editingDocument: Boolean(state['features/etherpad'].editing),
1228
         _editingDocument: Boolean(state['features/etherpad'].editing),
1147
         _etherpadInitialized: Boolean(state['features/etherpad'].initialized),
1229
         _etherpadInitialized: Boolean(state['features/etherpad'].initialized),
1149
         _hideInviteButton:
1231
         _hideInviteButton:
1150
             iAmRecorder || (!addPeopleEnabled && !dialOutEnabled),
1232
             iAmRecorder || (!addPeopleEnabled && !dialOutEnabled),
1151
         _isGuest: state['features/base/jwt'].isGuest,
1233
         _isGuest: state['features/base/jwt'].isGuest,
1152
-        _fileRecordingsEnabled: isModerator && fileRecordingsEnabled,
1234
+        _fileRecordingsDisabledTooltipKey: fileRecordingsDisabledTooltipKey,
1235
+        _fileRecordingsEnabled: fileRecordingsEnabled,
1153
         _fileRecordingSession:
1236
         _fileRecordingSession:
1154
             getActiveSession(state, JitsiRecordingConstants.mode.FILE),
1237
             getActiveSession(state, JitsiRecordingConstants.mode.FILE),
1155
         _fullScreen: fullScreen,
1238
         _fullScreen: fullScreen,
1156
-        _liveStreamingEnabled: isModerator && liveStreamingEnabled,
1239
+        _liveStreamingDisabledTooltipKey: liveStreamingDisabledTooltipKey,
1240
+        _liveStreamingEnabled: liveStreamingEnabled,
1157
         _liveStreamingSession:
1241
         _liveStreamingSession:
1158
              getActiveSession(state, JitsiRecordingConstants.mode.STREAM),
1242
              getActiveSession(state, JitsiRecordingConstants.mode.STREAM),
1159
         _localParticipantID: localParticipant.id,
1243
         _localParticipantID: localParticipant.id,

+ 26
- 0
resources/prosody-plugins/mod_filter_iq_jibri.lua 查看文件

1
+local st = require "util.stanza";
2
+local is_feature_allowed = module:require "util".is_feature_allowed;
3
+
4
+-- filters jibri iq in case of requested from jwt authenticated session that
5
+-- has features in the user context, but without feature for recording
6
+module:hook("pre-iq/full", function(event)
7
+    local stanza = event.stanza;
8
+    if stanza.name == "iq" then
9
+        local jibri = stanza:get_child('jibri', 'http://jitsi.org/protocol/jibri');
10
+        if jibri then
11
+            local session = event.origin;
12
+            local token = session.auth_token;
13
+
14
+            if jibri.attr.action == 'start'
15
+                and (token == nil
16
+                    or not is_feature_allowed(session,
17
+                        (jibri.attr.recording_mode == 'file' and 'recording' or 'livestreaming'))
18
+                ) then
19
+                module:log("info",
20
+                    "Filtering jibri start recording, stanza:%s", tostring(stanza));
21
+                session.send(st.error_reply(stanza, "auth", "forbidden"));
22
+                return true;
23
+            end
24
+        end
25
+    end
26
+end);

+ 8
- 2
resources/prosody-plugins/mod_filter_iq_rayo.lua 查看文件

2
 
2
 
3
 local token_util = module:require "token/util".new(module);
3
 local token_util = module:require "token/util".new(module);
4
 local room_jid_match_rewrite = module:require "util".room_jid_match_rewrite;
4
 local room_jid_match_rewrite = module:require "util".room_jid_match_rewrite;
5
+local is_feature_allowed = module:require "util".is_feature_allowed;
5
 
6
 
6
 -- no token configuration but required
7
 -- no token configuration but required
7
 if token_util == nil then
8
 if token_util == nil then
10
 end
11
 end
11
 
12
 
12
 -- filters rayo iq in case of requested from not jwt authenticated sessions
13
 -- filters rayo iq in case of requested from not jwt authenticated sessions
14
+-- or if the session has features in user context and it doesn't mention
15
+-- feature "outbound-call" to be enabled
13
 module:hook("pre-iq/full", function(event)
16
 module:hook("pre-iq/full", function(event)
14
     local stanza = event.stanza;
17
     local stanza = event.stanza;
15
     if stanza.name == "iq" then
18
     if stanza.name == "iq" then
31
 
34
 
32
             if token == nil
35
             if token == nil
33
                 or roomName == nil
36
                 or roomName == nil
34
-                or not token_util:verify_room(
35
-                            session, room_jid_match_rewrite(roomName)) then
37
+                or not token_util:verify_room(session, room_jid_match_rewrite(roomName))
38
+                or not is_feature_allowed(session,
39
+                            (dial.attr.to == 'jitsi_meet_transcribe' and 'transcription'
40
+                                or 'outbound-call'))
41
+            then
36
                 module:log("info",
42
                 module:log("info",
37
                     "Filtering stanza dial, stanza:%s", tostring(stanza));
43
                     "Filtering stanza dial, stanza:%s", tostring(stanza));
38
                 session.send(st.error_reply(stanza, "auth", "forbidden"));
44
                 session.send(st.error_reply(stanza, "auth", "forbidden"));

+ 6
- 0
resources/prosody-plugins/token/util.lib.lua 查看文件

235
 -- session.jitsi_meet_domain - the domain name value from the token
235
 -- session.jitsi_meet_domain - the domain name value from the token
236
 -- session.jitsi_meet_context_user - the user details from the token
236
 -- session.jitsi_meet_context_user - the user details from the token
237
 -- session.jitsi_meet_context_group - the group value from the token
237
 -- session.jitsi_meet_context_group - the group value from the token
238
+-- session.jitsi_meet_context_features - the features value from the token
238
 -- @param session the current session
239
 -- @param session the current session
239
 -- @return false and error
240
 -- @return false and error
240
 function Util:process_and_verify_token(session)
241
 function Util:process_and_verify_token(session)
285
             -- Binds any group details to the session
286
             -- Binds any group details to the session
286
             session.jitsi_meet_context_group = claims["context"]["group"];
287
             session.jitsi_meet_context_group = claims["context"]["group"];
287
           end
288
           end
289
+
290
+          if claims["context"]["features"] ~= nil then
291
+            -- Binds any features details to the session
292
+            session.jitsi_meet_context_features = claims["context"]["features"];
293
+          end
288
         end
294
         end
289
         return true;
295
         return true;
290
     else
296
     else

+ 15
- 0
resources/prosody-plugins/util.lib.lua 查看文件

133
         "Presence with identity inserted %s", tostring(stanza))
133
         "Presence with identity inserted %s", tostring(stanza))
134
 end
134
 end
135
 
135
 
136
+-- Utility function to check whether feature is present and enabled. Allow
137
+-- a feature if there are features present in the session(coming from
138
+-- the token) and the value of the feature is true.
139
+-- If features is not present in the token we skip feature detection and allow
140
+-- everything.
141
+function is_feature_allowed(session, feature)
142
+    if (session.jitsi_meet_context_features == nil
143
+        or session.jitsi_meet_context_features[feature] == "true") then
144
+        return true;
145
+    else
146
+        return false;
147
+    end
148
+end
149
+
136
 return {
150
 return {
151
+    is_feature_allowed = is_feature_allowed;
137
     get_room_from_jid = get_room_from_jid;
152
     get_room_from_jid = get_room_from_jid;
138
     wrap_async_run = wrap_async_run;
153
     wrap_async_run = wrap_async_run;
139
     room_jid_match_rewrite = room_jid_match_rewrite;
154
     room_jid_match_rewrite = room_jid_match_rewrite;

正在加载...
取消
保存