Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

middleware.ts 9.6KB

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