Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

middleware.ts 9.8KB

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