diff --git a/package-lock.json b/package-lock.json index 8ceb8ac694..9a28161a8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -121,7 +121,7 @@ "react-window": "1.8.6", "react-youtube": "7.13.1", "redux": "4.0.4", - "redux-thunk": "2.2.0", + "redux-thunk": "2.4.1", "resemblejs": "4.0.0", "seamless-scroll-polyfill": "2.1.8", "styled-components": "3.4.9", @@ -16941,9 +16941,12 @@ } }, "node_modules/redux-thunk": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.2.0.tgz", - "integrity": "sha1-5hWhbha0ehmlFXZhM9Hj6Zt4UuU=" + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz", + "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==", + "peerDependencies": { + "redux": "^4" + } }, "node_modules/regenerate": { "version": "1.4.2", @@ -33153,9 +33156,9 @@ } }, "redux-thunk": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.2.0.tgz", - "integrity": "sha1-5hWhbha0ehmlFXZhM9Hj6Zt4UuU=" + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz", + "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==" }, "regenerate": { "version": "1.4.2", diff --git a/package.json b/package.json index a1f51fed63..6ce5c0fcea 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "react-window": "1.8.6", "react-youtube": "7.13.1", "redux": "4.0.4", - "redux-thunk": "2.2.0", + "redux-thunk": "2.4.1", "resemblejs": "4.0.0", "seamless-scroll-polyfill": "2.1.8", "styled-components": "3.4.9", diff --git a/react/features/app/types.ts b/react/features/app/types.ts index a63baac3e1..e518c6748a 100644 --- a/react/features/app/types.ts +++ b/react/features/app/types.ts @@ -1,4 +1,5 @@ -import { AnyAction, Dispatch } from 'redux'; +import { AnyAction } from 'redux'; +import { ThunkDispatch } from 'redux-thunk'; import { IAnalyticsState } from '../analytics/reducer'; import { IAuthenticationState } from '../authentication/reducer'; @@ -75,7 +76,7 @@ import { IVideoSipGW } from '../videosipgw/reducer'; import { IVirtualBackground } from '../virtual-background/reducer'; export interface IStore { - dispatch: Dispatch; + dispatch: ThunkDispatch; getState: () => IState; } diff --git a/react/features/authentication/components/web/LoginDialog.tsx b/react/features/authentication/components/web/LoginDialog.tsx index cef839c6a0..e0d9b8c28e 100644 --- a/react/features/authentication/components/web/LoginDialog.tsx +++ b/react/features/authentication/components/web/LoginDialog.tsx @@ -145,7 +145,10 @@ class LoginDialog extends Component { dispatch } = this.props; const { password, username } = this.state; - const jid = toJid(username, configHosts); + const jid = toJid(username, configHosts ?? { + authdomain: '', + domain: '' + }); if (conference) { dispatch(authenticateAndUpgradeRole(jid, password, conference)); @@ -217,7 +220,8 @@ class LoginDialog extends Component { const { credentials } = error; if (credentials - && credentials.jid === toJid(username, configHosts) + && credentials.jid === toJid(username, configHosts ?? { authdomain: '', + domain: '' }) && credentials.password === password) { messageKey = t('dialog.incorrectPassword'); } diff --git a/react/features/base/conference/reducer.ts b/react/features/base/conference/reducer.ts index c955c0af4d..42d71308f0 100644 --- a/react/features/base/conference/reducer.ts +++ b/react/features/base/conference/reducer.ts @@ -60,7 +60,7 @@ export interface IConferenceState { password?: string; passwordRequired?: Object; pendingSubjectChange?: string; - room?: Object; + room?: string; startAudioMutedPolicy?: boolean; startReactionsMuted?: boolean; startVideoMutedPolicy?: boolean; diff --git a/react/features/base/config/actions.js b/react/features/base/config/actions.ts similarity index 92% rename from react/features/base/config/actions.js rename to react/features/base/config/actions.ts index 67be4a3114..5b7957b97e 100644 --- a/react/features/base/config/actions.js +++ b/react/features/base/config/actions.ts @@ -1,10 +1,9 @@ -// @flow - +// @ts-ignore import { jitsiLocalStorage } from '@jitsi/js-utils'; -import type { Dispatch } from 'redux'; +import { Dispatch } from 'redux'; -import { addKnownDomains } from '../known-domains'; -import { parseURIString } from '../util'; +import { addKnownDomains } from '../known-domains/actions'; +import { parseURIString } from '../util/uri'; import { CONFIG_WILL_LOAD, @@ -13,8 +12,9 @@ import { UPDATE_CONFIG, OVERWRITE_CONFIG } from './actionTypes'; +import { IConfig } from './configType'; import { _CONFIG_STORE_PREFIX } from './constants'; -import { setConfigFromURLParams } from './functions'; +import { setConfigFromURLParams } from './functions.any'; /** @@ -23,7 +23,7 @@ import { setConfigFromURLParams } from './functions'; * @param {Object} config - The new options (to add). * @returns {Function} */ -export function updateConfig(config: Object) { +export function updateConfig(config: IConfig) { return { type: UPDATE_CONFIG, config @@ -116,6 +116,8 @@ export function setConfig(config: Object = {}) { // On Web the config also comes from the window.config global, // but it is resolved in the loadConfig procedure. config, + + // @ts-ignore window.interfaceConfig, locationURL); @@ -142,6 +144,7 @@ export function storeConfig(baseURL: string, config: Object) { let b = false; try { + // @ts-ignore if (typeof window.config === 'undefined' || window.config !== config) { jitsiLocalStorage.setItem(`${_CONFIG_STORE_PREFIX}/${baseURL}`, JSON.stringify(config)); b = true; @@ -153,7 +156,7 @@ export function storeConfig(baseURL: string, config: Object) { // If base/config knows a domain, then the app knows it. if (b) { try { - dispatch(addKnownDomains(parseURIString(baseURL).host)); + dispatch(addKnownDomains(parseURIString(baseURL)?.host)); } catch (e) { // Ignore the error because the fiddling with "known domains" is // a side effect here. diff --git a/react/features/base/config/configType.ts b/react/features/base/config/configType.ts index bfe56a3c43..43e1d585ad 100644 --- a/react/features/base/config/configType.ts +++ b/react/features/base/config/configType.ts @@ -105,6 +105,7 @@ export interface IConfig { scriptURLs?: Array; }; apiLogLevels?: Array<'warn' | 'log' | 'error' | 'info' | 'debug'>; + appId?: string; audioLevelsInterval?: number; audioQuality?: { opusMaxAverageBitrate?: number | null; @@ -316,6 +317,8 @@ export interface IConfig { disabled?: boolean; }; gravatarBaseURL?: string; + guestDialOutStatusUrl?: string; + guestDialOutUrl?: string; helpCentreURL?: string; hiddenPremeetingButtons?: Array<'microphone' | 'camera' | 'select-background' | 'invite' | 'settings'>; hideAddRoomButton?: boolean; @@ -354,7 +357,7 @@ export interface IConfig { notifyAllParticipants?: boolean; }; localSubject?: string; - locationURL?: string; + locationURL?: URL; maxFullResolutionParticipants?: number; moderatedRoomServiceUrl?: string; mouseMoveCallbackInterval?: number; @@ -403,12 +406,14 @@ export interface IConfig { hideStorageWarning?: boolean; sharingEnabled?: boolean; }; + recordingSharingUrl?: string; remoteVideoMenu?: { disableGrantModerator?: boolean; disableKick?: boolean; disablePrivateChat?: boolean; disabled?: boolean; }; + replaceParticipant?: string; requireDisplayName?: boolean; resolution?: number; roomPasswordNumberOfDigits?: number; @@ -417,6 +422,7 @@ export interface IConfig { enabled?: boolean; mode?: 'always' | 'recording'; }; + serviceUrl?: string; speakerStatsOrder?: Array<'role' | 'name' | 'hasLeft'>; startAudioMuted?: boolean; startAudioOnly?: boolean; @@ -481,4 +487,5 @@ export interface IConfig { webrtcIceTcpDisable?: boolean; webrtcIceUdpDisable?: boolean; websocket?: string; + websocketKeepAliveUrl?: string; } diff --git a/react/features/base/config/configWhitelist.js b/react/features/base/config/configWhitelist.ts similarity index 100% rename from react/features/base/config/configWhitelist.js rename to react/features/base/config/configWhitelist.ts diff --git a/react/features/base/config/constants.js b/react/features/base/config/constants.ts similarity index 100% rename from react/features/base/config/constants.js rename to react/features/base/config/constants.ts diff --git a/react/features/base/config/extraConfigWhitelist.js b/react/features/base/config/extraConfigWhitelist.ts similarity index 100% rename from react/features/base/config/extraConfigWhitelist.js rename to react/features/base/config/extraConfigWhitelist.ts diff --git a/react/features/base/config/functions.any.js b/react/features/base/config/functions.any.ts similarity index 85% rename from react/features/base/config/functions.any.js rename to react/features/base/config/functions.any.ts index 8173ae84f0..aa011bc3e2 100644 --- a/react/features/base/config/functions.any.js +++ b/react/features/base/config/functions.any.ts @@ -1,12 +1,15 @@ -// @flow - +/* eslint-disable lines-around-comment */ +// @ts-ignore import Bourne from '@hapi/bourne'; +// @ts-ignore import { jitsiLocalStorage } from '@jitsi/js-utils'; import _ from 'lodash'; +import { IState } from '../../app/types'; import { browser } from '../lib-jitsi-meet'; -import { parseURLParams } from '../util'; +import { parseURLParams } from '../util/parseURLParams'; +import { IConfig } from './configType'; import CONFIG_WHITELIST from './configWhitelist'; import { _CONFIG_STORE_PREFIX, FEATURE_FLAGS } from './constants'; import INTERFACE_CONFIG_WHITELIST from './interfaceConfigWhitelist'; @@ -46,7 +49,7 @@ export function createFakeConfig(baseURL: string) { * @param {Object} state - The global state. * @returns {string} */ -export function getMeetingRegion(state: Object) { +export function getMeetingRegion(state: IState) { return state['features/base/config']?.deploymentInfo?.region || ''; } @@ -56,7 +59,7 @@ export function getMeetingRegion(state: Object) { * @param {Object} state - The global state. * @returns {boolean} */ -export function getMultipleVideoSupportFeatureFlag(state: Object) { +export function getMultipleVideoSupportFeatureFlag(state: IState) { return getFeatureFlag(state, FEATURE_FLAGS.MULTIPLE_VIDEO_STREAMS_SUPPORT) && getSourceNameSignalingFeatureFlag(state); } @@ -67,7 +70,7 @@ export function getMultipleVideoSupportFeatureFlag(state: Object) { * @param {Object} state - The global state. * @returns {boolean} */ -export function getMultipleVideoSendingSupportFeatureFlag(state: Object) { +export function getMultipleVideoSendingSupportFeatureFlag(state: IState) { return navigator.product !== 'ReactNative' && getMultipleVideoSupportFeatureFlag(state) && isUnifiedPlanEnabled(state); } @@ -78,7 +81,7 @@ export function getMultipleVideoSendingSupportFeatureFlag(state: Object) { * @param {Object} state - The global state. * @returns {boolean} */ -export function getSourceNameSignalingFeatureFlag(state: Object) { +export function getSourceNameSignalingFeatureFlag(state: IState) { return getFeatureFlag(state, FEATURE_FLAGS.SOURCE_NAME_SIGNALING); } @@ -89,10 +92,10 @@ export function getSourceNameSignalingFeatureFlag(state: Object) { * @param {string} featureFlag - The name of the feature flag. * @returns {boolean} */ -export function getFeatureFlag(state: Object, featureFlag: string) { +export function getFeatureFlag(state: IState, featureFlag: string) { const featureFlags = state['features/base/config']?.flags || {}; - return Boolean(featureFlags[featureFlag]); + return Boolean(featureFlags[featureFlag as keyof typeof featureFlags]); } /** @@ -101,7 +104,7 @@ export function getFeatureFlag(state: Object, featureFlag: string) { * @param {Object} state - The global state. * @returns {boolean} */ -export function getDisableRemoveRaisedHandOnFocus(state: Object) { +export function getDisableRemoveRaisedHandOnFocus(state: IState) { return state['features/base/config']?.disableRemoveRaisedHandOnFocus || false; } @@ -111,7 +114,7 @@ export function getDisableRemoveRaisedHandOnFocus(state: Object) { * @param {Object} state - The global state. * @returns {string} */ -export function getRecordingSharingUrl(state: Object) { +export function getRecordingSharingUrl(state: IState) { return state['features/base/config'].recordingSharingUrl; } @@ -136,7 +139,7 @@ export function getRecordingSharingUrl(state: Object) { * }. * @returns {void} */ -export function overrideConfigJSON(config: ?Object, interfaceConfig: ?Object, json: Object) { +export function overrideConfigJSON(config: IConfig, interfaceConfig: any, json: any) { for (const configName of Object.keys(json)) { let configObj; @@ -147,7 +150,7 @@ export function overrideConfigJSON(config: ?Object, interfaceConfig: ?Object, js } if (configObj) { const configJSON - = getWhitelistedJSON(configName, json[configName]); + = getWhitelistedJSON(configName as 'interfaceConfig' | 'config', json[configName]); if (!_.isEmpty(configJSON)) { logger.info( @@ -177,7 +180,7 @@ export function overrideConfigJSON(config: ?Object, interfaceConfig: ?Object, js * @returns {Object} - The result object only with the keys * that are whitelisted. */ -export function getWhitelistedJSON(configName: string, configJSON: Object): Object { +export function getWhitelistedJSON(configName: 'interfaceConfig' | 'config', configJSON: any): Object { if (configName === 'interfaceConfig') { return _.pick(configJSON, INTERFACE_CONFIG_WHITELIST); } else if (configName === 'config') { @@ -193,9 +196,9 @@ export function getWhitelistedJSON(configName: string, configJSON: Object): Obje * @param {Object} state - The state of the app. * @returns {boolean} */ -export function isNameReadOnly(state: Object): boolean { - return state['features/base/config'].disableProfile - || state['features/base/config'].readOnlyName; +export function isNameReadOnly(state: IState): boolean { + return Boolean(state['features/base/config'].disableProfile + || state['features/base/config'].readOnlyName); } /** @@ -204,7 +207,7 @@ export function isNameReadOnly(state: Object): boolean { * @param {Object} state - The state of the app. * @returns {boolean} */ -export function isDisplayNameVisible(state: Object): boolean { +export function isDisplayNameVisible(state: IState): boolean { return !state['features/base/config'].hideDisplayName; } @@ -214,7 +217,7 @@ export function isDisplayNameVisible(state: Object): boolean { * @param {Object} state - The state of the app. * @returns {boolean} */ -export function isUnifiedPlanEnabled(state: Object): boolean { +export function isUnifiedPlanEnabled(state: IState): boolean { const { enableUnifiedOnChrome = true } = state['features/base/config']; return browser.supportsUnifiedPlan() @@ -232,7 +235,7 @@ export function isUnifiedPlanEnabled(state: Object): boolean { * from {@code baseURL} and stored with {@code storeConfig} if it was restored; * otherwise, {@code undefined}. */ -export function restoreConfig(baseURL: string): ?Object { +export function restoreConfig(baseURL: string) { const key = `${_CONFIG_STORE_PREFIX}/${baseURL}`; const config = jitsiLocalStorage.getItem(key); @@ -266,9 +269,9 @@ export function restoreConfig(baseURL: string): ?Object { * @returns {void} */ export function setConfigFromURLParams( - config: ?Object, interfaceConfig: ?Object, location: Object) { + config: IConfig, interfaceConfig: any, location: string | URL) { const params = parseURLParams(location); - const json = {}; + const json: any = {}; // At this point we have: // params = { @@ -292,7 +295,7 @@ export function setConfigFromURLParams( for (const param of Object.keys(params)) { let base = json; const names = param.split('.'); - const last = names.pop(); + const last = names.pop() ?? ''; for (const name of names) { base = base[name] = base[name] || {}; diff --git a/react/features/base/config/functions.native.js b/react/features/base/config/functions.native.ts similarity index 70% rename from react/features/base/config/functions.native.js rename to react/features/base/config/functions.native.ts index c4d477c3b7..82692d9b83 100644 --- a/react/features/base/config/functions.native.js +++ b/react/features/base/config/functions.native.ts @@ -1,8 +1,10 @@ -// @flow - import { NativeModules } from 'react-native'; -import { getFeatureFlag, REPLACE_PARTICIPANT } from '../flags'; +import { IState } from '../../app/types'; +import { REPLACE_PARTICIPANT } from '../flags/constants'; +import { getFeatureFlag } from '../flags/functions'; + +import { IConfig } from './configType'; export * from './functions.any'; @@ -12,7 +14,8 @@ export * from './functions.any'; * @param {*} config - The configuration which needs to be cleaned up. * @returns {void} */ -export function _cleanupConfig(config: Object) { +export function _cleanupConfig(config: IConfig) { + config.analytics ??= {}; config.analytics.scriptURLs = []; if (NativeModules.AppInfo.LIBRE_BUILD) { delete config.analytics?.amplitudeAPPKey; @@ -29,6 +32,6 @@ export function _cleanupConfig(config: Object) { * @param {Object} state - The state of the app. * @returns {boolean} */ -export function getReplaceParticipant(state: Object): string { +export function getReplaceParticipant(state: IState): string { return getFeatureFlag(state, REPLACE_PARTICIPANT, false); } diff --git a/react/features/base/config/functions.web.js b/react/features/base/config/functions.web.ts similarity index 77% rename from react/features/base/config/functions.web.js rename to react/features/base/config/functions.web.ts index 07d7ba1032..3d19866ac6 100644 --- a/react/features/base/config/functions.web.js +++ b/react/features/base/config/functions.web.ts @@ -1,5 +1,6 @@ -// @flow +import { IState } from '../../app/types'; +import { IConfig } from './configType'; import { TOOLBAR_BUTTONS } from './constants'; export * from './functions.any'; @@ -10,7 +11,7 @@ export * from './functions.any'; * @param {*} config - The configuration which needs to be cleaned up. * @returns {void} */ -export function _cleanupConfig(config: Object) { // eslint-disable-line no-unused-vars +export function _cleanupConfig(config: IConfig) { // eslint-disable-line @typescript-eslint/no-unused-vars } /** @@ -19,7 +20,7 @@ export function _cleanupConfig(config: Object) { // eslint-disable-line no-unuse * @param {Object} state - The state of the app. * @returns {string} */ -export function getDialOutStatusUrl(state: Object): string { +export function getDialOutStatusUrl(state: IState): string | undefined { return state['features/base/config'].guestDialOutStatusUrl; } @@ -29,7 +30,7 @@ export function getDialOutStatusUrl(state: Object): string { * @param {Object} state - The state of the app. * @returns {string} */ -export function getDialOutUrl(state: Object): string { +export function getDialOutUrl(state: IState): string | undefined { return state['features/base/config'].guestDialOutUrl; } @@ -39,7 +40,7 @@ export function getDialOutUrl(state: Object): string { * @param {Object} state - The state of the app. * @returns {boolean} */ -export function getReplaceParticipant(state: Object): string { +export function getReplaceParticipant(state: IState): string | undefined { return state['features/base/config'].replaceParticipant; } @@ -49,7 +50,7 @@ export function getReplaceParticipant(state: Object): string { * @param {Object} state - The redux state. * @returns {Array} - The list of enabled toolbar buttons. */ -export function getToolbarButtons(state: Object): Array { +export function getToolbarButtons(state: IState): Array { const { toolbarButtons } = state['features/base/config']; return Array.isArray(toolbarButtons) ? toolbarButtons : TOOLBAR_BUTTONS; @@ -63,7 +64,7 @@ export function getToolbarButtons(state: Object): Array { * @param {Object|Array} state - The redux state or the array with the enabled buttons. * @returns {boolean} - True if the button is enabled and false otherwise. */ -export function isToolbarButtonEnabled(buttonName: string, state: Object | Array) { +export function isToolbarButtonEnabled(buttonName: string, state: IState | Array) { const buttons = Array.isArray(state) ? state : getToolbarButtons(state); return buttons.includes(buttonName); @@ -75,7 +76,7 @@ export function isToolbarButtonEnabled(buttonName: string, state: Object | Array * @param {Object} state - The state of the app. * @returns {boolean} */ -export function areAudioLevelsEnabled(state: Object): boolean { +export function areAudioLevelsEnabled(state: IState): boolean { // Default to false for React Native as audio levels are of no interest to the mobile app. return navigator.product !== 'ReactNative' && !state['features/base/config'].disableAudioLevels; } diff --git a/react/features/base/config/getRoomName.js b/react/features/base/config/getRoomName.ts similarity index 73% rename from react/features/base/config/getRoomName.js rename to react/features/base/config/getRoomName.ts index b787b6f7db..fa0ba05fd4 100644 --- a/react/features/base/config/getRoomName.js +++ b/react/features/base/config/getRoomName.ts @@ -1,13 +1,11 @@ -// @flow - -import { getBackendSafeRoomName } from '../util'; +import { getBackendSafeRoomName } from '../util/uri'; /** * Builds and returns the room name. * * @returns {string} */ -export default function getRoomName(): ?string { +export default function getRoomName(): string | undefined { const path = window.location.pathname; // The last non-directory component of the path (name) is the room. diff --git a/react/features/base/config/interfaceConfigWhitelist.js b/react/features/base/config/interfaceConfigWhitelist.ts similarity index 100% rename from react/features/base/config/interfaceConfigWhitelist.js rename to react/features/base/config/interfaceConfigWhitelist.ts diff --git a/react/features/base/config/logger.js b/react/features/base/config/logger.ts similarity index 90% rename from react/features/base/config/logger.js rename to react/features/base/config/logger.ts index 86b320c93a..3cc2d0acb9 100644 --- a/react/features/base/config/logger.js +++ b/react/features/base/config/logger.ts @@ -1,5 +1,3 @@ -// @flow - import { getLogger } from '../logging/functions'; export default getLogger('features/base/config'); diff --git a/react/features/base/config/middleware.js b/react/features/base/config/middleware.ts similarity index 87% rename from react/features/base/config/middleware.js rename to react/features/base/config/middleware.ts index ea2aa0c203..82adeb27d4 100644 --- a/react/features/base/config/middleware.js +++ b/react/features/base/config/middleware.ts @@ -1,9 +1,13 @@ +import { AnyAction } from 'redux'; + +import { IStore } from '../../app/types'; import { getFeatureFlag } from '../flags/functions'; -import { MiddlewareRegistry } from '../redux'; -import { updateSettings } from '../settings'; +import MiddlewareRegistry from '../redux/MiddlewareRegistry'; +import { updateSettings } from '../settings/actions'; import { SET_CONFIG, OVERWRITE_CONFIG } from './actionTypes'; import { updateConfig } from './actions'; +import { IConfig } from './configType'; /** * The middleware of the feature {@code base/config}. @@ -37,7 +41,7 @@ MiddlewareRegistry.register(store => next => action => { * @private * @returns {*} The return value of {@code next(action)}. */ -function _setConfig({ dispatch, getState }, 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. @@ -46,7 +50,7 @@ function _setConfig({ dispatch, getState }, next, action) { // Update the config with user defined settings. const settings = state['features/base/settings']; - const config = {}; + const config: IConfig = {}; if (typeof settings.disableP2P !== 'undefined') { config.p2p = { enabled: !settings.disableP2P }; @@ -77,7 +81,9 @@ function _setConfig({ dispatch, getState }, next, action) { // 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. + // @ts-ignore if (typeof window.config !== 'undefined') { + // @ts-ignore window.config = state['features/base/config']; } @@ -96,7 +102,7 @@ function _setConfig({ dispatch, getState }, next, action) { * @private * @returns {*} The return value of {@code next(action)}. */ -function _updateSettings({ dispatch }, next, action) { +function _updateSettings({ dispatch }: IStore, next: Function, action: AnyAction) { const { config: { doNotFlipLocalVideo } } = action; if (doNotFlipLocalVideo === true) { diff --git a/react/features/base/connection/actions.any.js b/react/features/base/connection/actions.any.ts similarity index 87% rename from react/features/base/connection/actions.any.js rename to react/features/base/connection/actions.any.ts index 4c36ef45af..8b1e66d4ca 100644 --- a/react/features/base/connection/actions.any.js +++ b/react/features/base/connection/actions.any.ts @@ -1,10 +1,11 @@ import _ from 'lodash'; +import { IState } from '../../app/types'; import { appendURLParam, getBackendSafeRoomName, parseURIString -} from '../util'; +} from '../util/uri'; import logger from './logger'; @@ -16,7 +17,7 @@ import logger from './logger'; * @returns {Object} The options to be passed to the constructor of * {@code JitsiConnection}. */ -export function constructOptions(state) { +export function constructOptions(state: IState) { // Deep clone the options to make sure we don't modify the object in the // redux store. const options = _.cloneDeep(state['features/base/config']); @@ -36,14 +37,14 @@ export function constructOptions(state) { if (bosh.startsWith('//')) { // By default our config.js doesn't include the protocol. - bosh = `${locationURL.protocol}${bosh}`; + bosh = `${locationURL?.protocol}${bosh}`; } else if (bosh.startsWith('/')) { // Handle relative URLs, which won't work on mobile. const { protocol, host, contextRoot - } = parseURIString(locationURL.href); + } = parseURIString(locationURL?.href); bosh = `${protocol}//${host}${contextRoot || '/'}${bosh.substr(1)}`; } @@ -60,10 +61,10 @@ export function constructOptions(state) { if (serviceUrl && room) { const roomName = getBackendSafeRoomName(room); - options.serviceUrl = appendURLParam(serviceUrl, 'room', roomName); + options.serviceUrl = appendURLParam(serviceUrl, 'room', roomName ?? ''); if (options.websocketKeepAliveUrl) { - options.websocketKeepAliveUrl = appendURLParam(options.websocketKeepAliveUrl, 'room', roomName); + options.websocketKeepAliveUrl = appendURLParam(options.websocketKeepAliveUrl, 'room', roomName ?? ''); } } diff --git a/react/features/base/connection/actions.native.js b/react/features/base/connection/actions.native.ts similarity index 95% rename from react/features/base/connection/actions.native.js rename to react/features/base/connection/actions.native.ts index 96bcb278e9..84831b16a2 100644 --- a/react/features/base/connection/actions.native.js +++ b/react/features/base/connection/actions.native.ts @@ -1,8 +1,9 @@ -// @flow - -import type { Dispatch } from 'redux'; +/* eslint-disable lines-around-comment */ +import { Dispatch } from 'redux'; +// @ts-ignore import { conferenceLeft, conferenceWillLeave } from '../conference/actions'; +// @ts-ignore import { getCurrentConference } from '../conference/functions'; import JitsiMeetJS, { JitsiConnectionEvents } from '../lib-jitsi-meet'; @@ -36,34 +37,34 @@ export type ConnectionFailedError = { /** * The XMPP user's ID. */ - jid: string, + jid: string; /** * The XMPP user's password. */ - password: string - }, + password: string; + }; /** * The details about the connection failed event. */ - details?: Object, + details?: Object; /** * Error message. */ - message?: string, + message?: string; /** * One of {@link JitsiConnectionError} constants (defined in * lib-jitsi-meet). */ - name: string, + name: string; /** * Indicates whether this event is recoverable or not. */ - recoverable?: boolean + recoverable?: boolean; }; /** @@ -73,7 +74,7 @@ export type ConnectionFailedError = { * @param {string} [password] - The XMPP user's password. * @returns {Function} */ -export function connect(id: ?string, password: ?string) { +export function connect(id?: string, password?: string) { return (dispatch: Dispatch, getState: Function) => { const state = getState(); const options = constructOptions(state); @@ -141,7 +142,7 @@ export function connect(id: ?string, password: ?string) { function _onConnectionFailed( // eslint-disable-line max-params err: string, msg: string, - credentials: Object, + credentials: any, details: Object) { unsubscribe(); dispatch( @@ -253,7 +254,7 @@ export function connectionFailed( * connection: JitsiConnection * }} */ -function _connectionWillConnect(connection) { +function _connectionWillConnect(connection: Object) { return { type: CONNECTION_WILL_CONNECT, connection @@ -286,7 +287,7 @@ export function disconnect() { promise = conference_.leave() - .catch(error => { + .catch((error: Error) => { logger.warn( 'JitsiConference.leave() rejected with:', error); @@ -328,7 +329,7 @@ export function disconnect() { * locationURL: URL * }} */ -export function setLocationURL(locationURL: ?URL) { +export function setLocationURL(locationURL?: URL) { return { type: SET_LOCATION_URL, locationURL diff --git a/react/features/base/connection/actions.web.js b/react/features/base/connection/actions.web.ts similarity index 75% rename from react/features/base/connection/actions.web.js rename to react/features/base/connection/actions.web.ts index b11c36fb54..a5b96c7988 100644 --- a/react/features/base/connection/actions.web.js +++ b/react/features/base/connection/actions.web.ts @@ -1,12 +1,8 @@ -// @flow +import { IStore } from '../../app/types'; +import { configureInitialDevices } from '../devices/actions'; +import { getBackendSafeRoomName } from '../util/uri'; -import type { Dispatch } from 'redux'; - -declare var APP: Object; -declare var config: Object; - -import { configureInitialDevices } from '../devices'; -import { getBackendSafeRoomName } from '../util'; +declare const APP: any; export { connectionDisconnected, @@ -24,7 +20,7 @@ export * from './actions.any'; * @returns {Promise} */ export function connect() { - return (dispatch: Dispatch, getState: Function) => { + return (dispatch: IStore['dispatch'], getState: IStore['getState']) => { const room = getBackendSafeRoomName(getState()['features/base/conference'].room); // XXX For web based version we use conference initialization logic @@ -32,7 +28,7 @@ export function connect() { return dispatch(configureInitialDevices()).then( () => APP.conference.init({ roomName: room - }).catch(error => { + }).catch((error: Error) => { APP.API.notifyConferenceLeft(APP.conference.roomName); logger.error(error); })); @@ -46,7 +42,7 @@ export function connect() { * request for call feedback. * @returns {Function} */ -export function disconnect(requestFeedback: boolean = false) { +export function disconnect(requestFeedback = false) { // XXX For web based version we use conference hanging up logic from the old // app. return () => APP.conference.hangup(requestFeedback); diff --git a/react/features/base/connection/constants.js b/react/features/base/connection/constants.ts similarity index 98% rename from react/features/base/connection/constants.js rename to react/features/base/connection/constants.ts index 895406e239..c8245657ce 100644 --- a/react/features/base/connection/constants.js +++ b/react/features/base/connection/constants.ts @@ -1,5 +1,3 @@ -// @flow - /** * The name of the {@code JitsiConnection} property which identifies the {@code JitsiConference} currently associated * with it. diff --git a/react/features/base/connection/functions.js b/react/features/base/connection/functions.ts similarity index 87% rename from react/features/base/connection/functions.js rename to react/features/base/connection/functions.ts index 0af78aa574..2440ef8906 100644 --- a/react/features/base/connection/functions.js +++ b/react/features/base/connection/functions.ts @@ -1,7 +1,6 @@ -/* @flow */ - -import { toState } from '../redux'; -import { toURLString } from '../util'; +import { IStateful } from '../app/types'; +import { toState } from '../redux/functions'; +import { toURLString } from '../util/uri'; import { getURLWithoutParams } from './utils'; @@ -13,7 +12,7 @@ import { getURLWithoutParams } from './utils'; * @returns {string|undefined} * @private */ -export function getCurrentConferenceUrl(stateful: Function | Object) { +export function getCurrentConferenceUrl(stateful: IStateful) { const state = toState(stateful); let currentUrl; @@ -40,7 +39,7 @@ export function getCurrentConferenceUrl(stateful: Function | Object) { * to be stripped. * @returns {string} */ -export function getInviteURL(stateOrGetState: Function | Object): string { +export function getInviteURL(stateOrGetState: IStateful): string { const state = toState(stateOrGetState); let locationURL = state instanceof URL @@ -75,7 +74,7 @@ export function getInviteURL(stateOrGetState: Function | Object): string { * @param {Function|Object} stateOrGetState - The redux state or redux's {@code getState} function. * @returns {boolean} */ -export function isInviteURLReady(stateOrGetState: Function | Object): boolean { +export function isInviteURLReady(stateOrGetState: IStateful): boolean { const state = toState(stateOrGetState); return Boolean(state['features/base/connection'].locationURL || state['features/base/config'].locationURL); @@ -90,6 +89,6 @@ export function isInviteURLReady(stateOrGetState: Function | Object): boolean { * @returns {string} A string in the form of a JID (i.e. * {@code user@server.com}). */ -export function toJid(id: string, { authdomain, domain }: Object): string { +export function toJid(id: string, { authdomain, domain }: { authdomain?: string; domain?: string; }): string { return id.indexOf('@') >= 0 ? id : `${id}@${authdomain || domain}`; } diff --git a/react/features/base/connection/logger.js b/react/features/base/connection/logger.ts similarity index 91% rename from react/features/base/connection/logger.js rename to react/features/base/connection/logger.ts index dc234ff788..ab9f232d03 100644 --- a/react/features/base/connection/logger.js +++ b/react/features/base/connection/logger.ts @@ -1,5 +1,3 @@ -// @flow - import { getLogger } from '../logging/functions'; export default getLogger('features/base/connection'); diff --git a/react/features/base/devices/actions.js b/react/features/base/devices/actions.ts similarity index 82% rename from react/features/base/devices/actions.js rename to react/features/base/devices/actions.ts index 3eebe0ec8e..16330b1e31 100644 --- a/react/features/base/devices/actions.js +++ b/react/features/base/devices/actions.ts @@ -1,8 +1,7 @@ +import { IStore } from '../../app/types'; import JitsiMeetJS from '../lib-jitsi-meet'; -import { - getUserSelectedOutputDeviceId, - updateSettings -} from '../settings'; +import { updateSettings } from '../settings/actions'; +import { getUserSelectedOutputDeviceId } from '../settings/functions.any'; import { ADD_PENDING_DEVICE_REQUEST, @@ -55,7 +54,7 @@ const DEVICE_TYPE_TO_SETTINGS_KEYS = { * request: Object * }} */ -export function addPendingDeviceRequest(request) { +export function addPendingDeviceRequest(request: Object) { return { type: ADD_PENDING_DEVICE_REQUEST, request @@ -68,7 +67,7 @@ export function addPendingDeviceRequest(request) { * @returns {Function} */ export function configureInitialDevices() { - return (dispatch, getState) => { + return (dispatch: IStore['dispatch'], getState: IStore['getState']) => { const deviceLabels = getDevicesFromURL(getState()); let updateSettingsPromise; @@ -86,9 +85,9 @@ export function configureInitialDevices() { name: 'setDevice', device: { kind: key.toLowerCase(), - label: deviceLabels[key] + label: deviceLabels[key as keyof typeof deviceLabels] }, - // eslint-disable-next-line no-empty-function + // eslint-disable-next-line @typescript-eslint/no-empty-function responseCallback() {} })); }); @@ -96,14 +95,17 @@ export function configureInitialDevices() { return; } - const newSettings = {}; + const newSettings: any = {}; Object.keys(deviceLabels).forEach(key => { - const label = deviceLabels[key]; + const label = deviceLabels[key as keyof typeof deviceLabels]; + + // @ts-ignore const deviceId = getDeviceIdByLabel(state, label, key); if (deviceId) { - const settingsTranslationMap = DEVICE_TYPE_TO_SETTINGS_KEYS[key]; + const settingsTranslationMap = DEVICE_TYPE_TO_SETTINGS_KEYS[ + key as keyof typeof DEVICE_TYPE_TO_SETTINGS_KEYS]; newSettings[settingsTranslationMap.currentDeviceId] = deviceId; newSettings[settingsTranslationMap.userSelectedDeviceId] = deviceId; @@ -135,12 +137,12 @@ export function configureInitialDevices() { * @returns {Function} */ export function getAvailableDevices() { - return dispatch => new Promise(resolve => { + return (dispatch: IStore['dispatch']) => new Promise(resolve => { const { mediaDevices } = JitsiMeetJS; if (mediaDevices.isDeviceListAvailable() && mediaDevices.isDeviceChangeAvailable()) { - mediaDevices.enumerateDevices(devices => { + mediaDevices.enumerateDevices((devices: any) => { dispatch(updateDeviceList(devices)); resolve(devices); @@ -163,7 +165,7 @@ export function getAvailableDevices() { * error: Object * }} */ -export function notifyCameraError(error) { +export function notifyCameraError(error: Error) { return { type: NOTIFY_CAMERA_ERROR, error @@ -182,7 +184,7 @@ export function notifyCameraError(error) { * error: Object * }} */ -export function notifyMicError(error) { +export function notifyMicError(error: Error) { return { type: NOTIFY_MIC_ERROR, error @@ -211,7 +213,7 @@ export function removePendingDeviceRequests() { * deviceId: string * }} */ -export function setAudioInputDevice(deviceId) { +export function setAudioInputDevice(deviceId: string) { return { type: SET_AUDIO_INPUT_DEVICE, deviceId @@ -225,8 +227,8 @@ export function setAudioInputDevice(deviceId) { * @param {string} deviceId - The id of the new audio input device. * @returns {Function} */ -export function setAudioInputDeviceAndUpdateSettings(deviceId) { - return function(dispatch, getState) { +export function setAudioInputDeviceAndUpdateSettings(deviceId: string) { + return function(dispatch: IStore['dispatch'], getState: IStore['getState']) { const deviceLabel = getDeviceLabelById(getState(), deviceId, 'audioInput'); dispatch(setAudioInputDevice(deviceId)); @@ -243,8 +245,8 @@ export function setAudioInputDeviceAndUpdateSettings(deviceId) { * @param {string} deviceId - The id of the new output device. * @returns {Function} */ -export function setAudioOutputDevice(deviceId) { - return function(dispatch, getState) { +export function setAudioOutputDevice(deviceId: string) { + return function(dispatch: IStore['dispatch'], getState: IStore['getState']) { const deviceLabel = getDeviceLabelById(getState(), deviceId, 'audioOutput'); return setAudioOutputDeviceId(deviceId, dispatch, true, deviceLabel); @@ -260,7 +262,7 @@ export function setAudioOutputDevice(deviceId) { * deviceId: string * }} */ -export function setVideoInputDevice(deviceId) { +export function setVideoInputDevice(deviceId: string) { return { type: SET_VIDEO_INPUT_DEVICE, deviceId @@ -274,8 +276,8 @@ export function setVideoInputDevice(deviceId) { * @param {string} deviceId - The id of the new video input device. * @returns {Function} */ -export function setVideoInputDeviceAndUpdateSettings(deviceId) { - return function(dispatch, getState) { +export function setVideoInputDeviceAndUpdateSettings(deviceId: string) { + return function(dispatch: IStore['dispatch'], getState: IStore['getState']) { const deviceLabel = getDeviceLabelById(getState(), deviceId, 'videoInput'); dispatch(setVideoInputDevice(deviceId)); @@ -296,7 +298,7 @@ export function setVideoInputDeviceAndUpdateSettings(deviceId) { * devices: Array * }} */ -export function updateDeviceList(devices) { +export function updateDeviceList(devices: MediaDeviceInfo[]) { return { type: UPDATE_DEVICE_LIST, devices @@ -314,7 +316,7 @@ export function updateDeviceList(devices) { * oldDevices: Array * }} */ -export function checkAndNotifyForNewDevice(newDevices, oldDevices) { +export function checkAndNotifyForNewDevice(newDevices: MediaDeviceInfo[], oldDevices: MediaDeviceInfo[]) { return { type: CHECK_AND_NOTIFY_FOR_NEW_DEVICE, newDevices, @@ -331,7 +333,7 @@ export function checkAndNotifyForNewDevice(newDevices, oldDevices) { * permissions: Object * }} */ -export function devicePermissionsChanged(permissions) { +export function devicePermissionsChanged(permissions: Object) { return { type: DEVICE_PERMISSIONS_CHANGED, permissions diff --git a/react/features/base/devices/functions.js b/react/features/base/devices/functions.ts similarity index 74% rename from react/features/base/devices/functions.js rename to react/features/base/devices/functions.ts index 2782ea0e2e..fda8a36948 100644 --- a/react/features/base/devices/functions.js +++ b/react/features/base/devices/functions.ts @@ -1,12 +1,14 @@ -// @flow - +import { IState } from '../../app/types'; import JitsiMeetJS from '../lib-jitsi-meet'; -import { updateSettings } from '../settings'; -import { parseURLParams } from '../util'; +import { updateSettings } from '../settings/actions'; +import { ISettingsState } from '../settings/reducer'; +import { parseURLParams } from '../util/parseURLParams'; import logger from './logger'; +import { IDevicesState } from './reducer'; + -declare var APP: Object; +declare const APP: any; const webrtcKindToJitsiKindTranslator = { audioinput: 'audioInput', @@ -22,14 +24,16 @@ const webrtcKindToJitsiKindTranslator = { * @returns {boolean} - True if the labels are already initialized and false * otherwise. */ -export function areDeviceLabelsInitialized(state: Object) { +export function areDeviceLabelsInitialized(state: IState) { // TODO: Replace with something that doesn't use APP when the conference.js logic is reactified. if (APP.conference._localTracksInitialized) { return true; } for (const type of [ 'audioInput', 'audioOutput', 'videoInput' ]) { - if ((state['features/base/devices'].availableDevices[type] || []).find(d => Boolean(d.label))) { + const availableDevices = state['features/base/devices'].availableDevices; + + if ((availableDevices[type as keyof typeof availableDevices] || []).find(d => Boolean(d.label))) { return true; } } @@ -56,14 +60,15 @@ export function getAudioOutputDeviceId() { * of the preceding types. * @returns {string|undefined} */ -export function getDefaultDeviceId(state: Object, kind: string) { - const kindToSearch = webrtcKindToJitsiKindTranslator[kind] || kind; - const defaultDevice = (state['features/base/devices'].availableDevices[kindToSearch] || []) +export function getDefaultDeviceId(state: IState, kind: string) { + const kindToSearch = webrtcKindToJitsiKindTranslator[kind as keyof typeof webrtcKindToJitsiKindTranslator] || kind; + const availableDevices = state['features/base/devices'].availableDevices; + const defaultDevice = (availableDevices[kindToSearch as keyof typeof availableDevices] || []) .find(d => d.deviceId === 'default'); // Find the device with a matching group id. - const matchingDevice = (state['features/base/devices'].availableDevices[kindToSearch] || []) - .find(d => d.deviceId !== 'default' && d.groupId === defaultDevice.groupId); + const matchingDevice = (availableDevices[kindToSearch as keyof typeof availableDevices] || []) + .find(d => d.deviceId !== 'default' && d.groupId === defaultDevice?.groupId); if (matchingDevice) { return matchingDevice.deviceId; @@ -80,11 +85,12 @@ export function getDefaultDeviceId(state: Object, kind: string) { * of the preceding types. * @returns {string|undefined} */ -export function getDeviceIdByLabel(state: Object, label: string, kind: string) { - const kindToSearch = webrtcKindToJitsiKindTranslator[kind] || kind; +export function getDeviceIdByLabel(state: IState, label: string, kind: string) { + const kindToSearch = webrtcKindToJitsiKindTranslator[kind as keyof typeof webrtcKindToJitsiKindTranslator] || kind; + const availableDevices = state['features/base/devices'].availableDevices; const device - = (state['features/base/devices'].availableDevices[kindToSearch] || []) + = (availableDevices[kindToSearch as keyof typeof availableDevices] || []) .find(d => d.label === label); if (device) { @@ -102,11 +108,12 @@ export function getDeviceIdByLabel(state: Object, label: string, kind: string) { * of the preceding types. * @returns {string|undefined} */ -export function getDeviceLabelById(state: Object, id: string, kind: string) { - const kindToSearch = webrtcKindToJitsiKindTranslator[kind] || kind; +export function getDeviceLabelById(state: IState, id: string, kind: string) { + const kindToSearch = webrtcKindToJitsiKindTranslator[kind as keyof typeof webrtcKindToJitsiKindTranslator] || kind; + const availableDevices = state['features/base/devices'].availableDevices; const device - = (state['features/base/devices'].availableDevices[kindToSearch] || []) + = (availableDevices[kindToSearch as keyof typeof availableDevices] || []) .find(d => d.deviceId === id); if (device) { @@ -120,9 +127,9 @@ export function getDeviceLabelById(state: Object, id: string, kind: string) { * @param {Object} state - The redux state. * @returns {Object|undefined} */ -export function getDevicesFromURL(state: Object) { +export function getDevicesFromURL(state: IState) { const urlParams - = parseURLParams(state['features/base/connection'].locationURL); + = parseURLParams(state['features/base/connection'].locationURL ?? ''); const audioOutput = urlParams['devices.audioOutput']; const videoInput = urlParams['devices.videoInput']; @@ -132,7 +139,7 @@ export function getDevicesFromURL(state: Object) { return undefined; } - const devices = {}; + const devices: IDevicesState['availableDevices'] = {}; audioOutput && (devices.audioOutput = audioOutput); videoInput && (devices.videoInput = videoInput); @@ -150,7 +157,7 @@ export function getDevicesFromURL(state: Object) { * are device type and the values are arrays with devices matching the device * type. */ -export function groupDevicesByKind(devices: Object[]): Object { +export function groupDevicesByKind(devices: MediaDeviceInfo[]): IDevicesState['availableDevices'] { return { audioInput: devices.filter(device => device.kind === 'audioinput'), audioOutput: devices.filter(device => device.kind === 'audiooutput'), @@ -165,7 +172,7 @@ export function groupDevicesByKind(devices: Object[]): Object { * @private * @returns {Array} Filtered audio devices. */ -export function filterAudioDevices(devices: Object[]): Object { +export function filterAudioDevices(devices: MediaDeviceInfo[]) { return devices.filter(device => device.kind === 'audioinput'); } @@ -197,8 +204,8 @@ export function formatDeviceLabel(label: string) { * @param {Object} state - The state of the application. * @returns {Object[]} */ -export function getAudioInputDeviceData(state: Object) { - return state['features/base/devices'].availableDevices.audioInput.map( +export function getAudioInputDeviceData(state: IState) { + return state['features/base/devices'].availableDevices.audioInput?.map( ({ deviceId, label }) => { return { deviceId, @@ -213,8 +220,8 @@ export function getAudioInputDeviceData(state: Object) { * @param {Object} state - The state of the application. * @returns {Object[]} */ -export function getAudioOutputDeviceData(state: Object) { - return state['features/base/devices'].availableDevices.audioOutput.map( +export function getAudioOutputDeviceData(state: IState) { + return state['features/base/devices'].availableDevices.audioOutput?.map( ({ deviceId, label }) => { return { deviceId, @@ -229,8 +236,8 @@ export function getAudioOutputDeviceData(state: Object) { * @param {Object} state - The state of the application. * @returns {string[]} */ -export function getVideoDeviceIds(state: Object) { - return state['features/base/devices'].availableDevices.videoInput.map(({ deviceId }) => deviceId); +export function getVideoDeviceIds(state: IState) { + return state['features/base/devices'].availableDevices.videoInput?.map(({ deviceId }) => deviceId); } /** @@ -241,12 +248,14 @@ export function getVideoDeviceIds(state: Object) { * * @returns {boolean} */ -export function hasAvailableDevices(state: Object, type: string) { +export function hasAvailableDevices(state: IState, type: string) { if (state['features/base/devices'] === undefined) { return true; } - return state['features/base/devices'].availableDevices[type].length > 0; + const availableDevices = state['features/base/devices'].availableDevices; + + return Number(availableDevices[type as keyof typeof availableDevices]?.length) > 0; } /** @@ -260,10 +269,10 @@ export function hasAvailableDevices(state: Object, type: string) { * @returns {Promise} */ export function setAudioOutputDeviceId( - newId: string = 'default', + newId = 'default', dispatch: Function, - userSelection: boolean = false, - newLabel: ?string): Promise<*> { + userSelection = false, + newLabel?: string): Promise { logger.debug(`setAudioOutputDevice: ${String(newLabel)}[${newId}]`); @@ -275,7 +284,7 @@ export function setAudioOutputDeviceId( return JitsiMeetJS.mediaDevices.setAudioOutputDevice(newId) .then(() => { - const newSettings = { + const newSettings: Partial = { audioOutputDeviceId: newId, userSelectedAudioOutputDeviceId: undefined, userSelectedAudioOutputDeviceLabel: undefined diff --git a/react/features/base/devices/reducer.ts b/react/features/base/devices/reducer.ts index 293240c8c5..39ab4c0a44 100644 --- a/react/features/base/devices/reducer.ts +++ b/react/features/base/devices/reducer.ts @@ -29,9 +29,9 @@ const DEFAULT_STATE: IDevicesState = { export interface IDevicesState { availableDevices: { - audioInput: MediaDeviceInfo[]; - audioOutput: MediaDeviceInfo[]; - videoInput: MediaDeviceInfo[]; + audioInput?: MediaDeviceInfo[]; + audioOutput?: MediaDeviceInfo[]; + videoInput?: MediaDeviceInfo[]; }; pendingRequests: Object[]; permissions: { diff --git a/react/features/base/flags/actions.js b/react/features/base/flags/actions.ts similarity index 97% rename from react/features/base/flags/actions.js rename to react/features/base/flags/actions.ts index e99f64c1bb..457a840086 100644 --- a/react/features/base/flags/actions.js +++ b/react/features/base/flags/actions.ts @@ -1,5 +1,3 @@ -// @flow - import { UPDATE_FLAGS } from './actionTypes'; /** diff --git a/react/features/base/flags/constants.js b/react/features/base/flags/constants.ts similarity index 100% rename from react/features/base/flags/constants.js rename to react/features/base/flags/constants.ts diff --git a/react/features/base/flags/functions.js b/react/features/base/flags/functions.ts similarity index 77% rename from react/features/base/flags/functions.js rename to react/features/base/flags/functions.ts index a3c0aadcc0..c5e16fb1b1 100644 --- a/react/features/base/flags/functions.js +++ b/react/features/base/flags/functions.ts @@ -1,7 +1,7 @@ -// @flow - +// @ts-ignore import { getAppProp } from '../app'; -import { toState } from '../redux'; +import { IStateful } from '../app/types'; +import { toState } from '../redux/functions'; /** * Gets the value of a specific feature flag. @@ -14,11 +14,11 @@ import { toState } from '../redux'; * @returns {*} The value of the specified React {@code Component} prop of the * currently mounted {@code App}. */ -export function getFeatureFlag(stateful: Function | Object, flag: string, defaultValue: any) { +export function getFeatureFlag(stateful: IStateful, flag: string, defaultValue?: any) { const state = toState(stateful)['features/base/flags']; if (state) { - const value = state[flag]; + const value = state[flag as keyof typeof state]; if (typeof value !== 'undefined') { return value; diff --git a/react/features/base/known-domains/actions.js b/react/features/base/known-domains/actions.ts similarity index 98% rename from react/features/base/known-domains/actions.js rename to react/features/base/known-domains/actions.ts index aac8c5bdf5..d534512a81 100644 --- a/react/features/base/known-domains/actions.js +++ b/react/features/base/known-domains/actions.ts @@ -1,5 +1,3 @@ -// @flow - import { ADD_KNOWN_DOMAINS } from './actionTypes'; /** diff --git a/react/features/base/known-domains/middleware.js b/react/features/base/known-domains/middleware.ts similarity index 70% rename from react/features/base/known-domains/middleware.js rename to react/features/base/known-domains/middleware.ts index e04cca9ac8..d742c6fb69 100644 --- a/react/features/base/known-domains/middleware.js +++ b/react/features/base/known-domains/middleware.ts @@ -1,10 +1,10 @@ -// @flow - +// @ts-ignore import { getDefaultURL } from '../../app/functions'; -import { APP_WILL_MOUNT } from '../app'; -import { SET_ROOM } from '../conference'; -import { MiddlewareRegistry } from '../redux'; -import { parseURIString } from '../util'; +import { IStore } from '../../app/types'; +import { APP_WILL_MOUNT } from '../app/actionTypes'; +import { SET_ROOM } from '../conference/actionTypes'; +import MiddlewareRegistry from '../redux/MiddlewareRegistry'; +import { parseURIString } from '../util/uri'; import { addKnownDomains } from './actions'; @@ -32,10 +32,10 @@ MiddlewareRegistry.register(store => next => action => { * @private * @returns {Promise} */ -function _appWillMount({ dispatch, getState }) { +function _appWillMount({ dispatch, getState }: IStore) { const defaultURL = parseURIString(getDefaultURL(getState)); - dispatch(addKnownDomains(defaultURL.host)); + dispatch(addKnownDomains(defaultURL?.host)); } /** @@ -46,7 +46,7 @@ function _appWillMount({ dispatch, getState }) { * @private * @returns {Promise} */ -function _setRoom({ dispatch, getState }) { +function _setRoom({ dispatch, getState }: IStore) { const { locationURL } = getState()['features/base/connection']; let host; diff --git a/react/features/base/media/functions.js b/react/features/base/media/functions.ts similarity index 87% rename from react/features/base/media/functions.js rename to react/features/base/media/functions.ts index d02b0aacf8..da337637f8 100644 --- a/react/features/base/media/functions.js +++ b/react/features/base/media/functions.ts @@ -1,7 +1,6 @@ -/* @flow */ - -import { toState } from '../redux'; -import { getPropertyValue } from '../settings'; +import { IStateful } from '../app/types'; +import { toState } from '../redux/functions'; +import { getPropertyValue } from '../settings/functions.any'; import { VIDEO_MUTISM_AUTHORITY } from './constants'; @@ -32,7 +31,7 @@ const START_WITH_AUDIO_VIDEO_MUTED_SOURCES = { * {@code getState} function. * @returns {boolean} */ -export function isAudioMuted(stateful: Function | Object) { +export function isAudioMuted(stateful: IStateful) { return Boolean(toState(stateful)['features/base/media'].audio.muted); } @@ -43,7 +42,7 @@ export function isAudioMuted(stateful: Function | Object) { * {@code getState} function. * @returns {boolean} */ -export function isVideoMutedByAudioOnly(stateful: Function | Object) { +export function isVideoMutedByAudioOnly(stateful: IStateful) { return ( _isVideoMutedByAuthority(stateful, VIDEO_MUTISM_AUTHORITY.AUDIO_ONLY)); } @@ -60,7 +59,7 @@ export function isVideoMutedByAudioOnly(stateful: Function | Object) { * {@code videoMutismAuthority}, then {@code true}; otherwise, {@code false}. */ function _isVideoMutedByAuthority( - stateful: Function | Object, + stateful: IStateful, videoMutismAuthority: number) { const { muted } = toState(stateful)['features/base/media'].video; @@ -74,7 +73,7 @@ function _isVideoMutedByAuthority( * @param {Object|Function} stateful - The redux state object or {@code getState} function. * @returns {boolean} - The computed startWithAudioMuted value that will be used. */ -export function getStartWithAudioMuted(stateful: Object | Function) { +export function getStartWithAudioMuted(stateful: IStateful) { return Boolean(getPropertyValue(stateful, 'startWithAudioMuted', START_WITH_AUDIO_VIDEO_MUTED_SOURCES)); } @@ -84,7 +83,7 @@ export function getStartWithAudioMuted(stateful: Object | Function) { * @param {Object|Function} stateful - The redux state object or {@code getState} function. * @returns {boolean} - The computed startWithVideoMuted value that will be used. */ -export function getStartWithVideoMuted(stateful: Object | Function) { +export function getStartWithVideoMuted(stateful: IStateful) { return Boolean(getPropertyValue(stateful, 'startWithVideoMuted', START_WITH_AUDIO_VIDEO_MUTED_SOURCES)); } @@ -94,7 +93,7 @@ export function getStartWithVideoMuted(stateful: Object | Function) { * @param {Function|Object} stateful - The redux store, state, or {@code getState} function. * @returns {boolean} */ -export function isVideoMuted(stateful: Function | Object) { +export function isVideoMuted(stateful: IStateful) { return Boolean(toState(stateful)['features/base/media'].video.muted); } @@ -105,7 +104,7 @@ export function isVideoMuted(stateful: Function | Object) { * {@code getState} function. * @returns {boolean} */ -export function isVideoMutedByUser(stateful: Function | Object) { +export function isVideoMutedByUser(stateful: IStateful) { return _isVideoMutedByAuthority(stateful, VIDEO_MUTISM_AUTHORITY.USER); } @@ -120,7 +119,7 @@ export function isVideoMutedByUser(stateful: Function | Object) { * otherwise, false. */ export function shouldRenderVideoTrack( - videoTrack: ?{ muted: boolean, videoStarted: boolean }, + videoTrack: { muted: boolean; videoStarted: boolean; }, waitForVideoStarted: boolean) { return ( videoTrack diff --git a/react/features/base/media/logger.js b/react/features/base/media/logger.ts similarity index 90% rename from react/features/base/media/logger.js rename to react/features/base/media/logger.ts index 6468f3e4ed..e89c863252 100644 --- a/react/features/base/media/logger.js +++ b/react/features/base/media/logger.ts @@ -1,5 +1,3 @@ -// @flow - import { getLogger } from '../logging/functions'; export default getLogger('features/base/media'); diff --git a/react/features/base/participants/functions.ts b/react/features/base/participants/functions.ts index a076fb11bb..a2b0dabb46 100644 --- a/react/features/base/participants/functions.ts +++ b/react/features/base/participants/functions.ts @@ -80,16 +80,16 @@ export function getActiveSpeakersToBeDisplayed(stateful: IStateful) { } let availableSlotsForActiveSpeakers = visibleRemoteParticipants.size; - if (activeSpeakers.has(dominantSpeaker)) { - activeSpeakers.delete(dominantSpeaker); + if (activeSpeakers.has(dominantSpeaker ?? '')) { + activeSpeakers.delete(dominantSpeaker ?? ''); } // Add dominant speaker to the beginning of the list (not including self) since the active speaker list is always // alphabetically sorted. - if (dominantSpeaker && dominantSpeaker !== getLocalParticipant(state).id) { + if (dominantSpeaker && dominantSpeaker !== getLocalParticipant(state)?.id) { const updatedSpeakers = Array.from(activeSpeakers); - updatedSpeakers.splice(0, 0, [ dominantSpeaker, getParticipantById(state, dominantSpeaker)?.name ]); + updatedSpeakers.splice(0, 0, [ dominantSpeaker, getParticipantById(state, dominantSpeaker)?.name ?? '' ]); activeSpeakers = new Map(updatedSpeakers); } @@ -359,11 +359,11 @@ export function getParticipantDisplayName(stateful: IStateful, id: string): stri } if (participant.local) { - return defaultLocalDisplayName; + return defaultLocalDisplayName ?? ''; } } - return defaultRemoteDisplayName; + return defaultRemoteDisplayName ?? ''; } /** @@ -439,7 +439,7 @@ export function getPinnedParticipant(stateful: IStateful) { if (stageFilmstrip) { const { activeParticipants } = state['features/filmstrip']; - const id = activeParticipants.find((p: Participant) => p.pinned)?.participantId; + const id = activeParticipants.find(p => p.pinned)?.participantId; return id ? getParticipantById(stateful, id) : undefined; } diff --git a/react/features/base/participants/types.ts b/react/features/base/participants/types.ts index 7343b823df..3ac619859e 100644 --- a/react/features/base/participants/types.ts +++ b/react/features/base/participants/types.ts @@ -30,6 +30,7 @@ export interface Participant { export interface LocalParticipant extends Participant { audioOutputDeviceId?: string; cameraDeviceId?: string; + jwtId?: string; micDeviceId?: string; startWithAudioMuted?: boolean; startWithVideoMuted?: boolean; diff --git a/react/features/base/redux/functions.ts b/react/features/base/redux/functions.ts index d7dd9ae120..1008021342 100644 --- a/react/features/base/redux/functions.ts +++ b/react/features/base/redux/functions.ts @@ -1,6 +1,7 @@ import _ from 'lodash'; import { connect as reduxConnect } from 'react-redux'; +import { IState } from '../../app/types'; import { IStateful } from '../app/types'; /** @@ -135,7 +136,7 @@ function _set( * returned. * @returns {Object} The redux state. */ -export function toState(stateful: IStateful) { +export function toState(stateful: IStateful): IState { if (stateful) { if (typeof stateful === 'function') { return stateful(); @@ -149,5 +150,6 @@ export function toState(stateful: IStateful) { } } + // @ts-ignore return stateful; } diff --git a/react/features/base/redux/middleware.js b/react/features/base/redux/middleware.ts similarity index 99% rename from react/features/base/redux/middleware.js rename to react/features/base/redux/middleware.ts index e1aa95b65f..2dbb8f760c 100644 --- a/react/features/base/redux/middleware.js +++ b/react/features/base/redux/middleware.ts @@ -1,5 +1,3 @@ -// @flow - import _ from 'lodash'; import MiddlewareRegistry from './MiddlewareRegistry'; diff --git a/react/features/base/settings/actions.js b/react/features/base/settings/actions.ts similarity index 87% rename from react/features/base/settings/actions.js rename to react/features/base/settings/actions.ts index 0d195ccc77..aeb91eb569 100644 --- a/react/features/base/settings/actions.js +++ b/react/features/base/settings/actions.ts @@ -1,4 +1,5 @@ import { SETTINGS_UPDATED } from './actionTypes'; +import { ISettingsState } from './reducer'; /** * Create an action for when the settings are updated. @@ -23,7 +24,7 @@ import { SETTINGS_UPDATED } from './actionTypes'; * } * }} */ -export function updateSettings(settings) { +export function updateSettings(settings: Partial) { return { type: SETTINGS_UPDATED, settings diff --git a/react/features/base/settings/constants.js b/react/features/base/settings/constants.ts similarity index 92% rename from react/features/base/settings/constants.js rename to react/features/base/settings/constants.ts index 7cbbd98483..2cbd65d852 100644 --- a/react/features/base/settings/constants.js +++ b/react/features/base/settings/constants.ts @@ -1,5 +1,3 @@ -// @flow - /** * The default server URL to open if no other was specified. */ diff --git a/react/features/base/settings/functions.any.js b/react/features/base/settings/functions.any.ts similarity index 83% rename from react/features/base/settings/functions.any.js rename to react/features/base/settings/functions.any.ts index 1a4a03c661..47b75fe092 100644 --- a/react/features/base/settings/functions.any.js +++ b/react/features/base/settings/functions.any.ts @@ -1,10 +1,14 @@ -// @flow -import { CONFIG_WHITELIST } from '../config'; -import { getParticipantCount } from '../participants'; -import { toState } from '../redux'; -import { parseURLParams } from '../util'; +import { IState } from '../../app/types'; +import { IStateful } from '../app/types'; +import CONFIG_WHITELIST from '../config/configWhitelist'; +import { IConfigState } from '../config/reducer'; +import { IJwtState } from '../jwt/reducer'; +import { getParticipantCount } from '../participants/functions'; +import { toState } from '../redux/functions'; +import { parseURLParams } from '../util/parseURLParams'; import { DEFAULT_SERVER_URL } from './constants'; +import { ISettingsState } from './reducer'; /** * Returns the effective value of a configuration/preference/setting by applying @@ -23,9 +27,9 @@ import { DEFAULT_SERVER_URL } from './constants'; * @returns {any} */ export function getPropertyValue( - stateful: Object | Function, + stateful: IStateful, propertyName: string, - sources?: Object + sources?: any ) { // Default values don't play nicely with partial objects and we want to make // the function easy to use without exhaustively defining all flags: @@ -45,10 +49,10 @@ export function getPropertyValue( // jwt if (sources.jwt) { - const value = state['features/base/jwt'][propertyName]; + const value = state['features/base/jwt'][propertyName as keyof IJwtState]; if (typeof value !== 'undefined') { - return value[propertyName]; + return value[propertyName as keyof typeof value]; } } @@ -56,7 +60,7 @@ export function getPropertyValue( if (sources.urlParams) { if (CONFIG_WHITELIST.indexOf(propertyName) !== -1) { const urlParams - = parseURLParams(state['features/base/connection'].locationURL); + = parseURLParams(state['features/base/connection'].locationURL ?? ''); const value = urlParams[`config.${propertyName}`]; if (typeof value !== 'undefined') { @@ -67,7 +71,7 @@ export function getPropertyValue( // settings if (sources.settings) { - const value = state['features/base/settings'][propertyName]; + const value = state['features/base/settings'][propertyName as keyof ISettingsState]; if (typeof value !== 'undefined') { return value; @@ -76,7 +80,7 @@ export function getPropertyValue( // config if (sources.config) { - const value = state['features/base/config'][propertyName]; + const value = state['features/base/config'][propertyName as keyof IConfigState]; if (typeof value !== 'undefined') { return value; @@ -93,7 +97,7 @@ export function getPropertyValue( * {@code getState} function. * @returns {string} - The currently configured server URL. */ -export function getServerURL(stateful: Object | Function) { +export function getServerURL(stateful: IStateful) { const state = toState(stateful); return state['features/base/settings'].serverURL || DEFAULT_SERVER_URL; @@ -107,7 +111,7 @@ export function getServerURL(stateful: Object | Function) { * {@code getState} function. * @returns {string} */ -export function getUserSelectedCameraDeviceId(stateful: Object | Function) { +export function getUserSelectedCameraDeviceId(stateful: IStateful) { const state = toState(stateful); const { userSelectedCameraDeviceId, @@ -135,7 +139,7 @@ export function getUserSelectedCameraDeviceId(stateful: Object | Function) { * {@code getState} function. * @returns {string} */ -export function getUserSelectedMicDeviceId(stateful: Object | Function) { +export function getUserSelectedMicDeviceId(stateful: IStateful) { const state = toState(stateful); const { userSelectedMicDeviceId, @@ -163,7 +167,7 @@ export function getUserSelectedMicDeviceId(stateful: Object | Function) { * {@code getState} function. * @returns {string} */ -export function getUserSelectedOutputDeviceId(stateful: Object | Function) { +export function getUserSelectedOutputDeviceId(stateful: IStateful) { const state = toState(stateful); const { userSelectedAudioOutputDeviceId, @@ -202,13 +206,19 @@ export function getUserSelectedOutputDeviceId(stateful: Object | Function) { * @private * @returns {string} The preferred device ID to use for media. */ -function _getUserSelectedDeviceId(options) { +function _getUserSelectedDeviceId(options: { + availableDevices: MediaDeviceInfo[] | undefined; + matchRegex?: RegExp; + replacement?: string; + userSelectedDeviceId?: string; + userSelectedDeviceLabel?: string; +}) { const { availableDevices, - matchRegex, + matchRegex = '', userSelectedDeviceId, userSelectedDeviceLabel, - replacement + replacement = '' } = options; // If there is no label at all, there is no need to fall back to checking @@ -217,7 +227,7 @@ function _getUserSelectedDeviceId(options) { return userSelectedDeviceId; } - const foundMatchingBasedonDeviceId = availableDevices.find( + const foundMatchingBasedonDeviceId = availableDevices?.find( candidate => candidate.deviceId === userSelectedDeviceId); // Prioritize matching the deviceId @@ -228,7 +238,7 @@ function _getUserSelectedDeviceId(options) { const strippedDeviceLabel = matchRegex ? userSelectedDeviceLabel.replace(matchRegex, replacement) : userSelectedDeviceLabel; - const foundMatchBasedOnLabel = availableDevices.find(candidate => { + const foundMatchBasedOnLabel = availableDevices?.find(candidate => { const { label } = candidate; if (!label) { @@ -253,7 +263,7 @@ function _getUserSelectedDeviceId(options) { * @param {Object} state - The state of the application. * @returns {boolean} */ -export function shouldHideShareAudioHelper(state: Object): boolean { +export function shouldHideShareAudioHelper(state: IState): boolean | undefined { return state['features/base/settings'].hideShareAudioHelper; } @@ -264,7 +274,7 @@ export function shouldHideShareAudioHelper(state: Object): boolean { * @param {Object} state - Redux state. * @returns {boolean} */ -export function shouldHideSelfView(state: Object) { +export function shouldHideSelfView(state: IState) { return getParticipantCount(state) === 1 ? false : getHideSelfView(state); } @@ -274,6 +284,6 @@ export function shouldHideSelfView(state: Object) { * @param {Object} state - Redux state. * @returns {boolean} */ -export function getHideSelfView(state: Object) { +export function getHideSelfView(state: IState) { return state['features/base/config'].disableSelfView || state['features/base/settings'].disableSelfView; } diff --git a/react/features/base/settings/functions.native.js b/react/features/base/settings/functions.native.ts similarity index 98% rename from react/features/base/settings/functions.native.js rename to react/features/base/settings/functions.native.ts index e7c563ab71..f6f8907e14 100644 --- a/react/features/base/settings/functions.native.js +++ b/react/features/base/settings/functions.native.ts @@ -1,5 +1,3 @@ -// @flow - import { NativeModules } from 'react-native'; import DefaultPreference from 'react-native-default-preference'; @@ -31,6 +29,6 @@ export function handleCallIntegrationChange(disabled: boolean) { * @returns {void} */ export function handleCrashReportingChange(disabled: boolean) { - DefaultPreference.setName('jitsi-default-preferences').then( + DefaultPreference.setName('jitsi-default-preferences').then( // @ts-ignore DefaultPreference.set('isCrashReportingDisabled', disabled.toString())); } diff --git a/react/features/base/settings/functions.web.js b/react/features/base/settings/functions.web.ts similarity index 70% rename from react/features/base/settings/functions.web.js rename to react/features/base/settings/functions.web.ts index da8668a72f..0819fba6f1 100644 --- a/react/features/base/settings/functions.web.js +++ b/react/features/base/settings/functions.web.ts @@ -1,4 +1,5 @@ -// @flow +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { IState } from '../../app/types'; export * from './functions.any'; @@ -8,7 +9,7 @@ export * from './functions.any'; * @param {Object} state - The state of the application. * @returns {void} */ -export function getCurrentCameraDeviceId(state: Object) { +export function getCurrentCameraDeviceId(state: IState) { return getDeviceIdByType(state, 'isVideoTrack'); } @@ -18,7 +19,7 @@ export function getCurrentCameraDeviceId(state: Object) { * @param {Object} state - The state of the application. * @returns {void} */ -export function getCurrentMicDeviceId(state: Object) { +export function getCurrentMicDeviceId(state: IState) { return getDeviceIdByType(state, 'isAudioTrack'); } @@ -28,7 +29,7 @@ export function getCurrentMicDeviceId(state: Object) { * @param {Object} state - The state of the application. * @returns {void} */ -export function getCurrentOutputDeviceId(state: Object) { +export function getCurrentOutputDeviceId(state: IState) { return state['features/base/settings'].audioOutputDeviceId; } @@ -39,11 +40,11 @@ export function getCurrentOutputDeviceId(state: Object) { * @param {string} isType - Can be 'isVideoTrack' | 'isAudioTrack'. * @returns {string} */ -function getDeviceIdByType(state: Object, isType: string) { +function getDeviceIdByType(state: IState, isType: string) { const [ deviceId ] = state['features/base/tracks'] - .map(t => t.jitsiTrack) - .filter(t => t && t.isLocal() && t[isType]()) - .map(t => t.getDeviceId()); + .map(t => t.jitsiTrack) + .filter(t => t?.isLocal() && t[isType as keyof typeof t]()) + .map(t => t.getDeviceId()); return deviceId || ''; } @@ -54,7 +55,7 @@ function getDeviceIdByType(state: Object, isType: string) { * @param {Object} state - The state of the application. * @returns {string} */ -export function getDisplayName(state: Object): string { +export function getDisplayName(state: IState): string { return state['features/base/settings'].displayName || ''; } @@ -66,8 +67,8 @@ export function getDisplayName(state: Object): string { * @param {boolean} disabled - Whether call integration is disabled or not. * @returns {void} */ -export function handleCallIntegrationChange(disabled: boolean) { // eslint-disable-line no-unused-vars -} +// eslint-disable-next-line @typescript-eslint/no-empty-function, require-jsdoc +export function handleCallIntegrationChange(disabled: boolean) { } /** * Handles changes to the `disableCrashReporting` setting. @@ -76,5 +77,5 @@ export function handleCallIntegrationChange(disabled: boolean) { // eslint-disab * @param {boolean} disabled - Whether crash reporting is disabled or not. * @returns {void} */ -export function handleCrashReportingChange(disabled: boolean) { // eslint-disable-line no-unused-vars -} +// eslint-disable-next-line @typescript-eslint/no-empty-function, require-jsdoc +export function handleCrashReportingChange(disabled: boolean) { } diff --git a/react/features/base/settings/logger.js b/react/features/base/settings/logger.ts similarity index 91% rename from react/features/base/settings/logger.js rename to react/features/base/settings/logger.ts index 824bcf3e96..84b91a66c2 100644 --- a/react/features/base/settings/logger.js +++ b/react/features/base/settings/logger.ts @@ -1,5 +1,3 @@ -// @flow - import { getLogger } from '../logging/functions'; export default getLogger('features/base/settings'); diff --git a/react/features/base/settings/middleware.js b/react/features/base/settings/middleware.ts similarity index 79% rename from react/features/base/settings/middleware.js rename to react/features/base/settings/middleware.ts index c6d1f1a70b..d76491873d 100644 --- a/react/features/base/settings/middleware.js +++ b/react/features/base/settings/middleware.ts @@ -1,19 +1,26 @@ -// @flow +/* eslint-disable lines-around-comment */ import _ from 'lodash'; +import { AnyAction } from 'redux'; +import { IStore } from '../../app/types'; import { PREJOIN_INITIALIZED } from '../../prejoin/actionTypes'; +// @ts-ignore import { setPrejoinPageVisibility } from '../../prejoin/actions'; -import { APP_WILL_MOUNT } from '../app'; -import { setAudioOnly } from '../audio-only'; +import { APP_WILL_MOUNT } from '../app/actionTypes'; +import { setAudioOnly } from '../audio-only/actions'; import { SET_LOCATION_URL } from '../connection/actionTypes'; // minimize imports to avoid circular imports +// @ts-ignore import { getJwtName } from '../jwt/functions'; -import { getLocalParticipant, participantUpdated } from '../participants'; -import { MiddlewareRegistry } from '../redux'; -import { parseURLParams } from '../util'; +import { participantUpdated } from '../participants/actions'; +import { getLocalParticipant } from '../participants/functions'; +import MiddlewareRegistry from '../redux/MiddlewareRegistry'; +import { parseURLParams } from '../util/parseURLParams'; import { SETTINGS_UPDATED } from './actionTypes'; import { updateSettings } from './actions'; +// @ts-ignore import { handleCallIntegrationChange, handleCrashReportingChange } from './functions'; +import { ISettingsState } from './reducer'; /** @@ -57,7 +64,7 @@ MiddlewareRegistry.register(store => next => action => { * @private * @returns {void} */ -function _initializeShowPrejoin({ dispatch, getState }) { +function _initializeShowPrejoin({ dispatch, getState }: IStore) { const { userSelectedSkipPrejoin } = getState()['features/base/settings']; if (userSelectedSkipPrejoin) { @@ -72,7 +79,7 @@ function _initializeShowPrejoin({ dispatch, getState }) { * @private * @returns {void} */ -function _initializeCallIntegration({ getState }) { +function _initializeCallIntegration({ getState }: IStore) { const { disableCallIntegration } = getState()['features/base/settings']; if (typeof disableCallIntegration === 'boolean') { @@ -88,7 +95,7 @@ function _initializeCallIntegration({ getState }) { * @param {string} settingsField - The name of the settings field to map. * @returns {string} */ -function _mapSettingsFieldToParticipant(settingsField) { +function _mapSettingsFieldToParticipant(settingsField: string) { switch (settingsField) { case 'displayName': return 'name'; @@ -104,7 +111,9 @@ function _mapSettingsFieldToParticipant(settingsField) { * @private * @returns {void} */ -function _maybeHandleCallIntegrationChange({ settings: { disableCallIntegration } }) { +function _maybeHandleCallIntegrationChange({ settings: { disableCallIntegration } }: { + settings: Partial; +}) { if (typeof disableCallIntegration === 'boolean') { handleCallIntegrationChange(disableCallIntegration); } @@ -117,7 +126,9 @@ function _maybeHandleCallIntegrationChange({ settings: { disableCallIntegration * @private * @returns {void} */ -function _maybeCrashReportingChange({ settings: { disableCrashReporting } }) { +function _maybeCrashReportingChange({ settings: { disableCrashReporting } }: { + settings: Partial; +}) { if (typeof disableCrashReporting === 'boolean') { handleCrashReportingChange(disableCrashReporting); } @@ -132,8 +143,8 @@ function _maybeCrashReportingChange({ settings: { disableCrashReporting } }) { * @returns {void} */ function _maybeSetAudioOnly( - { dispatch }, - { settings: { startAudioOnly } }) { + { dispatch }: IStore, + { settings: { startAudioOnly } }: { settings: Partial; }) { if (typeof startAudioOnly === 'boolean') { dispatch(setAudioOnly(startAudioOnly)); } @@ -146,7 +157,7 @@ function _maybeSetAudioOnly( * @private * @returns {void} */ -function _maybeUpdateDisplayName({ dispatch, getState }) { +function _maybeUpdateDisplayName({ dispatch, getState }: IStore) { const state = getState(); const hasJwt = Boolean(state['features/base/jwt'].jwt); @@ -169,7 +180,7 @@ function _maybeUpdateDisplayName({ dispatch, getState }) { * @private * @returns {void} */ -function _updateLocalParticipant({ dispatch, getState }, action) { +function _updateLocalParticipant({ dispatch, getState }: IStore, action: AnyAction) { const { settings } = action; const localParticipant = getLocalParticipant(getState()); const newLocalParticipant = { @@ -178,12 +189,15 @@ function _updateLocalParticipant({ dispatch, getState }, action) { for (const key in settings) { if (settings.hasOwnProperty(key)) { - newLocalParticipant[_mapSettingsFieldToParticipant(key)] + newLocalParticipant[_mapSettingsFieldToParticipant(key) as keyof typeof newLocalParticipant] = settings[key]; } } - dispatch(participantUpdated(newLocalParticipant)); + dispatch(participantUpdated({ + ...newLocalParticipant, + id: newLocalParticipant.id ?? '' + })); } @@ -194,9 +208,9 @@ function _updateLocalParticipant({ dispatch, getState }, action) { * @private * @returns {void} */ -function _updateLocalParticipantFromUrl({ dispatch, getState }) { +function _updateLocalParticipantFromUrl({ dispatch, getState }: IStore) { const urlParams - = parseURLParams(getState()['features/base/connection'].locationURL); + = parseURLParams(getState()['features/base/connection'].locationURL ?? ''); const urlEmail = urlParams['userInfo.email']; const urlDisplayName = urlParams['userInfo.displayName']; diff --git a/react/features/base/settings/reducer.ts b/react/features/base/settings/reducer.ts index f496465529..6c00c2d4af 100644 --- a/react/features/base/settings/reducer.ts +++ b/react/features/base/settings/reducer.ts @@ -52,18 +52,18 @@ const DEFAULT_STATE: ISettingsState = { export interface ISettingsState { audioOutputDeviceId?: string | boolean; - avatarURL?: string | boolean; + avatarURL?: string; cameraDeviceId?: string | boolean; disableCallIntegration?: boolean; disableCrashReporting?: boolean; disableP2P?: boolean; disableSelfView?: boolean; - displayName?: string | boolean; - email?: string | boolean; + displayName?: string; + email?: string; hideShareAudioHelper?: boolean; localFlipX?: boolean; micDeviceId?: string | boolean; - serverURL?: string | boolean; + serverURL?: string; soundsIncomingMessage?: boolean; soundsParticipantJoined?: boolean; soundsParticipantKnocking?: boolean; @@ -73,12 +73,12 @@ export interface ISettingsState { startAudioOnly?: boolean; startWithAudioMuted?: boolean; startWithVideoMuted?: boolean; - userSelectedAudioOutputDeviceId?: string | boolean; - userSelectedAudioOutputDeviceLabel?: string | boolean; - userSelectedCameraDeviceId?: string | boolean; - userSelectedCameraDeviceLabel?: string | boolean; - userSelectedMicDeviceId?: string | boolean; - userSelectedMicDeviceLabel?: string | boolean; + userSelectedAudioOutputDeviceId?: string; + userSelectedAudioOutputDeviceLabel?: string; + userSelectedCameraDeviceId?: string; + userSelectedCameraDeviceLabel?: string; + userSelectedMicDeviceId?: string; + userSelectedMicDeviceLabel?: string; userSelectedNotifications?: { [key: string]: boolean; } | boolean; @@ -97,6 +97,7 @@ const filterSubtree: ISettingsState = {}; Object.keys(DEFAULT_STATE).forEach(key => { const key1 = key as keyof typeof filterSubtree; + // @ts-ignore filterSubtree[key1] = true; }); diff --git a/react/features/base/tracks/reducer.ts b/react/features/base/tracks/reducer.ts index 0b8d5b4ad0..b3d2226203 100644 --- a/react/features/base/tracks/reducer.ts +++ b/react/features/base/tracks/reducer.ts @@ -16,7 +16,7 @@ import { interface ITrack { isReceivingData: boolean; - jitsiTrack: Object; + jitsiTrack: any; lastMediaEvent?: string; local: boolean; mediaType: string; diff --git a/react/features/base/util/logger.js b/react/features/base/util/logger.ts similarity index 90% rename from react/features/base/util/logger.js rename to react/features/base/util/logger.ts index 82d31be58e..dc4e5f3b91 100644 --- a/react/features/base/util/logger.js +++ b/react/features/base/util/logger.ts @@ -1,5 +1,3 @@ -// @flow - import { getLogger } from '../logging/functions'; export default getLogger('features/base/util'); diff --git a/react/features/base/util/parseURLParams.ts b/react/features/base/util/parseURLParams.ts index de43b503b5..1289fda15c 100644 --- a/react/features/base/util/parseURLParams.ts +++ b/react/features/base/util/parseURLParams.ts @@ -24,7 +24,7 @@ const blacklist = [ '__proto__', 'constructor', 'prototype' ]; export function parseURLParams( url: URL | string, dontParse = false, - source = 'hash'): Object { + source = 'hash') { if (typeof url === 'string') { // eslint-disable-next-line no-param-reassign url = new URL(url); diff --git a/react/features/base/util/uri.ts b/react/features/base/util/uri.ts index 02f1fecad7..9fe1846193 100644 --- a/react/features/base/util/uri.ts +++ b/react/features/base/util/uri.ts @@ -330,7 +330,7 @@ export function parseStandardURIString(str: string) { * search: string * }} */ -export function parseURIString(uri?: string) { +export function parseURIString(uri?: string): any { if (typeof uri !== 'string') { return undefined; } diff --git a/react/features/filmstrip/components/web/Thumbnail.tsx b/react/features/filmstrip/components/web/Thumbnail.tsx index 375ccf042f..3fe2793d1a 100644 --- a/react/features/filmstrip/components/web/Thumbnail.tsx +++ b/react/features/filmstrip/components/web/Thumbnail.tsx @@ -1276,7 +1276,7 @@ function _mapStateToProps(state: IState, ownProps: any): Object { const { gifUrl: gifSrc } = getGifForParticipant(state, id); const mode = getGifDisplayMode(state); - const participantId = isLocal ? getLocalParticipant(state).id : participantID; + const participantId = isLocal ? getLocalParticipant(state)?.id : participantID; return { _audioTrack, diff --git a/react/features/polls/components/AbstractPollAnswer.tsx b/react/features/polls/components/AbstractPollAnswer.tsx index 99cbfb182a..f48dfd4a13 100644 --- a/react/features/polls/components/AbstractPollAnswer.tsx +++ b/react/features/polls/components/AbstractPollAnswer.tsx @@ -50,7 +50,7 @@ const AbstractPollAnswer = (Component: ComponentType) => (props: const poll: Poll = useSelector((state: IState) => state['features/polls'].polls[pollId]); - const { id: localId } = useSelector(getLocalParticipant); + const { id: localId } = useSelector(getLocalParticipant) ?? { id: '' }; const [ checkBoxStates, setCheckBoxState ] = useState(() => { if (poll.lastVote !== null) { diff --git a/react/features/polls/components/native/PollAnswer.tsx b/react/features/polls/components/native/PollAnswer.tsx index b5b7c3a296..7eaf22cb8a 100644 --- a/react/features/polls/components/native/PollAnswer.tsx +++ b/react/features/polls/components/native/PollAnswer.tsx @@ -33,7 +33,7 @@ const PollAnswer = (props: AbstractProps) => { <> { poll.question } { - t('polls.by', { name: localParticipant.name }) + t('polls.by', { name: localParticipant?.name }) } diff --git a/react/features/reactions/components/web/ReactionsMenu.tsx b/react/features/reactions/components/web/ReactionsMenu.tsx index 06a441505e..c4139dd3ff 100644 --- a/react/features/reactions/components/web/ReactionsMenu.tsx +++ b/react/features/reactions/components/web/ReactionsMenu.tsx @@ -5,7 +5,6 @@ import clsx from 'clsx'; import React, { Component } from 'react'; import { WithTranslation } from 'react-i18next'; import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; import { createReactionMenuEvent, createToolbarEvent } from '../../../analytics/AnalyticsEvents'; import { sendAnalytics } from '../../../analytics/functions'; @@ -53,7 +52,7 @@ interface Props extends WithTranslation { /** * The ID of the local participant. */ - _localParticipantID: String; + _localParticipantID?: string; /** * Whether or not the local participant's hand is raised. @@ -241,7 +240,7 @@ function mapStateToProps(state: IState) { const localParticipant = getLocalParticipant(state); return { - _localParticipantID: localParticipant.id, + _localParticipantID: localParticipant?.id, _isMobile: isMobileBrowser(), _isGifEnabled: isGifEnabled(state), _isGifMenuVisible: isGifsMenuOpen(state), @@ -258,10 +257,7 @@ function mapStateToProps(state: IState) { function mapDispatchToProps(dispatch: IStore['dispatch']) { return { dispatch, - ...bindActionCreators( - { - _dockToolbox: dockToolbox - }, dispatch) + _dockToolbox: (dock: boolean) => dispatch(dockToolbox(dock)) }; } diff --git a/react/features/reactions/functions.any.ts b/react/features/reactions/functions.any.ts index 298117726a..a88cd3c9b7 100644 --- a/react/features/reactions/functions.any.ts +++ b/react/features/reactions/functions.any.ts @@ -72,8 +72,8 @@ export async function sendReactionsWebhook(state: IState, reactions: Array (next: Function) => (action: any) const { disableReactionsModeration } = state['features/base/config']; const customActions = [ 'notify.reactionSounds' ]; - const customFunctions = [ () => dispatch(updateSettings({ + const customFunctions: Function[] = [ () => dispatch(updateSettings({ soundsReactions: false })) ]; diff --git a/react/features/video-menu/components/web/LocalVideoMenuTriggerButton.tsx b/react/features/video-menu/components/web/LocalVideoMenuTriggerButton.tsx index 2a0cf85bf7..f991ac3189 100644 --- a/react/features/video-menu/components/web/LocalVideoMenuTriggerButton.tsx +++ b/react/features/video-menu/components/web/LocalVideoMenuTriggerButton.tsx @@ -303,7 +303,7 @@ function _mapStateToProps(state: IState, ownProps: Partial) { _showLocalVideoFlipButton: !disableLocalVideoFlip && videoTrack?.videoType !== 'desktop', _showHideSelfViewButton: showHideSelfViewButton, _overflowDrawer: overflowDrawer, - _localParticipantId: localParticipant.id, + _localParticipantId: localParticipant?.id, _showConnectionInfo: showConnectionInfo, _showPinToStage: isStageFilmstripAvailable(state) };