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.js 7.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. // @flow
  2. import { batch } from 'react-redux';
  3. import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app';
  4. import { CONFERENCE_FAILED, CONFERENCE_JOINED } from '../base/conference';
  5. import { JitsiConferenceErrors, JitsiConferenceEvents } from '../base/lib-jitsi-meet';
  6. import { getFirstLoadableAvatarUrl, getParticipantDisplayName } from '../base/participants';
  7. import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
  8. import { playSound, registerSound, unregisterSound } from '../base/sounds';
  9. import { isTestModeEnabled } from '../base/testing';
  10. import { NOTIFICATION_TYPE, showNotification } from '../notifications';
  11. import { shouldAutoKnock } from '../prejoin/functions';
  12. import { KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED } from './actionTypes';
  13. import {
  14. hideLobbyScreen,
  15. knockingParticipantLeft,
  16. openLobbyScreen,
  17. participantIsKnockingOrUpdated,
  18. setLobbyModeEnabled,
  19. startKnocking,
  20. setPasswordJoinFailed
  21. } from './actions';
  22. import { KNOCKING_PARTICIPANT_SOUND_ID } from './constants';
  23. import { KNOCKING_PARTICIPANT_FILE } from './sounds';
  24. MiddlewareRegistry.register(store => next => action => {
  25. switch (action.type) {
  26. case APP_WILL_MOUNT:
  27. store.dispatch(registerSound(KNOCKING_PARTICIPANT_SOUND_ID, KNOCKING_PARTICIPANT_FILE));
  28. break;
  29. case APP_WILL_UNMOUNT:
  30. store.dispatch(unregisterSound(KNOCKING_PARTICIPANT_SOUND_ID));
  31. break;
  32. case CONFERENCE_FAILED:
  33. return _conferenceFailed(store, next, action);
  34. case CONFERENCE_JOINED:
  35. return _conferenceJoined(store, next, action);
  36. case KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED: {
  37. // We need the full update result to be in the store already
  38. const result = next(action);
  39. _findLoadableAvatarForKnockingParticipant(store, action.participant);
  40. return result;
  41. }
  42. }
  43. return next(action);
  44. });
  45. /**
  46. * Registers a change handler for state['features/base/conference'].conference to
  47. * set the event listeners needed for the lobby feature to operate.
  48. */
  49. StateListenerRegistry.register(
  50. state => state['features/base/conference'].conference,
  51. (conference, { dispatch, getState }, previousConference) => {
  52. if (conference && !previousConference) {
  53. conference.on(JitsiConferenceEvents.MEMBERS_ONLY_CHANGED, enabled => {
  54. dispatch(setLobbyModeEnabled(enabled));
  55. });
  56. conference.on(JitsiConferenceEvents.LOBBY_USER_JOINED, (id, name) => {
  57. batch(() => {
  58. dispatch(participantIsKnockingOrUpdated({
  59. id,
  60. name
  61. }));
  62. dispatch(playSound(KNOCKING_PARTICIPANT_SOUND_ID));
  63. });
  64. });
  65. conference.on(JitsiConferenceEvents.LOBBY_USER_UPDATED, (id, participant) => {
  66. dispatch(participantIsKnockingOrUpdated({
  67. ...participant,
  68. id
  69. }));
  70. });
  71. conference.on(JitsiConferenceEvents.LOBBY_USER_LEFT, id => {
  72. dispatch(knockingParticipantLeft(id));
  73. });
  74. conference.on(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, (origin, sender) =>
  75. _maybeSendLobbyNotification(origin, sender, {
  76. dispatch,
  77. getState
  78. })
  79. );
  80. }
  81. });
  82. /**
  83. * Function to handle the conference failed event and navigate the user to the lobby screen
  84. * based on the failure reason.
  85. *
  86. * @param {Object} store - The Redux store.
  87. * @param {Function} next - The Redux next function.
  88. * @param {Object} action - The Redux action.
  89. * @returns {Object}
  90. */
  91. function _conferenceFailed({ dispatch, getState }, next, action) {
  92. const { error } = action;
  93. const state = getState();
  94. const nonFirstFailure = Boolean(state['features/base/conference'].membersOnly);
  95. if (error.name === JitsiConferenceErrors.MEMBERS_ONLY_ERROR) {
  96. if (typeof error.recoverable === 'undefined') {
  97. error.recoverable = true;
  98. }
  99. const result = next(action);
  100. dispatch(openLobbyScreen());
  101. if (shouldAutoKnock(state)) {
  102. dispatch(startKnocking());
  103. }
  104. dispatch(setPasswordJoinFailed(nonFirstFailure));
  105. return result;
  106. }
  107. dispatch(hideLobbyScreen());
  108. if (error.name === JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED) {
  109. dispatch(showNotification({
  110. appearance: NOTIFICATION_TYPE.ERROR,
  111. hideErrorSupportLink: true,
  112. titleKey: 'lobby.joinRejectedMessage'
  113. }));
  114. }
  115. return next(action);
  116. }
  117. /**
  118. * Handles cleanup of lobby state when a conference is joined.
  119. *
  120. * @param {Object} store - The Redux store.
  121. * @param {Function} next - The Redux next function.
  122. * @param {Object} action - The Redux action.
  123. * @returns {Object}
  124. */
  125. function _conferenceJoined({ dispatch }, next, action) {
  126. dispatch(hideLobbyScreen());
  127. return next(action);
  128. }
  129. /**
  130. * Finds the loadable avatar URL and updates the participant accordingly.
  131. *
  132. * @param {Object} store - The Redux store.
  133. * @param {Object} participant - The knocking participant.
  134. * @returns {void}
  135. */
  136. function _findLoadableAvatarForKnockingParticipant(store, { id }) {
  137. const { dispatch, getState } = store;
  138. const updatedParticipant = getState()['features/lobby'].knockingParticipants.find(p => p.id === id);
  139. const { disableThirdPartyRequests } = getState()['features/base/config'];
  140. if (!disableThirdPartyRequests && updatedParticipant && !updatedParticipant.loadableAvatarUrl) {
  141. getFirstLoadableAvatarUrl(updatedParticipant, store).then(loadableAvatarUrl => {
  142. if (loadableAvatarUrl) {
  143. dispatch(participantIsKnockingOrUpdated({
  144. loadableAvatarUrl,
  145. id
  146. }));
  147. }
  148. });
  149. }
  150. }
  151. /**
  152. * Check the endpoint message that arrived through the conference and
  153. * sends a lobby notification, if the message belongs to the feature.
  154. *
  155. * @param {Object} origin - The origin (initiator) of the message.
  156. * @param {Object} message - The actual message.
  157. * @param {Object} store - The Redux store.
  158. * @returns {void}
  159. */
  160. function _maybeSendLobbyNotification(origin, message, { dispatch, getState }) {
  161. if (!origin?._id || message?.type !== 'lobby-notify') {
  162. return;
  163. }
  164. const notificationProps: any = {
  165. descriptionArguments: {
  166. originParticipantName: getParticipantDisplayName(getState, origin._id),
  167. targetParticipantName: message.name
  168. },
  169. titleKey: 'lobby.notificationTitle'
  170. };
  171. switch (message.event) {
  172. case 'LOBBY-ENABLED':
  173. notificationProps.descriptionKey = `lobby.notificationLobby${message.value ? 'En' : 'Dis'}abled`;
  174. break;
  175. case 'LOBBY-ACCESS-GRANTED':
  176. notificationProps.descriptionKey = 'lobby.notificationLobbyAccessGranted';
  177. break;
  178. case 'LOBBY-ACCESS-DENIED':
  179. notificationProps.descriptionKey = 'lobby.notificationLobbyAccessDenied';
  180. break;
  181. }
  182. dispatch(showNotification(notificationProps, isTestModeEnabled(getState()) ? undefined : 5000));
  183. }