This commit is contained in:
61
react/features/base/config/actionTypes.ts
Normal file
61
react/features/base/config/actionTypes.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* The redux action which signals that a configuration (commonly known in Jitsi
|
||||
* Meet as config.js) will be loaded for a specific locationURL.
|
||||
*
|
||||
* {
|
||||
* type: CONFIG_WILL_LOAD,
|
||||
* locationURL: URL,
|
||||
* room: string
|
||||
* }
|
||||
*/
|
||||
export const CONFIG_WILL_LOAD = 'CONFIG_WILL_LOAD';
|
||||
|
||||
/**
|
||||
* The redux action which signals that a configuration (commonly known in Jitsi
|
||||
* Meet as config.js) could not be loaded due to a specific error.
|
||||
*
|
||||
* {
|
||||
* type: LOAD_CONFIG_ERROR,
|
||||
* error: Error,
|
||||
* locationURL: URL
|
||||
* }
|
||||
*/
|
||||
export const LOAD_CONFIG_ERROR = 'LOAD_CONFIG_ERROR';
|
||||
|
||||
/**
|
||||
* The redux action which sets the configuration represented by the feature
|
||||
* base/config. The configuration is defined and consumed by the library
|
||||
* lib-jitsi-meet but some of its properties are consumed by the application
|
||||
* jitsi-meet as well.
|
||||
*
|
||||
* {
|
||||
* type: SET_CONFIG,
|
||||
* config: Object
|
||||
* }
|
||||
*/
|
||||
export const SET_CONFIG = 'SET_CONFIG';
|
||||
|
||||
/**
|
||||
* The redux action which updates the configuration represented by the feature
|
||||
* base/config. The configuration is defined and consumed by the library
|
||||
* lib-jitsi-meet but some of its properties are consumed by the application
|
||||
* jitsi-meet as well. A merge operation is performed between the existing config
|
||||
* and the passed object.
|
||||
*
|
||||
* {
|
||||
* type: UPDATE_CONFIG,
|
||||
* config: Object
|
||||
* }
|
||||
*/
|
||||
export const UPDATE_CONFIG = 'UPDATE_CONFIG';
|
||||
|
||||
/**
|
||||
* The redux action which overwrites configurations represented by the feature
|
||||
* base/config. The passed on config values overwrite the current values for given props.
|
||||
*
|
||||
* {
|
||||
* type: OVERWRITE_CONFIG,
|
||||
* config: Object
|
||||
* }
|
||||
*/
|
||||
export const OVERWRITE_CONFIG = 'OVERWRITE_CONFIG';
|
||||
185
react/features/base/config/actions.ts
Normal file
185
react/features/base/config/actions.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
// @ts-expect-error
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
|
||||
import { IStore } from '../../app/types';
|
||||
import { addKnownDomains } from '../known-domains/actions';
|
||||
import { parseURIString } from '../util/uri';
|
||||
|
||||
import {
|
||||
CONFIG_WILL_LOAD,
|
||||
LOAD_CONFIG_ERROR,
|
||||
OVERWRITE_CONFIG,
|
||||
SET_CONFIG,
|
||||
UPDATE_CONFIG
|
||||
} from './actionTypes';
|
||||
import { IConfig } from './configType';
|
||||
import { _CONFIG_STORE_PREFIX } from './constants';
|
||||
import { setConfigFromURLParams } from './functions.any';
|
||||
|
||||
|
||||
/**
|
||||
* Updates the config with new options.
|
||||
*
|
||||
* @param {Object} config - The new options (to add).
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function updateConfig(config: IConfig) {
|
||||
return {
|
||||
type: UPDATE_CONFIG,
|
||||
config
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that the configuration (commonly known in Jitsi Meet as config.js)
|
||||
* for a specific locationURL will be loaded now.
|
||||
*
|
||||
* @param {URL} locationURL - The URL of the location which necessitated the
|
||||
* loading of a configuration.
|
||||
* @param {string} room - The name of the room (conference) for which we're loading the config for.
|
||||
* @returns {{
|
||||
* type: CONFIG_WILL_LOAD,
|
||||
* locationURL: URL,
|
||||
* room: string
|
||||
* }}
|
||||
*/
|
||||
export function configWillLoad(locationURL: URL, room: string) {
|
||||
return {
|
||||
type: CONFIG_WILL_LOAD,
|
||||
locationURL,
|
||||
room
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that a configuration (commonly known in Jitsi Meet as config.js)
|
||||
* could not be loaded due to a specific error.
|
||||
*
|
||||
* @param {Error} error - The {@code Error} which prevented the successful
|
||||
* loading of a configuration.
|
||||
* @param {URL} locationURL - The URL of the location which necessitated the
|
||||
* loading of a configuration.
|
||||
* @returns {{
|
||||
* type: LOAD_CONFIG_ERROR,
|
||||
* error: Error,
|
||||
* locationURL: URL
|
||||
* }}
|
||||
*/
|
||||
export function loadConfigError(error: Error, locationURL: URL) {
|
||||
return {
|
||||
type: LOAD_CONFIG_ERROR,
|
||||
error,
|
||||
locationURL
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrites some config values.
|
||||
*
|
||||
* @param {Object} config - The new options (to overwrite).
|
||||
* @returns {{
|
||||
* type: OVERWRITE_CONFIG,
|
||||
* config: Object
|
||||
* }}
|
||||
*/
|
||||
export function overwriteConfig(config: Object) {
|
||||
return {
|
||||
type: OVERWRITE_CONFIG,
|
||||
config
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the configuration represented by the feature base/config. The
|
||||
* configuration is defined and consumed by the library lib-jitsi-meet but some
|
||||
* of its properties are consumed by the application jitsi-meet as well.
|
||||
*
|
||||
* @param {Object} config - The configuration to be represented by the feature
|
||||
* base/config.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function setConfig(config: IConfig = {}) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const { locationURL } = getState()['features/base/connection'];
|
||||
|
||||
// Now that the loading of the config was successful override the values
|
||||
// with the parameters passed in the hash part of the location URI.
|
||||
// TODO We're still in the middle ground between old Web with config,
|
||||
// and interfaceConfig used via global variables and new
|
||||
// Web and mobile reading the respective values from the redux store.
|
||||
// Only the config will be overridden on React Native, as the other
|
||||
// globals will be undefined here. It's intentional - we do not care to
|
||||
// override those configs yet.
|
||||
locationURL
|
||||
&& setConfigFromURLParams(
|
||||
|
||||
// On Web the config also comes from the window.config global,
|
||||
// but it is resolved in the loadConfig procedure.
|
||||
config,
|
||||
window.interfaceConfig,
|
||||
locationURL);
|
||||
|
||||
let { bosh } = config;
|
||||
|
||||
if (bosh) {
|
||||
// Normalize the BOSH URL.
|
||||
if (bosh.startsWith('//')) {
|
||||
// By default our config.js doesn't include the protocol.
|
||||
bosh = `${locationURL?.protocol}${bosh}`;
|
||||
} else if (bosh.startsWith('/')) {
|
||||
// Handle relative URLs, which won't work on mobile.
|
||||
const {
|
||||
protocol,
|
||||
host,
|
||||
contextRoot
|
||||
} = parseURIString(locationURL?.href);
|
||||
|
||||
bosh = `${protocol}//${host}${contextRoot || '/'}${bosh.substr(1)}`;
|
||||
}
|
||||
config.bosh = bosh;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: SET_CONFIG,
|
||||
config
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a specific Jitsi Meet config.js object into {@code localStorage}.
|
||||
*
|
||||
* @param {string} baseURL - The base URL from which the config.js was
|
||||
* downloaded.
|
||||
* @param {Object} config - The Jitsi Meet config.js to store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function storeConfig(baseURL: string, config: Object) {
|
||||
return (dispatch: IStore['dispatch']) => {
|
||||
// Try to store the configuration in localStorage. If the deployment
|
||||
// specified 'getroom' as a function, for example, it does not make
|
||||
// sense to and it will not be stored.
|
||||
let b = false;
|
||||
|
||||
try {
|
||||
if (typeof window.config === 'undefined' || window.config !== config) {
|
||||
jitsiLocalStorage.setItem(`${_CONFIG_STORE_PREFIX}/${baseURL}`, JSON.stringify(config));
|
||||
b = true;
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore the error because the caching is optional.
|
||||
}
|
||||
|
||||
// If base/config knows a domain, then the app knows it.
|
||||
if (b) {
|
||||
try {
|
||||
dispatch(addKnownDomains(parseURIString(baseURL)?.host));
|
||||
} catch (e) {
|
||||
// Ignore the error because the fiddling with "known domains" is
|
||||
// a side effect here.
|
||||
}
|
||||
}
|
||||
|
||||
return b;
|
||||
};
|
||||
}
|
||||
665
react/features/base/config/configType.ts
Normal file
665
react/features/base/config/configType.ts
Normal file
@@ -0,0 +1,665 @@
|
||||
import { ToolbarButton } from '../../toolbox/types';
|
||||
import { ILoggingConfig } from '../logging/types';
|
||||
import { DesktopSharingSourceType } from '../tracks/types';
|
||||
|
||||
type ButtonsWithNotifyClick = 'camera' |
|
||||
'chat' |
|
||||
'closedcaptions' |
|
||||
'desktop' |
|
||||
'download' |
|
||||
'embedmeeting' |
|
||||
'end-meeting' |
|
||||
'etherpad' |
|
||||
'feedback' |
|
||||
'filmstrip' |
|
||||
'fullscreen' |
|
||||
'hangup' |
|
||||
'hangup-menu' |
|
||||
'help' |
|
||||
'invite' |
|
||||
'livestreaming' |
|
||||
'microphone' |
|
||||
'mute-everyone' |
|
||||
'mute-video-everyone' |
|
||||
'participants-pane' |
|
||||
'profile' |
|
||||
'raisehand' |
|
||||
'recording' |
|
||||
'security' |
|
||||
'select-background' |
|
||||
'settings' |
|
||||
'shareaudio' |
|
||||
'sharedvideo' |
|
||||
'shortcuts' |
|
||||
'stats' |
|
||||
'tileview' |
|
||||
'toggle-camera' |
|
||||
'videoquality' |
|
||||
'add-passcode' |
|
||||
'__end';
|
||||
|
||||
type ParticipantMenuButtonsWithNotifyClick = 'allow-video' |
|
||||
'ask-unmute' |
|
||||
'conn-status' |
|
||||
'flip-local-video' |
|
||||
'grant-moderator' |
|
||||
'hide-self-view' |
|
||||
'kick' |
|
||||
'mute' |
|
||||
'mute-others' |
|
||||
'mute-others-video' |
|
||||
'mute-video' |
|
||||
'pinToStage' |
|
||||
'privateMessage' |
|
||||
'remote-control' |
|
||||
'send-participant-to-room' |
|
||||
'verify';
|
||||
|
||||
type NotifyClickButtonKey = string |
|
||||
ButtonsWithNotifyClick |
|
||||
ParticipantMenuButtonsWithNotifyClick;
|
||||
|
||||
export type NotifyClickButton = NotifyClickButtonKey |
|
||||
{
|
||||
key: NotifyClickButtonKey;
|
||||
preventExecution: boolean;
|
||||
};
|
||||
|
||||
export type Sounds = 'ASKED_TO_UNMUTE_SOUND' |
|
||||
'E2EE_OFF_SOUND' |
|
||||
'E2EE_ON_SOUND' |
|
||||
'INCOMING_MSG_SOUND' |
|
||||
'KNOCKING_PARTICIPANT_SOUND' |
|
||||
'LIVE_STREAMING_OFF_SOUND' |
|
||||
'LIVE_STREAMING_ON_SOUND' |
|
||||
'NO_AUDIO_SIGNAL_SOUND' |
|
||||
'NOISY_AUDIO_INPUT_SOUND' |
|
||||
'OUTGOING_CALL_EXPIRED_SOUND' |
|
||||
'OUTGOING_CALL_REJECTED_SOUND' |
|
||||
'OUTGOING_CALL_RINGING_SOUND' |
|
||||
'OUTGOING_CALL_START_SOUND' |
|
||||
'PARTICIPANT_JOINED_SOUND' |
|
||||
'PARTICIPANT_LEFT_SOUND' |
|
||||
'RAISE_HAND_SOUND' |
|
||||
'REACTION_SOUND' |
|
||||
'RECORDING_OFF_SOUND' |
|
||||
'RECORDING_ON_SOUND' |
|
||||
'TALK_WHILE_MUTED_SOUND';
|
||||
|
||||
export interface IDeeplinkingPlatformConfig {
|
||||
appName: string;
|
||||
appScheme: string;
|
||||
}
|
||||
|
||||
export interface IDeeplinkingMobileConfig extends IDeeplinkingPlatformConfig {
|
||||
appPackage?: string;
|
||||
downloadLink: string;
|
||||
fDroidUrl?: string;
|
||||
}
|
||||
|
||||
export interface IDesktopDownloadConfig {
|
||||
linux?: string;
|
||||
macos?: string;
|
||||
windows?: string;
|
||||
}
|
||||
|
||||
export interface IDeeplinkingDesktopConfig extends IDeeplinkingPlatformConfig {
|
||||
download?: IDesktopDownloadConfig;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export interface IDeeplinkingConfig {
|
||||
android?: IDeeplinkingMobileConfig;
|
||||
desktop?: IDeeplinkingDesktopConfig;
|
||||
disabled?: boolean;
|
||||
hideLogo?: boolean;
|
||||
ios?: IDeeplinkingMobileConfig;
|
||||
}
|
||||
|
||||
export type PartialRecord<K extends keyof any, T> = {
|
||||
[P in K]?: T;
|
||||
};
|
||||
|
||||
export interface INoiseSuppressionConfig {
|
||||
krisp?: {
|
||||
bufferOverflowMS?: number;
|
||||
bvc?: {
|
||||
allowedDevices?: string;
|
||||
allowedDevicesExt?: string;
|
||||
};
|
||||
debugLogs: boolean;
|
||||
enableSessionStats?: boolean;
|
||||
enabled: boolean;
|
||||
inboundModels?: PartialRecord<string, string>;
|
||||
logProcessStats?: boolean;
|
||||
models?: PartialRecord<string, string>;
|
||||
preloadInboundModels?: PartialRecord<string, string>;
|
||||
preloadModels?: PartialRecord<string, string>;
|
||||
useBVC?: boolean;
|
||||
useSharedArrayBuffer?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IWhiteboardConfig {
|
||||
collabServerBaseUrl?: string;
|
||||
enabled?: boolean;
|
||||
limitUrl?: string;
|
||||
userLimit?: number;
|
||||
}
|
||||
|
||||
export interface IWatchRTCConfiguration {
|
||||
allowBrowserLogCollection?: boolean;
|
||||
collectionInterval?: number;
|
||||
console?: {
|
||||
level: string;
|
||||
override: boolean;
|
||||
};
|
||||
debug?: boolean;
|
||||
keys?: any;
|
||||
logGetStats?: boolean;
|
||||
proxyUrl?: string;
|
||||
rtcApiKey: string;
|
||||
rtcPeerId?: string;
|
||||
rtcRoomId?: string;
|
||||
rtcTags?: string[];
|
||||
rtcToken?: string;
|
||||
wsUrl?: string;
|
||||
}
|
||||
|
||||
export interface IConfig {
|
||||
_desktopSharingSourceDevice?: string;
|
||||
_immediateReloadThreshold?: string;
|
||||
_screenshotHistoryRegionUrl?: number;
|
||||
analytics?: {
|
||||
amplitudeAPPKey?: string;
|
||||
blackListedEvents?: string[];
|
||||
disabled?: boolean;
|
||||
matomoEndpoint?: string;
|
||||
matomoSiteID?: string;
|
||||
obfuscateRoomName?: boolean;
|
||||
rtcstatsEnabled?: boolean;
|
||||
rtcstatsEndpoint?: string;
|
||||
rtcstatsLogFlushSizeBytes?: number;
|
||||
rtcstatsPollInterval?: number;
|
||||
rtcstatsSendSdp?: boolean;
|
||||
rtcstatsStoreLogs?: boolean;
|
||||
scriptURLs?: Array<string>;
|
||||
watchRTCEnabled?: boolean;
|
||||
whiteListedEvents?: string[];
|
||||
};
|
||||
apiLogLevels?: Array<'warn' | 'log' | 'error' | 'info' | 'debug'>;
|
||||
appId?: string;
|
||||
audioLevelsInterval?: number;
|
||||
audioQuality?: {
|
||||
opusMaxAverageBitrate?: number | null;
|
||||
stereo?: boolean;
|
||||
};
|
||||
autoCaptionOnRecord?: boolean;
|
||||
autoKnockLobby?: boolean;
|
||||
backgroundAlpha?: number;
|
||||
bosh?: string;
|
||||
brandingDataUrl?: string;
|
||||
brandingRoomAlias?: string;
|
||||
breakoutRooms?: {
|
||||
hideAddRoomButton?: boolean;
|
||||
hideAutoAssignButton?: boolean;
|
||||
hideJoinRoomButton?: boolean;
|
||||
};
|
||||
bridgeChannel?: {
|
||||
ignoreDomain?: string;
|
||||
preferSctp?: boolean;
|
||||
};
|
||||
buttonsWithNotifyClick?: Array<ButtonsWithNotifyClick | {
|
||||
key: ButtonsWithNotifyClick;
|
||||
preventExecution: boolean;
|
||||
}>;
|
||||
callDisplayName?: string;
|
||||
callFlowsEnabled?: boolean;
|
||||
callHandle?: string;
|
||||
callUUID?: string;
|
||||
cameraFacingMode?: string;
|
||||
channelLastN?: number;
|
||||
chromeExtensionBanner?: {
|
||||
chromeExtensionsInfo?: Array<{ id: string; path: string; }>;
|
||||
edgeUrl?: string;
|
||||
url?: string;
|
||||
};
|
||||
conferenceInfo?: {
|
||||
alwaysVisible?: Array<string>;
|
||||
autoHide?: Array<string>;
|
||||
};
|
||||
conferenceRequestUrl?: string;
|
||||
connectionIndicators?: {
|
||||
autoHide?: boolean;
|
||||
autoHideTimeout?: number;
|
||||
disableDetails?: boolean;
|
||||
disabled?: boolean;
|
||||
inactiveDisabled?: boolean;
|
||||
};
|
||||
constraints?: {
|
||||
video?: {
|
||||
height?: {
|
||||
ideal?: number;
|
||||
max?: number;
|
||||
min?: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
corsAvatarURLs?: Array<string>;
|
||||
customParticipantMenuButtons?: Array<{ icon: string; id: string; text: string; }>;
|
||||
customToolbarButtons?: Array<{ backgroundColor?: string; icon: string; id: string; text: string; }>;
|
||||
deeplinking?: IDeeplinkingConfig;
|
||||
defaultLanguage?: string;
|
||||
defaultLocalDisplayName?: string;
|
||||
defaultLogoUrl?: string;
|
||||
defaultRemoteDisplayName?: string;
|
||||
deploymentInfo?: {
|
||||
envType?: string;
|
||||
environment?: string;
|
||||
product?: string;
|
||||
region?: string;
|
||||
shard?: string;
|
||||
userRegion?: string;
|
||||
};
|
||||
deploymentUrls?: {
|
||||
downloadAppsUrl?: string;
|
||||
userDocumentationURL?: string;
|
||||
};
|
||||
desktopSharingFrameRate?: {
|
||||
max?: number;
|
||||
min?: number;
|
||||
};
|
||||
desktopSharingSources?: Array<DesktopSharingSourceType>;
|
||||
dialInConfCodeUrl?: string;
|
||||
dialInNumbersUrl?: string;
|
||||
dialOutAuthUrl?: string;
|
||||
dialOutRegionUrl?: string;
|
||||
disable1On1Mode?: boolean | null;
|
||||
disableAEC?: boolean;
|
||||
disableAGC?: boolean;
|
||||
disableAP?: boolean;
|
||||
disableAddingBackgroundImages?: boolean;
|
||||
disableAudioLevels?: boolean;
|
||||
disableBeforeUnloadHandlers?: boolean;
|
||||
disableCameraTintForeground?: boolean;
|
||||
disableChatSmileys?: boolean;
|
||||
disableDeepLinking?: boolean;
|
||||
disableFilmstripAutohiding?: boolean;
|
||||
disableFocus?: boolean;
|
||||
disableIframeAPI?: boolean;
|
||||
disableIncomingMessageSound?: boolean;
|
||||
disableInitialGUM?: boolean;
|
||||
disableInviteFunctions?: boolean;
|
||||
disableJoinLeaveSounds?: boolean;
|
||||
disableLocalVideoFlip?: boolean;
|
||||
disableModeratorIndicator?: boolean;
|
||||
disableNS?: boolean;
|
||||
disablePolls?: boolean;
|
||||
disableProfile?: boolean;
|
||||
disableReactions?: boolean;
|
||||
disableReactionsInChat?: boolean;
|
||||
disableReactionsModeration?: boolean;
|
||||
disableRecordAudioNotification?: boolean;
|
||||
disableRemoteControl?: boolean;
|
||||
disableRemoteMute?: boolean;
|
||||
disableRemoveRaisedHandOnFocus?: boolean;
|
||||
disableResponsiveTiles?: boolean;
|
||||
disableRtx?: boolean;
|
||||
disableSelfDemote?: boolean;
|
||||
disableSelfView?: boolean;
|
||||
disableSelfViewSettings?: boolean;
|
||||
disableShortcuts?: boolean;
|
||||
disableShowMoreStats?: boolean;
|
||||
disableSimulcast?: boolean;
|
||||
disableSpeakerStatsSearch?: boolean;
|
||||
disableThirdPartyRequests?: boolean;
|
||||
disableTileEnlargement?: boolean;
|
||||
disableTileView?: boolean;
|
||||
disableVirtualBackground?: boolean;
|
||||
disabledNotifications?: Array<string>;
|
||||
disabledSounds?: Array<Sounds>;
|
||||
displayJids?: boolean;
|
||||
doNotFlipLocalVideo?: boolean;
|
||||
doNotStoreRoom?: boolean;
|
||||
dropbox?: {
|
||||
appKey: string;
|
||||
redirectURI?: string;
|
||||
};
|
||||
dynamicBrandingUrl?: string;
|
||||
e2ee?: {
|
||||
disabled?: boolean;
|
||||
externallyManagedKey?: boolean;
|
||||
labels?: {
|
||||
description?: string;
|
||||
label?: string;
|
||||
tooltip?: string;
|
||||
warning?: string;
|
||||
};
|
||||
};
|
||||
e2eeLabels?: {
|
||||
description?: string;
|
||||
label?: string;
|
||||
tooltip?: string;
|
||||
warning?: string;
|
||||
};
|
||||
e2eping?: {
|
||||
enabled?: boolean;
|
||||
maxConferenceSize?: number;
|
||||
maxMessagesPerSecond?: number;
|
||||
numRequests?: number;
|
||||
};
|
||||
enableCalendarIntegration?: boolean;
|
||||
enableClosePage?: boolean;
|
||||
enableDisplayNameInStats?: boolean;
|
||||
enableEmailInStats?: boolean;
|
||||
enableEncodedTransformSupport?: boolean;
|
||||
enableForcedReload?: boolean;
|
||||
enableInsecureRoomNameWarning?: boolean;
|
||||
enableLobbyChat?: boolean;
|
||||
enableNoAudioDetection?: boolean;
|
||||
enableNoisyMicDetection?: boolean;
|
||||
enableOpusRed?: boolean;
|
||||
enableRemb?: boolean;
|
||||
enableSaveLogs?: boolean;
|
||||
enableTalkWhileMuted?: boolean;
|
||||
enableTcc?: boolean;
|
||||
enableWebHIDFeature?: boolean;
|
||||
enableWelcomePage?: boolean;
|
||||
etherpad_base?: string;
|
||||
faceLandmarks?: {
|
||||
captureInterval?: number;
|
||||
enableDisplayFaceExpressions?: boolean;
|
||||
enableFaceCentering?: boolean;
|
||||
enableFaceExpressionsDetection?: boolean;
|
||||
enableRTCStats?: boolean;
|
||||
faceCenteringThreshold?: number;
|
||||
};
|
||||
feedbackPercentage?: number;
|
||||
fileRecordingsServiceEnabled?: boolean;
|
||||
fileRecordingsServiceSharingEnabled?: boolean;
|
||||
fileSharing?: {
|
||||
apiUrl?: string;
|
||||
enabled?: boolean;
|
||||
maxFileSize?: number;
|
||||
};
|
||||
filmstrip?: {
|
||||
alwaysShowResizeBar?: boolean;
|
||||
disableResizable?: boolean;
|
||||
disableStageFilmstrip?: boolean;
|
||||
disableTopPanel?: boolean;
|
||||
disabled?: boolean;
|
||||
initialWidth?: number;
|
||||
minParticipantCountForTopPanel?: number;
|
||||
};
|
||||
flags?: {
|
||||
ssrcRewritingEnabled: boolean;
|
||||
};
|
||||
focusUserJid?: string;
|
||||
forceTurnRelay?: boolean;
|
||||
gatherStats?: boolean;
|
||||
giphy?: {
|
||||
displayMode?: 'all' | 'tile' | 'chat';
|
||||
enabled?: boolean;
|
||||
rating?: 'g' | 'pg' | 'pg-13' | 'r';
|
||||
sdkKey?: string;
|
||||
tileTime?: number;
|
||||
};
|
||||
googleApiApplicationClientID?: string;
|
||||
gravatar?: {
|
||||
baseUrl?: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
gravatarBaseURL?: string;
|
||||
guestDialOutStatusUrl?: string;
|
||||
guestDialOutUrl?: string;
|
||||
helpCentreURL?: string;
|
||||
hiddenDomain?: string;
|
||||
hiddenPremeetingButtons?: Array<'microphone' | 'camera' | 'select-background' | 'invite' | 'settings'>;
|
||||
hideAddRoomButton?: boolean;
|
||||
hideConferenceSubject?: boolean;
|
||||
hideConferenceTimer?: boolean;
|
||||
hideDisplayName?: boolean;
|
||||
hideDominantSpeakerBadge?: boolean;
|
||||
hideEmailInSettings?: boolean;
|
||||
hideLobbyButton?: boolean;
|
||||
hideLoginButton?: boolean;
|
||||
hideParticipantsStats?: boolean;
|
||||
hideRecordingLabel?: boolean;
|
||||
hosts?: {
|
||||
anonymousdomain?: string;
|
||||
authdomain?: string;
|
||||
domain: string;
|
||||
focus?: string;
|
||||
muc: string;
|
||||
visitorFocus?: string;
|
||||
};
|
||||
iAmRecorder?: boolean;
|
||||
iAmSipGateway?: boolean;
|
||||
iAmSpot?: boolean;
|
||||
ignoreStartMuted?: boolean;
|
||||
inviteAppName?: string | null;
|
||||
inviteServiceCallFlowsUrl?: string;
|
||||
inviteServiceUrl?: string;
|
||||
jaasActuatorUrl?: string;
|
||||
jaasConferenceCreatorUrl?: string;
|
||||
jaasFeedbackMetadataURL?: string;
|
||||
jaasTokenUrl?: string;
|
||||
legalUrls?: {
|
||||
helpCentre: string;
|
||||
privacy: string;
|
||||
security: string;
|
||||
terms: string;
|
||||
};
|
||||
liveStreaming?: {
|
||||
dataPrivacyLink?: string;
|
||||
enabled?: boolean;
|
||||
helpLink?: string;
|
||||
termsLink?: string;
|
||||
validatorRegExpString?: string;
|
||||
};
|
||||
liveStreamingEnabled?: boolean;
|
||||
lobby?: {
|
||||
autoKnock?: boolean;
|
||||
enableChat?: boolean;
|
||||
};
|
||||
localRecording?: {
|
||||
disable?: boolean;
|
||||
disableSelfRecording?: boolean;
|
||||
notifyAllParticipants?: boolean;
|
||||
};
|
||||
localSubject?: string;
|
||||
locationURL?: URL;
|
||||
logging?: ILoggingConfig;
|
||||
mainToolbarButtons?: Array<Array<string>>;
|
||||
maxFullResolutionParticipants?: number;
|
||||
microsoftApiApplicationClientID?: string;
|
||||
moderatedRoomServiceUrl?: string;
|
||||
mouseMoveCallbackInterval?: number;
|
||||
noiseSuppression?: INoiseSuppressionConfig;
|
||||
noticeMessage?: string;
|
||||
notificationTimeouts?: {
|
||||
extraLong?: number;
|
||||
long?: number;
|
||||
medium?: number;
|
||||
short?: number;
|
||||
sticky?: number;
|
||||
};
|
||||
notifications?: Array<string>;
|
||||
notifyOnConferenceDestruction?: boolean;
|
||||
openSharedDocumentOnJoin?: boolean;
|
||||
opusMaxAverageBitrate?: number;
|
||||
p2p?: {
|
||||
backToP2PDelay?: number;
|
||||
codecPreferenceOrder?: Array<string>;
|
||||
enabled?: boolean;
|
||||
iceTransportPolicy?: string;
|
||||
mobileCodecPreferenceOrder?: Array<string>;
|
||||
mobileScreenshareCodec?: string;
|
||||
stunServers?: Array<{ urls: string; }>;
|
||||
};
|
||||
participantMenuButtonsWithNotifyClick?: Array<string | ParticipantMenuButtonsWithNotifyClick | {
|
||||
key: string | ParticipantMenuButtonsWithNotifyClick;
|
||||
preventExecution: boolean;
|
||||
}>;
|
||||
participantsPane?: {
|
||||
enabled?: boolean;
|
||||
hideModeratorSettingsTab?: boolean;
|
||||
hideMoreActionsButton?: boolean;
|
||||
hideMuteAllButton?: boolean;
|
||||
};
|
||||
pcStatsInterval?: number;
|
||||
peopleSearchQueryTypes?: string[];
|
||||
peopleSearchTokenLocation?: string;
|
||||
peopleSearchUrl?: string;
|
||||
preferBosh?: boolean;
|
||||
preferVisitor?: boolean;
|
||||
preferredTranscribeLanguage?: string;
|
||||
prejoinConfig?: {
|
||||
enabled?: boolean;
|
||||
hideDisplayName?: boolean;
|
||||
hideExtraJoinButtons?: Array<string>;
|
||||
preCallTestEnabled?: boolean;
|
||||
preCallTestICEUrl?: string;
|
||||
};
|
||||
raisedHands?: {
|
||||
disableLowerHandByModerator?: boolean;
|
||||
disableLowerHandNotification?: boolean;
|
||||
disableNextSpeakerNotification?: boolean;
|
||||
disableRemoveRaisedHandOnFocus?: boolean;
|
||||
};
|
||||
readOnlyName?: boolean;
|
||||
recordingLimit?: {
|
||||
appName?: string;
|
||||
appURL?: string;
|
||||
limit?: number;
|
||||
};
|
||||
recordingService?: {
|
||||
enabled?: boolean;
|
||||
hideStorageWarning?: boolean;
|
||||
sharingEnabled?: boolean;
|
||||
};
|
||||
recordingSharingUrl?: string;
|
||||
recordings?: {
|
||||
consentLearnMoreLink?: string;
|
||||
recordAudioAndVideo?: boolean;
|
||||
requireConsent?: boolean;
|
||||
showPrejoinWarning?: boolean;
|
||||
showRecordingLink?: boolean;
|
||||
skipConsentInMeeting?: boolean;
|
||||
suggestRecording?: boolean;
|
||||
};
|
||||
remoteVideoMenu?: {
|
||||
disableDemote?: boolean;
|
||||
disableGrantModerator?: boolean;
|
||||
disableKick?: boolean;
|
||||
disablePrivateChat?: 'all' | 'allow-moderator-chat' | 'disable-visitor-chat';
|
||||
disabled?: boolean;
|
||||
};
|
||||
replaceParticipant?: string;
|
||||
requireDisplayName?: boolean;
|
||||
resolution?: number;
|
||||
roomPasswordNumberOfDigits?: number;
|
||||
salesforceUrl?: string;
|
||||
screenshotCapture?: {
|
||||
enabled?: boolean;
|
||||
mode?: 'always' | 'recording';
|
||||
};
|
||||
securityUi?: {
|
||||
disableLobbyPassword?: boolean;
|
||||
hideLobbyButton?: boolean;
|
||||
};
|
||||
serviceUrl?: string;
|
||||
sharedVideoAllowedURLDomains?: Array<string>;
|
||||
sipInviteUrl?: string;
|
||||
speakerStats?: {
|
||||
disableSearch?: boolean;
|
||||
disabled?: boolean;
|
||||
order?: Array<'role' | 'name' | 'hasLeft'>;
|
||||
};
|
||||
speakerStatsOrder?: Array<'role' | 'name' | 'hasLeft'>;
|
||||
startAudioMuted?: number;
|
||||
startAudioOnly?: boolean;
|
||||
startLastN?: number;
|
||||
startScreenSharing?: boolean;
|
||||
startSilent?: boolean;
|
||||
startVideoMuted?: number;
|
||||
startWithAudioMuted?: boolean;
|
||||
startWithVideoMuted?: boolean;
|
||||
stereo?: boolean;
|
||||
subject?: string;
|
||||
testing?: {
|
||||
assumeBandwidth?: boolean;
|
||||
debugAudioLevels?: boolean;
|
||||
dumpTranscript?: boolean;
|
||||
failICE?: boolean;
|
||||
noAutoPlayVideo?: boolean;
|
||||
p2pTestMode?: boolean;
|
||||
showSpotConsentDialog?: boolean;
|
||||
skipInterimTranscriptions?: boolean;
|
||||
testMode?: boolean;
|
||||
};
|
||||
tileView?: {
|
||||
disabled?: boolean;
|
||||
numberOfVisibleTiles?: number;
|
||||
};
|
||||
tokenAuthUrl?: string;
|
||||
tokenAuthUrlAutoRedirect?: string;
|
||||
tokenGetUserInfoOutOfContext?: boolean;
|
||||
tokenLogoutUrl?: string;
|
||||
tokenRespectTenant?: boolean;
|
||||
toolbarButtons?: Array<ToolbarButton>;
|
||||
toolbarConfig?: {
|
||||
alwaysVisible?: boolean;
|
||||
autoHideWhileChatIsOpen?: boolean;
|
||||
initialTimeout?: number;
|
||||
timeout?: number;
|
||||
};
|
||||
transcribeWithAppLanguage?: boolean;
|
||||
transcribingEnabled?: boolean;
|
||||
transcription?: {
|
||||
autoCaptionOnTranscribe?: boolean;
|
||||
autoTranscribeOnRecord?: boolean;
|
||||
disableClosedCaptions?: boolean;
|
||||
enabled?: boolean;
|
||||
inviteJigasiOnBackendTranscribing?: boolean;
|
||||
preferredLanguage?: string;
|
||||
translationLanguages?: Array<string>;
|
||||
translationLanguagesHead?: Array<string>;
|
||||
useAppLanguage?: boolean;
|
||||
};
|
||||
useHostPageLocalStorage?: boolean;
|
||||
useTurnUdp?: boolean;
|
||||
videoQuality?: {
|
||||
codecPreferenceOrder?: Array<string>;
|
||||
maxBitratesVideo?: {
|
||||
[key: string]: {
|
||||
high?: number;
|
||||
low?: number;
|
||||
standard?: number;
|
||||
};
|
||||
};
|
||||
minHeightForQualityLvl?: {
|
||||
[key: number]: string;
|
||||
};
|
||||
mobileCodecPreferenceOrder?: Array<string>;
|
||||
persist?: boolean;
|
||||
};
|
||||
visitors?: {
|
||||
enableMediaOnPromote?: {
|
||||
audio?: boolean;
|
||||
video?: boolean;
|
||||
};
|
||||
queueService: string;
|
||||
};
|
||||
watchRTCConfigParams?: IWatchRTCConfiguration;
|
||||
webhookProxyUrl?: string;
|
||||
webrtcIceTcpDisable?: boolean;
|
||||
webrtcIceUdpDisable?: boolean;
|
||||
websocket?: string;
|
||||
websocketKeepAliveUrl?: string;
|
||||
welcomePage?: {
|
||||
customUrl?: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
whiteboard?: IWhiteboardConfig;
|
||||
}
|
||||
254
react/features/base/config/configWhitelist.ts
Normal file
254
react/features/base/config/configWhitelist.ts
Normal file
@@ -0,0 +1,254 @@
|
||||
import { isEmbedded } from '../util/embedUtils';
|
||||
|
||||
import extraConfigWhitelist from './extraConfigWhitelist';
|
||||
import isEmbeddedConfigWhitelist from './isEmbeddedConfigWhitelist';
|
||||
|
||||
/**
|
||||
* The config keys to whitelist, the keys that can be overridden.
|
||||
* Whitelisting a key allows all properties under that key to be overridden.
|
||||
* For example whitelisting 'p2p' allows 'p2p.enabled' to be overridden, and
|
||||
* overriding 'p2p.enabled' does not modify any other keys under 'p2p'.
|
||||
* The whitelist is used only for config.js.
|
||||
*
|
||||
* @type Array
|
||||
*/
|
||||
export default [
|
||||
'_desktopSharingSourceDevice',
|
||||
'_peerConnStatusOutOfLastNTimeout',
|
||||
'_peerConnStatusRtcMuteTimeout',
|
||||
'analytics.disabled',
|
||||
'analytics.rtcstatsEnabled',
|
||||
'analytics.watchRTCEnabled',
|
||||
'audioLevelsInterval',
|
||||
'audioQuality',
|
||||
'autoKnockLobby',
|
||||
'apiLogLevels',
|
||||
'avgRtpStatsN',
|
||||
'backgroundAlpha',
|
||||
'brandingRoomAlias',
|
||||
'breakoutRooms',
|
||||
'bridgeChannel',
|
||||
'buttonsWithNotifyClick',
|
||||
|
||||
/**
|
||||
* The display name of the CallKit call representing the conference/meeting
|
||||
* associated with this config.js including while the call is ongoing in the
|
||||
* UI presented by CallKit and in the system-wide call history. The property
|
||||
* is meant for use cases in which the room name is not desirable as a
|
||||
* display name for CallKit purposes and the desired display name is not
|
||||
* provided in the form of a JWT callee. As the value is associated with a
|
||||
* conference/meeting, the value makes sense not as a deployment-wide
|
||||
* configuration, only as a runtime configuration override/overwrite
|
||||
* provided by, for example, Jitsi Meet SDK for iOS.
|
||||
*
|
||||
* @type string
|
||||
*/
|
||||
'callDisplayName',
|
||||
'callFlowsEnabled',
|
||||
|
||||
/**
|
||||
* The handle
|
||||
* ({@link https://developer.apple.com/documentation/callkit/cxhandle}) of
|
||||
* the CallKit call representing the conference/meeting associated with this
|
||||
* config.js. The property is meant for use cases in which the room URL is
|
||||
* not desirable as the handle for CallKit purposes. As the value is
|
||||
* associated with a conference/meeting, the value makes sense not as a
|
||||
* deployment-wide configuration, only as a runtime configuration
|
||||
* override/overwrite provided by, for example, Jitsi Meet SDK for iOS.
|
||||
*
|
||||
* @type string
|
||||
*/
|
||||
'callHandle',
|
||||
|
||||
/**
|
||||
* The UUID of the CallKit call representing the conference/meeting
|
||||
* associated with this config.js. The property is meant for use cases in
|
||||
* which Jitsi Meet is to work with a CallKit call created outside of Jitsi
|
||||
* Meet and to be adopted by Jitsi Meet such as, for example, an incoming
|
||||
* and/or outgoing CallKit call created by Jitsi Meet SDK for iOS
|
||||
* clients/consumers prior to giving control to Jitsi Meet. As the value is
|
||||
* associated with a conference/meeting, the value makes sense not as a
|
||||
* deployment-wide configuration, only as a runtime configuration
|
||||
* override/overwrite provided by, for example, Jitsi Meet SDK for iOS.
|
||||
*
|
||||
* @type string
|
||||
*/
|
||||
'callUUID',
|
||||
|
||||
'cameraFacingMode',
|
||||
'conferenceInfo',
|
||||
'channelLastN',
|
||||
'connectionIndicators',
|
||||
'constraints',
|
||||
'deeplinking.disabled',
|
||||
'deeplinking.desktop.enabled',
|
||||
'defaultLocalDisplayName',
|
||||
'defaultRemoteDisplayName',
|
||||
'desktopSharingFrameRate',
|
||||
'desktopSharingSources',
|
||||
'disable1On1Mode',
|
||||
'disableAEC',
|
||||
'disableAGC',
|
||||
'disableAP',
|
||||
'disableAddingBackgroundImages',
|
||||
'disableAudioLevels',
|
||||
'disableBeforeUnloadHandlers',
|
||||
'disableCameraTintForeground',
|
||||
'disableChatSmileys',
|
||||
'disableDeepLinking',
|
||||
'disabledNotifications',
|
||||
'disabledSounds',
|
||||
'disableFilmstripAutohiding',
|
||||
'disableInitialGUM',
|
||||
'disableInviteFunctions',
|
||||
'disableIncomingMessageSound',
|
||||
'disableJoinLeaveSounds',
|
||||
'disableLocalVideoFlip',
|
||||
'disableModeratorIndicator',
|
||||
'disableNS',
|
||||
'disablePolls',
|
||||
'disableProfile',
|
||||
'disableReactions',
|
||||
'disableReactionsInChat',
|
||||
'disableReactionsModeration',
|
||||
'disableRecordAudioNotification',
|
||||
'disableRemoteControl',
|
||||
'disableRemoteMute',
|
||||
'disableResponsiveTiles',
|
||||
'disableRtx',
|
||||
'disableSelfDemote',
|
||||
'disableSelfView',
|
||||
'disableSelfViewSettings',
|
||||
'disableShortcuts',
|
||||
'disableShowMoreStats',
|
||||
'disableRemoveRaisedHandOnFocus',
|
||||
'disableSpeakerStatsSearch',
|
||||
'speakerStatsOrder',
|
||||
'disableSimulcast',
|
||||
'disableThirdPartyRequests',
|
||||
'disableTileView',
|
||||
'disableTileEnlargement',
|
||||
'disableVirtualBackground',
|
||||
'displayJids',
|
||||
'doNotStoreRoom',
|
||||
'doNotFlipLocalVideo',
|
||||
'dropbox.appKey',
|
||||
'e2eeLabels',
|
||||
'e2ee',
|
||||
'e2eping',
|
||||
'enableCalendarIntegration',
|
||||
'enableDisplayNameInStats',
|
||||
'enableEmailInStats',
|
||||
'enableEncodedTransformSupport',
|
||||
'enableInsecureRoomNameWarning',
|
||||
'enableLobbyChat',
|
||||
'enableOpusRed',
|
||||
'enableRemb',
|
||||
'enableSaveLogs',
|
||||
'enableTalkWhileMuted',
|
||||
'enableNoAudioDetection',
|
||||
'enableNoisyMicDetection',
|
||||
'enableTcc',
|
||||
'faceLandmarks',
|
||||
'feedbackPercentage',
|
||||
'fileSharing.enabled',
|
||||
'filmstrip',
|
||||
'flags',
|
||||
'forceTurnRelay',
|
||||
'gatherStats',
|
||||
'giphy',
|
||||
'googleApiApplicationClientID',
|
||||
'gravatar.disabled',
|
||||
'hiddenPremeetingButtons',
|
||||
'hideConferenceSubject',
|
||||
'hideDisplayName',
|
||||
'hideDominantSpeakerBadge',
|
||||
'hideRecordingLabel',
|
||||
'hideParticipantsStats',
|
||||
'hideConferenceTimer',
|
||||
'hideAddRoomButton',
|
||||
'hideEmailInSettings',
|
||||
'hideLobbyButton',
|
||||
'iAmRecorder',
|
||||
'iAmSipGateway',
|
||||
'iAmSpot',
|
||||
'ignoreStartMuted',
|
||||
'inviteAppName',
|
||||
'liveStreaming.enabled',
|
||||
'liveStreamingEnabled',
|
||||
'lobby',
|
||||
'localRecording',
|
||||
'localSubject',
|
||||
'logging',
|
||||
'mainToolbarButtons',
|
||||
'maxFullResolutionParticipants',
|
||||
'mouseMoveCallbackInterval',
|
||||
'notifications',
|
||||
'notificationTimeouts',
|
||||
'notifyOnConferenceDestruction',
|
||||
'openSharedDocumentOnJoin',
|
||||
'opusMaxAverageBitrate',
|
||||
'p2p.backToP2PDelay',
|
||||
'p2p.codecPreferenceOrder',
|
||||
'p2p.enabled',
|
||||
'p2p.iceTransportPolicy',
|
||||
'p2p.mobileCodecPreferenceOrder',
|
||||
'p2p.mobileScreenshareCodec',
|
||||
'participantMenuButtonsWithNotifyClick',
|
||||
'participantsPane',
|
||||
'pcStatsInterval',
|
||||
'preferBosh',
|
||||
'preferVisitor',
|
||||
'prejoinConfig.enabled',
|
||||
'prejoinConfig.hideDisplayName',
|
||||
'prejoinConfig.hideExtraJoinButtons',
|
||||
'raisedHands',
|
||||
'recordingService',
|
||||
'requireDisplayName',
|
||||
'remoteVideoMenu',
|
||||
'roomPasswordNumberOfDigits',
|
||||
'readOnlyName',
|
||||
'recordings.recordAudioAndVideo',
|
||||
'recordings.showPrejoinWarning',
|
||||
'recordings.showRecordingLink',
|
||||
'recordings.suggestRecording',
|
||||
'replaceParticipant',
|
||||
'resolution',
|
||||
'screenshotCapture',
|
||||
'securityUi',
|
||||
'speakerStats',
|
||||
'startAudioMuted',
|
||||
'startAudioOnly',
|
||||
'startLastN',
|
||||
'startScreenSharing',
|
||||
'startSilent',
|
||||
'startVideoMuted',
|
||||
'startWithAudioMuted',
|
||||
'startWithVideoMuted',
|
||||
'stereo',
|
||||
'subject',
|
||||
'testing',
|
||||
'toolbarButtons',
|
||||
'toolbarConfig',
|
||||
'tileView',
|
||||
'transcribingEnabled',
|
||||
'transcription',
|
||||
'useHostPageLocalStorage',
|
||||
'useTurnUdp',
|
||||
'videoQuality',
|
||||
'visitors.enableMediaOnPromote',
|
||||
'watchRTCConfigParams.allowBrowserLogCollection',
|
||||
'watchRTCConfigParams.collectionInterval',
|
||||
'watchRTCConfigParams.console',
|
||||
'watchRTCConfigParams.debug',
|
||||
'watchRTCConfigParams.keys',
|
||||
'watchRTCConfigParams.logGetStats',
|
||||
'watchRTCConfigParams.rtcApiKey',
|
||||
'watchRTCConfigParams.rtcPeerId',
|
||||
'watchRTCConfigParams.rtcRoomId',
|
||||
'watchRTCConfigParams.rtcTags',
|
||||
'watchRTCConfigParams.rtcToken',
|
||||
'webrtcIceTcpDisable',
|
||||
'webrtcIceUdpDisable',
|
||||
'whiteboard.enabled'
|
||||
].concat(extraConfigWhitelist).concat(isEmbedded() ? isEmbeddedConfigWhitelist : []);
|
||||
43
react/features/base/config/constants.ts
Normal file
43
react/features/base/config/constants.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* The prefix of the {@code localStorage} key into which {@link storeConfig}
|
||||
* stores and from which {@link restoreConfig} restores.
|
||||
*
|
||||
* @protected
|
||||
* @type string
|
||||
*/
|
||||
export const _CONFIG_STORE_PREFIX = 'config.js';
|
||||
|
||||
/**
|
||||
* The toolbar buttons to show on premeeting screens.
|
||||
*/
|
||||
export const PREMEETING_BUTTONS = [ 'microphone', 'camera', 'select-background', 'invite', 'settings' ];
|
||||
|
||||
/**
|
||||
* The toolbar buttons to show on 3rdParty prejoin screen.
|
||||
*/
|
||||
export const THIRD_PARTY_PREJOIN_BUTTONS = [ 'microphone', 'camera', 'select-background' ];
|
||||
|
||||
/**
|
||||
* The set of feature flags.
|
||||
*
|
||||
* @enum {string}
|
||||
*/
|
||||
|
||||
export const FEATURE_FLAGS = {
|
||||
SSRC_REWRITING: 'ssrcRewritingEnabled'
|
||||
};
|
||||
|
||||
/**
|
||||
* The URL at which the terms (of service/use) are available to the user.
|
||||
*/
|
||||
export const DEFAULT_TERMS_URL = 'https://jitsi.org/meet/terms';
|
||||
|
||||
/**
|
||||
* The URL at which the privacy policy is available to the user.
|
||||
*/
|
||||
export const DEFAULT_PRIVACY_URL = 'https://jitsi.org/meet/privacy';
|
||||
|
||||
/**
|
||||
* The URL at which the help centre is available to the user.
|
||||
*/
|
||||
export const DEFAULT_HELP_CENTRE_URL = 'https://web-cdn.jitsi.net/faq/meet-faq.html';
|
||||
4
react/features/base/config/extraConfigWhitelist.ts
Normal file
4
react/features/base/config/extraConfigWhitelist.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Deploy-specific configuration whitelists.
|
||||
*/
|
||||
export default [];
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Deploy-specific interface_config whitelists.
|
||||
*/
|
||||
export default [];
|
||||
453
react/features/base/config/functions.any.ts
Normal file
453
react/features/base/config/functions.any.ts
Normal file
@@ -0,0 +1,453 @@
|
||||
// @ts-ignore
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { safeJsonParse } from '@jitsi/js-utils/json';
|
||||
import { isEmpty, mergeWith, pick } from 'lodash-es';
|
||||
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { getLocalParticipant } from '../participants/functions';
|
||||
import { isEmbedded } from '../util/embedUtils';
|
||||
import { parseURLParams } from '../util/parseURLParams';
|
||||
|
||||
import { IConfig } from './configType';
|
||||
import CONFIG_WHITELIST from './configWhitelist';
|
||||
import {
|
||||
DEFAULT_HELP_CENTRE_URL,
|
||||
DEFAULT_PRIVACY_URL,
|
||||
DEFAULT_TERMS_URL,
|
||||
FEATURE_FLAGS,
|
||||
_CONFIG_STORE_PREFIX
|
||||
} from './constants';
|
||||
import INTERFACE_CONFIG_WHITELIST from './interfaceConfigWhitelist';
|
||||
import logger from './logger';
|
||||
|
||||
// XXX The function getRoomName is split out of
|
||||
// functions.any.js because it is bundled in both app.bundle and
|
||||
// do_external_connect, webpack 1 does not support tree shaking, and we don't
|
||||
// want all functions to be bundled in do_external_connect.
|
||||
export { default as getRoomName } from './getRoomName';
|
||||
|
||||
/**
|
||||
* Create a "fake" configuration object for the given base URL. This is used in case the config
|
||||
* couldn't be loaded in the welcome page, so at least we have something to try with.
|
||||
*
|
||||
* @param {string} baseURL - URL of the deployment for which we want the fake config.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function createFakeConfig(baseURL: string) {
|
||||
const url = new URL(baseURL);
|
||||
|
||||
return {
|
||||
hosts: {
|
||||
domain: url.hostname,
|
||||
muc: `conference.${url.hostname}`
|
||||
},
|
||||
bosh: `${baseURL}http-bind`,
|
||||
p2p: {
|
||||
enabled: true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector used to get the meeting region.
|
||||
*
|
||||
* @param {Object} state - The global state.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getMeetingRegion(state: IReduxState) {
|
||||
return state['features/base/config']?.deploymentInfo?.region || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector used to get the SSRC-rewriting feature flag.
|
||||
*
|
||||
* @param {Object} state - The global state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function getSsrcRewritingFeatureFlag(state: IReduxState) {
|
||||
return getFeatureFlag(state, FEATURE_FLAGS.SSRC_REWRITING) ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector used to get a feature flag.
|
||||
*
|
||||
* @param {Object} state - The global state.
|
||||
* @param {string} featureFlag - The name of the feature flag.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function getFeatureFlag(state: IReduxState, featureFlag: string) {
|
||||
const featureFlags = state['features/base/config']?.flags || {};
|
||||
|
||||
return featureFlags[featureFlag as keyof typeof featureFlags];
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector used to get the disableRemoveRaisedHandOnFocus.
|
||||
*
|
||||
* @param {Object} state - The global state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function getDisableRemoveRaisedHandOnFocus(state: IReduxState) {
|
||||
return state['features/base/config']?.raisedHands?.disableRemoveRaisedHandOnFocus || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector used to get the disableLowerHandByModerator.
|
||||
*
|
||||
* @param {Object} state - The global state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function getDisableLowerHandByModerator(state: IReduxState) {
|
||||
return state['features/base/config']?.raisedHands?.disableLowerHandByModerator || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector used to get the disableLowerHandNotification.
|
||||
*
|
||||
* @param {Object} state - The global state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function getDisableLowerHandNotification(state: IReduxState) {
|
||||
return state['features/base/config']?.raisedHands?.disableLowerHandNotification || true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector used to get the disableNextSpeakerNotification.
|
||||
*
|
||||
* @param {Object} state - The global state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function getDisableNextSpeakerNotification(state: IReduxState) {
|
||||
return state['features/base/config']?.raisedHands?.disableNextSpeakerNotification || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector used to get the endpoint used for fetching the recording.
|
||||
*
|
||||
* @param {Object} state - The global state.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getRecordingSharingUrl(state: IReduxState) {
|
||||
return state['features/base/config'].recordingSharingUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides JSON properties in {@code config} and
|
||||
* {@code interfaceConfig} Objects with the values from {@code newConfig}.
|
||||
* Overrides only the whitelisted keys.
|
||||
*
|
||||
* @param {Object} config - The config Object in which we'll be overriding
|
||||
* properties.
|
||||
* @param {Object} interfaceConfig - The interfaceConfig Object in which we'll
|
||||
* be overriding properties.
|
||||
* @param {Object} json - Object containing configuration properties.
|
||||
* Destination object is selected based on root property name:
|
||||
* {
|
||||
* config: {
|
||||
* // config.js properties here
|
||||
* },
|
||||
* interfaceConfig: {
|
||||
* // interface_config.js properties here
|
||||
* }
|
||||
* }.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function overrideConfigJSON(config: IConfig, interfaceConfig: any, json: any) {
|
||||
for (const configName of Object.keys(json)) {
|
||||
let configObj;
|
||||
|
||||
if (configName === 'config') {
|
||||
configObj = config;
|
||||
} else if (configName === 'interfaceConfig') {
|
||||
configObj = interfaceConfig;
|
||||
}
|
||||
if (configObj) {
|
||||
const configJSON
|
||||
= getWhitelistedJSON(configName as 'interfaceConfig' | 'config', json[configName]);
|
||||
|
||||
if (!isEmpty(configJSON)) {
|
||||
logger.info(`Extending ${configName} with: ${JSON.stringify(configJSON)}`);
|
||||
|
||||
// eslint-disable-next-line arrow-body-style
|
||||
mergeWith(configObj, configJSON, (oldValue, newValue) => {
|
||||
|
||||
// XXX We don't want to merge the arrays, we want to
|
||||
// overwrite them.
|
||||
return Array.isArray(oldValue) ? newValue : undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* eslint-enable max-params, no-shadow */
|
||||
|
||||
/**
|
||||
* Apply whitelist filtering for configs with whitelists.
|
||||
* Only extracts overridden values for keys we allow to be overridden.
|
||||
*
|
||||
* @param {string} configName - The config name, one of config or interfaceConfig.
|
||||
* @param {Object} configJSON - The object with keys and values to override.
|
||||
* @returns {Object} - The result object only with the keys
|
||||
* that are whitelisted.
|
||||
*/
|
||||
export function getWhitelistedJSON(configName: 'interfaceConfig' | 'config', configJSON: any): Object {
|
||||
// Disable whitelisting in dev mode.
|
||||
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
||||
logger.warn('Whitelisting is disabled in dev mode, accepting any overrides');
|
||||
|
||||
return configJSON;
|
||||
}
|
||||
|
||||
if (configName === 'interfaceConfig') {
|
||||
return pick(configJSON, INTERFACE_CONFIG_WHITELIST);
|
||||
} else if (configName === 'config') {
|
||||
return pick(configJSON, CONFIG_WHITELIST);
|
||||
}
|
||||
|
||||
return configJSON;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for determining if the display name is read only.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isNameReadOnly(state: IReduxState): boolean {
|
||||
return Boolean(state['features/base/config'].disableProfile
|
||||
|| state['features/base/config'].readOnlyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for determining if the participant is the next one in the queue to speak.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isNextToSpeak(state: IReduxState): boolean {
|
||||
const raisedHandsQueue = state['features/base/participants'].raisedHandsQueue || [];
|
||||
const participantId = getLocalParticipant(state)?.id;
|
||||
|
||||
return participantId === raisedHandsQueue[0]?.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for determining if the next to speak participant in the queue has been notified.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function hasBeenNotified(state: IReduxState): boolean {
|
||||
const raisedHandsQueue = state['features/base/participants'].raisedHandsQueue;
|
||||
|
||||
return Boolean(raisedHandsQueue[0]?.hasBeenNotified);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for determining if the display name is visible.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isDisplayNameVisible(state: IReduxState): boolean {
|
||||
return !state['features/base/config'].hideDisplayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores a Jitsi Meet config.js from {@code localStorage} if it was
|
||||
* previously downloaded from a specific {@code baseURL} and stored with
|
||||
* {@link storeConfig}.
|
||||
*
|
||||
* @param {string} baseURL - The base URL from which the config.js was
|
||||
* previously downloaded and stored with {@code storeConfig}.
|
||||
* @returns {?Object} The Jitsi Meet config.js which was previously downloaded
|
||||
* from {@code baseURL} and stored with {@code storeConfig} if it was restored;
|
||||
* otherwise, {@code undefined}.
|
||||
*/
|
||||
export function restoreConfig(baseURL: string) {
|
||||
const key = `${_CONFIG_STORE_PREFIX}/${baseURL}`;
|
||||
const config = jitsiLocalStorage.getItem(key);
|
||||
|
||||
if (config) {
|
||||
try {
|
||||
return safeJsonParse(config) || undefined;
|
||||
} catch (e) {
|
||||
// Somehow incorrect data ended up in the storage. Clean it up.
|
||||
jitsiLocalStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspects the hash part of the location URI and overrides values specified
|
||||
* there in the corresponding config objects given as the arguments. The syntax
|
||||
* is: {@code https://server.com/room#config.debug=true
|
||||
* &interfaceConfig.showButton=false}.
|
||||
*
|
||||
* In the hash part each parameter will be parsed to JSON and then the root
|
||||
* object will be matched with the corresponding config object given as the
|
||||
* argument to this function.
|
||||
*
|
||||
* @param {Object} config - This is the general config.
|
||||
* @param {Object} interfaceConfig - This is the interface config.
|
||||
* @param {URI} location - The new location to which the app is navigating to.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function setConfigFromURLParams(
|
||||
config: IConfig, interfaceConfig: any, location: string | URL) {
|
||||
const params = parseURLParams(location);
|
||||
const json: any = {};
|
||||
|
||||
// At this point we have:
|
||||
// params = {
|
||||
// "config.disableAudioLevels": false,
|
||||
// "config.channelLastN": -1,
|
||||
// "interfaceConfig.APP_NAME": "Jitsi Meet"
|
||||
// }
|
||||
// We want to have:
|
||||
// json = {
|
||||
// config: {
|
||||
// "disableAudioLevels": false,
|
||||
// "channelLastN": -1
|
||||
// },
|
||||
// interfaceConfig: {
|
||||
// "APP_NAME": "Jitsi Meet"
|
||||
// }
|
||||
// }
|
||||
config && (json.config = {});
|
||||
interfaceConfig && (json.interfaceConfig = {});
|
||||
|
||||
for (const param of Object.keys(params)) {
|
||||
let base = json;
|
||||
const names = param.split('.');
|
||||
const last = names.pop() ?? '';
|
||||
|
||||
for (const name of names) {
|
||||
base = base[name] = base[name] || {};
|
||||
}
|
||||
|
||||
base[last] = params[param];
|
||||
}
|
||||
|
||||
overrideConfigJSON(config, interfaceConfig, json);
|
||||
|
||||
// Print warning about deprecated URL params
|
||||
if ('interfaceConfig.SUPPORT_URL' in params) {
|
||||
logger.warn('Using SUPPORT_URL interfaceConfig URL overwrite is deprecated.'
|
||||
+ ' Please use supportUrl from advanced branding!');
|
||||
}
|
||||
|
||||
if ('config.defaultLogoUrl' in params) {
|
||||
logger.warn('Using defaultLogoUrl config URL overwrite is deprecated.'
|
||||
+ ' Please use logoImageUrl from advanced branding!');
|
||||
}
|
||||
|
||||
const deploymentUrlsConfig = params['config.deploymentUrls'] ?? {};
|
||||
|
||||
if ('config.deploymentUrls.downloadAppsUrl' in params || 'config.deploymentUrls.userDocumentationURL' in params
|
||||
|| (typeof deploymentUrlsConfig === 'object'
|
||||
&& ('downloadAppsUrl' in deploymentUrlsConfig || 'userDocumentationURL' in deploymentUrlsConfig))) {
|
||||
logger.warn('Using deploymentUrls config URL overwrite is deprecated.'
|
||||
+ ' Please use downloadAppsUrl and/or userDocumentationURL from advanced branding!');
|
||||
}
|
||||
|
||||
const liveStreamingConfig = params['config.liveStreaming'] ?? {};
|
||||
|
||||
if (('interfaceConfig.LIVE_STREAMING_HELP_LINK' in params)
|
||||
|| ('config.liveStreaming.termsLink' in params)
|
||||
|| ('config.liveStreaming.dataPrivacyLink' in params)
|
||||
|| ('config.liveStreaming.helpLink' in params)
|
||||
|| (typeof params['config.liveStreaming'] === 'object' && 'config.liveStreaming' in params
|
||||
&& (
|
||||
'termsLink' in liveStreamingConfig
|
||||
|| 'dataPrivacyLink' in liveStreamingConfig
|
||||
|| 'helpLink' in liveStreamingConfig
|
||||
)
|
||||
)) {
|
||||
logger.warn('Using liveStreaming config URL overwrite and/or LIVE_STREAMING_HELP_LINK interfaceConfig URL'
|
||||
+ ' overwrite is deprecated. Please use liveStreaming from advanced branding!');
|
||||
}
|
||||
|
||||
// When not in an iframe, start without media if the pre-join page is not enabled.
|
||||
if (!isEmbedded()
|
||||
&& 'config.prejoinConfig.enabled' in params && config.prejoinConfig?.enabled === false) {
|
||||
logger.warn('Using prejoinConfig.enabled config URL overwrite implies starting without media.');
|
||||
config.disableInitialGUM = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* eslint-enable max-params */
|
||||
|
||||
/**
|
||||
* Returns the dial out url.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getDialOutStatusUrl(state: IReduxState) {
|
||||
return state['features/base/config'].guestDialOutStatusUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the dial out status url.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getDialOutUrl(state: IReduxState) {
|
||||
return state['features/base/config'].guestDialOutUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector to return the security UI config.
|
||||
*
|
||||
* @param {IReduxState} state - State object.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function getSecurityUiConfig(state: IReduxState) {
|
||||
return state['features/base/config']?.securityUi || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the terms, privacy and help centre URL's.
|
||||
*
|
||||
* @param {IReduxState} state - The state of the application.
|
||||
* @returns {{
|
||||
* privacy: string,
|
||||
* helpCentre: string,
|
||||
* terms: string
|
||||
* }}
|
||||
*/
|
||||
export function getLegalUrls(state: IReduxState) {
|
||||
const helpCentreURL = state['features/base/config']?.helpCentreURL;
|
||||
const configLegalUrls = state['features/base/config']?.legalUrls;
|
||||
|
||||
return {
|
||||
privacy: configLegalUrls?.privacy || DEFAULT_PRIVACY_URL,
|
||||
helpCentre: helpCentreURL || configLegalUrls?.helpCentre || DEFAULT_HELP_CENTRE_URL,
|
||||
terms: configLegalUrls?.terms || DEFAULT_TERMS_URL
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to debounce the execution of a callback function.
|
||||
*
|
||||
* @param {Function} callback - The callback to debounce.
|
||||
* @param {number} delay - The debounce delay in milliseconds.
|
||||
* @returns {Function} - A debounced function that delays the execution of the callback.
|
||||
*/
|
||||
export function debounce(callback: (...args: any[]) => void, delay: number) {
|
||||
let timerId: any;
|
||||
|
||||
return (...args: any[]) => {
|
||||
if (timerId) {
|
||||
clearTimeout(timerId);
|
||||
}
|
||||
timerId = setTimeout(() => callback(...args), delay);
|
||||
};
|
||||
}
|
||||
53
react/features/base/config/functions.native.ts
Normal file
53
react/features/base/config/functions.native.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { NativeModules } from 'react-native';
|
||||
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { REPLACE_PARTICIPANT } from '../flags/constants';
|
||||
import { getFeatureFlag } from '../flags/functions';
|
||||
|
||||
import { IConfig, IDeeplinkingConfig } from './configType';
|
||||
|
||||
export * from './functions.any';
|
||||
|
||||
/**
|
||||
* Removes all analytics related options from the given configuration, in case of a libre build.
|
||||
*
|
||||
* @param {*} config - The configuration which needs to be cleaned up.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function _cleanupConfig(config: IConfig) {
|
||||
config.analytics = config.analytics ?? {};
|
||||
config.analytics.scriptURLs = [];
|
||||
|
||||
if (NativeModules.AppInfo.LIBRE_BUILD) {
|
||||
delete config.analytics?.amplitudeAPPKey;
|
||||
delete config.analytics?.rtcstatsEnabled;
|
||||
delete config.analytics?.rtcstatsEndpoint;
|
||||
delete config.analytics?.rtcstatsPollInterval;
|
||||
delete config.analytics?.rtcstatsSendSdp;
|
||||
delete config.analytics?.obfuscateRoomName;
|
||||
delete config.analytics?.watchRTCEnabled;
|
||||
delete config.watchRTCConfigParams;
|
||||
config.giphy = { enabled: false };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the replaceParticipant config.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function getReplaceParticipant(state: IReduxState): string {
|
||||
return getFeatureFlag(state, REPLACE_PARTICIPANT, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the defaults for deeplinking.
|
||||
*
|
||||
* @param {IDeeplinkingConfig} _deeplinking - The deeplinking config.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function _setDeeplinkingDefaults(_deeplinking: IDeeplinkingConfig) {
|
||||
return;
|
||||
}
|
||||
|
||||
87
react/features/base/config/functions.web.ts
Normal file
87
react/features/base/config/functions.web.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { IReduxState } from '../../app/types';
|
||||
import JitsiMeetJS from '../../base/lib-jitsi-meet';
|
||||
|
||||
import {
|
||||
IConfig,
|
||||
IDeeplinkingConfig,
|
||||
IDeeplinkingDesktopConfig,
|
||||
IDeeplinkingMobileConfig
|
||||
} from './configType';
|
||||
|
||||
export * from './functions.any';
|
||||
|
||||
/**
|
||||
* Removes all analytics related options from the given configuration, in case of a libre build.
|
||||
*
|
||||
* @param {*} _config - The configuration which needs to be cleaned up.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function _cleanupConfig(_config: IConfig) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the replaceParticipant config.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function getReplaceParticipant(state: IReduxState): string | undefined {
|
||||
return state['features/base/config'].replaceParticipant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configuration value of web-hid feature.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean} True if web-hid feature should be enabled, otherwise false.
|
||||
*/
|
||||
export function getWebHIDFeatureConfig(state: IReduxState): boolean {
|
||||
return state['features/base/config'].enableWebHIDFeature || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether audio level measurement is enabled or not.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function areAudioLevelsEnabled(state: IReduxState): boolean {
|
||||
return !state['features/base/config'].disableAudioLevels && JitsiMeetJS.isCollectingLocalStats();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the defaults for deeplinking.
|
||||
*
|
||||
* @param {IDeeplinkingConfig} deeplinking - The deeplinking config.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function _setDeeplinkingDefaults(deeplinking: IDeeplinkingConfig) {
|
||||
deeplinking.desktop = deeplinking.desktop || {} as IDeeplinkingDesktopConfig;
|
||||
deeplinking.android = deeplinking.android || {} as IDeeplinkingMobileConfig;
|
||||
deeplinking.ios = deeplinking.ios || {} as IDeeplinkingMobileConfig;
|
||||
|
||||
const { android, desktop, ios } = deeplinking;
|
||||
|
||||
desktop.appName = desktop.appName || 'Jitsi Meet';
|
||||
desktop.appScheme = desktop.appScheme || 'jitsi-meet';
|
||||
desktop.download = desktop.download || {};
|
||||
desktop.download.windows = desktop.download.windows
|
||||
|| 'https://github.com/jitsi/jitsi-meet-electron/releases/latest/download/jitsi-meet.exe';
|
||||
desktop.download.macos = desktop.download.macos
|
||||
|| 'https://github.com/jitsi/jitsi-meet-electron/releases/latest/download/jitsi-meet.dmg';
|
||||
desktop.download.linux = desktop.download.linux
|
||||
|| 'https://github.com/jitsi/jitsi-meet-electron/releases/latest/download/jitsi-meet-x86_64.AppImage';
|
||||
|
||||
ios.appName = ios.appName || 'Jitsi Meet';
|
||||
ios.appScheme = ios.appScheme || 'org.jitsi.meet';
|
||||
ios.downloadLink = ios.downloadLink
|
||||
|| 'https://itunes.apple.com/us/app/jitsi-meet/id1165103905';
|
||||
|
||||
android.appName = android.appName || 'Jitsi Meet';
|
||||
android.appScheme = android.appScheme || 'org.jitsi.meet';
|
||||
android.downloadLink = android.downloadLink
|
||||
|| 'https://play.google.com/store/apps/details?id=org.jitsi.meet';
|
||||
android.appPackage = android.appPackage || 'org.jitsi.meet';
|
||||
android.fDroidUrl = android.fDroidUrl || 'https://f-droid.org/packages/org.jitsi.meet/';
|
||||
}
|
||||
15
react/features/base/config/getRoomName.ts
Normal file
15
react/features/base/config/getRoomName.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { getBackendSafeRoomName } from '../util/uri';
|
||||
|
||||
/**
|
||||
* Builds and returns the room name.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
export default function getRoomName(): string | undefined {
|
||||
const path = window.location.pathname;
|
||||
|
||||
// The last non-directory component of the path (name) is the room.
|
||||
const roomName = path.substring(path.lastIndexOf('/') + 1) || undefined;
|
||||
|
||||
return getBackendSafeRoomName(roomName);
|
||||
}
|
||||
57
react/features/base/config/interfaceConfigWhitelist.ts
Normal file
57
react/features/base/config/interfaceConfigWhitelist.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { isEmbedded } from '../util/embedUtils';
|
||||
|
||||
import extraInterfaceConfigWhitelistCopy from './extraInterfaceConfigWhitelist';
|
||||
import isEmbeddedInterfaceConfigWhitelist from './isEmbeddedInterfaceConfigWhitelist';
|
||||
|
||||
/**
|
||||
* The interface config keys to whitelist, the keys that can be overridden.
|
||||
*
|
||||
* @private
|
||||
* @type Array
|
||||
*/
|
||||
export default [
|
||||
'AUDIO_LEVEL_PRIMARY_COLOR',
|
||||
'AUDIO_LEVEL_SECONDARY_COLOR',
|
||||
'AUTO_PIN_LATEST_SCREEN_SHARE',
|
||||
'CLOSE_PAGE_GUEST_HINT',
|
||||
'CONNECTION_INDICATOR_AUTO_HIDE_ENABLED',
|
||||
'CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT',
|
||||
'CONNECTION_INDICATOR_DISABLED',
|
||||
'DEFAULT_BACKGROUND',
|
||||
'DISABLE_PRESENCE_STATUS',
|
||||
'DISABLE_JOIN_LEAVE_NOTIFICATIONS',
|
||||
'DEFAULT_LOCAL_DISPLAY_NAME',
|
||||
'DEFAULT_REMOTE_DISPLAY_NAME',
|
||||
'DISABLE_DOMINANT_SPEAKER_INDICATOR',
|
||||
'DISABLE_FOCUS_INDICATOR',
|
||||
'DISABLE_PRIVATE_MESSAGES',
|
||||
'DISABLE_TRANSCRIPTION_SUBTITLES',
|
||||
'DISABLE_VIDEO_BACKGROUND',
|
||||
'DISPLAY_WELCOME_PAGE_CONTENT',
|
||||
'ENABLE_DIAL_OUT',
|
||||
'FILM_STRIP_MAX_HEIGHT',
|
||||
'GENERATE_ROOMNAMES_ON_WELCOME_PAGE',
|
||||
'INDICATOR_FONT_SIZES',
|
||||
'INITIAL_TOOLBAR_TIMEOUT',
|
||||
'LANG_DETECTION',
|
||||
'LOCAL_THUMBNAIL_RATIO',
|
||||
'MAXIMUM_ZOOMING_COEFFICIENT',
|
||||
'NATIVE_APP_NAME',
|
||||
'OPTIMAL_BROWSERS',
|
||||
'PHONE_NUMBER_REGEX',
|
||||
'PROVIDER_NAME',
|
||||
'RECENT_LIST_ENABLED',
|
||||
'REMOTE_THUMBNAIL_RATIO',
|
||||
'SETTINGS_SECTIONS',
|
||||
'SHARING_FEATURES',
|
||||
'SHOW_CHROME_EXTENSION_BANNER',
|
||||
'SHOW_POWERED_BY',
|
||||
'TILE_VIEW_MAX_COLUMNS',
|
||||
'TOOLBAR_ALWAYS_VISIBLE',
|
||||
'TOOLBAR_BUTTONS',
|
||||
'TOOLBAR_TIMEOUT',
|
||||
'UNSUPPORTED_BROWSERS',
|
||||
'VERTICAL_FILMSTRIP',
|
||||
'VIDEO_LAYOUT_FIT',
|
||||
'VIDEO_QUALITY_LABEL_DISABLED'
|
||||
].concat(extraInterfaceConfigWhitelistCopy).concat(isEmbedded() ? isEmbeddedInterfaceConfigWhitelist : []);
|
||||
10
react/features/base/config/isEmbeddedConfigWhitelist.ts
Normal file
10
react/features/base/config/isEmbeddedConfigWhitelist.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Additional config whitelist extending the original whitelist applied when Jitsi Meet is embedded
|
||||
* in another app be that with an iframe or a mobile SDK.
|
||||
*/
|
||||
export default [
|
||||
'customToolbarButtons',
|
||||
'defaultLogoUrl',
|
||||
'deploymentUrls',
|
||||
'liveStreaming'
|
||||
];
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Additional interface config whitelist extending the original whitelist applied when Jitsi Meet is embedded
|
||||
* in another app be that with an iframe or a mobile SDK.
|
||||
*/
|
||||
export default [
|
||||
];
|
||||
3
react/features/base/config/logger.ts
Normal file
3
react/features/base/config/logger.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { getLogger } from '../logging/functions';
|
||||
|
||||
export default getLogger('features/base/config');
|
||||
225
react/features/base/config/middleware.ts
Normal file
225
react/features/base/config/middleware.ts
Normal file
@@ -0,0 +1,225 @@
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
import { IStore } from '../../app/types';
|
||||
import { SET_DYNAMIC_BRANDING_DATA } from '../../dynamic-branding/actionTypes';
|
||||
import { setUserFilmstripWidth } from '../../filmstrip/actions.web';
|
||||
import { getFeatureFlag } from '../flags/functions';
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
import { updateSettings } from '../settings/actions';
|
||||
|
||||
import { OVERWRITE_CONFIG, SET_CONFIG } from './actionTypes';
|
||||
import { updateConfig } from './actions';
|
||||
import { IConfig } from './configType';
|
||||
|
||||
/**
|
||||
* The middleware of the feature {@code base/config}.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @private
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case SET_CONFIG:
|
||||
return _setConfig(store, next, action);
|
||||
|
||||
case SET_DYNAMIC_BRANDING_DATA:
|
||||
return _setDynamicBrandingData(store, next, action);
|
||||
|
||||
case OVERWRITE_CONFIG:
|
||||
return _updateSettings(store, next, action);
|
||||
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
|
||||
/**
|
||||
* Notifies the feature {@code base/config} that the {@link SET_CONFIG} redux
|
||||
* action is being {@code dispatch}ed in a specific redux store.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
|
||||
* specified {@code action} in the specified {@code store}.
|
||||
* @param {Action} action - The redux action which is being {@code dispatch}ed
|
||||
* in the specified {@code store}.
|
||||
* @private
|
||||
* @returns {*} The return value of {@code next(action)}.
|
||||
*/
|
||||
function _setConfig({ dispatch, getState }: IStore, next: Function, action: AnyAction) {
|
||||
// The reducer is doing some alterations to the config passed in the action,
|
||||
// so make sure it's the final state by waiting for the action to be
|
||||
// reduced.
|
||||
const result = next(action);
|
||||
const state = getState();
|
||||
|
||||
// Update the config with user defined settings.
|
||||
const settings = state['features/base/settings'];
|
||||
const config: IConfig = {};
|
||||
|
||||
if (typeof settings.disableP2P !== 'undefined') {
|
||||
config.p2p = { enabled: !settings.disableP2P };
|
||||
}
|
||||
|
||||
const resolutionFlag = getFeatureFlag(state, 'resolution');
|
||||
|
||||
if (typeof resolutionFlag !== 'undefined') {
|
||||
config.resolution = resolutionFlag;
|
||||
}
|
||||
|
||||
if (action.config.doNotFlipLocalVideo === true) {
|
||||
dispatch(updateSettings({
|
||||
localFlipX: false
|
||||
}));
|
||||
}
|
||||
|
||||
if (action.config.disableSelfView !== undefined) {
|
||||
dispatch(updateSettings({
|
||||
disableSelfView: action.config.disableSelfView
|
||||
}));
|
||||
}
|
||||
|
||||
const { initialWidth, stageFilmstripParticipants } = action.config.filmstrip || {};
|
||||
|
||||
if (stageFilmstripParticipants !== undefined) {
|
||||
dispatch(updateSettings({
|
||||
maxStageParticipants: stageFilmstripParticipants
|
||||
}));
|
||||
}
|
||||
|
||||
if (initialWidth) {
|
||||
dispatch(setUserFilmstripWidth(initialWidth));
|
||||
}
|
||||
|
||||
dispatch(updateConfig(config));
|
||||
|
||||
// FIXME On Web we rely on the global 'config' variable which gets altered
|
||||
// multiple times, before it makes it to the reducer. At some point it may
|
||||
// not be the global variable which is being modified anymore due to
|
||||
// different merge methods being used along the way. The global variable
|
||||
// must be synchronized with the final state resolved by the reducer.
|
||||
if (typeof window.config !== 'undefined') {
|
||||
window.config = state['features/base/config'];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates config based on dynamic branding data.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
|
||||
* specified {@code action} in the specified {@code store}.
|
||||
* @param {Action} action - The redux action which is being {@code dispatch}ed
|
||||
* in the specified {@code store}.
|
||||
* @private
|
||||
* @returns {*} The return value of {@code next(action)}.
|
||||
*/
|
||||
function _setDynamicBrandingData({ dispatch }: IStore, next: Function, action: AnyAction) {
|
||||
const config: IConfig = {};
|
||||
const {
|
||||
customParticipantMenuButtons,
|
||||
customToolbarButtons,
|
||||
downloadAppsUrl,
|
||||
etherpadBase,
|
||||
liveStreamingDialogUrls = {},
|
||||
preCallTest = {},
|
||||
salesforceUrl,
|
||||
userDocumentationUrl,
|
||||
peopleSearchUrl,
|
||||
} = action.value;
|
||||
|
||||
const { helpUrl, termsUrl, dataPrivacyUrl } = liveStreamingDialogUrls;
|
||||
|
||||
if (helpUrl || termsUrl || dataPrivacyUrl) {
|
||||
config.liveStreaming = {};
|
||||
if (helpUrl) {
|
||||
config.liveStreaming.helpLink = helpUrl;
|
||||
}
|
||||
|
||||
if (termsUrl) {
|
||||
config.liveStreaming.termsLink = termsUrl;
|
||||
}
|
||||
|
||||
if (dataPrivacyUrl) {
|
||||
config.liveStreaming.dataPrivacyLink = dataPrivacyUrl;
|
||||
}
|
||||
}
|
||||
|
||||
if (downloadAppsUrl || userDocumentationUrl) {
|
||||
config.deploymentUrls = {};
|
||||
|
||||
if (downloadAppsUrl) {
|
||||
config.deploymentUrls.downloadAppsUrl = downloadAppsUrl;
|
||||
}
|
||||
|
||||
if (userDocumentationUrl) {
|
||||
config.deploymentUrls.userDocumentationURL = userDocumentationUrl;
|
||||
}
|
||||
}
|
||||
|
||||
if (salesforceUrl) {
|
||||
config.salesforceUrl = salesforceUrl;
|
||||
}
|
||||
|
||||
if (peopleSearchUrl) {
|
||||
config.peopleSearchUrl = peopleSearchUrl;
|
||||
}
|
||||
|
||||
const { enabled, iceUrl } = preCallTest;
|
||||
|
||||
if (typeof enabled === 'boolean') {
|
||||
config.prejoinConfig = {
|
||||
preCallTestEnabled: enabled
|
||||
};
|
||||
}
|
||||
|
||||
if (etherpadBase) {
|
||||
// eslint-disable-next-line camelcase
|
||||
config.etherpad_base = etherpadBase;
|
||||
}
|
||||
|
||||
if (iceUrl) {
|
||||
config.prejoinConfig = config.prejoinConfig || {};
|
||||
config.prejoinConfig.preCallTestICEUrl = iceUrl;
|
||||
}
|
||||
|
||||
if (customToolbarButtons) {
|
||||
config.customToolbarButtons = customToolbarButtons;
|
||||
}
|
||||
|
||||
if (customParticipantMenuButtons) {
|
||||
config.customParticipantMenuButtons = customParticipantMenuButtons;
|
||||
}
|
||||
|
||||
dispatch(updateConfig(config));
|
||||
|
||||
return next(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates settings based on some config values.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
|
||||
* specified {@code action} in the specified {@code store}.
|
||||
* @param {Action} action - The redux action which is being {@code dispatch}ed
|
||||
* in the specified {@code store}.
|
||||
* @private
|
||||
* @returns {*} The return value of {@code next(action)}.
|
||||
*/
|
||||
function _updateSettings({ dispatch }: IStore, next: Function, action: AnyAction) {
|
||||
const { config: { doNotFlipLocalVideo } } = action;
|
||||
|
||||
if (doNotFlipLocalVideo === true) {
|
||||
dispatch(updateSettings({
|
||||
localFlipX: false
|
||||
}));
|
||||
}
|
||||
|
||||
return next(action);
|
||||
}
|
||||
592
react/features/base/config/reducer.ts
Normal file
592
react/features/base/config/reducer.ts
Normal file
@@ -0,0 +1,592 @@
|
||||
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
|
||||
} 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 { alwaysShowResizeBar, disableResizable } = config.filmstrip || {};
|
||||
|
||||
if (alwaysShowResizeBar && disableResizable) {
|
||||
config.filmstrip = {
|
||||
...config.filmstrip,
|
||||
alwaysShowResizeBar: 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') {
|
||||
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
|
||||
};
|
||||
deeplinking.ios = {
|
||||
appName: interfaceConfig.NATIVE_APP_NAME,
|
||||
appScheme: interfaceConfig.APP_SCHEME,
|
||||
downloadLink: interfaceConfig.MOBILE_DOWNLOAD_LINK_IOS
|
||||
};
|
||||
}
|
||||
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.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
|
||||
};
|
||||
}
|
||||
|
||||
// Profile button is not available on mobile
|
||||
if (navigator.product !== 'ReactNative') {
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user