Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

reducer.ts 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. import _ from 'lodash';
  2. import { CONFERENCE_INFO } from '../../conference/components/constants';
  3. import ReducerRegistry from '../redux/ReducerRegistry';
  4. import { equals } from '../redux/functions';
  5. import {
  6. UPDATE_CONFIG,
  7. CONFIG_WILL_LOAD,
  8. LOAD_CONFIG_ERROR,
  9. SET_CONFIG,
  10. OVERWRITE_CONFIG
  11. } from './actionTypes';
  12. import { IConfig } from './configType';
  13. // eslint-disable-next-line lines-around-comment
  14. // @ts-ignore
  15. import { _cleanupConfig } from './functions';
  16. declare let interfaceConfig: any;
  17. /**
  18. * The initial state of the feature base/config when executing in a
  19. * non-React Native environment. The mandatory configuration to be passed to
  20. * JitsiMeetJS#init(). The app will download config.js from the Jitsi Meet
  21. * deployment and take its values into account but the values below will be
  22. * enforced (because they are essential to the correct execution of the
  23. * application).
  24. *
  25. * @type {Object}
  26. */
  27. const INITIAL_NON_RN_STATE: IConfig = {
  28. };
  29. /**
  30. * The initial state of the feature base/config when executing in a React Native
  31. * environment. The mandatory configuration to be passed to JitsiMeetJS#init().
  32. * The app will download config.js from the Jitsi Meet deployment and take its
  33. * values into account but the values below will be enforced (because they are
  34. * essential to the correct execution of the application).
  35. *
  36. * @type {Object}
  37. */
  38. const INITIAL_RN_STATE: IConfig = {
  39. analytics: {},
  40. // FIXME The support for audio levels in lib-jitsi-meet polls the statistics
  41. // of WebRTC at a short interval multiple times a second. Unfortunately,
  42. // React Native is slow to fetch these statistics from the native WebRTC
  43. // API, through the React Native bridge and eventually to JavaScript.
  44. // Because the audio levels are of no interest to the mobile app, it is
  45. // fastest to merely disable them.
  46. disableAudioLevels: true,
  47. p2p: {
  48. disabledCodec: '',
  49. disableH264: false, // deprecated
  50. preferredCodec: 'H264',
  51. preferH264: true // deprecated
  52. }
  53. };
  54. /**
  55. * Mapping between old configs controlling the conference info headers visibility and the
  56. * new configs. Needed in order to keep backwards compatibility.
  57. */
  58. const CONFERENCE_HEADER_MAPPING: any = {
  59. hideConferenceTimer: [ 'conference-timer' ],
  60. hideConferenceSubject: [ 'subject' ],
  61. hideParticipantsStats: [ 'participants-count' ],
  62. hideRecordingLabel: [ 'recording' ]
  63. };
  64. export interface IConfigState extends IConfig {
  65. analysis?: {
  66. obfuscateRoomName?: boolean;
  67. };
  68. error?: Error;
  69. firefox_fake_device?: string;
  70. }
  71. ReducerRegistry.register<IConfigState>('features/base/config', (state = _getInitialState(), action): IConfigState => {
  72. switch (action.type) {
  73. case UPDATE_CONFIG:
  74. return _updateConfig(state, action);
  75. case CONFIG_WILL_LOAD:
  76. return {
  77. error: undefined,
  78. /**
  79. * The URL of the location associated with/configured by this
  80. * configuration.
  81. *
  82. * @type URL
  83. */
  84. locationURL: action.locationURL
  85. };
  86. case LOAD_CONFIG_ERROR:
  87. // XXX LOAD_CONFIG_ERROR is one of the settlement execution paths of
  88. // the asynchronous "loadConfig procedure/process" started with
  89. // CONFIG_WILL_LOAD. Due to the asynchronous nature of it, whoever
  90. // is settling the process needs to provide proof that they have
  91. // started it and that the iteration of the process being completed
  92. // now is still of interest to the app.
  93. if (state.locationURL === action.locationURL) {
  94. return {
  95. /**
  96. * The {@link Error} which prevented the loading of the
  97. * configuration of the associated {@code locationURL}.
  98. *
  99. * @type Error
  100. */
  101. error: action.error
  102. };
  103. }
  104. break;
  105. case SET_CONFIG:
  106. return _setConfig(state, action);
  107. case OVERWRITE_CONFIG:
  108. return {
  109. ...state,
  110. ...action.config
  111. };
  112. }
  113. return state;
  114. });
  115. /**
  116. * Gets the initial state of the feature base/config. The mandatory
  117. * configuration to be passed to JitsiMeetJS#init(). The app will download
  118. * config.js from the Jitsi Meet deployment and take its values into account but
  119. * the values below will be enforced (because they are essential to the correct
  120. * execution of the application).
  121. *
  122. * @returns {Object}
  123. */
  124. function _getInitialState() {
  125. return (
  126. navigator.product === 'ReactNative'
  127. ? INITIAL_RN_STATE
  128. : INITIAL_NON_RN_STATE);
  129. }
  130. /**
  131. * Reduces a specific Redux action SET_CONFIG of the feature
  132. * base/lib-jitsi-meet.
  133. *
  134. * @param {IConfig} state - The Redux state of the feature base/config.
  135. * @param {Action} action - The Redux action SET_CONFIG to reduce.
  136. * @private
  137. * @returns {Object} The new state after the reduction of the specified action.
  138. */
  139. function _setConfig(state: IConfig, { config }: { config: IConfig; }) {
  140. // eslint-disable-next-line no-param-reassign
  141. config = _translateLegacyConfig(config);
  142. const { audioQuality } = config;
  143. const hdAudioOptions = {};
  144. if (audioQuality?.stereo) {
  145. Object.assign(hdAudioOptions, {
  146. disableAP: true,
  147. enableNoAudioDetection: false,
  148. enableNoisyMicDetection: false,
  149. enableTalkWhileMuted: false
  150. });
  151. }
  152. const newState = _.merge(
  153. {},
  154. config,
  155. hdAudioOptions,
  156. { error: undefined },
  157. // The config of _getInitialState() is meant to override the config
  158. // downloaded from the Jitsi Meet deployment because the former contains
  159. // values that are mandatory.
  160. _getInitialState()
  161. );
  162. _cleanupConfig(newState);
  163. return equals(state, newState) ? state : newState;
  164. }
  165. /**
  166. * Processes the conferenceInfo object against the defaults.
  167. *
  168. * @param {IConfig} config - The old config.
  169. * @returns {Object} The processed conferenceInfo object.
  170. */
  171. function _getConferenceInfo(config: IConfig) {
  172. const { conferenceInfo } = config;
  173. if (conferenceInfo) {
  174. return {
  175. alwaysVisible: conferenceInfo.alwaysVisible ?? [ ...CONFERENCE_INFO.alwaysVisible ],
  176. autoHide: conferenceInfo.autoHide ?? [ ...CONFERENCE_INFO.autoHide ]
  177. };
  178. }
  179. return {
  180. ...CONFERENCE_INFO
  181. };
  182. }
  183. /**
  184. * Constructs a new config {@code Object}, if necessary, out of a specific
  185. * interface_config {@code Object} which is in the latest format supported by jitsi-meet.
  186. *
  187. * @param {Object} oldValue - The config {@code Object} which may or may not be
  188. * in the latest form supported by jitsi-meet and from which a new config
  189. * {@code Object} is to be constructed if necessary.
  190. * @returns {Object} A config {@code Object} which is in the latest format
  191. * supported by jitsi-meet.
  192. */
  193. function _translateInterfaceConfig(oldValue: IConfig) {
  194. const newValue = oldValue;
  195. if (!Array.isArray(oldValue.toolbarButtons)
  196. && typeof interfaceConfig === 'object' && Array.isArray(interfaceConfig.TOOLBAR_BUTTONS)) {
  197. newValue.toolbarButtons = interfaceConfig.TOOLBAR_BUTTONS;
  198. }
  199. if (!oldValue.toolbarConfig) {
  200. oldValue.toolbarConfig = {};
  201. }
  202. newValue.toolbarConfig = oldValue.toolbarConfig || {};
  203. if (typeof oldValue.toolbarConfig.alwaysVisible !== 'boolean'
  204. && typeof interfaceConfig === 'object'
  205. && typeof interfaceConfig.TOOLBAR_ALWAYS_VISIBLE === 'boolean') {
  206. newValue.toolbarConfig.alwaysVisible = interfaceConfig.TOOLBAR_ALWAYS_VISIBLE;
  207. }
  208. if (typeof oldValue.toolbarConfig.initialTimeout !== 'number'
  209. && typeof interfaceConfig === 'object'
  210. && typeof interfaceConfig.INITIAL_TOOLBAR_TIMEOUT === 'number') {
  211. newValue.toolbarConfig.initialTimeout = interfaceConfig.INITIAL_TOOLBAR_TIMEOUT;
  212. }
  213. if (typeof oldValue.toolbarConfig.timeout !== 'number'
  214. && typeof interfaceConfig === 'object'
  215. && typeof interfaceConfig.TOOLBAR_TIMEOUT === 'number') {
  216. newValue.toolbarConfig.timeout = interfaceConfig.TOOLBAR_TIMEOUT;
  217. }
  218. if (!oldValue.connectionIndicators
  219. && typeof interfaceConfig === 'object'
  220. && (interfaceConfig.hasOwnProperty('CONNECTION_INDICATOR_DISABLED')
  221. || interfaceConfig.hasOwnProperty('CONNECTION_INDICATOR_AUTO_HIDE_ENABLED')
  222. || interfaceConfig.hasOwnProperty('CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT'))) {
  223. newValue.connectionIndicators = {
  224. disabled: interfaceConfig.CONNECTION_INDICATOR_DISABLED,
  225. autoHide: interfaceConfig.CONNECTION_INDICATOR_AUTO_HIDE_ENABLED,
  226. autoHideTimeout: interfaceConfig.CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT
  227. };
  228. }
  229. if (oldValue.disableModeratorIndicator === undefined
  230. && typeof interfaceConfig === 'object'
  231. && interfaceConfig.hasOwnProperty('DISABLE_FOCUS_INDICATOR')) {
  232. newValue.disableModeratorIndicator = interfaceConfig.DISABLE_FOCUS_INDICATOR;
  233. }
  234. if (oldValue.defaultLocalDisplayName === undefined
  235. && typeof interfaceConfig === 'object'
  236. && interfaceConfig.hasOwnProperty('DEFAULT_LOCAL_DISPLAY_NAME')) {
  237. newValue.defaultLocalDisplayName = interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME;
  238. }
  239. if (oldValue.defaultRemoteDisplayName === undefined
  240. && typeof interfaceConfig === 'object'
  241. && interfaceConfig.hasOwnProperty('DEFAULT_REMOTE_DISPLAY_NAME')) {
  242. newValue.defaultRemoteDisplayName = interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME;
  243. }
  244. if (oldValue.defaultLogoUrl === undefined) {
  245. if (typeof interfaceConfig === 'object'
  246. && interfaceConfig.hasOwnProperty('DEFAULT_LOGO_URL')) {
  247. newValue.defaultLogoUrl = interfaceConfig.DEFAULT_LOGO_URL;
  248. } else {
  249. newValue.defaultLogoUrl = 'images/watermark.svg';
  250. }
  251. }
  252. return newValue;
  253. }
  254. /**
  255. * Constructs a new config {@code Object}, if necessary, out of a specific
  256. * config {@code Object} which is in the latest format supported by jitsi-meet.
  257. * Such a translation from an old config format to a new/the latest config
  258. * format is necessary because the mobile app bundles jitsi-meet and
  259. * lib-jitsi-meet at build time and does not download them at runtime from the
  260. * deployment on which it will join a conference.
  261. *
  262. * @param {Object} oldValue - The config {@code Object} which may or may not be
  263. * in the latest form supported by jitsi-meet and from which a new config
  264. * {@code Object} is to be constructed if necessary.
  265. * @returns {Object} A config {@code Object} which is in the latest format
  266. * supported by jitsi-meet.
  267. */
  268. function _translateLegacyConfig(oldValue: IConfig) {
  269. const newValue = _translateInterfaceConfig(oldValue);
  270. // Translate deprecated config values to new config values.
  271. const filteredConferenceInfo = Object.keys(CONFERENCE_HEADER_MAPPING).filter(key => oldValue[key as keyof IConfig]);
  272. if (filteredConferenceInfo.length) {
  273. newValue.conferenceInfo = _getConferenceInfo(oldValue);
  274. filteredConferenceInfo.forEach(key => {
  275. newValue.conferenceInfo = oldValue.conferenceInfo ?? {};
  276. // hideRecordingLabel does not mean not render it at all, but autoHide it
  277. if (key === 'hideRecordingLabel') {
  278. newValue.conferenceInfo.alwaysVisible
  279. = (newValue.conferenceInfo?.alwaysVisible ?? [])
  280. .filter(c => !CONFERENCE_HEADER_MAPPING[key].includes(c));
  281. newValue.conferenceInfo.autoHide
  282. = _.union(newValue.conferenceInfo.autoHide, CONFERENCE_HEADER_MAPPING[key]);
  283. } else {
  284. newValue.conferenceInfo.alwaysVisible
  285. = (newValue.conferenceInfo.alwaysVisible ?? [])
  286. .filter(c => !CONFERENCE_HEADER_MAPPING[key].includes(c));
  287. newValue.conferenceInfo.autoHide
  288. = (newValue.conferenceInfo.autoHide ?? []).filter(c => !CONFERENCE_HEADER_MAPPING[key].includes(c));
  289. }
  290. });
  291. }
  292. newValue.prejoinConfig = oldValue.prejoinConfig || {};
  293. if (oldValue.hasOwnProperty('prejoinPageEnabled')
  294. && !newValue.prejoinConfig.hasOwnProperty('enabled')
  295. ) {
  296. newValue.prejoinConfig.enabled = oldValue.prejoinPageEnabled;
  297. }
  298. newValue.disabledSounds = newValue.disabledSounds || [];
  299. if (oldValue.disableJoinLeaveSounds) {
  300. newValue.disabledSounds.unshift('PARTICIPANT_LEFT_SOUND', 'PARTICIPANT_JOINED_SOUND');
  301. }
  302. if (oldValue.disableRecordAudioNotification) {
  303. newValue.disabledSounds.unshift(
  304. 'RECORDING_ON_SOUND',
  305. 'RECORDING_OFF_SOUND',
  306. 'LIVE_STREAMING_ON_SOUND',
  307. 'LIVE_STREAMING_OFF_SOUND'
  308. );
  309. }
  310. if (oldValue.disableIncomingMessageSound) {
  311. newValue.disabledSounds.unshift('INCOMING_MSG_SOUND');
  312. }
  313. if (oldValue.stereo || oldValue.opusMaxAverageBitrate) {
  314. newValue.audioQuality = {
  315. opusMaxAverageBitrate: oldValue.audioQuality?.opusMaxAverageBitrate ?? oldValue.opusMaxAverageBitrate,
  316. stereo: oldValue.audioQuality?.stereo ?? oldValue.stereo
  317. };
  318. }
  319. newValue.e2ee = newValue.e2ee || {};
  320. if (oldValue.e2eeLabels) {
  321. newValue.e2ee.e2eeLabels = oldValue.e2eeLabels;
  322. }
  323. newValue.defaultLocalDisplayName
  324. = newValue.defaultLocalDisplayName || 'me';
  325. if (oldValue.hideAddRoomButton) {
  326. newValue.breakoutRooms = {
  327. /* eslint-disable-next-line no-extra-parens */
  328. ...(newValue.breakoutRooms || {}),
  329. hideAddRoomButton: oldValue.hideAddRoomButton
  330. };
  331. }
  332. newValue.defaultRemoteDisplayName
  333. = newValue.defaultRemoteDisplayName || 'Fellow Jitster';
  334. newValue.transcription = newValue.transcription || {};
  335. if (oldValue.transcribingEnabled !== undefined) {
  336. newValue.transcription = {
  337. ...newValue.transcription,
  338. enabled: oldValue.transcribingEnabled
  339. };
  340. }
  341. if (oldValue.transcribeWithAppLanguage !== undefined) {
  342. newValue.transcription = {
  343. ...newValue.transcription,
  344. useAppLanguage: oldValue.transcribeWithAppLanguage
  345. };
  346. }
  347. if (oldValue.preferredTranscribeLanguage !== undefined) {
  348. newValue.transcription = {
  349. ...newValue.transcription,
  350. preferredLanguage: oldValue.preferredTranscribeLanguage
  351. };
  352. }
  353. if (oldValue.autoCaptionOnRecord !== undefined) {
  354. newValue.transcription = {
  355. ...newValue.transcription,
  356. autoCaptionOnRecord: oldValue.autoCaptionOnRecord
  357. };
  358. }
  359. newValue.recordingService = newValue.recordingService || {};
  360. if (oldValue.fileRecordingsServiceEnabled !== undefined
  361. && newValue.recordingService.enabled === undefined) {
  362. newValue.recordingService = {
  363. ...newValue.recordingService,
  364. enabled: oldValue.fileRecordingsServiceEnabled
  365. };
  366. }
  367. if (oldValue.fileRecordingsServiceSharingEnabled !== undefined
  368. && newValue.recordingService.sharingEnabled === undefined) {
  369. newValue.recordingService = {
  370. ...newValue.recordingService,
  371. sharingEnabled: oldValue.fileRecordingsServiceSharingEnabled
  372. };
  373. }
  374. newValue.liveStreaming = newValue.liveStreaming || {};
  375. // Migrate config.liveStreamingEnabled
  376. if (oldValue.liveStreamingEnabled !== undefined) {
  377. newValue.liveStreaming = {
  378. ...newValue.liveStreaming,
  379. enabled: oldValue.liveStreamingEnabled
  380. };
  381. }
  382. // Migrate interfaceConfig.LIVE_STREAMING_HELP_LINK
  383. if (oldValue.liveStreaming === undefined
  384. && typeof interfaceConfig === 'object'
  385. && interfaceConfig.hasOwnProperty('LIVE_STREAMING_HELP_LINK')) {
  386. newValue.liveStreaming = {
  387. ...newValue.liveStreaming,
  388. helpLink: interfaceConfig.LIVE_STREAMING_HELP_LINK
  389. };
  390. }
  391. return newValue;
  392. }
  393. /**
  394. * Updates the stored configuration with the given extra options.
  395. *
  396. * @param {Object} state - The Redux state of the feature base/config.
  397. * @param {Action} action - The Redux action to reduce.
  398. * @private
  399. * @returns {Object} The new state after the reduction of the specified action.
  400. */
  401. function _updateConfig(state: IConfig, { config }: { config: IConfig; }) {
  402. const newState = _.merge({}, state, config);
  403. _cleanupConfig(newState);
  404. return equals(state, newState) ? state : newState;
  405. }