Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

middleware.ts 9.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. /* eslint-disable import/order */
  2. // @ts-ignore
  3. import { batch } from 'react-redux';
  4. // @ts-ignore
  5. import { createReactionSoundsDisabledEvent, sendAnalytics } from '../analytics';
  6. // @ts-ignore
  7. import { IStore } from '../app/types';
  8. import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app/actionTypes';
  9. import {
  10. CONFERENCE_JOIN_IN_PROGRESS,
  11. SET_START_REACTIONS_MUTED,
  12. setStartReactionsMuted
  13. // @ts-ignore
  14. } from '../base/conference';
  15. import {
  16. getParticipantById,
  17. getParticipantCount,
  18. isLocalParticipantModerator
  19. // @ts-ignore
  20. } from '../base/participants';
  21. // @ts-ignore
  22. import { MiddlewareRegistry } from '../base/redux';
  23. import { SETTINGS_UPDATED } from '../base/settings/actionTypes';
  24. // @ts-ignore
  25. import { updateSettings } from '../base/settings/actions';
  26. // @ts-ignore
  27. import { playSound, registerSound, unregisterSound } from '../base/sounds';
  28. // @ts-ignore
  29. import { getDisabledSounds } from '../base/sounds/functions.any';
  30. // @ts-ignore
  31. import { NOTIFICATION_TIMEOUT_TYPE, showNotification } from '../notifications';
  32. import {
  33. ADD_REACTION_BUFFER,
  34. FLUSH_REACTION_BUFFER,
  35. SEND_REACTIONS,
  36. PUSH_REACTIONS,
  37. SHOW_SOUNDS_NOTIFICATION
  38. } from './actionTypes';
  39. import {
  40. addReactionsToChat,
  41. flushReactionBuffer,
  42. pushReactions,
  43. sendReactions,
  44. setReactionQueue
  45. } from './actions.any';
  46. import { displayReactionSoundsNotification } from './actions.web';
  47. import {
  48. ENDPOINT_REACTION_NAME,
  49. RAISE_HAND_SOUND_ID,
  50. REACTIONS,
  51. REACTION_SOUND,
  52. SOUNDS_THRESHOLDS,
  53. MUTE_REACTIONS_COMMAND,
  54. MuteCommandAttributes
  55. } from './constants';
  56. import {
  57. getReactionMessageFromBuffer,
  58. getReactionsSoundsThresholds,
  59. getReactionsWithId,
  60. sendReactionsWebhook
  61. } from './functions.any';
  62. import logger from './logger';
  63. import { RAISE_HAND_SOUND_FILE } from './sounds';
  64. /**
  65. * Middleware which intercepts Reactions actions to handle changes to the
  66. * visibility timeout of the Reactions.
  67. *
  68. * @param {IStore} store - The redux store.
  69. * @returns {Function}
  70. */
  71. MiddlewareRegistry.register((store: IStore) => (next: Function) => (action:any) => {
  72. const { dispatch, getState } = store;
  73. switch (action.type) {
  74. case APP_WILL_MOUNT:
  75. batch(() => {
  76. Object.keys(REACTIONS).forEach(key => {
  77. for (let i = 0; i < SOUNDS_THRESHOLDS.length; i++) {
  78. dispatch(registerSound(
  79. `${REACTIONS[key].soundId}${SOUNDS_THRESHOLDS[i]}`,
  80. REACTIONS[key].soundFiles[i]
  81. )
  82. );
  83. }
  84. }
  85. );
  86. dispatch(registerSound(RAISE_HAND_SOUND_ID, RAISE_HAND_SOUND_FILE));
  87. });
  88. break;
  89. case APP_WILL_UNMOUNT:
  90. batch(() => {
  91. Object.keys(REACTIONS).forEach(key => {
  92. for (let i = 0; i < SOUNDS_THRESHOLDS.length; i++) {
  93. dispatch(unregisterSound(`${REACTIONS[key].soundId}${SOUNDS_THRESHOLDS[i]}`));
  94. }
  95. });
  96. dispatch(unregisterSound(RAISE_HAND_SOUND_ID));
  97. });
  98. break;
  99. case ADD_REACTION_BUFFER: {
  100. const { timeoutID, buffer } = getState()['features/reactions'];
  101. const { reaction } = action;
  102. clearTimeout(timeoutID);
  103. buffer.push(reaction);
  104. action.buffer = buffer;
  105. action.timeoutID = setTimeout(() => {
  106. dispatch(flushReactionBuffer());
  107. }, 500);
  108. break;
  109. }
  110. case CONFERENCE_JOIN_IN_PROGRESS: {
  111. const { conference } = action;
  112. conference.addCommandListener(
  113. MUTE_REACTIONS_COMMAND, ({ attributes }: { attributes: MuteCommandAttributes }, id: any) => {
  114. _onMuteReactionsCommand(attributes, id, store);
  115. });
  116. break;
  117. }
  118. case FLUSH_REACTION_BUFFER: {
  119. const state = getState();
  120. const { buffer } = state['features/reactions'];
  121. const participantCount = getParticipantCount(state);
  122. batch(() => {
  123. if (participantCount > 1) {
  124. dispatch(sendReactions());
  125. }
  126. dispatch(addReactionsToChat(getReactionMessageFromBuffer(buffer)));
  127. dispatch(pushReactions(buffer));
  128. });
  129. sendReactionsWebhook(state, buffer);
  130. break;
  131. }
  132. case PUSH_REACTIONS: {
  133. const state = getState();
  134. const { queue, notificationDisplayed } = state['features/reactions'];
  135. const { soundsReactions } = state['features/base/settings'];
  136. const disabledSounds = getDisabledSounds(state);
  137. const reactions = action.reactions;
  138. batch(() => {
  139. if (!notificationDisplayed && soundsReactions && !disabledSounds.includes(REACTION_SOUND)
  140. && displayReactionSoundsNotification) {
  141. dispatch(displayReactionSoundsNotification());
  142. }
  143. if (soundsReactions) {
  144. const reactionSoundsThresholds = getReactionsSoundsThresholds(reactions);
  145. reactionSoundsThresholds.forEach(reaction =>
  146. dispatch(playSound(`${REACTIONS[reaction.reaction].soundId}${reaction.threshold}`))
  147. );
  148. }
  149. dispatch(setReactionQueue([ ...queue, ...getReactionsWithId(reactions) ]));
  150. });
  151. break;
  152. }
  153. case SEND_REACTIONS: {
  154. const state = getState();
  155. const { buffer } = state['features/reactions'];
  156. const { conference } = state['features/base/conference'];
  157. if (conference) {
  158. conference.sendEndpointMessage('', {
  159. name: ENDPOINT_REACTION_NAME,
  160. reactions: buffer,
  161. timestamp: Date.now()
  162. });
  163. }
  164. break;
  165. }
  166. // Settings changed for mute reactions in the meeting
  167. case SET_START_REACTIONS_MUTED: {
  168. const state = getState();
  169. const { conference } = state['features/base/conference'];
  170. const { muted, updateBackend } = action;
  171. if (conference && isLocalParticipantModerator(state) && updateBackend) {
  172. conference.sendCommand(MUTE_REACTIONS_COMMAND, { attributes: { startReactionsMuted: Boolean(muted) } });
  173. }
  174. break;
  175. }
  176. case SETTINGS_UPDATED: {
  177. const { soundsReactions } = getState()['features/base/settings'];
  178. if (action.settings.soundsReactions === false && soundsReactions === true) {
  179. sendAnalytics(createReactionSoundsDisabledEvent());
  180. }
  181. break;
  182. }
  183. case SHOW_SOUNDS_NOTIFICATION: {
  184. const state = getState();
  185. const isModerator = isLocalParticipantModerator(state);
  186. const { disableReactionsModeration } = state['features/base/config'];
  187. const customActions = [ 'notify.reactionSounds' ];
  188. const customFunctions = [ () => dispatch(updateSettings({
  189. soundsReactions: false
  190. })) ];
  191. if (isModerator && !disableReactionsModeration) {
  192. customActions.push('notify.reactionSoundsForAll');
  193. customFunctions.push(() => batch(() => {
  194. dispatch(setStartReactionsMuted(true));
  195. dispatch(updateSettings({ soundsReactions: false }));
  196. }));
  197. }
  198. dispatch(showNotification({
  199. titleKey: 'toolbar.disableReactionSounds',
  200. customActionNameKey: customActions,
  201. customActionHandler: customFunctions
  202. }, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
  203. break;
  204. }
  205. }
  206. return next(action);
  207. });
  208. /**
  209. * Notifies this instance about a "Mute Reaction Sounds" command received by the Jitsi
  210. * conference.
  211. *
  212. * @param {Object} attributes - The attributes carried by the command.
  213. * @param {string} id - The identifier of the participant who issuing the
  214. * command. A notable idiosyncrasy to be mindful of here is that the command
  215. * may be issued by the local participant.
  216. * @param {Object} store - The redux store. Used to calculate and dispatch
  217. * updates.
  218. * @private
  219. * @returns {void}
  220. */
  221. function _onMuteReactionsCommand(attributes: MuteCommandAttributes = {}, id: string, store: IStore) {
  222. const state = store.getState();
  223. // We require to know who issued the command because (1) only a
  224. // moderator is allowed to send commands and (2) a command MUST be
  225. // issued by a defined commander.
  226. if (typeof id === 'undefined') {
  227. return;
  228. }
  229. const participantSendingCommand = getParticipantById(state, id);
  230. // The Command(s) API will send us our own commands and we don't want
  231. // to act upon them.
  232. if (participantSendingCommand.local) {
  233. return;
  234. }
  235. if (participantSendingCommand.role !== 'moderator') {
  236. logger.warn('Received mute-reactions command not from moderator');
  237. return;
  238. }
  239. const oldState = Boolean(state['features/base/conference'].startReactionsMuted);
  240. // @ts-ignore
  241. const newState = attributes.startReactionsMuted === 'true';
  242. if (oldState !== newState) {
  243. batch(() => {
  244. store.dispatch(setStartReactionsMuted(newState));
  245. store.dispatch(updateSettings({ soundsReactions: !newState }));
  246. });
  247. }
  248. }