feat(authentication) refactor auth dialogs to use React
This commit is contained in:
parent
11202595bd
commit
e035d33fa9
|
@ -309,11 +309,6 @@ class ConferenceConnector {
|
|||
room.join();
|
||||
}, 5000);
|
||||
|
||||
const { password }
|
||||
= APP.store.getState()['features/base/conference'];
|
||||
|
||||
AuthHandler.requireAuth(room, password);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -378,7 +373,6 @@ class ConferenceConnector {
|
|||
if (this.reconnectTimeout !== null) {
|
||||
clearTimeout(this.reconnectTimeout);
|
||||
}
|
||||
AuthHandler.closeAuth();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2242,7 +2236,7 @@ export default {
|
|||
});
|
||||
|
||||
APP.UI.addListener(UIEvents.AUTH_CLICKED, () => {
|
||||
AuthHandler.authenticate(room);
|
||||
AuthHandler.authenticateExternal(room);
|
||||
});
|
||||
|
||||
APP.UI.addListener(
|
||||
|
|
|
@ -3,18 +3,21 @@
|
|||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
import Logger from 'jitsi-meet-logger';
|
||||
|
||||
import AuthHandler from './modules/UI/authentication/AuthHandler';
|
||||
import { redirectToTokenAuthService } from './modules/UI/authentication/AuthHandler';
|
||||
import { hideLoginDialog } from './react/features/authentication/actions.web';
|
||||
import { LoginDialog } from './react/features/authentication/components';
|
||||
import { isTokenAuthEnabled } from './react/features/authentication/functions';
|
||||
import {
|
||||
connectionEstablished,
|
||||
connectionFailed
|
||||
} from './react/features/base/connection/actions';
|
||||
import { openDialog } from './react/features/base/dialog/actions';
|
||||
import {
|
||||
isFatalJitsiConnectionError,
|
||||
JitsiConnectionErrors,
|
||||
JitsiConnectionEvents
|
||||
} from './react/features/base/lib-jitsi-meet';
|
||||
import { setPrejoinDisplayNameRequired } from './react/features/prejoin/actions';
|
||||
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
||||
/**
|
||||
|
@ -80,7 +83,7 @@ function checkForAttachParametersAndConnect(id, password, connection) {
|
|||
* @returns {Promise<JitsiConnection>} connection if
|
||||
* everything is ok, else error.
|
||||
*/
|
||||
function connect(id, password, roomName) {
|
||||
export function connect(id, password, roomName) {
|
||||
const connectionConfig = Object.assign({}, config);
|
||||
const { jwt } = APP.store.getState()['features/base/jwt'];
|
||||
|
||||
|
@ -214,10 +217,39 @@ export function openConnection({ id, password, retry, roomName }) {
|
|||
const { jwt } = APP.store.getState()['features/base/jwt'];
|
||||
|
||||
if (err === JitsiConnectionErrors.PASSWORD_REQUIRED && !jwt) {
|
||||
return AuthHandler.requestAuth(roomName, connect);
|
||||
return requestAuth(roomName);
|
||||
}
|
||||
}
|
||||
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show Authentication Dialog and try to connect with new credentials.
|
||||
* If failed to connect because of PASSWORD_REQUIRED error
|
||||
* then ask for password again.
|
||||
* @param {string} [roomName] name of the conference room
|
||||
*
|
||||
* @returns {Promise<JitsiConnection>}
|
||||
*/
|
||||
function requestAuth(roomName) {
|
||||
const config = APP.store.getState()['features/base/config'];
|
||||
|
||||
if (isTokenAuthEnabled(config)) {
|
||||
// This Promise never resolves as user gets redirected to another URL
|
||||
return new Promise(() => redirectToTokenAuthService(roomName));
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
const onSuccess = connection => {
|
||||
APP.store.dispatch(hideLoginDialog());
|
||||
resolve(connection);
|
||||
};
|
||||
|
||||
APP.store.dispatch(
|
||||
openDialog(LoginDialog, { onSuccess,
|
||||
roomName })
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -175,6 +175,7 @@
|
|||
"alreadySharedVideoMsg": "Another participant is already sharing a video. This conference allows only one shared video at a time.",
|
||||
"alreadySharedVideoTitle": "Only one shared video is allowed at a time",
|
||||
"applicationWindow": "Application window",
|
||||
"authenticationRequired": "Authentication required",
|
||||
"Back": "Back",
|
||||
"cameraConstraintFailedError": "Your camera does not satisfy some of the required constraints.",
|
||||
"cameraNotFoundError": "Camera was not found.",
|
||||
|
@ -227,6 +228,7 @@
|
|||
"lockRoom": "Add meeting $t(lockRoomPasswordUppercase)",
|
||||
"lockTitle": "Lock failed",
|
||||
"logoutQuestion": "Are you sure you want to logout and stop the conference?",
|
||||
"login": "Login",
|
||||
"logoutTitle": "Logout",
|
||||
"maxUsersLimitReached": "The limit for maximum number of participants has been reached. The conference is full. Please contact the meeting owner or try again later!",
|
||||
"maxUsersLimitReachedTitle": "Maximum participants limit reached",
|
||||
|
@ -312,12 +314,13 @@
|
|||
"tokenAuthFailedTitle": "Authentication failed",
|
||||
"transcribing": "Transcribing",
|
||||
"unlockRoom": "Remove meeting $t(lockRoomPassword)",
|
||||
"user": "user",
|
||||
"userPassword": "user password",
|
||||
"user": "User",
|
||||
"userIdentifier": "User identifier",
|
||||
"userPassword": "User password",
|
||||
"videoLink": "Video link",
|
||||
"WaitForHostMsg": "The conference <b>{{room}}</b> has not yet started. If you are the host then please authenticate. Otherwise, please wait for the host to arrive.",
|
||||
"WaitForHostMsgWOk": "The conference <b>{{room}}</b> has not yet started. If you are the host then please press Ok to authenticate. Otherwise, please wait for the host to arrive.",
|
||||
"WaitingForHost": "Waiting for the host ...",
|
||||
"WaitingForHostTitle": "Waiting for the host ...",
|
||||
"Yes": "Yes",
|
||||
"yourEntireScreen": "Your entire screen"
|
||||
},
|
||||
|
|
|
@ -146,7 +146,6 @@ UI.start = function() {
|
|||
}
|
||||
|
||||
APP.store.dispatch(setToolboxEnabled(false));
|
||||
UI.messageHandler.enablePopups(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,25 +1,22 @@
|
|||
/* global APP, config, JitsiMeetJS, Promise */
|
||||
// @flow
|
||||
|
||||
import Logger from 'jitsi-meet-logger';
|
||||
|
||||
import { openConnection } from '../../../connection';
|
||||
import { setJWT } from '../../../react/features/base/jwt';
|
||||
import {
|
||||
JitsiConnectionErrors
|
||||
} from '../../../react/features/base/lib-jitsi-meet';
|
||||
isTokenAuthEnabled,
|
||||
getTokenAuthUrl
|
||||
} from '../../../react/features/authentication/functions';
|
||||
import { setJWT } from '../../../react/features/base/jwt';
|
||||
import UIUtil from '../util/UIUtil';
|
||||
|
||||
import LoginDialog from './LoginDialog';
|
||||
|
||||
let externalAuthWindow;
|
||||
declare var APP: Object;
|
||||
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
||||
let externalAuthWindow;
|
||||
let authRequiredDialog;
|
||||
|
||||
const isTokenAuthEnabled
|
||||
= typeof config.tokenAuthUrl === 'string' && config.tokenAuthUrl.length;
|
||||
const getTokenAuthUrl
|
||||
= JitsiMeetJS.util.AuthUtil.getTokenAuthUrl.bind(null, config.tokenAuthUrl);
|
||||
|
||||
/**
|
||||
* Authenticate using external service or just focus
|
||||
|
@ -29,6 +26,8 @@ const getTokenAuthUrl
|
|||
* @param {string} [lockPassword] password to use if the conference is locked
|
||||
*/
|
||||
function doExternalAuth(room, lockPassword) {
|
||||
const config = APP.store.getState()['features/base/config'];
|
||||
|
||||
if (externalAuthWindow) {
|
||||
externalAuthWindow.focus();
|
||||
|
||||
|
@ -37,8 +36,8 @@ function doExternalAuth(room, lockPassword) {
|
|||
if (room.isJoined()) {
|
||||
let getUrl;
|
||||
|
||||
if (isTokenAuthEnabled) {
|
||||
getUrl = Promise.resolve(getTokenAuthUrl(room.getName(), true));
|
||||
if (isTokenAuthEnabled(config)) {
|
||||
getUrl = Promise.resolve(getTokenAuthUrl(config)(room.getName(), true));
|
||||
initJWTTokenListener(room);
|
||||
} else {
|
||||
getUrl = room.getExternalAuthUrl(true);
|
||||
|
@ -48,13 +47,13 @@ function doExternalAuth(room, lockPassword) {
|
|||
url,
|
||||
() => {
|
||||
externalAuthWindow = null;
|
||||
if (!isTokenAuthEnabled) {
|
||||
if (!isTokenAuthEnabled(config)) {
|
||||
room.join(lockPassword);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
} else if (isTokenAuthEnabled) {
|
||||
} else if (isTokenAuthEnabled(config)) {
|
||||
redirectToTokenAuthService(room.getName());
|
||||
} else {
|
||||
room.getExternalAuthUrl().then(UIUtil.redirect);
|
||||
|
@ -67,10 +66,12 @@ function doExternalAuth(room, lockPassword) {
|
|||
* back with "?jwt={the JWT token}" query parameter added.
|
||||
* @param {string} [roomName] the name of the conference room.
|
||||
*/
|
||||
function redirectToTokenAuthService(roomName) {
|
||||
export function redirectToTokenAuthService(roomName: string) {
|
||||
const config = APP.store.getState()['features/base/config'];
|
||||
|
||||
// FIXME: This method will not preserve the other URL params that were
|
||||
// originally passed.
|
||||
UIUtil.redirect(getTokenAuthUrl(roomName, false));
|
||||
UIUtil.redirect(getTokenAuthUrl(config)(roomName, false));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -157,58 +158,15 @@ function initJWTTokenListener(room) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Authenticate on the server.
|
||||
* @param {JitsiConference} room
|
||||
* @param {string} [lockPassword] password to use if the conference is locked
|
||||
*/
|
||||
function doXmppAuth(room, lockPassword) {
|
||||
const loginDialog = LoginDialog.showAuthDialog(
|
||||
/* successCallback */ (id, password) => {
|
||||
room.authenticateAndUpgradeRole({
|
||||
id,
|
||||
password,
|
||||
roomPassword: lockPassword,
|
||||
|
||||
/** Called when the XMPP login succeeds. */
|
||||
onLoginSuccessful() {
|
||||
loginDialog.displayConnectionStatus(
|
||||
'connection.FETCH_SESSION_ID');
|
||||
}
|
||||
})
|
||||
.then(
|
||||
/* onFulfilled */ () => {
|
||||
loginDialog.displayConnectionStatus(
|
||||
'connection.GOT_SESSION_ID');
|
||||
loginDialog.close();
|
||||
},
|
||||
/* onRejected */ error => {
|
||||
logger.error('authenticateAndUpgradeRole failed', error);
|
||||
|
||||
const { authenticationError, connectionError } = error;
|
||||
|
||||
if (authenticationError) {
|
||||
loginDialog.displayError(
|
||||
'connection.GET_SESSION_ID_ERROR',
|
||||
{ msg: authenticationError });
|
||||
} else if (connectionError) {
|
||||
loginDialog.displayError(connectionError);
|
||||
}
|
||||
});
|
||||
},
|
||||
/* cancelCallback */ () => loginDialog.close());
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate for the conference.
|
||||
* Uses external service for auth if conference supports that.
|
||||
* @param {JitsiConference} room
|
||||
* @param {string} [lockPassword] password to use if the conference is locked
|
||||
*/
|
||||
function authenticate(room, lockPassword) {
|
||||
if (isTokenAuthEnabled || room.isExternalAuthEnabled()) {
|
||||
function authenticateExternal(room: Object, lockPassword: string) {
|
||||
const config = APP.store.getState()['features/base/config'];
|
||||
|
||||
if (isTokenAuthEnabled(config) || room.isExternalAuthEnabled()) {
|
||||
doExternalAuth(room, lockPassword);
|
||||
} else {
|
||||
doXmppAuth(room, lockPassword);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -219,7 +177,7 @@ function authenticate(room, lockPassword) {
|
|||
* @param {string} [lockPassword] password to use if the conference is locked
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function logout(room) {
|
||||
function logout(room: Object) {
|
||||
return new Promise(resolve => {
|
||||
room.room.moderator.logout(resolve);
|
||||
}).then(url => {
|
||||
|
@ -232,83 +190,7 @@ function logout(room) {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify user that authentication is required to create the conference.
|
||||
* @param {JitsiConference} room
|
||||
* @param {string} [lockPassword] password to use if the conference is locked
|
||||
*/
|
||||
function requireAuth(room, lockPassword) {
|
||||
if (authRequiredDialog) {
|
||||
return;
|
||||
}
|
||||
|
||||
authRequiredDialog = LoginDialog.showAuthRequiredDialog(
|
||||
room.getName(), authenticate.bind(null, room, lockPassword)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close auth-related dialogs if there are any.
|
||||
*/
|
||||
function closeAuth() {
|
||||
if (externalAuthWindow) {
|
||||
externalAuthWindow.close();
|
||||
externalAuthWindow = null;
|
||||
}
|
||||
|
||||
if (authRequiredDialog) {
|
||||
authRequiredDialog.close();
|
||||
authRequiredDialog = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function showXmppPasswordPrompt(roomName, connect) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const authDialog = LoginDialog.showAuthDialog(
|
||||
(id, password) => {
|
||||
connect(id, password, roomName).then(connection => {
|
||||
authDialog.close();
|
||||
resolve(connection);
|
||||
}, err => {
|
||||
if (err === JitsiConnectionErrors.PASSWORD_REQUIRED) {
|
||||
authDialog.displayError(err);
|
||||
} else {
|
||||
authDialog.close();
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show Authentication Dialog and try to connect with new credentials.
|
||||
* If failed to connect because of PASSWORD_REQUIRED error
|
||||
* then ask for password again.
|
||||
* @param {string} [roomName] name of the conference room
|
||||
* @param {function(id, password, roomName)} [connect] function that returns
|
||||
* a Promise which resolves with JitsiConnection or fails with one of
|
||||
* JitsiConnectionErrors.
|
||||
* @returns {Promise<JitsiConnection>}
|
||||
*/
|
||||
function requestAuth(roomName, connect) {
|
||||
if (isTokenAuthEnabled) {
|
||||
// This Promise never resolves as user gets redirected to another URL
|
||||
return new Promise(() => redirectToTokenAuthService(roomName));
|
||||
}
|
||||
|
||||
return showXmppPasswordPrompt(roomName, connect);
|
||||
|
||||
}
|
||||
|
||||
export default {
|
||||
authenticate,
|
||||
requireAuth,
|
||||
requestAuth,
|
||||
closeAuth,
|
||||
authenticateExternal,
|
||||
logout
|
||||
};
|
||||
|
|
|
@ -212,45 +212,5 @@ export default {
|
|||
}
|
||||
|
||||
return dialog;
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows a notification that authentication is required to create the
|
||||
* conference, so the local participant should authenticate or wait for a
|
||||
* host.
|
||||
*
|
||||
* @param {string} room - The name of the conference.
|
||||
* @param {function} onAuthNow - The callback to invoke if the local
|
||||
* participant wants to authenticate.
|
||||
* @returns dialog
|
||||
*/
|
||||
showAuthRequiredDialog(room, onAuthNow) {
|
||||
const msg = APP.translation.generateTranslationHTML(
|
||||
'[html]dialog.WaitForHostMsg',
|
||||
{ room }
|
||||
);
|
||||
const buttonTxt = APP.translation.generateTranslationHTML(
|
||||
'dialog.IamHost'
|
||||
);
|
||||
const buttons = [ {
|
||||
title: buttonTxt,
|
||||
value: 'authNow'
|
||||
} ];
|
||||
|
||||
return APP.UI.messageHandler.openDialog(
|
||||
'dialog.WaitingForHost',
|
||||
msg,
|
||||
true,
|
||||
buttons,
|
||||
(e, submitValue) => {
|
||||
// Do not close the dialog yet.
|
||||
e.preventDefault();
|
||||
|
||||
// Open login popup.
|
||||
if (submitValue === 'authNow') {
|
||||
onAuthNow();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -12,12 +12,6 @@ import {
|
|||
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Flag for enabling/disabling popups.
|
||||
* @type {boolean}
|
||||
*/
|
||||
let popupEnabled = true;
|
||||
|
||||
/**
|
||||
* Currently displayed two button dialog.
|
||||
* @type {null}
|
||||
|
@ -167,7 +161,7 @@ const messageHandler = {
|
|||
|
||||
let { classes } = options;
|
||||
|
||||
if (!popupEnabled || twoButtonDialog) {
|
||||
if (twoButtonDialog) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -233,88 +227,6 @@ const messageHandler = {
|
|||
return $.prompt.getApi();
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows a message to the user with two buttons: first is given as a
|
||||
* parameter and the second is Cancel.
|
||||
*
|
||||
* @param titleKey the key for the title of the message
|
||||
* @param msgString the text of the message
|
||||
* @param persistent boolean value which determines whether the message is
|
||||
* persistent or not
|
||||
* @param buttons object with the buttons. The keys must be the name of the
|
||||
* button and value is the value that will be passed to
|
||||
* submitFunction
|
||||
* @param submitFunction function to be called on submit
|
||||
* @param loadedFunction function to be called after the prompt is fully
|
||||
* loaded
|
||||
* @param closeFunction function to be called on dialog close
|
||||
* @param {object} dontShowAgain - options for dont show again checkbox.
|
||||
* @param {string} dontShowAgain.id the id of the checkbox.
|
||||
* @param {string} dontShowAgain.textKey the key for the text displayed
|
||||
* next to checkbox
|
||||
* @param {boolean} dontShowAgain.checked if true the checkbox is foing to
|
||||
* be checked
|
||||
* @param {Array} dontShowAgain.buttonValues The button values that will
|
||||
* trigger storing the checkbox value
|
||||
* @param {string} dontShowAgain.localStorageKey the key for the local
|
||||
* storage. if not provided dontShowAgain.id will be used.
|
||||
*/
|
||||
openDialog(// eslint-disable-line max-params
|
||||
titleKey,
|
||||
msgString,
|
||||
persistent,
|
||||
buttons,
|
||||
submitFunction,
|
||||
loadedFunction,
|
||||
closeFunction,
|
||||
dontShowAgain) {
|
||||
if (!popupEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dontShowTheDialog(dontShowAgain)) {
|
||||
// Maybe we should pass some parameters here? I'm not sure
|
||||
// and currently we don't need any parameters.
|
||||
submitFunction();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const args = {
|
||||
title: this._getFormattedTitleString(titleKey),
|
||||
persistent,
|
||||
buttons,
|
||||
defaultButton: 1,
|
||||
promptspeed: 0,
|
||||
loaded() {
|
||||
if (loadedFunction) {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
loadedFunction.apply(this, arguments);
|
||||
}
|
||||
|
||||
// Hide the close button
|
||||
if (persistent) {
|
||||
$('.jqiclose', this).hide();
|
||||
}
|
||||
},
|
||||
submit: dontShowAgainSubmitFunctionWrapper(
|
||||
dontShowAgain, submitFunction),
|
||||
close: closeFunction,
|
||||
classes: this._getDialogClasses()
|
||||
};
|
||||
|
||||
if (persistent) {
|
||||
args.closeText = '';
|
||||
}
|
||||
|
||||
const dialog = $.prompt(
|
||||
msgString + generateDontShowCheckbox(dontShowAgain), args);
|
||||
|
||||
APP.translation.translateElement(dialog);
|
||||
|
||||
return $.prompt.getApi();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the formatted title string.
|
||||
*
|
||||
|
@ -358,9 +270,6 @@ const messageHandler = {
|
|||
* @param translateOptions options passed to translation
|
||||
*/
|
||||
openDialogWithStates(statesObject, options, translateOptions) {
|
||||
if (!popupEnabled) {
|
||||
return;
|
||||
}
|
||||
const { classes, size } = options;
|
||||
const defaultClasses = this._getDialogClasses(size);
|
||||
|
||||
|
@ -397,10 +306,6 @@ const messageHandler = {
|
|||
*/
|
||||
// eslint-disable-next-line max-params
|
||||
openCenteredPopup(url, w, h, onPopupClosed) {
|
||||
if (!popupEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const l = window.screenX + (window.innerWidth / 2) - (w / 2);
|
||||
const t = window.screenY + (window.innerHeight / 2) - (h / 2);
|
||||
const popup = window.open(
|
||||
|
@ -481,19 +386,6 @@ const messageHandler = {
|
|||
notify(titleKey, messageKey, messageArguments) {
|
||||
this.participantNotification(
|
||||
null, titleKey, null, messageKey, messageArguments);
|
||||
},
|
||||
|
||||
enablePopups(enable) {
|
||||
popupEnabled = enable;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if dialog is opened
|
||||
* false otherwise
|
||||
* @returns {boolean} isOpened
|
||||
*/
|
||||
isDialogOpened() {
|
||||
return Boolean($.prompt.getCurrentStateName());
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// @flow
|
||||
|
||||
import '../authentication/middleware';
|
||||
import '../base/devices/middleware';
|
||||
import '../e2ee/middleware';
|
||||
import '../external-api/middleware';
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// @flow
|
||||
|
||||
import '../analytics/reducer';
|
||||
import '../authentication/reducer';
|
||||
import '../base/app/reducer';
|
||||
import '../base/audio-only/reducer';
|
||||
import '../base/color-scheme/reducer';
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import '../authentication/reducer';
|
||||
import '../mobile/audio-mode/reducer';
|
||||
import '../mobile/background/reducer';
|
||||
import '../mobile/call-integration/reducer';
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { appNavigate } from '../app/actions';
|
||||
import { checkIfCanJoin, conferenceLeft } from '../base/conference';
|
||||
import { connectionFailed } from '../base/connection';
|
||||
import { openDialog } from '../base/dialog';
|
||||
import { checkIfCanJoin, conferenceLeft } from '../base/conference/actions';
|
||||
import { connectionFailed } from '../base/connection/actions.native';
|
||||
import { openDialog } from '../base/dialog/actions';
|
||||
import { set } from '../base/redux';
|
||||
|
||||
import {
|
|
@ -0,0 +1,67 @@
|
|||
// @flow
|
||||
|
||||
import { maybeRedirectToWelcomePage } from '../app/actions';
|
||||
import { hideDialog, openDialog } from '../base/dialog/actions';
|
||||
|
||||
import {
|
||||
CANCEL_LOGIN
|
||||
} from './actionTypes';
|
||||
import { WaitForOwnerDialog, LoginDialog } from './components';
|
||||
|
||||
/**
|
||||
* Cancels {@ink LoginDialog}.
|
||||
*
|
||||
* @returns {{
|
||||
* type: CANCEL_LOGIN
|
||||
* }}
|
||||
*/
|
||||
export function cancelLogin() {
|
||||
return {
|
||||
type: CANCEL_LOGIN
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels authentication, closes {@link WaitForOwnerDialog}
|
||||
* and navigates back to the welcome page.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function cancelWaitForOwner() {
|
||||
return (dispatch: Function) => {
|
||||
dispatch(maybeRedirectToWelcomePage());
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides a authentication dialog where the local participant
|
||||
* should authenticate.
|
||||
*
|
||||
* @returns {Function}.
|
||||
*/
|
||||
export function hideLoginDialog() {
|
||||
return hideDialog(LoginDialog);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a authentication dialog where the local participant
|
||||
* should authenticate.
|
||||
*
|
||||
* @returns {Function}.
|
||||
*/
|
||||
export function openLoginDialog() {
|
||||
return openDialog(LoginDialog);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a notification dialog that authentication is required to create the.
|
||||
* Conference, so the local participant should authenticate or wait for a
|
||||
* host.
|
||||
*
|
||||
* @returns {Function}.
|
||||
*/
|
||||
export function openWaitForOwnerDialog() {
|
||||
return openDialog(WaitForOwnerDialog);
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from './native';
|
|
@ -0,0 +1 @@
|
|||
export * from './web';
|
|
@ -1,2 +1 @@
|
|||
export { default as LoginDialog } from './LoginDialog';
|
||||
export { default as WaitForOwnerDialog } from './WaitForOwnerDialog';
|
||||
export * from './_';
|
||||
|
|
|
@ -5,20 +5,20 @@ import { Text, TextInput, View } from 'react-native';
|
|||
import { connect as reduxConnect } from 'react-redux';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../base/color-scheme';
|
||||
import { toJid } from '../../base/connection';
|
||||
import { connect } from '../../base/connection/actions.native';
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import { toJid } from '../../../base/connection';
|
||||
import { connect } from '../../../base/connection/actions.native';
|
||||
import {
|
||||
CustomSubmitDialog,
|
||||
FIELD_UNDERLINE,
|
||||
PLACEHOLDER_COLOR,
|
||||
_abstractMapStateToProps,
|
||||
inputDialog as inputDialogStyle
|
||||
} from '../../base/dialog';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { JitsiConnectionErrors } from '../../base/lib-jitsi-meet';
|
||||
import type { StyleType } from '../../base/styles';
|
||||
import { authenticateAndUpgradeRole, cancelLogin } from '../actions';
|
||||
} from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { JitsiConnectionErrors } from '../../../base/lib-jitsi-meet';
|
||||
import type { StyleType } from '../../../base/styles';
|
||||
import { authenticateAndUpgradeRole, cancelLogin } from '../../actions.native';
|
||||
|
||||
// Register styles.
|
||||
import './styles';
|
|
@ -3,10 +3,10 @@
|
|||
import React, { Component } from 'react';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { ConfirmDialog } from '../../base/dialog';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { connect } from '../../base/redux';
|
||||
import { cancelWaitForOwner, _openLoginDialog } from '../actions';
|
||||
import { ConfirmDialog } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { cancelWaitForOwner, _openLoginDialog } from '../../actions.native';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link WaitForOwnerDialog}.
|
||||
|
@ -107,9 +107,7 @@ class WaitForOwnerDialog extends Component<Props> {
|
|||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _room: string
|
||||
* }}
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const { authRequired } = state['features/base/conference'];
|
|
@ -0,0 +1,2 @@
|
|||
export { default as LoginDialog } from './LoginDialog';
|
||||
export { default as WaitForOwnerDialog } from './WaitForOwnerDialog';
|
|
@ -1,5 +1,5 @@
|
|||
import { ColorSchemeRegistry, schemeColor } from '../../base/color-scheme';
|
||||
import { BoxModel } from '../../base/styles';
|
||||
import { ColorSchemeRegistry, schemeColor } from '../../../base/color-scheme';
|
||||
import { BoxModel } from '../../../base/styles';
|
||||
|
||||
/**
|
||||
* The styles of the authentication feature.
|
|
@ -0,0 +1,313 @@
|
|||
// @flow
|
||||
|
||||
import { FieldTextStateless as TextField } from '@atlaskit/field-text';
|
||||
import React, { Component } from 'react';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { connect } from '../../../../../connection';
|
||||
import { toJid } from '../../../base/connection/functions';
|
||||
import { Dialog } from '../../../base/dialog';
|
||||
import { translate, translateToHTML } from '../../../base/i18n';
|
||||
import { JitsiConnectionErrors } from '../../../base/lib-jitsi-meet';
|
||||
import { connect as reduxConnect } from '../../../base/redux';
|
||||
import { authenticateAndUpgradeRole } from '../../actions.native';
|
||||
import { cancelLogin } from '../../actions.web';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link LoginDialog}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* {@link JitsiConference} that needs authentication - will hold a valid
|
||||
* value in XMPP login + guest access mode.
|
||||
*/
|
||||
_conference: Object,
|
||||
|
||||
/**
|
||||
* The server hosts specified in the global config.
|
||||
*/
|
||||
_configHosts: Object,
|
||||
|
||||
/**
|
||||
* Indicates if the dialog should display "connecting" status message.
|
||||
*/
|
||||
_connecting: boolean,
|
||||
|
||||
/**
|
||||
* The error which occurred during login/authentication.
|
||||
*/
|
||||
_error: Object,
|
||||
|
||||
/**
|
||||
* The progress in the floating range between 0 and 1 of the authenticating
|
||||
* and upgrading the role of the local participant/user.
|
||||
*/
|
||||
_progress: number,
|
||||
|
||||
/**
|
||||
* Redux store dispatch method.
|
||||
*/
|
||||
dispatch: Dispatch<any>,
|
||||
|
||||
/**
|
||||
* Invoked when username and password are submitted.
|
||||
*/
|
||||
onSuccess: Function,
|
||||
|
||||
/**
|
||||
* Conference room name.
|
||||
*/
|
||||
roomName: string,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} state of {@link LoginDialog}.
|
||||
*/
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* The user entered password for the conference.
|
||||
*/
|
||||
password: string,
|
||||
|
||||
/**
|
||||
* The user entered local participant name.
|
||||
*/
|
||||
username: string,
|
||||
|
||||
/**
|
||||
* Authentication process starts before joining the conference room.
|
||||
*/
|
||||
loginStarted: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Component that renders the login in conference dialog.
|
||||
*
|
||||
* @returns {React$Element<any>}
|
||||
*/
|
||||
class LoginDialog extends Component<Props, State> {
|
||||
/**
|
||||
* Initializes a new {@code LoginDialog} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
username: '',
|
||||
password: '',
|
||||
loginStarted: false
|
||||
};
|
||||
|
||||
this._onCancelLogin = this._onCancelLogin.bind(this);
|
||||
this._onLogin = this._onLogin.bind(this);
|
||||
this._onChange = this._onChange.bind(this);
|
||||
}
|
||||
|
||||
_onCancelLogin: () => void;
|
||||
|
||||
/**
|
||||
* Called when the cancel button is clicked.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onCancelLogin() {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(cancelLogin());
|
||||
}
|
||||
|
||||
_onLogin: () => void;
|
||||
|
||||
/**
|
||||
* Notifies this LoginDialog that the login button (OK) has been pressed by
|
||||
* the user.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onLogin() {
|
||||
const {
|
||||
_conference: conference,
|
||||
_configHosts: configHosts,
|
||||
roomName,
|
||||
onSuccess,
|
||||
dispatch
|
||||
} = this.props;
|
||||
const { password, username } = this.state;
|
||||
const jid = toJid(username, configHosts);
|
||||
|
||||
if (conference) {
|
||||
dispatch(authenticateAndUpgradeRole(jid, password, conference));
|
||||
} else {
|
||||
this.setState({
|
||||
loginStarted: true
|
||||
});
|
||||
|
||||
connect(jid, password, roomName)
|
||||
.then(connection => {
|
||||
onSuccess && onSuccess(connection);
|
||||
})
|
||||
.catch(() => {
|
||||
this.setState({
|
||||
loginStarted: false
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_onChange: Object => void;
|
||||
|
||||
/**
|
||||
* Callback for the onChange event of the field.
|
||||
*
|
||||
* @param {Object} evt - The static event.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onChange(evt: Object) {
|
||||
this.setState({
|
||||
[evt.target.name]: evt.target.value
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders an optional message, if applicable.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
* @private
|
||||
*/
|
||||
renderMessage() {
|
||||
const {
|
||||
_configHosts: configHosts,
|
||||
_connecting: connecting,
|
||||
_error: error,
|
||||
_progress: progress,
|
||||
t
|
||||
} = this.props;
|
||||
const { username, password } = this.state;
|
||||
const messageOptions = {};
|
||||
let messageKey;
|
||||
|
||||
if (progress && progress >= 0.5) {
|
||||
messageKey = t('connection.FETCH_SESSION_ID');
|
||||
} else if (error) {
|
||||
const { name } = error;
|
||||
|
||||
if (name === JitsiConnectionErrors.PASSWORD_REQUIRED) {
|
||||
const { credentials } = error;
|
||||
|
||||
if (credentials
|
||||
&& credentials.jid === toJid(username, configHosts)
|
||||
&& credentials.password === password) {
|
||||
messageKey = t('dialog.incorrectPassword');
|
||||
}
|
||||
} else if (name) {
|
||||
messageKey = t('dialog.connectErrorWithMsg');
|
||||
messageOptions.msg = `${name} ${error.message}`;
|
||||
}
|
||||
} else if (connecting) {
|
||||
messageKey = t('connection.CONNECTING');
|
||||
}
|
||||
|
||||
if (messageKey) {
|
||||
return (
|
||||
<span>
|
||||
{ translateToHTML(t, messageKey, messageOptions) }
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
_connecting: connecting,
|
||||
t
|
||||
} = this.props;
|
||||
const { password, loginStarted, username } = this.state;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
okDisabled = {
|
||||
connecting
|
||||
|| loginStarted
|
||||
|| !password
|
||||
|| !username
|
||||
}
|
||||
okKey = { t('dialog.login') }
|
||||
onCancel = { this._onCancelLogin }
|
||||
onSubmit = { this._onLogin }
|
||||
titleKey = { t('dialog.authenticationRequired') }
|
||||
width = { 'small' }>
|
||||
<TextField
|
||||
autoFocus = { true }
|
||||
className = 'input-control'
|
||||
compact = { false }
|
||||
label = { t('dialog.user') }
|
||||
name = 'username'
|
||||
onChange = { this._onChange }
|
||||
placeholder = { t('dialog.userIdentifier') }
|
||||
shouldFitContainer = { true }
|
||||
type = 'text'
|
||||
value = { username } />
|
||||
<TextField
|
||||
className = 'input-control'
|
||||
compact = { false }
|
||||
label = { t('dialog.userPassword') }
|
||||
name = 'password'
|
||||
onChange = { this._onChange }
|
||||
shouldFitContainer = { true }
|
||||
type = 'password'
|
||||
value = { password } />
|
||||
{ this.renderMessage() }
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated props for the
|
||||
* {@code LoginDialog} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
*/
|
||||
function mapStateToProps(state) {
|
||||
const {
|
||||
error: authenticateAndUpgradeRoleError,
|
||||
progress,
|
||||
thenableWithCancel
|
||||
} = state['features/authentication'];
|
||||
const { authRequired } = state['features/base/conference'];
|
||||
const { hosts: configHosts } = state['features/base/config'];
|
||||
const {
|
||||
connecting,
|
||||
error: connectionError
|
||||
} = state['features/base/connection'];
|
||||
|
||||
return {
|
||||
_conference: authRequired,
|
||||
_configHosts: configHosts,
|
||||
_connecting: connecting || thenableWithCancel,
|
||||
_error: connectionError || authenticateAndUpgradeRoleError,
|
||||
_progress: progress
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(reduxConnect(mapStateToProps)(LoginDialog));
|
|
@ -0,0 +1,129 @@
|
|||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { Dialog } from '../../../base/dialog';
|
||||
import { translate, translateToHTML } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { openLoginDialog, cancelWaitForOwner } from '../../actions.web';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link WaitForOwnerDialog}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The name of the conference room (without the domain part).
|
||||
*/
|
||||
_room: string,
|
||||
|
||||
/**
|
||||
* Redux store dispatch method.
|
||||
*/
|
||||
dispatch: Dispatch<any>,
|
||||
|
||||
/**
|
||||
* Function to be invoked after click.
|
||||
*/
|
||||
onAuthNow: ?Function,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
}
|
||||
|
||||
/**
|
||||
* Authentication message dialog for host confirmation.
|
||||
*
|
||||
* @returns {React$Element<any>}
|
||||
*/
|
||||
class WaitForOwnerDialog extends PureComponent<Props> {
|
||||
/**
|
||||
* Instantiates a new component.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._onCancelWaitForOwner = this._onCancelWaitForOwner.bind(this);
|
||||
this._onIAmHost = this._onIAmHost.bind(this);
|
||||
}
|
||||
|
||||
_onCancelWaitForOwner: () => void;
|
||||
|
||||
/**
|
||||
* Called when the cancel button is clicked.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onCancelWaitForOwner() {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(cancelWaitForOwner());
|
||||
}
|
||||
|
||||
_onIAmHost: () => void;
|
||||
|
||||
/**
|
||||
* Called when the OK button is clicked.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onIAmHost() {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(openLoginDialog());
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
_room,
|
||||
t
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
okKey = { t('dialog.IamHost') }
|
||||
onCancel = { this._onCancelWaitForOwner }
|
||||
onSubmit = { this._onIAmHost }
|
||||
titleKey = { t('dialog.WaitingForHostTitle') }
|
||||
width = { 'small' }>
|
||||
<span>
|
||||
{
|
||||
translateToHTML(
|
||||
t, 'dialog.WaitForHostMsg', { room: _room })
|
||||
}
|
||||
</span>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated props for the
|
||||
* {@code WaitForOwnerDialog} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
*/
|
||||
function mapStateToProps(state) {
|
||||
const { authRequired } = state['features/base/conference'];
|
||||
|
||||
return {
|
||||
_room: authRequired && authRequired.getName()
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(mapStateToProps)(WaitForOwnerDialog));
|
|
@ -0,0 +1,4 @@
|
|||
// @flow
|
||||
|
||||
export { default as WaitForOwnerDialog } from './WaitForOwnerDialog';
|
||||
export { default as LoginDialog } from './LoginDialog';
|
|
@ -0,0 +1,25 @@
|
|||
// @flow
|
||||
|
||||
import JitsiMeetJS from '../../../react/features/base/lib-jitsi-meet';
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the token for authentication is available.
|
||||
*
|
||||
* @param {Object} config - Configuration state object from store.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isTokenAuthEnabled = (config: Object) =>
|
||||
typeof config.tokenAuthUrl === 'string'
|
||||
&& config.tokenAuthUrl.length;
|
||||
|
||||
|
||||
/**
|
||||
* Token url.
|
||||
*
|
||||
* @param {Object} config - Configuration state object from store.
|
||||
* @returns {string}
|
||||
*/
|
||||
export const getTokenAuthUrl = (config: Object) =>
|
||||
JitsiMeetJS.util.AuthUtil.getTokenAuthUrl.bind(null,
|
||||
config.tokenAuthUrl);
|
|
@ -1,3 +0,0 @@
|
|||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './components';
|
|
@ -26,7 +26,7 @@ import {
|
|||
_openWaitForOwnerDialog,
|
||||
stopWaitForOwner,
|
||||
waitForOwner
|
||||
} from './actions';
|
||||
} from './actions.native';
|
||||
import { LoginDialog, WaitForOwnerDialog } from './components';
|
||||
|
||||
/**
|
|
@ -0,0 +1,134 @@
|
|||
// @flow
|
||||
|
||||
import { maybeRedirectToWelcomePage } from '../app/actions';
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_LEFT
|
||||
} from '../base/conference';
|
||||
import { CONNECTION_ESTABLISHED } from '../base/connection';
|
||||
import { hideDialog, isDialogOpen } from '../base/dialog';
|
||||
import {
|
||||
JitsiConferenceErrors
|
||||
} from '../base/lib-jitsi-meet';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
|
||||
import {
|
||||
CANCEL_LOGIN,
|
||||
STOP_WAIT_FOR_OWNER,
|
||||
WAIT_FOR_OWNER
|
||||
} from './actionTypes';
|
||||
import {
|
||||
stopWaitForOwner,
|
||||
waitForOwner
|
||||
} from './actions.native';
|
||||
import {
|
||||
hideLoginDialog,
|
||||
openWaitForOwnerDialog
|
||||
} from './actions.web';
|
||||
import { LoginDialog, WaitForOwnerDialog } from './components';
|
||||
|
||||
/**
|
||||
* 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: {
|
||||
if (!isDialogOpen(store, WaitForOwnerDialog)) {
|
||||
if (isWaitingForOwner(store)) {
|
||||
store.dispatch(openWaitForOwnerDialog());
|
||||
|
||||
return next(action);
|
||||
}
|
||||
|
||||
store.dispatch(hideLoginDialog());
|
||||
|
||||
store.dispatch(maybeRedirectToWelcomePage());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case CONFERENCE_FAILED: {
|
||||
const { error } = action;
|
||||
let recoverable;
|
||||
|
||||
if (error.name === JitsiConferenceErrors.AUTHENTICATION_REQUIRED) {
|
||||
if (typeof error.recoverable === 'undefined') {
|
||||
error.recoverable = true;
|
||||
}
|
||||
recoverable = error.recoverable;
|
||||
}
|
||||
if (recoverable) {
|
||||
store.dispatch(waitForOwner());
|
||||
} else {
|
||||
store.dispatch(stopWaitForOwner());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case CONFERENCE_JOINED:
|
||||
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 STOP_WAIT_FOR_OWNER:
|
||||
clearExistingWaitForOwnerTimeout(store);
|
||||
store.dispatch(hideDialog(WaitForOwnerDialog));
|
||||
break;
|
||||
|
||||
case WAIT_FOR_OWNER: {
|
||||
clearExistingWaitForOwnerTimeout(store);
|
||||
|
||||
const { handler, timeoutMs } = action;
|
||||
|
||||
action.waitForOwnerTimeoutID = setTimeout(handler, timeoutMs);
|
||||
|
||||
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 }: { getState: Function }) {
|
||||
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 {void}
|
||||
*/
|
||||
function isWaitingForOwner({ getState }: { getState: Function }) {
|
||||
return getState()['features/authentication'].waitForOwnerTimeoutID;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
/* @flow */
|
||||
// @flow
|
||||
|
||||
import { assign, ReducerRegistry } from '../base/redux';
|
||||
|
||||
|
@ -10,6 +10,14 @@ import {
|
|||
WAIT_FOR_OWNER
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* Listens for actions which change the state of the authentication feature.
|
||||
*
|
||||
* @param {Object} state - The Redux state of the authentication feature.
|
||||
* @param {Object} action - Action object.
|
||||
* @param {string} action.type - Type of action.
|
||||
* @returns {Object}
|
||||
*/
|
||||
ReducerRegistry.register('features/authentication', (state = {}, action) => {
|
||||
switch (action.type) {
|
||||
case CANCEL_LOGIN:
|
||||
|
|
Loading…
Reference in New Issue