From e5f6c78346b4cc1d850ac39a62db0d2725c04a6e Mon Sep 17 00:00:00 2001 From: damencho Date: Fri, 30 Aug 2024 09:09:15 -0500 Subject: [PATCH 01/14] fix(transcriptions): Uses dial command to invite transcriber. --- react/features/subtitles/middleware.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/react/features/subtitles/middleware.ts b/react/features/subtitles/middleware.ts index 85c8c8b8137..527bf8051ea 100644 --- a/react/features/subtitles/middleware.ts +++ b/react/features/subtitles/middleware.ts @@ -2,6 +2,8 @@ import { AnyAction } from 'redux'; import { IStore } from '../app/types'; import { ENDPOINT_MESSAGE_RECEIVED } from '../base/conference/actionTypes'; +import { isJwtFeatureEnabled } from '../base/jwt/functions'; +import { isLocalParticipantModerator } from '../base/participants/functions'; import MiddlewareRegistry from '../base/redux/MiddlewareRegistry'; import { @@ -40,6 +42,11 @@ const P_NAME_REQUESTING_TRANSCRIPTION = 'requestingTranscription'; */ const P_NAME_TRANSLATION_LANGUAGE = 'translation_language'; +/** + * The dial command to use for starting a transcriber. + */ +const TRANSCRIBER_DIAL_COMMAND = 'jitsi_meet_transcribe'; + /** * Time after which the rendered subtitles will be removed. */ @@ -239,6 +246,17 @@ function _requestingSubtitlesChange( P_NAME_REQUESTING_TRANSCRIPTION, enabled); + if (enabled && conference?.getTranscriptionStatus() === 'off') { + const featureAllowed = isJwtFeatureEnabled(getState(), 'transcription', false, false); + + if (isLocalParticipantModerator(state) || featureAllowed) { + conference.dial(TRANSCRIBER_DIAL_COMMAND) + .catch((e: any) => { + console.error('some error', e); + }); + } + } + if (enabled && language) { conference?.setLocalParticipantProperty( P_NAME_TRANSLATION_LANGUAGE, From 1f4ae528879b9a24dae819320f08563655c889e2 Mon Sep 17 00:00:00 2001 From: damencho Date: Fri, 30 Aug 2024 09:10:01 -0500 Subject: [PATCH 02/14] fix(transcriptions,recording): Allows non moderators with features to dial, record or transcribe. --- react/features/base/conference/reducer.ts | 1 + react/features/base/jwt/constants.ts | 2 +- react/features/invite/functions.ts | 5 +-- .../LiveStream/AbstractLiveStreamButton.ts | 2 +- .../AbstractStartRecordingDialogContent.tsx | 10 ++++- .../web/StartRecordingDialogContent.tsx | 6 ++- react/features/recording/functions.ts | 11 ++--- react/features/transcribing/functions.ts | 4 +- .../prosody-plugins/mod_filter_iq_jibri.lua | 33 ++++++++++++--- .../prosody-plugins/mod_filter_iq_rayo.lua | 41 +++++++++++++++++-- .../mod_muc_transcription_filter.lua | 31 -------------- .../mod_room_metadata_component.lua | 26 +++++++++--- .../mod_system_chat_message.lua | 1 - resources/prosody-plugins/util.lib.lua | 4 +- 14 files changed, 111 insertions(+), 66 deletions(-) delete mode 100644 resources/prosody-plugins/mod_muc_transcription_filter.lua diff --git a/react/features/base/conference/reducer.ts b/react/features/base/conference/reducer.ts index e0ece7bd237..3af9c06f470 100644 --- a/react/features/base/conference/reducer.ts +++ b/react/features/base/conference/reducer.ts @@ -93,6 +93,7 @@ export interface IJitsiConference { getRole: Function; getSpeakerStats: () => ISpeakerStats; getSsrcByTrack: Function; + getTranscriptionStatus: Function; grantOwner: Function; isAVModerationSupported: Function; isE2EEEnabled: Function; diff --git a/react/features/base/jwt/constants.ts b/react/features/base/jwt/constants.ts index 12b4123997d..ff075cb7e6a 100644 --- a/react/features/base/jwt/constants.ts +++ b/react/features/base/jwt/constants.ts @@ -20,10 +20,10 @@ export const MEET_FEATURES = { /** * A mapping between jwt features and toolbar buttons keys. + * We don't need recording in here, as it will disable and local recording. */ export const FEATURES_TO_BUTTONS_MAPPING = { 'livestreaming': 'livestreaming', - 'recording': 'recording', 'transcription': 'closedcaptions' }; diff --git a/react/features/invite/functions.ts b/react/features/invite/functions.ts index 872b6bdba87..9c30b1cf863 100644 --- a/react/features/invite/functions.ts +++ b/react/features/invite/functions.ts @@ -492,7 +492,7 @@ export function isAddPeopleEnabled(state: IReduxState): boolean { export function isDialOutEnabled(state: IReduxState): boolean { const { conference } = state['features/base/conference']; - return isLocalParticipantModerator(state) + return (isLocalParticipantModerator(state) || isJwtFeatureEnabled(state, 'outbound-call', false, false)) && conference && conference.isSIPCallingSupported(); } @@ -505,8 +505,7 @@ export function isDialOutEnabled(state: IReduxState): boolean { export function isSipInviteEnabled(state: IReduxState): boolean { const { sipInviteUrl } = state['features/base/config']; - return isLocalParticipantModerator(state) - && isJwtFeatureEnabled(state, 'sip-outbound-call') + return (isLocalParticipantModerator(state) || isJwtFeatureEnabled(state, 'sip-outbound-call', false, false)) && Boolean(sipInviteUrl); } diff --git a/react/features/recording/components/LiveStream/AbstractLiveStreamButton.ts b/react/features/recording/components/LiveStream/AbstractLiveStreamButton.ts index ffb162d70d6..aed02e41993 100644 --- a/react/features/recording/components/LiveStream/AbstractLiveStreamButton.ts +++ b/react/features/recording/components/LiveStream/AbstractLiveStreamButton.ts @@ -135,7 +135,7 @@ export function _mapStateToProps(state: IReduxState, ownProps: IProps) { visible = isLiveStreamingButtonVisible({ localParticipantIsModerator: isModerator, liveStreamingEnabled: liveStreaming?.enabled, - liveStreamingEnabledInJwt: isJwtFeatureEnabled(state, 'livestreaming', true), + liveStreamingEnabledInJwt: isJwtFeatureEnabled(state, 'livestreaming', false, false), isInBreakoutRoom: isInBreakoutRoom(state) }); } diff --git a/react/features/recording/components/Recording/AbstractStartRecordingDialogContent.tsx b/react/features/recording/components/Recording/AbstractStartRecordingDialogContent.tsx index 6251e6d02fd..4863aca189e 100644 --- a/react/features/recording/components/Recording/AbstractStartRecordingDialogContent.tsx +++ b/react/features/recording/components/Recording/AbstractStartRecordingDialogContent.tsx @@ -6,6 +6,7 @@ import { sendAnalytics } from '../../../analytics/functions'; import { IReduxState, IStore } from '../../../app/types'; import ColorSchemeRegistry from '../../../base/color-scheme/ColorSchemeRegistry'; import { _abstractMapStateToProps } from '../../../base/dialog/functions'; +import { isJwtFeatureEnabled } from '../../../base/jwt/functions'; import { isLocalParticipantModerator } from '../../../base/participants/functions'; import { authorizeDropbox, updateDropboxToken } from '../../../dropbox/actions'; import { isVpaasMeeting } from '../../../jaas/functions'; @@ -29,6 +30,11 @@ export interface IProps extends WithTranslation { */ _dialogStyles: any; + /** + * Whether recording feature is enabled. + */ + _hasRecordingFeature: boolean; + /** * Whether to hide the storage warning or not. */ @@ -412,13 +418,13 @@ class AbstractStartRecordingDialogContent extends Component { */ export function mapStateToProps(state: IReduxState) { const { localRecording, recordingService } = state['features/base/config']; - const _localRecordingAvailable - = !localRecording?.disable && supportsLocalRecording(); + const _localRecordingAvailable = !localRecording?.disable && supportsLocalRecording(); return { ..._abstractMapStateToProps(state), isVpaas: isVpaasMeeting(state), _canStartTranscribing: canAddTranscriber(state), + _hasRecordingFeature: isJwtFeatureEnabled(state, 'recording', false, false), _hideStorageWarning: Boolean(recordingService?.hideStorageWarning), _isModerator: isLocalParticipantModerator(state), _localRecordingAvailable, diff --git a/react/features/recording/components/Recording/web/StartRecordingDialogContent.tsx b/react/features/recording/components/Recording/web/StartRecordingDialogContent.tsx index 5534803f7c2..6b7f14a5783 100644 --- a/react/features/recording/components/Recording/web/StartRecordingDialogContent.tsx +++ b/react/features/recording/components/Recording/web/StartRecordingDialogContent.tsx @@ -37,9 +37,11 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent { * @returns {React$Component} */ render() { + const _renderRecording = this.props._isModerator || this.props._hasRecordingFeature; + return ( - { this.props._isModerator && ( + { _renderRecording && ( <> { this._renderNoIntegrationsContent() } { this._renderFileSharingContent() } @@ -48,7 +50,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent { )} { this._renderLocalRecordingContent() } - { this._renderAdvancedOptions() } + { _renderRecording && <> { this._renderAdvancedOptions() } } ); } diff --git a/react/features/recording/functions.ts b/react/features/recording/functions.ts index b7adffa3ea7..d9d458e1af7 100644 --- a/react/features/recording/functions.ts +++ b/react/features/recording/functions.ts @@ -267,8 +267,8 @@ export function getRecordButtonProps(state: IReduxState) { if (localRecordingEnabled) { visible = true; - } else if (isModerator) { - visible = recordingEnabled ? isJwtFeatureEnabled(state, 'recording', true) : false; + } else if (isModerator || isJwtFeatureEnabled(state, 'recording', false, false)) { + visible = recordingEnabled; } // disable the button if the livestreaming is running. @@ -420,7 +420,8 @@ export function registerRecordingAudioFiles(dispatch: IStore['dispatch'], should * * @param {boolean} localParticipantIsModerator - True if the local participant is moderator. * @param {boolean} liveStreamingEnabled - True if the live streaming is enabled. - * @param {boolean} liveStreamingEnabledInJwt - True if the lives treaming feature is enabled in JWT. + * @param {boolean} liveStreamingEnabledInJwt - True if the lives streaming feature is enabled in JWT. + * @param {boolean} isInBreakoutRoom - True if currently in breakout room. * @returns {boolean} */ export function isLiveStreamingButtonVisible({ @@ -436,8 +437,8 @@ export function isLiveStreamingButtonVisible({ }) { let visible = false; - if (localParticipantIsModerator && !isInBreakoutRoom) { - visible = liveStreamingEnabled ? liveStreamingEnabledInJwt : false; + if (!isInBreakoutRoom) { + visible = liveStreamingEnabled ? localParticipantIsModerator || liveStreamingEnabledInJwt : false; } return visible; diff --git a/react/features/transcribing/functions.ts b/react/features/transcribing/functions.ts index 430dfa9a647..58d2c4d5fe7 100644 --- a/react/features/transcribing/functions.ts +++ b/react/features/transcribing/functions.ts @@ -77,13 +77,13 @@ export function isRecorderTranscriptionsRunning(state: IReduxState) { */ export function canAddTranscriber(state: IReduxState) { const { transcription } = state['features/base/config']; - const isJwtTranscribingEnabled = isJwtFeatureEnabled(state, 'transcription', isLocalParticipantModerator(state)); + const isJwtTranscribingEnabled = isJwtFeatureEnabled(state, 'transcription', false, false); if (!transcription?.enabled) { return false; } - if (isJwtTranscribingEnabled) { + if (isLocalParticipantModerator(state) || isJwtTranscribingEnabled) { return true; } diff --git a/resources/prosody-plugins/mod_filter_iq_jibri.lua b/resources/prosody-plugins/mod_filter_iq_jibri.lua index b56cf54a76a..99fba5f6c60 100644 --- a/resources/prosody-plugins/mod_filter_iq_jibri.lua +++ b/resources/prosody-plugins/mod_filter_iq_jibri.lua @@ -1,5 +1,10 @@ +-- This module is enabled under the main virtual host local st = require "util.stanza"; -local is_feature_allowed = module:require "util".is_feature_allowed; +local jid_bare = require "util.jid".bare; +local util = module:require 'util'; +local is_feature_allowed = util.is_feature_allowed; +local get_room_from_jid = util.get_room_from_jid; +local room_jid_match_rewrite = util.room_jid_match_rewrite; -- filters jibri iq in case of requested from jwt authenticated session that -- has features in the user context, but without feature for recording @@ -10,12 +15,30 @@ module:hook("pre-iq/full", function(event) if jibri then local session = event.origin; local token = session.auth_token; + local feature = jibri.attr.recording_mode == 'file' and 'recording' or 'livestreaming'; + local is_allowed = is_feature_allowed(session.jitsi_meet_context_features, feature); + + -- if current user is not allowed, but was granted moderation by a user + -- that is allowed by its features we want to allow it + local is_granting_allowed = false; + if session.granted_jitsi_meet_context_features then + is_granting_allowed = is_feature_allowed(session.granted_jitsi_meet_context_features, feature); + end if jibri.attr.action == 'start' then - if token == nil - or not is_feature_allowed(session.jitsi_meet_context_features, - (jibri.attr.recording_mode == 'file' and 'recording' or 'livestreaming') - ) then + if token == nil or not (is_allowed or is_granting_allowed) + then + if not session.jitsi_meet_context_features and not session.granted_jitsi_meet_context_features then + -- we need to check for moderator rights + -- when there are no features and the occupant is moderator we allow recording + local room = get_room_from_jid(room_jid_match_rewrite(jid_bare(stanza.attr.to))); + local occupant = room:get_occupant_by_real_jid(stanza.attr.from); + + if occupant.role == 'moderator' then + return; + end + end + module:log("info", "Filtering jibri start recording, stanza:%s", tostring(stanza)); session.send(st.error_reply(stanza, "auth", "forbidden")); diff --git a/resources/prosody-plugins/mod_filter_iq_rayo.lua b/resources/prosody-plugins/mod_filter_iq_rayo.lua index 86bd0cc841f..9bcdad6ba79 100644 --- a/resources/prosody-plugins/mod_filter_iq_rayo.lua +++ b/resources/prosody-plugins/mod_filter_iq_rayo.lua @@ -1,3 +1,4 @@ +-- This module is enabled under the main virtual host local new_throttle = require "util.throttle".create; local st = require "util.stanza"; @@ -75,13 +76,21 @@ module:hook("pre-iq/full", function(event) -- if current user is not allowed, but was granted moderation by a user -- that is allowed by its features we want to allow it local is_granting_session_allowed = false; - if (session.granted_jitsi_meet_context_features) then + if session.granted_jitsi_meet_context_features then is_granting_session_allowed = is_feature_allowed(session.granted_jitsi_meet_context_features, feature); end + local room_real_jid = room_jid_match_rewrite(roomName); + + if not session.jitsi_meet_context_features and not session.granted_jitsi_meet_context_features then + -- if there is no features in the token we need to check whether the participants is moderator + local room = main_muc_service.get_room_from_jid(room_real_jid); + is_session_allowed = room:get_affiliation(stanza.attr.from) == 'owner'; + end + if (token == nil or roomName == nil - or not token_util:verify_room(session, room_jid_match_rewrite(roomName)) + or not token_util:verify_room(session, room_real_jid) or not (is_session_allowed or is_granting_session_allowed)) then module:log("warn", "Filtering stanza dial, stanza:%s", tostring(stanza)); @@ -99,8 +108,8 @@ module:hook("pre-iq/full", function(event) group_id = session.granted_jitsi_meet_context_group_id; end - -- now lets check any limits if configured - if limit_outgoing_calls > 0 then + -- now lets check any limits for outgoing calls if configured + if feature == 'outbound-call' and limit_outgoing_calls > 0 then if not session.dial_out_throttle then -- module:log("debug", "Enabling dial-out throttle session=%s.", session); session.dial_out_throttle = new_throttle(limit_outgoing_calls, OUTGOING_CALLS_THROTTLE_INTERVAL); @@ -259,3 +268,27 @@ process_host_module(main_muc_component_host, function(host_module, host) end); end end); + +-- when recording participants may enable and backend transcriptions +-- it is possible that participant is not moderator, but has the features enabled for +-- transcribing, we need to allow that operation +module:hook('jitsi-metadata-allow-moderation', function (event) + local data, key, occupant, session = event.data, event.key, event.actor, event.session; + + if occupant.role == 'moderator' then + return data; + end + + if key == 'recording' and data and data.isTranscribingEnabled ~= nil + and occupant.role ~= 'moderator' + and is_feature_allowed(session.jitsi_meet_context_features, 'transcription') + and is_feature_allowed(session.jitsi_meet_context_features, 'recording') then + + local res = {}; + res.isTranscribingEnabled = data.isTranscribingEnabled; + return res; + end + + return nil; +end); + diff --git a/resources/prosody-plugins/mod_muc_transcription_filter.lua b/resources/prosody-plugins/mod_muc_transcription_filter.lua deleted file mode 100644 index b66e44a1d93..00000000000 --- a/resources/prosody-plugins/mod_muc_transcription_filter.lua +++ /dev/null @@ -1,31 +0,0 @@ ---This module performs features checking when a transcription is requested. ---If the transcription feature is not allowed, the tag indicating that a ---transcription is being requested will be stripped from the presence stanza. ---The module must be enabled under the muc component. -local is_feature_allowed = module:require "util".is_feature_allowed; - -module:log("info", "Loading mod_muc_transcription_filter!"); -local filtered_tag_name = "jitsi_participant_requestingTranscription"; - -function filter_transcription_tag(event) - local stanza = event.stanza; - local session = event.origin; - if stanza and stanza.name == "presence" then - if not is_feature_allowed(session.jitsi_meet_context_features,'transcription') then - stanza:maptags(function(tag) - if tag and tag.name == filtered_tag_name then - module:log("info", "Removing %s tag from presence stanza!", filtered_tag_name); - return nil; - else - return tag; - end - end) - end - end -end - -module:hook("presence/bare", filter_transcription_tag); -module:hook("presence/full", filter_transcription_tag); -module:hook("presence/host", filter_transcription_tag); - -module:log("info", "Loaded mod_muc_transcription_filter!"); diff --git a/resources/prosody-plugins/mod_room_metadata_component.lua b/resources/prosody-plugins/mod_room_metadata_component.lua index 9459172b318..2e29222f7b4 100644 --- a/resources/prosody-plugins/mod_room_metadata_component.lua +++ b/resources/prosody-plugins/mod_room_metadata_component.lua @@ -28,7 +28,13 @@ local FORM_KEY = 'muc#roominfo_jitsimetadata'; local muc_component_host = module:get_option_string('muc_component'); if muc_component_host == nil then - module:log("error", "No muc_component specified. No muc to operate on!"); + module:log('error', 'No muc_component specified. No muc to operate on!'); + return; +end + +local muc_domain_base = module:get_option_string('muc_mapper_domain_base'); +if not muc_domain_base then + module:log('warn', 'No muc_domain_base option set.'); return; end @@ -129,11 +135,6 @@ function on_message(event) return false; end - if occupant.role ~= 'moderator' then - module:log('warn', 'Occupant %s is not moderator and not allowed this operation for %s', from, room.jid); - return false; - end - local jsonData, error = json.decode(messageText); if jsonData == nil then -- invalid JSON module:log("error", "Invalid JSON message: %s error:%s", messageText, error); @@ -145,6 +146,19 @@ function on_message(event) return false; end + if occupant.role ~= 'moderator' then + -- will return a non nil filtered data to use, if it is nil, it is not allowed + local res = module:context(muc_domain_base):fire_event('jitsi-metadata-allow-moderation', + { room = room; actor = occupant; key = jsonData.key ; data = jsonData.data; session = session; }); + + if not res then + module:log('warn', 'Occupant %s is not moderator and not allowed this operation for %s', from, room.jid); + return false; + end + + jsonData.data = res; + end + room.jitsiMetadata[jsonData.key] = jsonData.data; broadcastMetadata(room); diff --git a/resources/prosody-plugins/mod_system_chat_message.lua b/resources/prosody-plugins/mod_system_chat_message.lua index 79c71e1f201..ddb37238bc0 100644 --- a/resources/prosody-plugins/mod_system_chat_message.lua +++ b/resources/prosody-plugins/mod_system_chat_message.lua @@ -15,7 +15,6 @@ local get_room_from_jid = util.get_room_from_jid; local st = require "util.stanza"; local json = require "cjson.safe"; -local muc_domain_base = module:get_option_string("muc_mapper_domain_base"); local asapKeyServer = module:get_option_string("prosody_password_public_key_repo_url", ""); if asapKeyServer then diff --git a/resources/prosody-plugins/util.lib.lua b/resources/prosody-plugins/util.lib.lua index d51f5ae71c3..60769e8f3e5 100644 --- a/resources/prosody-plugins/util.lib.lua +++ b/resources/prosody-plugins/util.lib.lua @@ -250,10 +250,8 @@ 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(features, ft) - if (features == nil or features[ft] == "true" or features[ft] == true) then + if features ~= nil and (features[ft] == "true" or features[ft] == true) then return true; else return false; From 1a0164f519264086a60ec502c12ff3862356c2fd Mon Sep 17 00:00:00 2001 From: damencho Date: Tue, 3 Sep 2024 08:37:21 -0500 Subject: [PATCH 03/14] sqaush: Make sure filtering works when only is a moderator. It works now and without a token and no features, but being moderator. --- resources/prosody-plugins/mod_filter_iq_rayo.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/resources/prosody-plugins/mod_filter_iq_rayo.lua b/resources/prosody-plugins/mod_filter_iq_rayo.lua index 9bcdad6ba79..aa1ac9273f8 100644 --- a/resources/prosody-plugins/mod_filter_iq_rayo.lua +++ b/resources/prosody-plugins/mod_filter_iq_rayo.lua @@ -88,9 +88,8 @@ module:hook("pre-iq/full", function(event) is_session_allowed = room:get_affiliation(stanza.attr.from) == 'owner'; end - if (token == nil - or roomName == nil - or not token_util:verify_room(session, room_real_jid) + if (roomName == nil + or (token ~= nil and not token_util:verify_room(session, room_real_jid)) or not (is_session_allowed or is_granting_session_allowed)) then module:log("warn", "Filtering stanza dial, stanza:%s", tostring(stanza)); From 3770eabf8cb3a5e906c93039dbc7d81df244c39d Mon Sep 17 00:00:00 2001 From: damencho Date: Wed, 4 Sep 2024 16:44:39 -0500 Subject: [PATCH 04/14] squash: Rename constant. --- react/features/subtitles/middleware.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/react/features/subtitles/middleware.ts b/react/features/subtitles/middleware.ts index 527bf8051ea..5c080cd8bd5 100644 --- a/react/features/subtitles/middleware.ts +++ b/react/features/subtitles/middleware.ts @@ -45,7 +45,7 @@ const P_NAME_TRANSLATION_LANGUAGE = 'translation_language'; /** * The dial command to use for starting a transcriber. */ -const TRANSCRIBER_DIAL_COMMAND = 'jitsi_meet_transcribe'; +const TRANSCRIBER_DIAL_NUMBER = 'jitsi_meet_transcribe'; /** * Time after which the rendered subtitles will be removed. @@ -250,7 +250,7 @@ function _requestingSubtitlesChange( const featureAllowed = isJwtFeatureEnabled(getState(), 'transcription', false, false); if (isLocalParticipantModerator(state) || featureAllowed) { - conference.dial(TRANSCRIBER_DIAL_COMMAND) + conference.dial(TRANSCRIBER_DIAL_NUMBER) .catch((e: any) => { console.error('some error', e); }); From 1756c4395985cbec161b105033a9e5f98dc417e3 Mon Sep 17 00:00:00 2001 From: damencho Date: Wed, 4 Sep 2024 16:52:09 -0500 Subject: [PATCH 05/14] squash: Checks features first before defaulting to moderator when filtering metadata service. --- .../prosody-plugins/mod_filter_iq_rayo.lua | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/resources/prosody-plugins/mod_filter_iq_rayo.lua b/resources/prosody-plugins/mod_filter_iq_rayo.lua index aa1ac9273f8..7433bc7d3a1 100644 --- a/resources/prosody-plugins/mod_filter_iq_rayo.lua +++ b/resources/prosody-plugins/mod_filter_iq_rayo.lua @@ -274,18 +274,21 @@ end); module:hook('jitsi-metadata-allow-moderation', function (event) local data, key, occupant, session = event.data, event.key, event.actor, event.session; - if occupant.role == 'moderator' then - return data; + if key == 'recording' and data and data.isTranscribingEnabled ~= nil then + if session.jitsi_meet_context_features + and occupant.role ~= 'moderator' + and is_feature_allowed(session.jitsi_meet_context_features, 'transcription') + and is_feature_allowed(session.jitsi_meet_context_features, 'recording') then + local res = {}; + res.isTranscribingEnabled = data.isTranscribingEnabled; + return res; + elseif occupant.role == 'moderator' then + return data; + end end - if key == 'recording' and data and data.isTranscribingEnabled ~= nil - and occupant.role ~= 'moderator' - and is_feature_allowed(session.jitsi_meet_context_features, 'transcription') - and is_feature_allowed(session.jitsi_meet_context_features, 'recording') then - - local res = {}; - res.isTranscribingEnabled = data.isTranscribingEnabled; - return res; + if occupant.role == 'moderator' then + return data; end return nil; From 08eff231880568d8da23a35c99b84537a2874465 Mon Sep 17 00:00:00 2001 From: damencho Date: Wed, 4 Sep 2024 17:05:41 -0500 Subject: [PATCH 06/14] squash: Checks features first before defaulting to moderator in UI. --- react/features/invite/functions.ts | 6 ++-- .../LiveStream/AbstractLiveStreamButton.ts | 11 +++---- react/features/recording/functions.ts | 31 +------------------ react/features/recording/hooks.web.ts | 15 ++++----- react/features/subtitles/middleware.ts | 5 +-- react/features/transcribing/functions.ts | 13 ++------ 6 files changed, 21 insertions(+), 60 deletions(-) diff --git a/react/features/invite/functions.ts b/react/features/invite/functions.ts index 9c30b1cf863..c1f5c32dd57 100644 --- a/react/features/invite/functions.ts +++ b/react/features/invite/functions.ts @@ -491,8 +491,9 @@ export function isAddPeopleEnabled(state: IReduxState): boolean { */ export function isDialOutEnabled(state: IReduxState): boolean { const { conference } = state['features/base/conference']; + const isModerator = isLocalParticipantModerator(state); - return (isLocalParticipantModerator(state) || isJwtFeatureEnabled(state, 'outbound-call', false, false)) + return isJwtFeatureEnabled(state, 'outbound-call', isModerator, isModerator) && conference && conference.isSIPCallingSupported(); } @@ -504,8 +505,9 @@ export function isDialOutEnabled(state: IReduxState): boolean { */ export function isSipInviteEnabled(state: IReduxState): boolean { const { sipInviteUrl } = state['features/base/config']; + const isModerator = isLocalParticipantModerator(state); - return (isLocalParticipantModerator(state) || isJwtFeatureEnabled(state, 'sip-outbound-call', false, false)) + return isJwtFeatureEnabled(state, 'sip-outbound-call', isModerator, isModerator) && Boolean(sipInviteUrl); } diff --git a/react/features/recording/components/LiveStream/AbstractLiveStreamButton.ts b/react/features/recording/components/LiveStream/AbstractLiveStreamButton.ts index aed02e41993..8c9619541d0 100644 --- a/react/features/recording/components/LiveStream/AbstractLiveStreamButton.ts +++ b/react/features/recording/components/LiveStream/AbstractLiveStreamButton.ts @@ -7,7 +7,7 @@ import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/too import { isInBreakoutRoom } from '../../../breakout-rooms/functions'; import { maybeShowPremiumFeatureDialog } from '../../../jaas/actions'; import { isRecorderTranscriptionsRunning } from '../../../transcribing/functions'; -import { isCloudRecordingRunning, isLiveStreamingButtonVisible, isLiveStreamingRunning } from '../../functions'; +import { isCloudRecordingRunning, isLiveStreamingRunning } from '../../functions'; import { getLiveStreaming } from './functions'; @@ -132,12 +132,9 @@ export function _mapStateToProps(state: IReduxState, ownProps: IProps) { const isModerator = isLocalParticipantModerator(state); const liveStreaming = getLiveStreaming(state); - visible = isLiveStreamingButtonVisible({ - localParticipantIsModerator: isModerator, - liveStreamingEnabled: liveStreaming?.enabled, - liveStreamingEnabledInJwt: isJwtFeatureEnabled(state, 'livestreaming', false, false), - isInBreakoutRoom: isInBreakoutRoom(state) - }); + visible = !isInBreakoutRoom(state) + && liveStreaming?.enabled + && isJwtFeatureEnabled(state, 'livestreaming', isModerator, isModerator); } // disable the button if the recording is running. diff --git a/react/features/recording/functions.ts b/react/features/recording/functions.ts index d9d458e1af7..80b18c7f052 100644 --- a/react/features/recording/functions.ts +++ b/react/features/recording/functions.ts @@ -267,7 +267,7 @@ export function getRecordButtonProps(state: IReduxState) { if (localRecordingEnabled) { visible = true; - } else if (isModerator || isJwtFeatureEnabled(state, 'recording', false, false)) { + } else if (isJwtFeatureEnabled(state, 'recording', isModerator, isModerator)) { visible = recordingEnabled; } @@ -414,32 +414,3 @@ export function registerRecordingAudioFiles(dispatch: IStore['dispatch'], should RECORDING_ON_SOUND_ID, getSoundFileSrc(RECORDING_ON_SOUND_FILE, language))); } - -/** - * Returns true if the live streaming button should be visible. - * - * @param {boolean} localParticipantIsModerator - True if the local participant is moderator. - * @param {boolean} liveStreamingEnabled - True if the live streaming is enabled. - * @param {boolean} liveStreamingEnabledInJwt - True if the lives streaming feature is enabled in JWT. - * @param {boolean} isInBreakoutRoom - True if currently in breakout room. - * @returns {boolean} - */ -export function isLiveStreamingButtonVisible({ - localParticipantIsModerator, - liveStreamingEnabled, - liveStreamingEnabledInJwt, - isInBreakoutRoom -}: { - isInBreakoutRoom: boolean; - liveStreamingEnabled: boolean; - liveStreamingEnabledInJwt: boolean; - localParticipantIsModerator: boolean; -}) { - let visible = false; - - if (!isInBreakoutRoom) { - visible = liveStreamingEnabled ? localParticipantIsModerator || liveStreamingEnabledInJwt : false; - } - - return visible; -} diff --git a/react/features/recording/hooks.web.ts b/react/features/recording/hooks.web.ts index 3ae4baf67ed..cc7875e69d0 100644 --- a/react/features/recording/hooks.web.ts +++ b/react/features/recording/hooks.web.ts @@ -8,7 +8,7 @@ import { isInBreakoutRoom } from '../breakout-rooms/functions'; import { getLiveStreaming } from './components/LiveStream/functions'; import LiveStreamButton from './components/LiveStream/web/LiveStreamButton'; import RecordButton from './components/Recording/web/RecordButton'; -import { getRecordButtonProps, isLiveStreamingButtonVisible } from './functions'; +import { getRecordButtonProps } from './functions'; const recording = { @@ -47,17 +47,14 @@ export function useLiveStreamingButton() { const toolbarButtons = useSelector((state: IReduxState) => state['features/toolbox'].toolbarButtons); const localParticipantIsModerator = useSelector(isLocalParticipantModerator); const liveStreaming = useSelector(getLiveStreaming); - const liveStreamingEnabledInJwt - = useSelector((state: IReduxState) => isJwtFeatureEnabled(state, 'livestreaming', true)); + const liveStreamingAllowed = useSelector((state: IReduxState) => + isJwtFeatureEnabled(state, 'livestreaming', localParticipantIsModerator, localParticipantIsModerator)); const _isInBreakoutRoom = useSelector(isInBreakoutRoom); if (toolbarButtons?.includes('recording') - && isLiveStreamingButtonVisible({ - localParticipantIsModerator, - liveStreamingEnabled: liveStreaming?.enabled, - liveStreamingEnabledInJwt, - isInBreakoutRoom: _isInBreakoutRoom - })) { + && !_isInBreakoutRoom + && liveStreaming?.enabled + && liveStreamingAllowed) { return livestreaming; } } diff --git a/react/features/subtitles/middleware.ts b/react/features/subtitles/middleware.ts index 5c080cd8bd5..7134b3ab306 100644 --- a/react/features/subtitles/middleware.ts +++ b/react/features/subtitles/middleware.ts @@ -247,9 +247,10 @@ function _requestingSubtitlesChange( enabled); if (enabled && conference?.getTranscriptionStatus() === 'off') { - const featureAllowed = isJwtFeatureEnabled(getState(), 'transcription', false, false); + const isModerator = isLocalParticipantModerator(state); + const featureAllowed = isJwtFeatureEnabled(getState(), 'transcription', isModerator, isModerator); - if (isLocalParticipantModerator(state) || featureAllowed) { + if (featureAllowed) { conference.dial(TRANSCRIBER_DIAL_NUMBER) .catch((e: any) => { console.error('some error', e); diff --git a/react/features/transcribing/functions.ts b/react/features/transcribing/functions.ts index 58d2c4d5fe7..79451fb9d86 100644 --- a/react/features/transcribing/functions.ts +++ b/react/features/transcribing/functions.ts @@ -77,15 +77,8 @@ export function isRecorderTranscriptionsRunning(state: IReduxState) { */ export function canAddTranscriber(state: IReduxState) { const { transcription } = state['features/base/config']; - const isJwtTranscribingEnabled = isJwtFeatureEnabled(state, 'transcription', false, false); + const isModerator = isLocalParticipantModerator(state); + const isTranscribingAllowed = isJwtFeatureEnabled(state, 'transcription', isModerator, isModerator); - if (!transcription?.enabled) { - return false; - } - - if (isLocalParticipantModerator(state) || isJwtTranscribingEnabled) { - return true; - } - - return false; + return transcription?.enabled && isTranscribingAllowed; } From 936e35e93842953db28ab930d9ef190a8fa744ff Mon Sep 17 00:00:00 2001 From: damencho Date: Wed, 4 Sep 2024 17:41:56 -0500 Subject: [PATCH 07/14] squash: Fixes lint and one other check. --- .../AbstractStartRecordingDialogContent.tsx | 19 +++++++------------ .../web/StartRecordingDialogContent.tsx | 2 +- react/features/transcribing/functions.ts | 2 +- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/react/features/recording/components/Recording/AbstractStartRecordingDialogContent.tsx b/react/features/recording/components/Recording/AbstractStartRecordingDialogContent.tsx index 4863aca189e..e17fec52415 100644 --- a/react/features/recording/components/Recording/AbstractStartRecordingDialogContent.tsx +++ b/react/features/recording/components/Recording/AbstractStartRecordingDialogContent.tsx @@ -30,21 +30,11 @@ export interface IProps extends WithTranslation { */ _dialogStyles: any; - /** - * Whether recording feature is enabled. - */ - _hasRecordingFeature: boolean; - /** * Whether to hide the storage warning or not. */ _hideStorageWarning: boolean; - /** - * Whether local participant is moderator. - */ - _isModerator: boolean; - /** * Whether local recording is available or not. */ @@ -65,6 +55,11 @@ export interface IProps extends WithTranslation { */ _localRecordingSelfEnabled: boolean; + /** + * Whether to render recording. + */ + _renderRecording: boolean; + /** * The color-schemed stylesheet of this component. */ @@ -419,14 +414,14 @@ class AbstractStartRecordingDialogContent extends Component { export function mapStateToProps(state: IReduxState) { const { localRecording, recordingService } = state['features/base/config']; const _localRecordingAvailable = !localRecording?.disable && supportsLocalRecording(); + const isModerator = isLocalParticipantModerator(state); return { ..._abstractMapStateToProps(state), isVpaas: isVpaasMeeting(state), _canStartTranscribing: canAddTranscriber(state), - _hasRecordingFeature: isJwtFeatureEnabled(state, 'recording', false, false), _hideStorageWarning: Boolean(recordingService?.hideStorageWarning), - _isModerator: isLocalParticipantModerator(state), + _renderRecording: isJwtFeatureEnabled(state, 'recording', isModerator, isModerator), _localRecordingAvailable, _localRecordingEnabled: !localRecording?.disable, _localRecordingSelfEnabled: !localRecording?.disableSelfRecording, diff --git a/react/features/recording/components/Recording/web/StartRecordingDialogContent.tsx b/react/features/recording/components/Recording/web/StartRecordingDialogContent.tsx index 6b7f14a5783..f3e2c15953f 100644 --- a/react/features/recording/components/Recording/web/StartRecordingDialogContent.tsx +++ b/react/features/recording/components/Recording/web/StartRecordingDialogContent.tsx @@ -37,7 +37,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent { * @returns {React$Component} */ render() { - const _renderRecording = this.props._isModerator || this.props._hasRecordingFeature; + const _renderRecording = this.props._renderRecording; return ( diff --git a/react/features/transcribing/functions.ts b/react/features/transcribing/functions.ts index 79451fb9d86..7641fc44a48 100644 --- a/react/features/transcribing/functions.ts +++ b/react/features/transcribing/functions.ts @@ -80,5 +80,5 @@ export function canAddTranscriber(state: IReduxState) { const isModerator = isLocalParticipantModerator(state); const isTranscribingAllowed = isJwtFeatureEnabled(state, 'transcription', isModerator, isModerator); - return transcription?.enabled && isTranscribingAllowed; + return Boolean(transcription?.enabled) && isTranscribingAllowed; } From 853ad68dd6060d25942424e3ea81fe9e17af6d4b Mon Sep 17 00:00:00 2001 From: damencho Date: Wed, 4 Sep 2024 18:00:55 -0500 Subject: [PATCH 08/14] squash: Moves more logic to is_feature_allowed. --- .../prosody-plugins/mod_filter_iq_jibri.lua | 29 +++++---------- .../prosody-plugins/mod_filter_iq_rayo.lua | 36 +++++++++---------- resources/prosody-plugins/util.lib.lua | 12 ++++--- 3 files changed, 32 insertions(+), 45 deletions(-) diff --git a/resources/prosody-plugins/mod_filter_iq_jibri.lua b/resources/prosody-plugins/mod_filter_iq_jibri.lua index 99fba5f6c60..d8e93e5dbca 100644 --- a/resources/prosody-plugins/mod_filter_iq_jibri.lua +++ b/resources/prosody-plugins/mod_filter_iq_jibri.lua @@ -15,30 +15,17 @@ module:hook("pre-iq/full", function(event) if jibri then local session = event.origin; local token = session.auth_token; + local room = get_room_from_jid(room_jid_match_rewrite(jid_bare(stanza.attr.to))); + local occupant = room:get_occupant_by_real_jid(stanza.attr.from); local feature = jibri.attr.recording_mode == 'file' and 'recording' or 'livestreaming'; - local is_allowed = is_feature_allowed(session.jitsi_meet_context_features, feature); - - -- if current user is not allowed, but was granted moderation by a user - -- that is allowed by its features we want to allow it - local is_granting_allowed = false; - if session.granted_jitsi_meet_context_features then - is_granting_allowed = is_feature_allowed(session.granted_jitsi_meet_context_features, feature); - end + local is_allowed = is_feature_allowed( + feature, + session.jitsi_meet_context_features, + session.granted_jitsi_meet_context_features, + occupant.role == 'moderator'); if jibri.attr.action == 'start' then - if token == nil or not (is_allowed or is_granting_allowed) - then - if not session.jitsi_meet_context_features and not session.granted_jitsi_meet_context_features then - -- we need to check for moderator rights - -- when there are no features and the occupant is moderator we allow recording - local room = get_room_from_jid(room_jid_match_rewrite(jid_bare(stanza.attr.to))); - local occupant = room:get_occupant_by_real_jid(stanza.attr.from); - - if occupant.role == 'moderator' then - return; - end - end - + if token == nil or not is_allowed then module:log("info", "Filtering jibri start recording, stanza:%s", tostring(stanza)); session.send(st.error_reply(stanza, "auth", "forbidden")); diff --git a/resources/prosody-plugins/mod_filter_iq_rayo.lua b/resources/prosody-plugins/mod_filter_iq_rayo.lua index 7433bc7d3a1..535f024f4a5 100644 --- a/resources/prosody-plugins/mod_filter_iq_rayo.lua +++ b/resources/prosody-plugins/mod_filter_iq_rayo.lua @@ -70,27 +70,19 @@ module:hook("pre-iq/full", function(event) end end - local feature = dial.attr.to == 'jitsi_meet_transcribe' and 'transcription' or 'outbound-call'; - local is_session_allowed = is_feature_allowed(session.jitsi_meet_context_features, feature); - - -- if current user is not allowed, but was granted moderation by a user - -- that is allowed by its features we want to allow it - local is_granting_session_allowed = false; - if session.granted_jitsi_meet_context_features then - is_granting_session_allowed = is_feature_allowed(session.granted_jitsi_meet_context_features, feature); - end - local room_real_jid = room_jid_match_rewrite(roomName); + local room = main_muc_service.get_room_from_jid(room_real_jid); - if not session.jitsi_meet_context_features and not session.granted_jitsi_meet_context_features then - -- if there is no features in the token we need to check whether the participants is moderator - local room = main_muc_service.get_room_from_jid(room_real_jid); - is_session_allowed = room:get_affiliation(stanza.attr.from) == 'owner'; - end + local feature = dial.attr.to == 'jitsi_meet_transcribe' and 'transcription' or 'outbound-call'; + local is_session_allowed = is_feature_allowed( + feature, + session.jitsi_meet_context_features, + session.granted_jitsi_meet_context_features, + room:get_affiliation(stanza.attr.from) == 'owner'); - if (roomName == nil + if roomName == nil or (token ~= nil and not token_util:verify_room(session, room_real_jid)) - or not (is_session_allowed or is_granting_session_allowed)) + or not is_session_allowed then module:log("warn", "Filtering stanza dial, stanza:%s", tostring(stanza)); session.send(st.error_reply(stanza, "auth", "forbidden")); @@ -275,15 +267,19 @@ module:hook('jitsi-metadata-allow-moderation', function (event) local data, key, occupant, session = event.data, event.key, event.actor, event.session; if key == 'recording' and data and data.isTranscribingEnabled ~= nil then + -- if it is recording we want to allow setting in metadata if not moderator but features + -- are present if session.jitsi_meet_context_features and occupant.role ~= 'moderator' - and is_feature_allowed(session.jitsi_meet_context_features, 'transcription') - and is_feature_allowed(session.jitsi_meet_context_features, 'recording') then + and is_feature_allowed('transcription', session.jitsi_meet_context_features) + and is_feature_allowed('recording', session.jitsi_meet_context_features) then local res = {}; res.isTranscribingEnabled = data.isTranscribingEnabled; return res; - elseif occupant.role == 'moderator' then + elseif not session.jitsi_meet_context_features and occupant.role == 'moderator' then return data; + else + return nil; end end diff --git a/resources/prosody-plugins/util.lib.lua b/resources/prosody-plugins/util.lib.lua index 60769e8f3e5..d7f07c1a17c 100644 --- a/resources/prosody-plugins/util.lib.lua +++ b/resources/prosody-plugins/util.lib.lua @@ -250,11 +250,15 @@ 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. -function is_feature_allowed(features, ft) - if features ~= nil and (features[ft] == "true" or features[ft] == true) then - return true; +-- If features are missing but we have granted_features check that +-- if features are missing from the token we check whether it is moderator +function is_feature_allowed(ft, features, granted_features, is_moderator) + if features then + return features[ft] == "true" or features[ft] == true; + elseif granted_features then + return granted_features[ft] == "true" or granted_features[ft] == true; else - return false; + return is_moderator; end end From ef7ef96d21acf2c02d8203de9f18e63ad5f8a4f2 Mon Sep 17 00:00:00 2001 From: damencho Date: Thu, 5 Sep 2024 15:59:51 -0500 Subject: [PATCH 09/14] squash: Drops unnecessary check. --- resources/prosody-plugins/mod_filter_iq_jibri.lua | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/resources/prosody-plugins/mod_filter_iq_jibri.lua b/resources/prosody-plugins/mod_filter_iq_jibri.lua index d8e93e5dbca..be3d82339de 100644 --- a/resources/prosody-plugins/mod_filter_iq_jibri.lua +++ b/resources/prosody-plugins/mod_filter_iq_jibri.lua @@ -24,13 +24,10 @@ module:hook("pre-iq/full", function(event) session.granted_jitsi_meet_context_features, occupant.role == 'moderator'); - if jibri.attr.action == 'start' then - if token == nil or not is_allowed then - module:log("info", - "Filtering jibri start recording, stanza:%s", tostring(stanza)); - session.send(st.error_reply(stanza, "auth", "forbidden")); - return true; - end + if jibri.attr.action == 'start' and not is_allowed 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 From bccbb5b9267ddaa8da73a40e077114b003f75612 Mon Sep 17 00:00:00 2001 From: damencho Date: Tue, 10 Sep 2024 13:16:32 -0500 Subject: [PATCH 10/14] squash: Uses constant coming from ljm. --- react/features/subtitles/middleware.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/react/features/subtitles/middleware.ts b/react/features/subtitles/middleware.ts index 7134b3ab306..a5129c2d8ec 100644 --- a/react/features/subtitles/middleware.ts +++ b/react/features/subtitles/middleware.ts @@ -3,6 +3,7 @@ import { AnyAction } from 'redux'; import { IStore } from '../app/types'; import { ENDPOINT_MESSAGE_RECEIVED } from '../base/conference/actionTypes'; import { isJwtFeatureEnabled } from '../base/jwt/functions'; +import JitsiMeetJS from '../base/lib-jitsi-meet'; import { isLocalParticipantModerator } from '../base/participants/functions'; import MiddlewareRegistry from '../base/redux/MiddlewareRegistry'; @@ -246,12 +247,12 @@ function _requestingSubtitlesChange( P_NAME_REQUESTING_TRANSCRIPTION, enabled); - if (enabled && conference?.getTranscriptionStatus() === 'off') { + if (enabled && conference?.getTranscriptionStatus() === JitsiMeetJS.constants.transcriptionStatus.OFF) { const isModerator = isLocalParticipantModerator(state); const featureAllowed = isJwtFeatureEnabled(getState(), 'transcription', isModerator, isModerator); if (featureAllowed) { - conference.dial(TRANSCRIBER_DIAL_NUMBER) + conference?.dial(TRANSCRIBER_DIAL_NUMBER) .catch((e: any) => { console.error('some error', e); }); From 7e7b17e13886c38ff927892f71d4c44957290f48 Mon Sep 17 00:00:00 2001 From: damencho Date: Tue, 10 Sep 2024 18:13:27 -0500 Subject: [PATCH 11/14] squash: Toggles back captions button on error. --- react/features/subtitles/middleware.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/react/features/subtitles/middleware.ts b/react/features/subtitles/middleware.ts index a5129c2d8ec..eb7e925338f 100644 --- a/react/features/subtitles/middleware.ts +++ b/react/features/subtitles/middleware.ts @@ -12,10 +12,11 @@ import { TOGGLE_REQUESTING_SUBTITLES } from './actionTypes'; import { - removeTranscriptMessage, + removeTranscriptMessage, setRequestingSubtitles, updateTranscriptMessage } from './actions.any'; import { notifyTranscriptionChunkReceived } from './functions'; +import logger from './logger'; import { ITranscriptMessage } from './types'; @@ -237,7 +238,7 @@ function _endpointMessageReceived(store: IStore, next: Function, action: AnyActi * @returns {void} */ function _requestingSubtitlesChange( - { getState }: IStore, + { dispatch, getState }: IStore, enabled: boolean, language?: string | null) { const state = getState(); @@ -254,7 +255,10 @@ function _requestingSubtitlesChange( if (featureAllowed) { conference?.dial(TRANSCRIBER_DIAL_NUMBER) .catch((e: any) => { - console.error('some error', e); + logger.error('Error dialing', e); + + // let's back to the correct state + dispatch(setRequestingSubtitles(false, false)); }); } } From 708d1d28a79ae8efaf4b19489a7b2ed65a48dba1 Mon Sep 17 00:00:00 2001 From: damencho Date: Thu, 12 Sep 2024 08:50:41 -0500 Subject: [PATCH 12/14] squash: Fix comment. --- react/features/base/jwt/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react/features/base/jwt/constants.ts b/react/features/base/jwt/constants.ts index ff075cb7e6a..7c1d6487ec4 100644 --- a/react/features/base/jwt/constants.ts +++ b/react/features/base/jwt/constants.ts @@ -20,7 +20,7 @@ export const MEET_FEATURES = { /** * A mapping between jwt features and toolbar buttons keys. - * We don't need recording in here, as it will disable and local recording. + * We don't need recording in here, as it will disable the local recording too. */ export const FEATURES_TO_BUTTONS_MAPPING = { 'livestreaming': 'livestreaming', From d00219c63b758031d321a9b366f8befff00cc5d3 Mon Sep 17 00:00:00 2001 From: damencho Date: Thu, 12 Sep 2024 09:20:37 -0500 Subject: [PATCH 13/14] squash: Reverting back isLiveStreamingButtonVisible. --- .../LiveStream/AbstractLiveStreamButton.ts | 10 +++++---- react/features/recording/functions.ts | 21 +++++++++++++++++++ react/features/recording/hooks.web.ts | 10 +++++---- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/react/features/recording/components/LiveStream/AbstractLiveStreamButton.ts b/react/features/recording/components/LiveStream/AbstractLiveStreamButton.ts index 8c9619541d0..2a49c74030c 100644 --- a/react/features/recording/components/LiveStream/AbstractLiveStreamButton.ts +++ b/react/features/recording/components/LiveStream/AbstractLiveStreamButton.ts @@ -7,7 +7,7 @@ import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/too import { isInBreakoutRoom } from '../../../breakout-rooms/functions'; import { maybeShowPremiumFeatureDialog } from '../../../jaas/actions'; import { isRecorderTranscriptionsRunning } from '../../../transcribing/functions'; -import { isCloudRecordingRunning, isLiveStreamingRunning } from '../../functions'; +import { isCloudRecordingRunning, isLiveStreamingButtonVisible, isLiveStreamingRunning } from '../../functions'; import { getLiveStreaming } from './functions'; @@ -132,9 +132,11 @@ export function _mapStateToProps(state: IReduxState, ownProps: IProps) { const isModerator = isLocalParticipantModerator(state); const liveStreaming = getLiveStreaming(state); - visible = !isInBreakoutRoom(state) - && liveStreaming?.enabled - && isJwtFeatureEnabled(state, 'livestreaming', isModerator, isModerator); + visible = isLiveStreamingButtonVisible({ + liveStreamingAllowed: isJwtFeatureEnabled(state, 'livestreaming', isModerator, isModerator), + liveStreamingEnabled: liveStreaming?.enabled, + isInBreakoutRoom: isInBreakoutRoom(state) + }); } // disable the button if the recording is running. diff --git a/react/features/recording/functions.ts b/react/features/recording/functions.ts index 80b18c7f052..ab2e8ccfc62 100644 --- a/react/features/recording/functions.ts +++ b/react/features/recording/functions.ts @@ -414,3 +414,24 @@ export function registerRecordingAudioFiles(dispatch: IStore['dispatch'], should RECORDING_ON_SOUND_ID, getSoundFileSrc(RECORDING_ON_SOUND_FILE, language))); } + +/** + * Returns true if the live-streaming button should be visible. + * + * @param {boolean} liveStreamingEnabled - True if the live-streaming is enabled. + * @param {boolean} liveStreamingAllowed - True if the live-streaming feature is enabled in JWT + * or is a moderator if JWT is missing or features are missing in JWT. + * @param {boolean} isInBreakoutRoom - True if in breakout room. + * @returns {boolean} + */ +export function isLiveStreamingButtonVisible({ + liveStreamingAllowed, + liveStreamingEnabled, + isInBreakoutRoom +}: { + isInBreakoutRoom: boolean; + liveStreamingAllowed: boolean; + liveStreamingEnabled: boolean; +}) { + return !isInBreakoutRoom && liveStreamingEnabled && liveStreamingAllowed; +} diff --git a/react/features/recording/hooks.web.ts b/react/features/recording/hooks.web.ts index cc7875e69d0..2a831611400 100644 --- a/react/features/recording/hooks.web.ts +++ b/react/features/recording/hooks.web.ts @@ -8,7 +8,7 @@ import { isInBreakoutRoom } from '../breakout-rooms/functions'; import { getLiveStreaming } from './components/LiveStream/functions'; import LiveStreamButton from './components/LiveStream/web/LiveStreamButton'; import RecordButton from './components/Recording/web/RecordButton'; -import { getRecordButtonProps } from './functions'; +import { getRecordButtonProps, isLiveStreamingButtonVisible } from './functions'; const recording = { @@ -52,9 +52,11 @@ export function useLiveStreamingButton() { const _isInBreakoutRoom = useSelector(isInBreakoutRoom); if (toolbarButtons?.includes('recording') - && !_isInBreakoutRoom - && liveStreaming?.enabled - && liveStreamingAllowed) { + && isLiveStreamingButtonVisible({ + liveStreamingAllowed, + liveStreamingEnabled: liveStreaming?.enabled, + isInBreakoutRoom: _isInBreakoutRoom + })) { return livestreaming; } } From c48a94c8ec00939135aab9e87688b1e359adaa40 Mon Sep 17 00:00:00 2001 From: damencho Date: Thu, 12 Sep 2024 09:24:01 -0500 Subject: [PATCH 14/14] squash: Fix imports. --- react/features/subtitles/middleware.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/react/features/subtitles/middleware.ts b/react/features/subtitles/middleware.ts index eb7e925338f..16d3795bfc7 100644 --- a/react/features/subtitles/middleware.ts +++ b/react/features/subtitles/middleware.ts @@ -12,7 +12,8 @@ import { TOGGLE_REQUESTING_SUBTITLES } from './actionTypes'; import { - removeTranscriptMessage, setRequestingSubtitles, + removeTranscriptMessage, + setRequestingSubtitles, updateTranscriptMessage } from './actions.any'; import { notifyTranscriptionChunkReceived } from './functions';