diff --git a/react/features/base/premeeting/functions.ts b/react/features/base/premeeting/functions.ts index 2ac52e9dd04c..10f329fc9959 100644 --- a/react/features/base/premeeting/functions.ts +++ b/react/features/base/premeeting/functions.ts @@ -254,8 +254,9 @@ export function getConnectionData(state: IReduxState) { */ export function isPreCallTestEnabled(state: IReduxState): boolean { const { prejoinConfig } = state['features/base/config']; + const { preCallTest } = state['features/dynamic-branding']; - return prejoinConfig?.preCallTestEnabled ?? false; + return preCallTest?.enabled ?? prejoinConfig?.preCallTestEnabled ?? false; } /** @@ -266,6 +267,7 @@ export function isPreCallTestEnabled(state: IReduxState): boolean { */ export function getPreCallICEUrl(state: IReduxState): string | undefined { const { prejoinConfig } = state['features/base/config']; + const { preCallTest } = state['features/dynamic-branding']; - return prejoinConfig?.preCallTestICEUrl; + return preCallTest?.iceUrl || prejoinConfig?.preCallTestICEUrl; } diff --git a/react/features/dynamic-branding/middleware.native.ts b/react/features/dynamic-branding/middleware.native.ts index c19dcff24577..f48c46d531bf 100644 --- a/react/features/dynamic-branding/middleware.native.ts +++ b/react/features/dynamic-branding/middleware.native.ts @@ -23,10 +23,14 @@ MiddlewareRegistry.register(store => next => action => { backgroundImageUrl, brandedIcons, didPageUrl, + downloadAppsUrl, inviteDomain, labels, + liveStreamingDialogUrls, + salesforceUrl, sharedVideoAllowedURLDomains, - supportUrl + supportUrl, + userDocumentationUrl } = action.value; action.value = { @@ -35,10 +39,14 @@ MiddlewareRegistry.register(store => next => action => { backgroundImageUrl, brandedIcons, didPageUrl, + downloadAppsUrl, inviteDomain, labels, + liveStreamingDialogUrls, + salesforceUrl, sharedVideoAllowedURLDomains, - supportUrl + supportUrl, + userDocumentationUrl }; // The backend may send an empty string, make sure we skip that. diff --git a/react/features/dynamic-branding/reducer.ts b/react/features/dynamic-branding/reducer.ts index 39bbf9c859ce..2ee4a68d9572 100644 --- a/react/features/dynamic-branding/reducer.ts +++ b/react/features/dynamic-branding/reducer.ts @@ -152,16 +152,28 @@ export interface IDynamicBrandingState { defaultBranding: boolean; defaultTranscriptionLanguage?: boolean; didPageUrl: string; + downloadAppsUrl?: string; inviteDomain: string; labels: Object | null; + liveStreamingDialogUrls?: { + dataPrivacyUrl?: string; + helpUrl?: string; + termsUrl?: string; + }; logoClickUrl: string; logoImageUrl: string; muiBrandedTheme?: boolean; + preCallTest?: { + enabled?: boolean; + iceUrl?: string; + }; premeetingBackground: string; + salesforceUrl?: string; sharedVideoAllowedURLDomains?: Array; showGiphyIntegration?: boolean; supportUrl?: string; useDynamicBrandingData: boolean; + userDocumentationUrl?: string; virtualBackgrounds: Array; } @@ -178,15 +190,20 @@ ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STA brandedIcons, defaultBranding, didPageUrl, + downloadAppsUrl, inviteDomain, labels, + liveStreamingDialogUrls, logoClickUrl, logoImageUrl, muiBrandedTheme, + preCallTest, premeetingBackground, + salesforceUrl, sharedVideoAllowedURLDomains, showGiphyIntegration, supportUrl, + userDocumentationUrl, virtualBackgrounds } = action.value; @@ -197,18 +214,23 @@ ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STA brandedIcons, defaultBranding, didPageUrl, + downloadAppsUrl, inviteDomain, labels, + liveStreamingDialogUrls, logoClickUrl, logoImageUrl, muiBrandedTheme, + preCallTest, premeetingBackground, + salesforceUrl, sharedVideoAllowedURLDomains, showGiphyIntegration, supportUrl, customizationFailed: false, customizationReady: true, useDynamicBrandingData: true, + userDocumentationUrl, virtualBackgrounds: formatImages(virtualBackgrounds || []) }; } diff --git a/react/features/recording/components/LiveStream/functions.ts b/react/features/recording/components/LiveStream/functions.ts index a382bc439e74..49c6052e1453 100644 --- a/react/features/recording/components/LiveStream/functions.ts +++ b/react/features/recording/components/LiveStream/functions.ts @@ -16,13 +16,18 @@ import { */ export function getLiveStreaming(state: IReduxState) { const { liveStreaming = {} } = state['features/base/config']; + const { liveStreamingDialogUrls = {} } = state['features/dynamic-branding']; const regexp = liveStreaming.validatorRegExpString && new RegExp(liveStreaming.validatorRegExpString); return { enabled: Boolean(liveStreaming.enabled), - helpURL: sanitizeUrl(liveStreaming.helpLink || JITSI_LIVE_STREAMING_HELP_LINK)?.toString(), - termsURL: sanitizeUrl(liveStreaming.termsLink || YOUTUBE_TERMS_URL)?.toString(), - dataPrivacyURL: sanitizeUrl(liveStreaming.dataPrivacyLink || GOOGLE_PRIVACY_POLICY)?.toString(), + helpURL: sanitizeUrl( + liveStreamingDialogUrls.helpUrl || liveStreaming.helpLink || JITSI_LIVE_STREAMING_HELP_LINK)?.toString(), + termsURL: sanitizeUrl( + liveStreamingDialogUrls.termsUrl || liveStreaming.termsLink || YOUTUBE_TERMS_URL)?.toString(), + dataPrivacyURL: sanitizeUrl( + liveStreamingDialogUrls.dataPrivacyUrl || liveStreaming.dataPrivacyLink || GOOGLE_PRIVACY_POLICY + )?.toString(), streamLinkRegexp: regexp || FOUR_GROUPS_DASH_SEPARATED }; } diff --git a/react/features/salesforce/functions.ts b/react/features/salesforce/functions.ts index e117f674bb8a..6bc9ec2f0eeb 100644 --- a/react/features/salesforce/functions.ts +++ b/react/features/salesforce/functions.ts @@ -1,4 +1,6 @@ import { IReduxState } from '../app/types'; +import { IStateful } from '../base/app/types'; +import { toState } from '../base/redux/functions'; import { doGetJSON } from '../base/util/httpUtils'; import { isInBreakoutRoom } from '../breakout-rooms/functions'; @@ -9,12 +11,24 @@ import { isInBreakoutRoom } from '../breakout-rooms/functions'; * {@code getState} function, or the redux state itself. * @returns {boolean} */ -export const isSalesforceEnabled = (state: IReduxState) => { - const { salesforceUrl } = state['features/base/config']; +export function isSalesforceEnabled(state: IReduxState) { + const salesforceUrl = getSalesforceUrl(state); const isBreakoutRoom = isInBreakoutRoom(state); return Boolean(salesforceUrl) && !isBreakoutRoom; -}; +} + +/** + * Returns the salesforce integration URL. + * + * @param {Function|Object} stateful - The redux store or {@code getState} function. + * @returns {URL} - The salesforce integration URL. + */ +export function getSalesforceUrl(stateful: IStateful) { + const state = toState(stateful); + + return state['features/dynamic-branding'].salesforceUrl || state['features/base/config'].salesforceUrl; +} /** * Fetches the Salesforce records that were most recently interacted with. diff --git a/react/features/salesforce/useSalesforceLinkDialog.ts b/react/features/salesforce/useSalesforceLinkDialog.ts index d5c86d33c9d8..0027a85041fc 100644 --- a/react/features/salesforce/useSalesforceLinkDialog.ts +++ b/react/features/salesforce/useSalesforceLinkDialog.ts @@ -15,6 +15,7 @@ import { import { executeLinkMeetingRequest, getRecentSessionRecords, + getSalesforceUrl, getSessionRecordDetails, searchSessionRecords } from './functions'; @@ -40,7 +41,7 @@ export const useSalesforceLinkDialog = () => { const [ hasDetailsErrors, setDetailsErrors ] = useState(false); const conference = useSelector(getCurrentConference); const sessionId = conference?.getMeetingUniqueId(); - const { salesforceUrl = '' } = useSelector((state: IReduxState) => state['features/base/config']); + const salesforceUrl = useSelector((state: IReduxState) => getSalesforceUrl(state) ?? ''); const { jwt = '' } = useSelector((state: IReduxState) => state['features/base/jwt']); const showSearchResults = searchTerm && searchTerm.length > 1; const showNoResults = showSearchResults && records.length === 0; diff --git a/react/features/toolbox/components/DownloadButton.ts b/react/features/toolbox/components/DownloadButton.ts index 67eec709767e..2037bc6d5e94 100644 --- a/react/features/toolbox/components/DownloadButton.ts +++ b/react/features/toolbox/components/DownloadButton.ts @@ -7,6 +7,7 @@ import { translate } from '../../base/i18n/functions'; import { IconDownload } from '../../base/icons/svg'; import AbstractButton, { IProps as AbstractButtonProps } from '../../base/toolbox/components/AbstractButton'; import { openURLInBrowser } from '../../base/util/openURLInBrowser'; +import { getDownloadAppsUrl } from '../functions.any'; interface IProps extends AbstractButtonProps { @@ -47,7 +48,7 @@ class DownloadButton extends AbstractButton { * @returns {Object} */ function _mapStateToProps(state: IReduxState) { - const { downloadAppsUrl } = state['features/base/config'].deploymentUrls || {}; + const downloadAppsUrl = getDownloadAppsUrl(state); const visible = typeof downloadAppsUrl === 'string'; return { diff --git a/react/features/toolbox/components/HelpButton.ts b/react/features/toolbox/components/HelpButton.ts index 0a60901df48f..c62e58289747 100644 --- a/react/features/toolbox/components/HelpButton.ts +++ b/react/features/toolbox/components/HelpButton.ts @@ -9,6 +9,7 @@ import { translate } from '../../base/i18n/functions'; import { IconHelp } from '../../base/icons/svg'; import AbstractButton, { IProps as AbstractButtonProps } from '../../base/toolbox/components/AbstractButton'; import { openURLInBrowser } from '../../base/util/openURLInBrowser'; +import { getUserDocumentationUrl } from '../functions.any'; interface IProps extends AbstractButtonProps { @@ -49,7 +50,7 @@ class HelpButton extends AbstractButton { * @returns {Object} */ function _mapStateToProps(state: IReduxState) { - const { userDocumentationURL } = state['features/base/config'].deploymentUrls || {}; + const userDocumentationURL = getUserDocumentationUrl(state); const enabled = getFeatureFlag(state, HELP_BUTTON_ENABLED, true); const visible = typeof userDocumentationURL === 'string' && enabled; diff --git a/react/features/toolbox/functions.any.ts b/react/features/toolbox/functions.any.ts index 55519ea71683..8ffc1d5b7d6c 100644 --- a/react/features/toolbox/functions.any.ts +++ b/react/features/toolbox/functions.any.ts @@ -1,7 +1,9 @@ import { IReduxState } from '../app/types'; +import { IStateful } from '../base/app/types'; import { isJwtFeatureEnabledStateless } from '../base/jwt/functions'; import { IGUMPendingState } from '../base/media/types'; import { IParticipantFeatures } from '../base/participants/types'; +import { toState } from '../base/redux/functions'; import { iAmVisitor } from '../visitors/functions'; /** @@ -57,3 +59,32 @@ export function getJwtDisabledButtons( return acc; } + +/** + * Returns the URL to the user documentation. + * + * @param {Function|Object} stateful - The redux store or {@code getState} function. + * @returns {URL} - The URL to the user documentation. + */ +export function getUserDocumentationUrl(stateful: IStateful) { + const state = toState(stateful); + const { userDocumentationUrl } = state['features/dynamic-branding']; + const { deploymentUrls } = state['features/base/config']; + + return userDocumentationUrl || deploymentUrls?.userDocumentationURL; +} + +/** + * Returns the URL for downloading the mobile app. + * + * @param {Function|Object} stateful - The redux store or {@code getState} function. + * @returns {URL} - The URL for downloading the mobile app. + */ +export function getDownloadAppsUrl(stateful: IStateful) { + const state = toState(stateful); + const { downloadAppsUrl } = state['features/dynamic-branding']; + const { deploymentUrls } = state['features/base/config']; + + return downloadAppsUrl || deploymentUrls?.downloadAppsUrl; +} + diff --git a/react/features/toolbox/hooks.web.ts b/react/features/toolbox/hooks.web.ts index 7aeeaa94deb2..459ea85ebf35 100644 --- a/react/features/toolbox/hooks.web.ts +++ b/react/features/toolbox/hooks.web.ts @@ -69,7 +69,12 @@ import ProfileButton from './components/web/ProfileButton'; import ShareDesktopButton from './components/web/ShareDesktopButton'; import ToggleCameraButton from './components/web/ToggleCameraButton'; import VideoSettingsButton from './components/web/VideoSettingsButton'; -import { isButtonEnabled, isDesktopShareButtonDisabled } from './functions.web'; +import { + getDownloadAppsUrl, + getUserDocumentationUrl, + isButtonEnabled, + isDesktopShareButtonDisabled +} from './functions.web'; import { ICustomToolbarButton, IToolboxButton, ToolbarButton } from './types'; @@ -239,7 +244,7 @@ function getShareAudioButton() { */ function useDownloadButton() { const visible = useSelector( - (state: IReduxState) => typeof state['features/base/config'].deploymentUrls?.downloadAppsUrl === 'string'); + (state: IReduxState) => typeof getDownloadAppsUrl(state) === 'string'); if (visible) { return download; @@ -254,7 +259,7 @@ function useDownloadButton() { function useHelpButton() { const visible = useSelector( (state: IReduxState) => - typeof state['features/base/config'].deploymentUrls?.userDocumentationURL === 'string' + typeof getUserDocumentationUrl(state) === 'string' && getFeatureFlag(state, HELP_BUTTON_ENABLED, true)); if (visible) {