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 6.0KB

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