123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599 |
- import { merge, union } from 'lodash-es';
-
- import { CONFERENCE_INFO } from '../../conference/components/constants';
- import { TOOLBAR_BUTTONS } from '../../toolbox/constants';
- import { ToolbarButton } from '../../toolbox/types';
- import { CONNECTION_PROPERTIES_UPDATED } from '../connection/actionTypes';
- import ReducerRegistry from '../redux/ReducerRegistry';
- import { equals } from '../redux/functions';
-
- import {
- CONFIG_WILL_LOAD,
- LOAD_CONFIG_ERROR,
- OVERWRITE_CONFIG,
- SET_CONFIG,
- UPDATE_CONFIG
- } from './actionTypes';
- import {
- IConfig,
- IDeeplinkingConfig,
- IDeeplinkingDesktopConfig,
- IDeeplinkingMobileConfig,
- IMobileDynamicLink
- } from './configType';
- import { _cleanupConfig, _setDeeplinkingDefaults } from './functions';
-
- /**
- * The initial state of the feature base/config when executing in a
- * non-React Native environment. The mandatory configuration to be passed to
- * JitsiMeetJS#init(). The app will download config.js from the Jitsi Meet
- * deployment and take its values into account but the values below will be
- * enforced (because they are essential to the correct execution of the
- * application).
- *
- * @type {Object}
- */
- const INITIAL_NON_RN_STATE: IConfig = {
- };
-
- /**
- * The initial state of the feature base/config when executing in a React Native
- * environment. The mandatory configuration to be passed to JitsiMeetJS#init().
- * The app will download config.js from the Jitsi Meet deployment and take its
- * values into account but the values below will be enforced (because they are
- * essential to the correct execution of the application).
- *
- * @type {Object}
- */
- const INITIAL_RN_STATE: IConfig = {
- };
-
- /**
- * Mapping between old configs controlling the conference info headers visibility and the
- * new configs. Needed in order to keep backwards compatibility.
- */
- const CONFERENCE_HEADER_MAPPING = {
- hideConferenceTimer: [ 'conference-timer' ],
- hideConferenceSubject: [ 'subject' ],
- hideParticipantsStats: [ 'participants-count' ],
- hideRecordingLabel: [ 'recording' ]
- };
-
- export interface IConfigState extends IConfig {
- analysis?: {
- obfuscateRoomName?: boolean;
- };
- error?: Error;
- oldConfig?: {
- bosh?: string;
- focusUserJid?: string;
- hosts: {
- domain: string;
- muc: string;
- };
- p2p?: object;
- websocket?: string;
- };
- }
-
- ReducerRegistry.register<IConfigState>('features/base/config', (state = _getInitialState(), action): IConfigState => {
- switch (action.type) {
- case UPDATE_CONFIG:
- return _updateConfig(state, action);
-
- case CONFIG_WILL_LOAD:
- return {
- error: undefined,
-
- /**
- * The URL of the location associated with/configured by this
- * configuration.
- *
- * @type URL
- */
- locationURL: action.locationURL
- };
-
- case CONNECTION_PROPERTIES_UPDATED: {
- const { region, shard } = action.properties;
- const { deploymentInfo } = state;
-
- if (deploymentInfo?.region === region && deploymentInfo?.shard === shard) {
- return state;
- }
-
- return {
- ...state,
- deploymentInfo: JSON.parse(JSON.stringify({
- ...deploymentInfo,
- region,
- shard
- }))
- };
- }
-
- case LOAD_CONFIG_ERROR:
- // XXX LOAD_CONFIG_ERROR is one of the settlement execution paths of
- // the asynchronous "loadConfig procedure/process" started with
- // CONFIG_WILL_LOAD. Due to the asynchronous nature of it, whoever
- // is settling the process needs to provide proof that they have
- // started it and that the iteration of the process being completed
- // now is still of interest to the app.
- if (state.locationURL === action.locationURL) {
- return {
- /**
- * The {@link Error} which prevented the loading of the
- * configuration of the associated {@code locationURL}.
- *
- * @type Error
- */
- error: action.error
- };
- }
- break;
-
- case SET_CONFIG:
- return _setConfig(state, action);
-
- case OVERWRITE_CONFIG:
- return {
- ...state,
- ...action.config
- };
- }
-
- return state;
- });
-
- /**
- * Gets the initial state of the feature base/config. The mandatory
- * configuration to be passed to JitsiMeetJS#init(). The app will download
- * config.js from the Jitsi Meet deployment and take its values into account but
- * the values below will be enforced (because they are essential to the correct
- * execution of the application).
- *
- * @returns {Object}
- */
- function _getInitialState() {
- return (
- navigator.product === 'ReactNative'
- ? INITIAL_RN_STATE
- : INITIAL_NON_RN_STATE);
- }
-
- /**
- * Reduces a specific Redux action SET_CONFIG of the feature
- * base/lib-jitsi-meet.
- *
- * @param {IConfig} state - The Redux state of the feature base/config.
- * @param {Action} action - The Redux action SET_CONFIG to reduce.
- * @private
- * @returns {Object} The new state after the reduction of the specified action.
- */
- function _setConfig(state: IConfig, { config }: { config: IConfig; }) {
- // eslint-disable-next-line no-param-reassign
- config = _translateLegacyConfig(config);
-
- const { audioQuality } = config;
- const hdAudioOptions = {};
-
- if (audioQuality?.stereo) {
- Object.assign(hdAudioOptions, {
- disableAP: true,
- enableNoAudioDetection: false,
- enableNoisyMicDetection: false,
- enableTalkWhileMuted: false
- });
- }
-
- const newState = merge(
- {},
- config,
- hdAudioOptions,
- { error: undefined },
-
- // The config of _getInitialState() is meant to override the config
- // downloaded from the Jitsi Meet deployment because the former contains
- // values that are mandatory.
- _getInitialState()
- );
-
- _cleanupConfig(newState);
-
- return equals(state, newState) ? state : newState;
- }
-
- /**
- * Processes the conferenceInfo object against the defaults.
- *
- * @param {IConfig} config - The old config.
- * @returns {Object} The processed conferenceInfo object.
- */
- function _getConferenceInfo(config: IConfig) {
- const { conferenceInfo } = config;
-
- if (conferenceInfo) {
- return {
- alwaysVisible: conferenceInfo.alwaysVisible ?? [ ...CONFERENCE_INFO.alwaysVisible ],
- autoHide: conferenceInfo.autoHide ?? [ ...CONFERENCE_INFO.autoHide ]
- };
- }
-
- return {
- ...CONFERENCE_INFO
- };
- }
-
- /**
- * Constructs a new config {@code Object}, if necessary, out of a specific
- * interface_config {@code Object} which is in the latest format supported by jitsi-meet.
- *
- * @param {Object} oldValue - The config {@code Object} which may or may not be
- * in the latest form supported by jitsi-meet and from which a new config
- * {@code Object} is to be constructed if necessary.
- * @returns {Object} A config {@code Object} which is in the latest format
- * supported by jitsi-meet.
- */
- function _translateInterfaceConfig(oldValue: IConfig) {
- const newValue = oldValue;
-
- if (!Array.isArray(oldValue.toolbarButtons)
- && typeof interfaceConfig === 'object' && Array.isArray(interfaceConfig.TOOLBAR_BUTTONS)) {
- newValue.toolbarButtons = interfaceConfig.TOOLBAR_BUTTONS;
- }
-
- if (!oldValue.toolbarConfig) {
- oldValue.toolbarConfig = {};
- }
-
- newValue.toolbarConfig = oldValue.toolbarConfig || {};
- if (typeof oldValue.toolbarConfig.alwaysVisible !== 'boolean'
- && typeof interfaceConfig === 'object'
- && typeof interfaceConfig.TOOLBAR_ALWAYS_VISIBLE === 'boolean') {
- newValue.toolbarConfig.alwaysVisible = interfaceConfig.TOOLBAR_ALWAYS_VISIBLE;
- }
-
- if (typeof oldValue.toolbarConfig.initialTimeout !== 'number'
- && typeof interfaceConfig === 'object'
- && typeof interfaceConfig.INITIAL_TOOLBAR_TIMEOUT === 'number') {
- newValue.toolbarConfig.initialTimeout = interfaceConfig.INITIAL_TOOLBAR_TIMEOUT;
- }
-
- if (typeof oldValue.toolbarConfig.timeout !== 'number'
- && typeof interfaceConfig === 'object'
- && typeof interfaceConfig.TOOLBAR_TIMEOUT === 'number') {
- newValue.toolbarConfig.timeout = interfaceConfig.TOOLBAR_TIMEOUT;
- }
-
- if (!oldValue.connectionIndicators
- && typeof interfaceConfig === 'object'
- && (interfaceConfig.hasOwnProperty('CONNECTION_INDICATOR_DISABLED')
- || interfaceConfig.hasOwnProperty('CONNECTION_INDICATOR_AUTO_HIDE_ENABLED')
- || interfaceConfig.hasOwnProperty('CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT'))) {
- newValue.connectionIndicators = {
- disabled: interfaceConfig.CONNECTION_INDICATOR_DISABLED,
- autoHide: interfaceConfig.CONNECTION_INDICATOR_AUTO_HIDE_ENABLED,
- autoHideTimeout: interfaceConfig.CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT
- };
- }
-
- if (oldValue.disableModeratorIndicator === undefined
- && typeof interfaceConfig === 'object'
- && interfaceConfig.hasOwnProperty('DISABLE_FOCUS_INDICATOR')) {
- newValue.disableModeratorIndicator = interfaceConfig.DISABLE_FOCUS_INDICATOR;
- }
-
- if (oldValue.defaultLocalDisplayName === undefined
- && typeof interfaceConfig === 'object'
- && interfaceConfig.hasOwnProperty('DEFAULT_LOCAL_DISPLAY_NAME')) {
- newValue.defaultLocalDisplayName = interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME;
- }
-
- if (oldValue.defaultRemoteDisplayName === undefined
- && typeof interfaceConfig === 'object'
- && interfaceConfig.hasOwnProperty('DEFAULT_REMOTE_DISPLAY_NAME')) {
- newValue.defaultRemoteDisplayName = interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME;
- }
-
- if (oldValue.defaultLogoUrl === undefined) {
- if (typeof interfaceConfig === 'object'
- && interfaceConfig.hasOwnProperty('DEFAULT_LOGO_URL')) {
- newValue.defaultLogoUrl = interfaceConfig.DEFAULT_LOGO_URL;
- } else {
- newValue.defaultLogoUrl = 'images/watermark.svg';
- }
- }
-
- // if we have `deeplinking` defined, ignore deprecated values, except `disableDeepLinking`.
- // Otherwise, compose the config.
- if (oldValue.deeplinking && newValue.deeplinking) { // make TS happy
- newValue.deeplinking.disabled = oldValue.deeplinking.hasOwnProperty('disabled')
- ? oldValue.deeplinking.disabled
- : Boolean(oldValue.disableDeepLinking);
- } else {
- const disabled = Boolean(oldValue.disableDeepLinking);
- const deeplinking: IDeeplinkingConfig = {
- desktop: {} as IDeeplinkingDesktopConfig,
- hideLogo: false,
- disabled,
- android: {} as IDeeplinkingMobileConfig,
- ios: {} as IDeeplinkingMobileConfig
- };
-
- if (typeof interfaceConfig === 'object') {
- const mobileDynamicLink = interfaceConfig.MOBILE_DYNAMIC_LINK;
- const dynamicLink: IMobileDynamicLink | undefined = mobileDynamicLink ? {
- apn: mobileDynamicLink.APN,
- appCode: mobileDynamicLink.APP_CODE,
- ibi: mobileDynamicLink.IBI,
- isi: mobileDynamicLink.ISI,
- customDomain: mobileDynamicLink.CUSTOM_DOMAIN
- } : undefined;
-
- if (deeplinking.desktop) {
- deeplinking.desktop.appName = interfaceConfig.NATIVE_APP_NAME;
- }
-
- deeplinking.hideLogo = Boolean(interfaceConfig.HIDE_DEEP_LINKING_LOGO);
- deeplinking.android = {
- appName: interfaceConfig.NATIVE_APP_NAME,
- appScheme: interfaceConfig.APP_SCHEME,
- downloadLink: interfaceConfig.MOBILE_DOWNLOAD_LINK_ANDROID,
- appPackage: interfaceConfig.ANDROID_APP_PACKAGE,
- fDroidUrl: interfaceConfig.MOBILE_DOWNLOAD_LINK_F_DROID,
- dynamicLink
- };
- deeplinking.ios = {
- appName: interfaceConfig.NATIVE_APP_NAME,
- appScheme: interfaceConfig.APP_SCHEME,
- downloadLink: interfaceConfig.MOBILE_DOWNLOAD_LINK_IOS,
- dynamicLink
- };
- }
- newValue.deeplinking = deeplinking;
- }
-
- return newValue;
- }
-
- /**
- * Constructs a new config {@code Object}, if necessary, out of a specific
- * config {@code Object} which is in the latest format supported by jitsi-meet.
- * Such a translation from an old config format to a new/the latest config
- * format is necessary because the mobile app bundles jitsi-meet and
- * lib-jitsi-meet at build time and does not download them at runtime from the
- * deployment on which it will join a conference.
- *
- * @param {Object} oldValue - The config {@code Object} which may or may not be
- * in the latest form supported by jitsi-meet and from which a new config
- * {@code Object} is to be constructed if necessary.
- * @returns {Object} A config {@code Object} which is in the latest format
- * supported by jitsi-meet.
- */
- function _translateLegacyConfig(oldValue: IConfig) {
- const newValue = _translateInterfaceConfig(oldValue);
-
- // Translate deprecated config values to new config values.
-
- const filteredConferenceInfo = Object.keys(CONFERENCE_HEADER_MAPPING).filter(key => oldValue[key as keyof IConfig]);
-
- if (filteredConferenceInfo.length) {
- newValue.conferenceInfo = _getConferenceInfo(oldValue);
-
- filteredConferenceInfo.forEach(key => {
- newValue.conferenceInfo = oldValue.conferenceInfo ?? {};
-
- // hideRecordingLabel does not mean not render it at all, but autoHide it
- if (key === 'hideRecordingLabel') {
- newValue.conferenceInfo.alwaysVisible
- = (newValue.conferenceInfo?.alwaysVisible ?? [])
- .filter(c => !CONFERENCE_HEADER_MAPPING[key].includes(c));
- newValue.conferenceInfo.autoHide
- = union(newValue.conferenceInfo.autoHide, CONFERENCE_HEADER_MAPPING[key]);
- } else {
- newValue.conferenceInfo.alwaysVisible
- = (newValue.conferenceInfo.alwaysVisible ?? [])
- .filter(c => !CONFERENCE_HEADER_MAPPING[key as keyof typeof CONFERENCE_HEADER_MAPPING].includes(c));
- newValue.conferenceInfo.autoHide
- = (newValue.conferenceInfo.autoHide ?? []).filter(c =>
- !CONFERENCE_HEADER_MAPPING[key as keyof typeof CONFERENCE_HEADER_MAPPING].includes(c));
- }
- });
- }
-
- newValue.welcomePage = oldValue.welcomePage || {};
- if (oldValue.hasOwnProperty('enableWelcomePage')
- && !newValue.welcomePage.hasOwnProperty('disabled')
- ) {
- newValue.welcomePage.disabled = !oldValue.enableWelcomePage;
- }
-
- newValue.prejoinConfig = oldValue.prejoinConfig || {};
- if (oldValue.hasOwnProperty('prejoinPageEnabled')
- && !newValue.prejoinConfig.hasOwnProperty('enabled')
- ) {
- newValue.prejoinConfig.enabled = oldValue.prejoinPageEnabled;
- }
-
- newValue.disabledSounds = newValue.disabledSounds || [];
-
- if (oldValue.disableJoinLeaveSounds) {
- newValue.disabledSounds.unshift('PARTICIPANT_LEFT_SOUND', 'PARTICIPANT_JOINED_SOUND');
- }
-
- if (oldValue.disableRecordAudioNotification) {
- newValue.disabledSounds.unshift(
- 'RECORDING_ON_SOUND',
- 'RECORDING_OFF_SOUND',
- 'LIVE_STREAMING_ON_SOUND',
- 'LIVE_STREAMING_OFF_SOUND'
- );
- }
-
- if (oldValue.disableIncomingMessageSound) {
- newValue.disabledSounds.unshift('INCOMING_MSG_SOUND');
- }
-
- newValue.raisedHands = newValue.raisedHands || {};
-
- if (oldValue.disableRemoveRaisedHandOnFocus) {
- newValue.raisedHands.disableRemoveRaisedHandOnFocus = oldValue.disableRemoveRaisedHandOnFocus;
- }
-
- if (oldValue.stereo || oldValue.opusMaxAverageBitrate) {
- newValue.audioQuality = {
- opusMaxAverageBitrate: oldValue.audioQuality?.opusMaxAverageBitrate ?? oldValue.opusMaxAverageBitrate,
- stereo: oldValue.audioQuality?.stereo ?? oldValue.stereo
- };
- }
-
- newValue.e2ee = newValue.e2ee || {};
-
- if (oldValue.e2eeLabels) {
- newValue.e2ee.labels = oldValue.e2eeLabels;
- }
-
- newValue.defaultLocalDisplayName
- = newValue.defaultLocalDisplayName || 'me';
-
- if (oldValue.hideAddRoomButton) {
- newValue.breakoutRooms = {
- /* eslint-disable-next-line no-extra-parens */
- ...(newValue.breakoutRooms || {}),
- hideAddRoomButton: oldValue.hideAddRoomButton
- };
- }
-
- newValue.defaultRemoteDisplayName
- = newValue.defaultRemoteDisplayName || 'Fellow Jitster';
-
- newValue.transcription = newValue.transcription || {};
- if (oldValue.transcribingEnabled !== undefined) {
- newValue.transcription = {
- ...newValue.transcription,
- enabled: oldValue.transcribingEnabled
- };
- }
- if (oldValue.transcribeWithAppLanguage !== undefined) {
- newValue.transcription = {
- ...newValue.transcription,
- useAppLanguage: oldValue.transcribeWithAppLanguage
- };
- }
- if (oldValue.preferredTranscribeLanguage !== undefined) {
- newValue.transcription = {
- ...newValue.transcription,
- preferredLanguage: oldValue.preferredTranscribeLanguage
- };
- }
- if (oldValue.autoCaptionOnRecord !== undefined) {
- newValue.transcription = {
- ...newValue.transcription,
- autoTranscribeOnRecord: oldValue.autoCaptionOnRecord
- };
- }
-
- newValue.recordingService = newValue.recordingService || {};
- if (oldValue.fileRecordingsServiceEnabled !== undefined
- && newValue.recordingService.enabled === undefined) {
- newValue.recordingService = {
- ...newValue.recordingService,
- enabled: oldValue.fileRecordingsServiceEnabled
- };
- }
- if (oldValue.fileRecordingsServiceSharingEnabled !== undefined
- && newValue.recordingService.sharingEnabled === undefined) {
- newValue.recordingService = {
- ...newValue.recordingService,
- sharingEnabled: oldValue.fileRecordingsServiceSharingEnabled
- };
- }
-
- newValue.liveStreaming = newValue.liveStreaming || {};
-
- // Migrate config.liveStreamingEnabled
- if (oldValue.liveStreamingEnabled !== undefined) {
- newValue.liveStreaming = {
- ...newValue.liveStreaming,
- enabled: oldValue.liveStreamingEnabled
- };
- }
-
- // Migrate interfaceConfig.LIVE_STREAMING_HELP_LINK
- if (oldValue.liveStreaming === undefined
- && typeof interfaceConfig === 'object'
- && interfaceConfig.hasOwnProperty('LIVE_STREAMING_HELP_LINK')) {
- newValue.liveStreaming = {
- ...newValue.liveStreaming,
- helpLink: interfaceConfig.LIVE_STREAMING_HELP_LINK
- };
- }
-
- newValue.speakerStats = newValue.speakerStats || {};
-
- if (oldValue.disableSpeakerStatsSearch !== undefined
- && newValue.speakerStats.disableSearch === undefined
- ) {
- newValue.speakerStats = {
- ...newValue.speakerStats,
- disableSearch: oldValue.disableSpeakerStatsSearch
- };
- }
-
- if (oldValue.speakerStatsOrder !== undefined
- && newValue.speakerStats.order === undefined) {
- newValue.speakerStats = {
- ...newValue.speakerStats,
- order: oldValue.speakerStatsOrder
- };
- }
-
- if (oldValue.autoKnockLobby !== undefined
- && newValue.lobby?.autoKnock === undefined) {
- newValue.lobby = {
- ...newValue.lobby || {},
- autoKnock: oldValue.autoKnockLobby
- };
- }
-
- if (oldValue.enableLobbyChat !== undefined
- && newValue.lobby?.enableChat === undefined) {
- newValue.lobby = {
- ...newValue.lobby || {},
- enableChat: oldValue.enableLobbyChat
- };
- }
-
- if (oldValue.hideLobbyButton !== undefined
- && newValue.securityUi?.hideLobbyButton === undefined) {
- newValue.securityUi = {
- ...newValue.securityUi || {},
- hideLobbyButton: oldValue.hideLobbyButton
- };
- }
-
- if (oldValue.disableProfile) {
- newValue.toolbarButtons = (newValue.toolbarButtons || TOOLBAR_BUTTONS)
- .filter((button: ToolbarButton) => button !== 'profile');
- }
-
- _setDeeplinkingDefaults(newValue.deeplinking as IDeeplinkingConfig);
-
- return newValue;
- }
-
- /**
- * Updates the stored configuration with the given extra options.
- *
- * @param {Object} state - The Redux state of the feature base/config.
- * @param {Action} action - The Redux action to reduce.
- * @private
- * @returns {Object} The new state after the reduction of the specified action.
- */
- function _updateConfig(state: IConfig, { config }: { config: IConfig; }) {
- const newState = merge({}, state, config);
-
- _cleanupConfig(newState);
-
- return equals(state, newState) ? state : newState;
- }
|