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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. import { IStore } from '../app/types';
  2. import {
  3. CONFERENCE_FAILED,
  4. CONFERENCE_JOINED,
  5. CONFERENCE_LEFT
  6. } from '../base/conference/actionTypes';
  7. import { CONNECTION_ESTABLISHED, CONNECTION_FAILED } from '../base/connection/actionTypes';
  8. import { hangup } from '../base/connection/actions';
  9. import { hideDialog } from '../base/dialog/actions';
  10. import { isDialogOpen } from '../base/dialog/functions';
  11. import {
  12. JitsiConferenceErrors,
  13. JitsiConnectionErrors
  14. } from '../base/lib-jitsi-meet';
  15. import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
  16. import { getBackendSafeRoomName } from '../base/util/uri';
  17. import { showErrorNotification } from '../notifications/actions';
  18. import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
  19. import { openLogoutDialog } from '../settings/actions';
  20. import {
  21. CANCEL_LOGIN,
  22. LOGIN,
  23. LOGOUT,
  24. STOP_WAIT_FOR_OWNER,
  25. UPGRADE_ROLE_FINISHED,
  26. WAIT_FOR_OWNER
  27. } from './actionTypes';
  28. import {
  29. hideLoginDialog,
  30. openLoginDialog,
  31. openWaitForOwnerDialog,
  32. redirectToDefaultLocation,
  33. stopWaitForOwner,
  34. waitForOwner } from './actions';
  35. import { LoginDialog, WaitForOwnerDialog } from './components';
  36. import { getTokenAuthUrl, isTokenAuthEnabled } from './functions';
  37. /**
  38. * Middleware that captures connection or conference failed errors and controls
  39. * {@link WaitForOwnerDialog} and {@link LoginDialog}.
  40. *
  41. * FIXME Some of the complexity was introduced by the lack of dialog stacking.
  42. *
  43. * @param {Store} store - Redux store.
  44. * @returns {Function}
  45. */
  46. MiddlewareRegistry.register(store => next => action => {
  47. switch (action.type) {
  48. case CANCEL_LOGIN: {
  49. const { dispatch, getState } = store;
  50. const state = getState();
  51. const { thenableWithCancel } = state['features/authentication'];
  52. thenableWithCancel?.cancel();
  53. // The LoginDialog can be opened on top of "wait for owner". The app
  54. // should navigate only if LoginDialog was open without the
  55. // WaitForOwnerDialog.
  56. if (!isDialogOpen(store, WaitForOwnerDialog)) {
  57. if (_isWaitingForOwner(store)) {
  58. // Instead of hiding show the new one.
  59. const result = next(action);
  60. dispatch(openWaitForOwnerDialog());
  61. return result;
  62. }
  63. dispatch(hideLoginDialog());
  64. const { authRequired, conference } = state['features/base/conference'];
  65. const { passwordRequired } = state['features/base/connection'];
  66. // Only end the meeting if we are not already inside and trying to upgrade.
  67. // NOTE: Despite it's confusing name, `passwordRequired` implies an XMPP
  68. // connection auth error.
  69. if ((passwordRequired || authRequired) && !conference) {
  70. dispatch(redirectToDefaultLocation());
  71. }
  72. }
  73. break;
  74. }
  75. case CONFERENCE_FAILED: {
  76. const { error } = action;
  77. // XXX The feature authentication affords recovery from
  78. // CONFERENCE_FAILED caused by
  79. // JitsiConferenceErrors.AUTHENTICATION_REQUIRED.
  80. let recoverable;
  81. if (error.name === JitsiConferenceErrors.AUTHENTICATION_REQUIRED) {
  82. if (typeof error.recoverable === 'undefined') {
  83. error.recoverable = true;
  84. }
  85. recoverable = error.recoverable;
  86. }
  87. if (recoverable) {
  88. store.dispatch(waitForOwner());
  89. } else {
  90. store.dispatch(stopWaitForOwner());
  91. }
  92. break;
  93. }
  94. case CONFERENCE_JOINED:
  95. if (_isWaitingForOwner(store)) {
  96. store.dispatch(stopWaitForOwner());
  97. }
  98. store.dispatch(hideLoginDialog());
  99. break;
  100. case CONFERENCE_LEFT:
  101. store.dispatch(stopWaitForOwner());
  102. break;
  103. case CONNECTION_ESTABLISHED:
  104. store.dispatch(hideLoginDialog());
  105. break;
  106. case CONNECTION_FAILED: {
  107. const { error } = action;
  108. const state = store.getState();
  109. const { jwt } = state['features/base/jwt'];
  110. if (error
  111. && error.name === JitsiConnectionErrors.PASSWORD_REQUIRED
  112. && typeof error.recoverable === 'undefined'
  113. && !jwt) {
  114. error.recoverable = true;
  115. _handleLogin(store);
  116. }
  117. break;
  118. }
  119. case LOGIN: {
  120. _handleLogin(store);
  121. break;
  122. }
  123. case LOGOUT: {
  124. const { conference } = store.getState()['features/base/conference'];
  125. if (!conference) {
  126. break;
  127. }
  128. store.dispatch(openLogoutDialog(() =>
  129. conference.room.moderator.logout(() => store.dispatch(hangup(true)))
  130. ));
  131. break;
  132. }
  133. case STOP_WAIT_FOR_OWNER:
  134. _clearExistingWaitForOwnerTimeout(store);
  135. store.dispatch(hideDialog(WaitForOwnerDialog));
  136. break;
  137. case UPGRADE_ROLE_FINISHED: {
  138. const { error, progress } = action;
  139. if (!error && progress === 1) {
  140. store.dispatch(hideLoginDialog());
  141. }
  142. break;
  143. }
  144. case WAIT_FOR_OWNER: {
  145. _clearExistingWaitForOwnerTimeout(store);
  146. const { handler, timeoutMs }: { handler: () => void; timeoutMs: number; } = action;
  147. action.waitForOwnerTimeoutID = setTimeout(handler, timeoutMs);
  148. // The WAIT_FOR_OWNER action is cyclic, and we don't want to hide the
  149. // login dialog every few seconds.
  150. isDialogOpen(store, LoginDialog)
  151. || store.dispatch(openWaitForOwnerDialog());
  152. break;
  153. }
  154. }
  155. return next(action);
  156. });
  157. /**
  158. * Will clear the wait for conference owner timeout handler if any is currently
  159. * set.
  160. *
  161. * @param {Object} store - The redux store.
  162. * @returns {void}
  163. */
  164. function _clearExistingWaitForOwnerTimeout({ getState }: IStore) {
  165. const { waitForOwnerTimeoutID } = getState()['features/authentication'];
  166. waitForOwnerTimeoutID && clearTimeout(waitForOwnerTimeoutID);
  167. }
  168. /**
  169. * Checks if the cyclic "wait for conference owner" task is currently scheduled.
  170. *
  171. * @param {Object} store - The redux store.
  172. * @returns {boolean}
  173. */
  174. function _isWaitingForOwner({ getState }: IStore) {
  175. return Boolean(getState()['features/authentication'].waitForOwnerTimeoutID);
  176. }
  177. /**
  178. * Handles login challenge. Opens login dialog or redirects to token auth URL.
  179. *
  180. * @param {Store} store - The redux store in which the specified {@code action}
  181. * is being dispatched.
  182. * @returns {void}
  183. */
  184. function _handleLogin({ dispatch, getState }: IStore) {
  185. const state = getState();
  186. const config = state['features/base/config'];
  187. const room = getBackendSafeRoomName(state['features/base/conference'].room);
  188. if (isTokenAuthEnabled(config)) {
  189. if (typeof APP === 'undefined') {
  190. dispatch(showErrorNotification({
  191. descriptionKey: 'dialog.tokenAuthUnsupported',
  192. titleKey: 'dialog.tokenAuthFailedTitle'
  193. }, NOTIFICATION_TIMEOUT_TYPE.LONG));
  194. dispatch(redirectToDefaultLocation());
  195. return;
  196. }
  197. // FIXME: This method will not preserve the other URL params that were originally passed.
  198. // redirectToTokenAuthService
  199. window.location.href = getTokenAuthUrl(config)(room, false);
  200. } else {
  201. dispatch(openLoginDialog());
  202. }
  203. }