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.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  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 { room } = state['features/base/conference'];
  82. const { loadableAvatarUrl, name, id } = getLocalParticipant(state);
  83. const breakoutRoom = APP.conference.roomName.toString() !== room.toLowerCase();
  84. // we use APP.conference.roomName as we do not update state['features/base/conference'].room when
  85. // moving between rooms in case of breakout rooms and it stays always with the name of the main room
  86. APP.API.notifyConferenceJoined(
  87. APP.conference.roomName,
  88. id,
  89. {
  90. displayName: name,
  91. formattedDisplayName: appendSuffix(
  92. name,
  93. defaultLocalDisplayName
  94. ),
  95. avatarURL: loadableAvatarUrl,
  96. breakoutRoom
  97. }
  98. );
  99. break;
  100. }
  101. case DATA_CHANNEL_OPENED:
  102. APP.API.notifyDataChannelOpened();
  103. break;
  104. case DOMINANT_SPEAKER_CHANGED:
  105. APP.API.notifyDominantSpeakerChanged(action.participant.id);
  106. break;
  107. case KICKED_OUT:
  108. APP.API.notifyKickedOut(
  109. {
  110. id: getLocalParticipant(store.getState()).id,
  111. local: true
  112. },
  113. { id: action.participant ? action.participant.getId() : undefined }
  114. );
  115. break;
  116. case NOTIFY_CAMERA_ERROR:
  117. if (action.error) {
  118. APP.API.notifyOnCameraError(
  119. action.error.name, action.error.message);
  120. }
  121. break;
  122. case NOTIFY_MIC_ERROR:
  123. if (action.error) {
  124. APP.API.notifyOnMicError(action.error.name, action.error.message);
  125. }
  126. break;
  127. case PARTICIPANT_KICKED:
  128. APP.API.notifyKickedOut(
  129. {
  130. id: action.kicked,
  131. local: false
  132. },
  133. { id: action.kicker });
  134. break;
  135. case PARTICIPANT_LEFT:
  136. APP.API.notifyUserLeft(action.participant.id);
  137. break;
  138. case PARTICIPANT_JOINED: {
  139. const state = store.getState();
  140. const { defaultRemoteDisplayName } = state['features/base/config'];
  141. const { participant } = action;
  142. const { id, local, name } = participant;
  143. // The version of external api outside of middleware did not emit
  144. // the local participant being created.
  145. if (!local) {
  146. APP.API.notifyUserJoined(id, {
  147. displayName: name,
  148. formattedDisplayName: appendSuffix(
  149. name || defaultRemoteDisplayName)
  150. });
  151. }
  152. break;
  153. }
  154. case PARTICIPANT_ROLE_CHANGED:
  155. APP.API.notifyUserRoleChanged(action.participant.id, action.participant.role);
  156. break;
  157. case SET_CONFIG: {
  158. const state = store.getState();
  159. const { disableBeforeUnloadHandlers = false } = state['features/base/config'];
  160. /**
  161. * Disposing the API when the user closes the page.
  162. */
  163. window.addEventListener(disableBeforeUnloadHandlers ? 'unload' : 'beforeunload', () => {
  164. APP.API.notifyConferenceLeft(APP.conference.roomName);
  165. APP.API.dispose();
  166. getJitsiMeetTransport().dispose();
  167. });
  168. break;
  169. }
  170. case SET_FILMSTRIP_VISIBLE:
  171. APP.API.notifyFilmstripDisplayChanged(action.visible);
  172. break;
  173. case SUBMIT_FEEDBACK_ERROR:
  174. APP.API.notifyFeedbackSubmitted(action.error || 'Unknown error');
  175. break;
  176. case SUBMIT_FEEDBACK_SUCCESS:
  177. APP.API.notifyFeedbackSubmitted();
  178. break;
  179. }
  180. return result;
  181. });
  182. /**
  183. * Returns the absolute URL of the default avatar.
  184. *
  185. * @returns {string}
  186. */
  187. function _getDefaultAvatarUrl() {
  188. return new URL('images/avatar.png', getBaseUrl()).href;
  189. }