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

middleware.ts 9.4KB

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