diff --git a/config.js b/config.js index ad7185aec0..59d0d171b3 100644 --- a/config.js +++ b/config.js @@ -143,6 +143,7 @@ var config = { // Disable measuring of audio levels. // disableAudioLevels: false, + // audioLevelsInterval: 200, // Enabling this will run the lib-jitsi-meet no audio detection module which @@ -415,7 +416,7 @@ var config = { // // This will result in Safari not being able to decode video from endpoints sending VP9 video. // // When set to false, the conference falls back to VP8 whenever there is an endpoint that doesn't support the // // preferred codec and goes back to the preferred codec when that endpoint leaves. - // // enforcePreferredCodec: false, + // enforcePreferredCodec: false, // // // Provides a way to configure the maximum bitrates that will be enforced on the simulcast streams for // // video tracks. The keys in the object represent the type of the stream (LD, SD or HD) and the values @@ -583,8 +584,8 @@ var config = { // enableFeaturesBasedOnToken: false, // When enabled the password used for locking a room is restricted to up to the number of digits specified - // roomPasswordNumberOfDigits: 10, // default: roomPasswordNumberOfDigits: false, + // roomPasswordNumberOfDigits: 10, // Message to show the users. Example: 'The service will be down for // maintenance at 01:00 AM GMT, diff --git a/package-lock.json b/package-lock.json index 7086976939..611449cf10 100644 --- a/package-lock.json +++ b/package-lock.json @@ -143,6 +143,7 @@ "@babel/preset-react": "7.16.0", "@babel/runtime": "7.16.0", "@jitsi/eslint-config": "4.0.0", + "@types/lodash": "4.14.182", "@types/react": "17.0.14", "@types/react-native": "0.67.6", "@types/react-redux": "7.1.24", @@ -5443,6 +5444,12 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "node_modules/@types/lodash": { + "version": "4.14.182", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", + "integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==", + "dev": true + }, "node_modules/@types/long": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", @@ -24109,6 +24116,12 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "@types/lodash": { + "version": "4.14.182", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", + "integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==", + "dev": true + }, "@types/long": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", diff --git a/package.json b/package.json index bef91ab3bc..420d3fa20f 100644 --- a/package.json +++ b/package.json @@ -148,6 +148,7 @@ "@babel/preset-react": "7.16.0", "@babel/runtime": "7.16.0", "@jitsi/eslint-config": "4.0.0", + "@types/lodash": "4.14.182", "@types/react": "17.0.14", "@types/react-native": "0.67.6", "@types/react-redux": "7.1.24", diff --git a/react/features/app/types.ts b/react/features/app/types.ts index fa6429499f..96ba981589 100644 --- a/react/features/app/types.ts +++ b/react/features/app/types.ts @@ -4,6 +4,7 @@ import { IAVModerationState } from "../av-moderation/reducer" import { IAppState } from "../base/app/reducer" import { IAudioOnlyState } from "../base/audio-only/reducer" import { IConferenceState } from "../base/conference/reducer" +import { IConfig } from "../base/config/configType" export interface IStore { getState: Function, @@ -16,5 +17,6 @@ export interface IState { 'features/av-moderation': IAVModerationState, 'features/base/app': IAppState, 'features/base/audio-only': IAudioOnlyState, - 'features/base/conference': IConferenceState + 'features/base/conference': IConferenceState, + 'features/base/config': IConfig, } diff --git a/react/features/base/config/configType.ts b/react/features/base/config/configType.ts new file mode 100644 index 0000000000..abdd157977 --- /dev/null +++ b/react/features/base/config/configType.ts @@ -0,0 +1,467 @@ +type ToolbarButtons = 'camera' | + 'chat' | + 'closedcaptions' | + 'desktop' | + 'dock-iframe' | + 'download' | + 'embedmeeting' | + 'etherpad' | + 'feedback' | + 'filmstrip' | + 'fullscreen' | + 'hangup' | + 'help' | + 'highlight' | + 'invite' | + 'linktosalesforce' | + 'livestreaming' | + 'microphone' | + 'participants-pane' | + 'profile' | + 'raisehand' | + 'recording' | + 'security' | + 'select-background' | + 'settings' | + 'shareaudio' | + 'sharedvideo' | + 'shortcuts' | + 'stats' | + 'tileview' | + 'toggle-camera' | + 'undock-iframe' | + 'videoquality' | + '__end'; + +type ButtonsWithNotifyClick = 'camera' | + 'chat' | + 'closedcaptions' | + 'desktop' | + 'download' | + 'embedmeeting' | + 'etherpad' | + 'feedback' | + 'filmstrip' | + 'fullscreen' | + 'hangup' | + '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 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 IConfig { + hosts?: { + domain: string; + anonymousdomain?: string; + authdomain?: string; + focus?: string; + muc: string; + }; + bosh?: string; + websocket?: string; + focusUserJid?: string; + testing?: { + disableE2EE?: boolean; + enableThumbnailReordering?: boolean; + mobileXmppWsThreshold?: number; + p2pTestMode?: boolean; + testMode?: boolean; + noAutoPlayVideo?: boolean; + capScreenshareBitrate?: number; + setScreenSharingResolutionConstraints?: boolean; + callStatsThreshold?: number; + }; + flags?: { + sourceNameSignaling?: boolean; + sendMultipleVideoStreams?: boolean; + }; + disableModeratorIndicator?: boolean; + disableReactions?: boolean; + disableReactionsModeration?: boolean; + disablePolls?: boolean; + disableSelfView?: boolean; + disableSelfViewSettings?: boolean; + screenshotCapture?: { + enabled?: boolean; + mode?: 'always' | 'recording'; + }; + webrtcIceUdpDisable?: boolean; + webrtcIceTcpDisable?: boolean; + enableUnifiedOnChrome?: boolean; + disableAudioLevels?: boolean; + audioLevelsInterval?: number; + enableNoAudioDetection?: boolean; + enableSaveLogs?: boolean; + disableShowMoreStats?: boolean; + enableNoisyMicDetection?: boolean; + startAudioOnly?: boolean; + startAudioMuted?: boolean; + startWithAudioMuted?: boolean; + startSilent?: boolean; + enableOpusRed?: boolean; + audioQuality?: { + stereo?: boolean; + opusMaxAverageBitrate?: number|null; + }; + stereo?: boolean; + opusMaxAverageBitrate?: number; + resolution?: number; + disableRemoveRaisedHandOnFocus?: boolean; + disableSpeakerStatsSearch?: boolean; + speakerStatsOrder?: Array<'role'|'name'|'hasLeft'>; + maxFullResolutionParticipants?: number; + constraints?: { + video?: { + height?: { + ideal?: number; + max?: number; + min?: number; + } + } + }; + disableSimulcast?: boolean; + enableLayerSuspension?: boolean; + startVideoMuted?: number; + startWithVideoMuted?: boolean; + preferH264?: boolean; + disableH264?: boolean; + desktopSharingFrameRate?: { + min?: number; + max?: number; + }; + startScreenSharing?: boolean; + fileRecordingsEnabled?: boolean; + dropbox?: { + appKey: string; + redirectURI?: string; + }; + recordingService?: { + enabled?: boolean; + sharingEnabled?: boolean; + hideStorageWarning?: boolean; + }; + fileRecordingsServiceEnabled?: boolean; + fileRecordingsServiceSharingEnabled?: boolean; + liveStreamingEnabled?: boolean; + localRecording?: { + disable?: boolean; + notifyAllParticipants?: boolean; + }; + transcribingEnabled?: boolean; + transcribeWithAppLanguage?: boolean; + preferredTranscribeLanguage?: string; + autoCaptionOnRecord?: boolean; + transcription?: { + enabled?: boolean; + useAppLanguage?: boolean; + preferredLanguage?: string; + disableStartForAll?: boolean; + autoCaptionOnRecord?: boolean; + }; + channelLastN?: number; + connectionIndicators?: { + autoHide?: boolean; + autoHideTimeout?: number; + disabled?: boolean; + disableDetails?: boolean; + inactiveDisabled?: boolean; + }; + startLastN?: number; + lastNLimits?: { + [key: number]: number; + }; + useNewBandwidthAllocationStrategy?: boolean; + videoQuality?: { + disabledCodec?: string; + preferredCodec?: string; + enforcePreferredCodec?: boolean; + maxBitratesVideo?: { + [key: string]: { + low?: number; + standard?: number; + high?: number; + } + }; + minHeightForQualityLvl: { + [key: number]: string; + }; + resizeDesktopForPresenter?: boolean; + }; + notificationTimeouts?: { + short?: number; + medium?: number; + long?: number; + }; + recordingLimit?: { + limit?: number; + appName?: string; + appURL?: string; + }; + disableRtx?: boolean; + disableBeforeUnloadHandlers?: boolean; + enableTcc?: boolean; + enableRemb?: boolean; + enableIceRestart?: boolean; + enableForcedReload?: boolean; + useTurnUdp?: boolean; + enableEncodedTransformSupport?: boolean; + disableResponsiveTiles?: boolean; + hideLobbyButton?: boolean; + autoKnockLobby?: boolean; + enableLobbyChat?: boolean; + hideAddRoomButton?: boolean; + requireDisplayName?: boolean; + enableWelcomePage?: boolean; + disableShortcuts?: boolean; + disableInitialGUM?: boolean; + enableClosePage?: boolean; + disable1On1Mode?: boolean|null; + defaultLocalDisplayName?: string; + defaultRemoteDisplayName?: string; + hideDisplayName?: boolean; + hideDominantSpeakerBadge?: boolean; + defaultLanguage?: string; + disableProfile?: boolean; + hideEmailInSettings?: boolean; + enableFeaturesBasedOnToken?: boolean; + roomPasswordNumberOfDigits?: number; + noticeMessage?: string; + enableCalendarIntegration?: boolean; + prejoinConfig?: { + enabled?: boolean; + hideDisplayName?: boolean; + hideExtraJoinButtons?: Array; + }; + prejoinPageEnabled?: boolean; + readOnlyName?: boolean; + openSharedDocumentOnJoin?: boolean; + enableInsecureRoomNameWarning?: boolean; + enableAutomaticUrlCopy?: boolean; + corsAvatarURLs?: Array; + gravatarBaseURL?: string; + gravatar?: { + baseUrl?: string; + disabled?: boolean; + }; + inviteAppName?: string|null; + toolbarButtons?: Array; + toolbarConfig?: { + initialTimeout?: number; + timeout?: number; + alwaysVisible?: boolean; + autoHideWhileChatIsOpen?: boolean; + }; + buttonsWithNotifyClick?: Array; + hiddenPremeetingButtons?: Array<'microphone' | 'camera' | 'select-background' | 'invite' | 'settings'>; + gatherStats?: boolean; + pcStatsInterval?: number; + callStatsID?: string; + callStatsSecret?: string; + callStatsConfigParams?: { + disableBeforeUnloadHandler?: boolean; + applicationVersion?: string; + disablePrecalltest?: boolean; + siteID?: string; + additionalIDs?: { + customerID?: string; + tenantID?: string; + productName?: string; + meetingsName?: string; + serverName?: string; + pbxID?: string; + pbxExtensionID?: string; + fqExtensionID?: string; + sessionID?: string; + }; + collectLegacyStats?: boolean; + collectIP?: boolean; + }; + enableDisplayNameInStats?: boolean; + enableEmailInStats?: boolean; + faceLandmarks?: { + enableFaceCentering?: boolean; + enableFaceExpressionsDetection?: boolean; + enableDisplayFaceExpressions?: boolean; + enableRTCStats?: boolean; + faceCenteringThreshold?: number; + captureInterval?: number; + }; + feedbackPercentage?: number; + disableThirdPartyRequests?: boolean; + p2p?: { + enabled?: boolean; + enableUnifiedOnChrome?: boolean; + iceTransportPolicy?: string; + preferH264?: boolean; + preferredCodec?: string; + disableH264?: boolean; + disabledCodec?: string; + backToP2PDelay?: number; + stunServers?: Array<{urls: string}>; + }; + analytics?: { + disabled?: boolean; + googleAnalyticsTrackingId?: string; + matomoEndpoint?: string; + matomoSiteID?: string; + amplitudeAPPKey?: string; + obfuscateRoomName?: boolean; + rtcstatsEnabled?: boolean; + rtcstatsEndpoint?: string; + rtcstatsPolIInterval?: number; + scriptURLs?: Array; + }; + apiLogLevels?: Array<'warn' | 'log' | 'error' | 'info' | 'debug'>; + deploymentInfo?: { + shard?: string; + region?: string; + userRegion?: string; + }; + disabledSounds?: Array; + disableRecordAudioNotification?: boolean; + disableJoinLeaveSounds?: boolean; + disableIncomingMessageSound?: boolean; + chromeExtensionBanner?: { + url?: string; + edgeUrl?: string; + chromeExtensionsInfo?: Array<{id: string; path: string}>; + }; + e2ee?: { + labels?: { + tooltip?: string; + description?: string; + label?: string; + warning?: string; + }; + externallyManagedKey?: boolean; + e2eeLabels?: { + tooltip?: string; + description?: string; + label?: string; + warning?: string; + }; + }; + e2eeLabels?: { + tooltip?: string; + description?: string; + label?: string; + warning?: string; + }; + e2eping?: { + enabled?: boolean; + numRequests?: number; + maxConferenceSize?: number; + maxMessagesPerSecond?: number; + }; + _desktopSharingSourceDevice?: string; + disableDeepLinking?: boolean; + disableLocalVideoFlip?: boolean; + doNotFlipLocalVideo?: boolean; + disableInviteFunctions?: boolean; + doNotStoreRoom?: boolean; + deploymentUrls?: { + userDocumentationURL?: string; + downloadAppsUrl?: string; + }; + remoteVideoMenu?: { + disabled?: boolean; + disableKick?: boolean; + disableGrantModerator?: boolean; + disablePrivateChat?: boolean; + }; + salesforceUrl?: string; + disableRemoteMute?: boolean; + enableLipSync?: boolean; + dynamicBrandingUrl?: string; + participantsPane?: { + hideModeratorSettingsTab?: boolean; + hideMoreActionsButton?: boolean; + hideMuteAllButton?: boolean; + }; + breakoutRooms?: { + hideAddRoomButton?: boolean; + hideAutoAssignButton?: boolean; + hideJoinRoomButton?: boolean; + }; + disableAddingBackgroundImages?: boolean; + disableScreensharingVirtualBackground?: boolean; + backgroundAlpha?: number; + moderatedRoomServiceUrl?: string; + disableTileView?: boolean; + disableTileEnlargement?: boolean; + conferenceInfo?: { + alwaysVisible?: Array; + autoHide?: Array; + }; + hideConferenceSubject?: boolean; + hideConferenceTimer?: boolean; + hideRecordingLabel?: boolean; + hideParticipantsStats?: boolean; + subject?: string; + localSubject?: string; + useHostPageLocalStorage?: boolean; + etherpad_base?: string; + dialInNumbersUrl?: string; + dialInConfCodeUrl?: string; + brandingRoomAlias?: string; + mouseMoveCallbackInterval?: number; + notifications?: Array; + disabledNotifications?: Array; + disableFilmstripAutohiding?: boolean; + filmstrip?: { + disableResizable?: boolean; + disableStageFilmstrip?: boolean; + disableTopPanel?: boolean; + minParticipantCountForTopPanel?: number; + }; + tileView?: { + numberOfVisibleTiles?: number; + }; + disableChatSmileys?: boolean; + giphy?: { + enabled?: boolean; + sdkKey?: ''; + displayMode?: 'all' | 'tile' | 'chat'; + tileTime?: number; + }; + locationURL?: string; +} \ No newline at end of file diff --git a/react/features/base/config/reducer.js b/react/features/base/config/reducer.ts similarity index 91% rename from react/features/base/config/reducer.js rename to react/features/base/config/reducer.ts index 33603f6dc1..98fa648e7c 100644 --- a/react/features/base/config/reducer.js +++ b/react/features/base/config/reducer.ts @@ -1,9 +1,9 @@ -// @flow - import _ from 'lodash'; import { CONFERENCE_INFO } from '../../conference/components/constants'; -import { equals, ReducerRegistry } from '../redux'; +// @ts-ignore +import { equals } from '../redux'; +import ReducerRegistry from '../redux/ReducerRegistry'; import { UPDATE_CONFIG, @@ -12,9 +12,11 @@ import { SET_CONFIG, OVERWRITE_CONFIG } from './actionTypes'; +import { IConfig } from './configType'; +// @ts-ignore import { _cleanupConfig } from './functions'; -declare var interfaceConfig: Object; +declare var interfaceConfig: any; /** * The initial state of the feature base/config when executing in a @@ -26,7 +28,7 @@ declare var interfaceConfig: Object; * * @type {Object} */ -const INITIAL_NON_RN_STATE = { +const INITIAL_NON_RN_STATE: IConfig = { }; /** @@ -38,7 +40,7 @@ const INITIAL_NON_RN_STATE = { * * @type {Object} */ -const INITIAL_RN_STATE = { +const INITIAL_RN_STATE: IConfig = { analytics: {}, // FIXME The support for audio levels in lib-jitsi-meet polls the statistics @@ -61,14 +63,14 @@ const INITIAL_RN_STATE = { * 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 = { +const CONFERENCE_HEADER_MAPPING: any = { hideConferenceTimer: [ 'conference-timer' ], hideConferenceSubject: [ 'subject' ], hideParticipantsStats: [ 'participants-count' ], hideRecordingLabel: [ 'recording' ] }; -ReducerRegistry.register('features/base/config', (state = _getInitialState(), action) => { +ReducerRegistry.register('features/base/config', (state: IConfig = _getInitialState(), action: any) => { switch (action.type) { case UPDATE_CONFIG: return _updateConfig(state, action); @@ -139,12 +141,12 @@ function _getInitialState() { * Reduces a specific Redux action SET_CONFIG of the feature * base/lib-jitsi-meet. * - * @param {Object} state - The Redux state of the feature base/config. + * @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, { config }) { +function _setConfig(state: IConfig, { config }: {config: IConfig}) { // 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. The downloading is planned for implementation in the @@ -187,10 +189,10 @@ function _setConfig(state, { config }) { /** * Processes the conferenceInfo object against the defaults. * - * @param {Object} config - The old config. + * @param {IConfig} config - The old config. * @returns {Object} The processed conferenceInfo object. */ -function _getConferenceInfo(config) { +function _getConferenceInfo(config: IConfig) { const { conferenceInfo } = config; if (conferenceInfo) { @@ -215,7 +217,7 @@ function _getConferenceInfo(config) { * @returns {Object} A config {@code Object} which is in the latest format * supported by jitsi-meet. */ -function _translateInterfaceConfig(oldValue: Object) { +function _translateInterfaceConfig(oldValue: IConfig) { const newValue = oldValue; if (!Array.isArray(oldValue.toolbarButtons) @@ -227,6 +229,7 @@ function _translateInterfaceConfig(oldValue: Object) { oldValue.toolbarConfig = {}; } + newValue.toolbarConfig = oldValue.toolbarConfig || {}; if (typeof oldValue.toolbarConfig.alwaysVisible !== 'boolean' && typeof interfaceConfig === 'object' && typeof interfaceConfig.TOOLBAR_ALWAYS_VISIBLE === 'boolean') { @@ -292,28 +295,29 @@ function _translateInterfaceConfig(oldValue: Object) { * @returns {Object} A config {@code Object} which is in the latest format * supported by jitsi-meet. */ -function _translateLegacyConfig(oldValue: Object) { +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]); + 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 ??= {}; // 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?.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].includes(c)); + = (newValue.conferenceInfo.alwaysVisible ?? []).filter(c => !CONFERENCE_HEADER_MAPPING[key].includes(c)); newValue.conferenceInfo.autoHide - = newValue.conferenceInfo.autoHide.filter(c => !CONFERENCE_HEADER_MAPPING[key].includes(c)); + = (newValue.conferenceInfo.autoHide ?? []).filter(c => !CONFERENCE_HEADER_MAPPING[key].includes(c)); } }); } @@ -422,7 +426,7 @@ function _translateLegacyConfig(oldValue: Object) { * @private * @returns {Object} The new state after the reduction of the specified action. */ -function _updateConfig(state, { config }) { +function _updateConfig(state: IConfig, { config }: {config: IConfig}) { const newState = _.merge({}, state, config); _cleanupConfig(newState); diff --git a/react/features/conference/components/constants.js b/react/features/conference/components/constants.ts similarity index 100% rename from react/features/conference/components/constants.js rename to react/features/conference/components/constants.ts