You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

middleware.ts 5.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. import { IReduxState, IStore } from '../app/types';
  2. import { getCurrentConference } from '../base/conference/functions';
  3. import {
  4. PARTICIPANT_JOINED,
  5. PARTICIPANT_LEFT,
  6. PARTICIPANT_UPDATED
  7. } from '../base/participants/actionTypes';
  8. import { PARTICIPANT_ROLE } from '../base/participants/constants';
  9. import {
  10. getLocalParticipant,
  11. getParticipantById,
  12. getParticipantDisplayName,
  13. isScreenShareParticipant,
  14. isWhiteboardParticipant
  15. } from '../base/participants/functions';
  16. import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
  17. import StateListenerRegistry from '../base/redux/StateListenerRegistry';
  18. import { PARTICIPANTS_PANE_OPEN } from '../participants-pane/actionTypes';
  19. import {
  20. CLEAR_NOTIFICATIONS,
  21. HIDE_NOTIFICATION,
  22. SHOW_NOTIFICATION
  23. } from './actionTypes';
  24. import {
  25. clearNotifications,
  26. hideNotification,
  27. showNotification,
  28. showParticipantJoinedNotification,
  29. showParticipantLeftNotification
  30. } from './actions';
  31. import {
  32. NOTIFICATION_TIMEOUT_TYPE,
  33. RAISE_HAND_NOTIFICATION_ID
  34. } from './constants';
  35. import { areThereNotifications, joinLeaveNotificationsDisabled } from './functions';
  36. /**
  37. * Map of timers.
  38. *
  39. * @type {Map}
  40. */
  41. const timers = new Map();
  42. /**
  43. * Function that creates a timeout id for specific notification.
  44. *
  45. * @param {Object} notification - Notification for which we want to create a timeout.
  46. * @param {Function} dispatch - The Redux dispatch function.
  47. * @returns {void}
  48. */
  49. const createTimeoutId = (notification: { timeout: number; uid: string; }, dispatch: IStore['dispatch']) => {
  50. const {
  51. timeout,
  52. uid
  53. } = notification;
  54. if (timeout) {
  55. const timerID = setTimeout(() => {
  56. dispatch(hideNotification(uid));
  57. }, timeout);
  58. timers.set(uid, timerID);
  59. }
  60. };
  61. /**
  62. * Returns notifications state.
  63. *
  64. * @param {Object} state - Global state.
  65. * @returns {Array<Object>} - Notifications state.
  66. */
  67. const getNotifications = (state: IReduxState) => {
  68. const _visible = areThereNotifications(state);
  69. const { notifications } = state['features/notifications'];
  70. return _visible ? notifications : [];
  71. };
  72. /**
  73. * Middleware that captures actions to display notifications.
  74. *
  75. * @param {Store} store - The redux store.
  76. * @returns {Function}
  77. */
  78. MiddlewareRegistry.register(store => next => action => {
  79. const { dispatch, getState } = store;
  80. const state = getState();
  81. switch (action.type) {
  82. case CLEAR_NOTIFICATIONS: {
  83. const _notifications = getNotifications(state);
  84. for (const notification of _notifications) {
  85. if (timers.has(notification.uid)) {
  86. const timeout = timers.get(notification.uid);
  87. clearTimeout(timeout);
  88. timers.delete(notification.uid);
  89. }
  90. }
  91. timers.clear();
  92. break;
  93. }
  94. case SHOW_NOTIFICATION: {
  95. if (timers.has(action.uid)) {
  96. const timer = timers.get(action.uid);
  97. clearTimeout(timer);
  98. timers.delete(action.uid);
  99. }
  100. createTimeoutId(action, dispatch);
  101. break;
  102. }
  103. case HIDE_NOTIFICATION: {
  104. const timer = timers.get(action.uid);
  105. clearTimeout(timer);
  106. timers.delete(action.uid);
  107. break;
  108. }
  109. case PARTICIPANT_JOINED: {
  110. const result = next(action);
  111. const { participant: p } = action;
  112. const { conference } = state['features/base/conference'];
  113. // Do not display notifications for the virtual screenshare and whiteboard tiles.
  114. if (conference
  115. && !p.local
  116. && !isScreenShareParticipant(p)
  117. && !isWhiteboardParticipant(p)
  118. && !joinLeaveNotificationsDisabled()
  119. && !p.isReplacing) {
  120. dispatch(showParticipantJoinedNotification(
  121. getParticipantDisplayName(state, p.id)
  122. ));
  123. }
  124. return result;
  125. }
  126. case PARTICIPANT_LEFT: {
  127. if (!joinLeaveNotificationsDisabled()) {
  128. const participant = getParticipantById(
  129. store.getState(),
  130. action.participant.id
  131. );
  132. // Do not display notifications for the virtual screenshare tiles.
  133. if (participant
  134. && !participant.local
  135. && !isScreenShareParticipant(participant)
  136. && !isWhiteboardParticipant(participant)
  137. && !action.participant.isReplaced) {
  138. dispatch(showParticipantLeftNotification(
  139. getParticipantDisplayName(state, participant.id)
  140. ));
  141. }
  142. }
  143. return next(action);
  144. }
  145. case PARTICIPANT_UPDATED: {
  146. const { disableModeratorIndicator } = state['features/base/config'];
  147. if (disableModeratorIndicator) {
  148. return next(action);
  149. }
  150. const { id, role } = action.participant;
  151. const localParticipant = getLocalParticipant(state);
  152. if (localParticipant?.id !== id) {
  153. return next(action);
  154. }
  155. const oldParticipant = getParticipantById(state, id);
  156. const oldRole = oldParticipant?.role;
  157. if (oldRole && oldRole !== role && role === PARTICIPANT_ROLE.MODERATOR) {
  158. store.dispatch(showNotification({
  159. titleKey: 'notify.moderator'
  160. },
  161. NOTIFICATION_TIMEOUT_TYPE.SHORT));
  162. }
  163. return next(action);
  164. }
  165. case PARTICIPANTS_PANE_OPEN: {
  166. store.dispatch(hideNotification(RAISE_HAND_NOTIFICATION_ID));
  167. break;
  168. }
  169. }
  170. return next(action);
  171. });
  172. /**
  173. * StateListenerRegistry provides a reliable way to detect the leaving of a
  174. * conference, where we need to clean up the notifications.
  175. */
  176. StateListenerRegistry.register(
  177. /* selector */ state => getCurrentConference(state),
  178. /* listener */ (conference, { dispatch }) => {
  179. if (!conference) {
  180. dispatch(clearNotifications());
  181. }
  182. }
  183. );