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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  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. if (navigator.product !== 'ReactNative') {
  84. const _notifications = getNotifications(state);
  85. for (const notification of _notifications) {
  86. if (timers.has(notification.uid)) {
  87. const timeout = timers.get(notification.uid);
  88. clearTimeout(timeout);
  89. timers.delete(notification.uid);
  90. }
  91. }
  92. timers.clear();
  93. }
  94. break;
  95. }
  96. case SHOW_NOTIFICATION: {
  97. if (navigator.product !== 'ReactNative') {
  98. if (timers.has(action.uid)) {
  99. const timer = timers.get(action.uid);
  100. clearTimeout(timer);
  101. timers.delete(action.uid);
  102. }
  103. createTimeoutId(action, dispatch);
  104. }
  105. break;
  106. }
  107. case HIDE_NOTIFICATION: {
  108. if (navigator.product !== 'ReactNative') {
  109. const timer = timers.get(action.uid);
  110. clearTimeout(timer);
  111. timers.delete(action.uid);
  112. }
  113. break;
  114. }
  115. case PARTICIPANT_JOINED: {
  116. const result = next(action);
  117. const { participant: p } = action;
  118. const { conference } = state['features/base/conference'];
  119. // Do not display notifications for the virtual screenshare and whiteboard tiles.
  120. if (conference
  121. && !p.local
  122. && !isScreenShareParticipant(p)
  123. && !isWhiteboardParticipant(p)
  124. && !joinLeaveNotificationsDisabled()
  125. && !p.isReplacing) {
  126. dispatch(showParticipantJoinedNotification(
  127. getParticipantDisplayName(state, p.id)
  128. ));
  129. }
  130. return result;
  131. }
  132. case PARTICIPANT_LEFT: {
  133. if (!joinLeaveNotificationsDisabled()) {
  134. const participant = getParticipantById(
  135. store.getState(),
  136. action.participant.id
  137. );
  138. // Do not display notifications for the virtual screenshare tiles.
  139. if (participant
  140. && !participant.local
  141. && !isScreenShareParticipant(participant)
  142. && !isWhiteboardParticipant(participant)
  143. && !action.participant.isReplaced) {
  144. dispatch(showParticipantLeftNotification(
  145. getParticipantDisplayName(state, participant.id)
  146. ));
  147. }
  148. }
  149. return next(action);
  150. }
  151. case PARTICIPANT_UPDATED: {
  152. const { disableModeratorIndicator } = state['features/base/config'];
  153. if (disableModeratorIndicator) {
  154. return next(action);
  155. }
  156. const { id, role } = action.participant;
  157. const localParticipant = getLocalParticipant(state);
  158. if (localParticipant?.id !== id) {
  159. return next(action);
  160. }
  161. const oldParticipant = getParticipantById(state, id);
  162. const oldRole = oldParticipant?.role;
  163. if (oldRole && oldRole !== role && role === PARTICIPANT_ROLE.MODERATOR) {
  164. store.dispatch(showNotification({
  165. titleKey: 'notify.moderator'
  166. },
  167. NOTIFICATION_TIMEOUT_TYPE.SHORT));
  168. }
  169. return next(action);
  170. }
  171. case PARTICIPANTS_PANE_OPEN: {
  172. store.dispatch(hideNotification(RAISE_HAND_NOTIFICATION_ID));
  173. break;
  174. }
  175. }
  176. return next(action);
  177. });
  178. /**
  179. * StateListenerRegistry provides a reliable way to detect the leaving of a
  180. * conference, where we need to clean up the notifications.
  181. */
  182. StateListenerRegistry.register(
  183. /* selector */ state => getCurrentConference(state),
  184. /* listener */ (conference, { dispatch }) => {
  185. if (!conference) {
  186. dispatch(clearNotifications());
  187. }
  188. }
  189. );