123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319 |
- import { IStore } from '../app/types';
- import { APP_WILL_NAVIGATE } from '../base/app/actionTypes';
- import {
- CONFERENCE_FAILED,
- CONFERENCE_JOINED,
- CONFERENCE_LEFT
- } from '../base/conference/actionTypes';
- import { isRoomValid } from '../base/conference/functions';
- import { CONNECTION_ESTABLISHED, CONNECTION_FAILED } from '../base/connection/actionTypes';
- import { hideDialog } from '../base/dialog/actions';
- import { isDialogOpen } from '../base/dialog/functions';
- import {
- JitsiConferenceErrors,
- JitsiConnectionErrors
- } from '../base/lib-jitsi-meet';
- import { MEDIA_TYPE } from '../base/media/constants';
- import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
- import { isLocalTrackMuted } from '../base/tracks/functions.any';
- import { parseURIString } from '../base/util/uri';
- import { openLogoutDialog } from '../settings/actions';
-
- import {
- CANCEL_LOGIN,
- LOGIN,
- LOGOUT,
- STOP_WAIT_FOR_OWNER,
- UPGRADE_ROLE_FINISHED,
- WAIT_FOR_OWNER
- } from './actionTypes';
- import {
- hideLoginDialog,
- openLoginDialog,
- openTokenAuthUrl,
- openWaitForOwnerDialog,
- redirectToDefaultLocation,
- setTokenAuthUrlSuccess,
- stopWaitForOwner,
- waitForOwner
- } from './actions';
- import { LoginDialog, WaitForOwnerDialog } from './components';
- import { getTokenAuthUrl, isTokenAuthEnabled } from './functions';
- import logger from './logger';
-
-
- /**
- * Middleware that captures connection or conference failed errors and controls
- * {@link WaitForOwnerDialog} and {@link LoginDialog}.
- *
- * FIXME Some of the complexity was introduced by the lack of dialog stacking.
- *
- * @param {Store} store - Redux store.
- * @returns {Function}
- */
- MiddlewareRegistry.register(store => next => action => {
- switch (action.type) {
- case CANCEL_LOGIN: {
- const { dispatch, getState } = store;
- const state = getState();
- const { thenableWithCancel } = state['features/authentication'];
-
- thenableWithCancel?.cancel();
-
- // The LoginDialog can be opened on top of "wait for owner". The app
- // should navigate only if LoginDialog was open without the
- // WaitForOwnerDialog.
- if (!isDialogOpen(store, WaitForOwnerDialog)) {
- if (_isWaitingForOwner(store)) {
- // Instead of hiding show the new one.
- const result = next(action);
-
- dispatch(openWaitForOwnerDialog());
-
- return result;
- }
-
- dispatch(hideLoginDialog());
-
- const { authRequired, conference } = state['features/base/conference'];
- const { passwordRequired } = state['features/base/connection'];
-
- // Only end the meeting if we are not already inside and trying to upgrade.
- // NOTE: Despite it's confusing name, `passwordRequired` implies an XMPP
- // connection auth error.
- if ((passwordRequired || authRequired) && !conference) {
- dispatch(redirectToDefaultLocation());
- }
- }
- break;
- }
-
- case CONFERENCE_FAILED: {
- const { error } = action;
-
- // XXX The feature authentication affords recovery from
- // CONFERENCE_FAILED caused by
- // JitsiConferenceErrors.AUTHENTICATION_REQUIRED.
- let recoverable;
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- const [ _lobbyJid, lobbyWaitingForHost ] = error.params;
-
- if (error.name === JitsiConferenceErrors.AUTHENTICATION_REQUIRED
- || (error.name === JitsiConferenceErrors.MEMBERS_ONLY_ERROR && lobbyWaitingForHost)) {
- if (typeof error.recoverable === 'undefined') {
- error.recoverable = true;
- }
- recoverable = error.recoverable;
- }
- if (recoverable) {
- store.dispatch(waitForOwner());
- } else {
- store.dispatch(stopWaitForOwner());
- }
- break;
- }
-
- case CONFERENCE_JOINED: {
- const { dispatch, getState } = store;
- const state = getState();
- const config = state['features/base/config'];
-
- if (isTokenAuthEnabled(config)
- && config.tokenAuthUrlAutoRedirect
- && state['features/base/jwt'].jwt) {
- // auto redirect is turned on and we have successfully logged in
- // let's mark that
- dispatch(setTokenAuthUrlSuccess(true));
- }
-
- if (_isWaitingForOwner(store)) {
- store.dispatch(stopWaitForOwner());
- }
- store.dispatch(hideLoginDialog());
- break;
- }
-
- case CONFERENCE_LEFT:
- store.dispatch(stopWaitForOwner());
- break;
-
- case CONNECTION_ESTABLISHED:
- store.dispatch(hideLoginDialog());
- break;
-
- case CONNECTION_FAILED: {
- const { error } = action;
- const { getState } = store;
- const state = getState();
- const { jwt } = state['features/base/jwt'];
-
- if (error
- && error.name === JitsiConnectionErrors.PASSWORD_REQUIRED
- && typeof error.recoverable === 'undefined'
- && !jwt) {
- error.recoverable = true;
-
- _handleLogin(store);
- }
-
- break;
- }
-
- case LOGIN: {
- _handleLogin(store);
-
- break;
- }
-
- case LOGOUT: {
- _handleLogout(store);
-
- break;
- }
-
- case APP_WILL_NAVIGATE: {
- const { dispatch, getState } = store;
- const state = getState();
- const config = state['features/base/config'];
- const room = state['features/base/conference'].room;
-
- if (isRoomValid(room)
- && config.tokenAuthUrl && config.tokenAuthUrlAutoRedirect
- && state['features/authentication'].tokenAuthUrlSuccessful
- && !state['features/base/jwt'].jwt) {
- // if we have auto redirect enabled, and we have previously logged in successfully
- // we will redirect to the auth url to get the token and login again
- // we want to mark token auth success to false as if login is unsuccessful
- // the participant can join anonymously and not go in login loop
- dispatch(setTokenAuthUrlSuccess(false));
- }
-
- break;
- }
-
- case STOP_WAIT_FOR_OWNER:
- _clearExistingWaitForOwnerTimeout(store);
- store.dispatch(hideDialog(WaitForOwnerDialog));
- break;
-
- case UPGRADE_ROLE_FINISHED: {
- const { error, progress } = action;
-
- if (!error && progress === 1) {
- store.dispatch(hideLoginDialog());
- }
- break;
- }
-
- case WAIT_FOR_OWNER: {
- _clearExistingWaitForOwnerTimeout(store);
-
- const { handler, timeoutMs }: { handler: () => void; timeoutMs: number; } = action;
-
- action.waitForOwnerTimeoutID = setTimeout(handler, timeoutMs);
-
- // The WAIT_FOR_OWNER action is cyclic, and we don't want to hide the
- // login dialog every few seconds.
- isDialogOpen(store, LoginDialog)
- || store.dispatch(openWaitForOwnerDialog());
- break;
- }
- }
-
- return next(action);
- });
-
- /**
- * Will clear the wait for conference owner timeout handler if any is currently
- * set.
- *
- * @param {Object} store - The redux store.
- * @returns {void}
- */
- function _clearExistingWaitForOwnerTimeout({ getState }: IStore) {
- const { waitForOwnerTimeoutID } = getState()['features/authentication'];
-
- waitForOwnerTimeoutID && clearTimeout(waitForOwnerTimeoutID);
- }
-
-
- /**
- * Checks if the cyclic "wait for conference owner" task is currently scheduled.
- *
- * @param {Object} store - The redux store.
- * @returns {boolean}
- */
- function _isWaitingForOwner({ getState }: IStore) {
- return Boolean(getState()['features/authentication'].waitForOwnerTimeoutID);
- }
-
- /**
- * Handles login challenge. Opens login dialog or redirects to token auth URL.
- *
- * @param {Store} store - The redux store in which the specified {@code action}
- * is being dispatched.
- * @returns {void}
- */
- function _handleLogin({ dispatch, getState }: IStore) {
- const state = getState();
- const config = state['features/base/config'];
- const room = state['features/base/conference'].room;
- const { locationURL = { href: '' } as URL } = state['features/base/connection'];
- const { tenant } = parseURIString(locationURL.href) || {};
- const { enabled: audioOnlyEnabled } = state['features/base/audio-only'];
- const audioMuted = isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.AUDIO);
- const videoMuted = isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.VIDEO);
-
- if (!room) {
- logger.warn('Cannot handle login, room is undefined!');
-
- return;
- }
-
- if (!isTokenAuthEnabled(config)) {
- dispatch(openLoginDialog());
-
- return;
- }
-
- getTokenAuthUrl(
- config,
- locationURL,
- {
- audioMuted,
- audioOnlyEnabled,
- skipPrejoin: true,
- videoMuted
- },
- room,
- tenant
- )
- .then((tokenAuthServiceUrl: string | undefined) => {
- if (!tokenAuthServiceUrl) {
- logger.warn('Cannot handle login, token service URL is not set');
-
- return;
- }
-
- return dispatch(openTokenAuthUrl(tokenAuthServiceUrl));
- });
- }
-
- /**
- * Handles logout challenge. Opens logout dialog and hangs up the conference.
- *
- * @param {Store} store - The redux store in which the specified {@code action}
- * is being dispatched.
- * @param {string} logoutUrl - The url for logging out.
- * @returns {void}
- */
- function _handleLogout({ dispatch, getState }: IStore) {
- const state = getState();
- const { conference } = state['features/base/conference'];
-
- if (!conference) {
- return;
- }
-
- dispatch(openLogoutDialog());
- }
|