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

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