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

middleware.js 7.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. // @flow
  2. import { getJitsiMeetTransport } from '../../../modules/transport';
  3. import {
  4. CONFERENCE_FAILED,
  5. CONFERENCE_JOINED,
  6. DATA_CHANNEL_OPENED,
  7. KICKED_OUT
  8. } from '../base/conference';
  9. import { SET_CONFIG } from '../base/config';
  10. import { NOTIFY_CAMERA_ERROR, NOTIFY_MIC_ERROR } from '../base/devices';
  11. import { JitsiConferenceErrors } from '../base/lib-jitsi-meet';
  12. import {
  13. DOMINANT_SPEAKER_CHANGED,
  14. PARTICIPANT_JOINED,
  15. PARTICIPANT_KICKED,
  16. PARTICIPANT_LEFT,
  17. PARTICIPANT_ROLE_CHANGED,
  18. SET_LOADABLE_AVATAR_URL,
  19. getDominantSpeakerParticipant,
  20. getLocalParticipant,
  21. getParticipantById
  22. } from '../base/participants';
  23. import { MiddlewareRegistry } from '../base/redux';
  24. import { getBaseUrl } from '../base/util';
  25. import { appendSuffix } from '../display-name';
  26. import { SUBMIT_FEEDBACK_ERROR, SUBMIT_FEEDBACK_SUCCESS } from '../feedback';
  27. import { SET_FILMSTRIP_VISIBLE } from '../filmstrip';
  28. import './subscriber';
  29. declare var APP: Object;
  30. /**
  31. * The middleware of the feature {@code external-api}.
  32. *
  33. * @returns {Function}
  34. */
  35. MiddlewareRegistry.register(store => next => action => {
  36. // We need to do these before executing the rest of the middelware chain
  37. switch (action.type) {
  38. case DOMINANT_SPEAKER_CHANGED: {
  39. const dominantSpeaker = getDominantSpeakerParticipant(store.getState());
  40. if (dominantSpeaker?.id !== action.participant.id) {
  41. const result = next(action);
  42. APP.API.notifyDominantSpeakerChanged(action.participant.id);
  43. return result;
  44. }
  45. break;
  46. }
  47. case SET_LOADABLE_AVATAR_URL: {
  48. const { id, loadableAvatarUrl } = action.participant;
  49. const participant = getParticipantById(
  50. store.getState(),
  51. id
  52. );
  53. const result = next(action);
  54. if (participant) {
  55. if (loadableAvatarUrl) {
  56. participant.loadableAvatarUrl !== loadableAvatarUrl && APP.API.notifyAvatarChanged(
  57. id,
  58. loadableAvatarUrl
  59. );
  60. } else {
  61. // There is no loadable explicit URL. In this case the Avatar component would
  62. // decide to render initials or the default avatar, but the external API needs
  63. // a URL when it needs to be rendered, so if there is no initials, we return the default
  64. // Avatar URL as if it was a usual avatar URL. If there are (or may be) initials
  65. // we send undefined to signal the api user that it's not an URL that needs to be rendered.
  66. //
  67. // NOTE: we may implement a special URL format later to signal that the avatar is based
  68. // on initials, that API consumers can handle as they want, e.g. initials://jm
  69. APP.API.notifyAvatarChanged(
  70. id,
  71. participant.name ? undefined : _getDefaultAvatarUrl()
  72. );
  73. }
  74. }
  75. return result;
  76. }
  77. }
  78. const result = next(action);
  79. // These should happen after the rest of the middleware chain ran
  80. switch (action.type) {
  81. case CONFERENCE_FAILED: {
  82. if (action.conference
  83. && action.error.name === JitsiConferenceErrors.PASSWORD_REQUIRED) {
  84. APP.API.notifyOnPasswordRequired();
  85. }
  86. break;
  87. }
  88. case CONFERENCE_JOINED: {
  89. const state = store.getState();
  90. const { defaultLocalDisplayName } = state['features/base/config'];
  91. const { room } = state['features/base/conference'];
  92. const { loadableAvatarUrl, name, id, email } = getLocalParticipant(state);
  93. const breakoutRoom = APP.conference.roomName.toString() !== room.toLowerCase();
  94. // we use APP.conference.roomName as we do not update state['features/base/conference'].room when
  95. // moving between rooms in case of breakout rooms and it stays always with the name of the main room
  96. APP.API.notifyConferenceJoined(
  97. APP.conference.roomName,
  98. id,
  99. {
  100. displayName: name,
  101. formattedDisplayName: appendSuffix(
  102. name,
  103. defaultLocalDisplayName
  104. ),
  105. avatarURL: loadableAvatarUrl,
  106. breakoutRoom,
  107. email
  108. }
  109. );
  110. break;
  111. }
  112. case DATA_CHANNEL_OPENED:
  113. APP.API.notifyDataChannelOpened();
  114. break;
  115. case KICKED_OUT:
  116. APP.API.notifyKickedOut(
  117. {
  118. id: getLocalParticipant(store.getState()).id,
  119. local: true
  120. },
  121. { id: action.participant ? action.participant.getId() : undefined }
  122. );
  123. break;
  124. case NOTIFY_CAMERA_ERROR:
  125. if (action.error) {
  126. APP.API.notifyOnCameraError(
  127. action.error.name, action.error.message);
  128. }
  129. break;
  130. case NOTIFY_MIC_ERROR:
  131. if (action.error) {
  132. APP.API.notifyOnMicError(action.error.name, action.error.message);
  133. }
  134. break;
  135. case PARTICIPANT_KICKED:
  136. APP.API.notifyKickedOut(
  137. {
  138. id: action.kicked,
  139. local: false
  140. },
  141. { id: action.kicker });
  142. break;
  143. case PARTICIPANT_LEFT: {
  144. const { participant } = action;
  145. const { fakeParticipant } = participant;
  146. // Skip sending participant left event for fake participants.
  147. if (fakeParticipant) {
  148. break;
  149. }
  150. APP.API.notifyUserLeft(action.participant.id);
  151. break;
  152. }
  153. case PARTICIPANT_JOINED: {
  154. const state = store.getState();
  155. const { defaultRemoteDisplayName } = state['features/base/config'];
  156. const { participant } = action;
  157. const { fakeParticipant, id, local, name } = participant;
  158. // The version of external api outside of middleware did not emit
  159. // the local participant being created.
  160. if (!local) {
  161. // Skip sending participant joined event for fake participants.
  162. if (fakeParticipant) {
  163. break;
  164. }
  165. APP.API.notifyUserJoined(id, {
  166. displayName: name,
  167. formattedDisplayName: appendSuffix(
  168. name || defaultRemoteDisplayName)
  169. });
  170. }
  171. break;
  172. }
  173. case PARTICIPANT_ROLE_CHANGED:
  174. APP.API.notifyUserRoleChanged(action.participant.id, action.participant.role);
  175. break;
  176. case SET_CONFIG: {
  177. const state = store.getState();
  178. const { disableBeforeUnloadHandlers = false } = state['features/base/config'];
  179. /**
  180. * Disposing the API when the user closes the page.
  181. */
  182. window.addEventListener(disableBeforeUnloadHandlers ? 'unload' : 'beforeunload', () => {
  183. APP.API.notifyConferenceLeft(APP.conference.roomName);
  184. APP.API.dispose();
  185. getJitsiMeetTransport().dispose();
  186. });
  187. break;
  188. }
  189. case SET_FILMSTRIP_VISIBLE:
  190. APP.API.notifyFilmstripDisplayChanged(action.visible);
  191. break;
  192. case SUBMIT_FEEDBACK_ERROR:
  193. APP.API.notifyFeedbackSubmitted(action.error || 'Unknown error');
  194. break;
  195. case SUBMIT_FEEDBACK_SUCCESS:
  196. APP.API.notifyFeedbackSubmitted();
  197. break;
  198. }
  199. return result;
  200. });
  201. /**
  202. * Returns the absolute URL of the default avatar.
  203. *
  204. * @returns {string}
  205. */
  206. function _getDefaultAvatarUrl() {
  207. return new URL('images/avatar.png', getBaseUrl()).href;
  208. }