[RN] Support XMPP authentication

This commit is contained in:
Lyubo Marinov 2017-09-18 02:09:43 -05:00
parent 141acea194
commit 241dc3b147
16 changed files with 494 additions and 566 deletions

View File

@ -264,7 +264,7 @@
"removeSharedVideoMsg": "Are you sure you would like to remove your shared video?",
"alreadySharedVideoMsg": "Another member is already sharing video. This conference allows only one shared video at a time.",
"WaitingForHost": "Waiting for the host ...",
"WaitForHostMsg": "The conference '__room__' has not yet started. If you are the host then please authenticate. Otherwise, please wait for the host to arrive.",
"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.",
"IamHost": "I am the host",
"Cancel": "Cancel",
"Submit": "Submit",

View File

@ -140,37 +140,40 @@ function initJWTTokenListener(room) {
* @param {string} [lockPassword] password to use if the conference is locked
*/
function doXmppAuth(room, lockPassword) {
const loginDialog = LoginDialog.showAuthDialog(function (id, password) {
const authConnection = room.createAuthenticationConnection();
authConnection.authenticateAndUpgradeRole({
const loginDialog = LoginDialog.showAuthDialog(
/* successCallback */ (id, password) => {
room.authenticateAndUpgradeRole({
id,
password,
roomPassword: lockPassword,
onLoginSuccessful: () => { /* Called when XMPP login succeeds */
/** Called when the XMPP login succeeds. */
onLoginSuccessful() {
loginDialog.displayConnectionStatus(
'connection.FETCH_SESSION_ID');
}
})
.then(() => {
.then(
/* onFulfilled */ () => {
loginDialog.displayConnectionStatus(
'connection.GOT_SESSION_ID');
loginDialog.close();
})
.catch(error => {
},
/* onRejected */ error => {
logger.error('authenticateAndUpgradeRole failed', error);
if (error.authenticationError) {
const { authenticationError, connectionError } = error;
if (authenticationError) {
loginDialog.displayError(
'connection.GET_SESSION_ID_ERROR', {
msg: error.authenticationError
});
} else {
loginDialog.displayError(error.connectionError);
'connection.GET_SESSION_ID_ERROR',
{ msg: authenticationError });
} else if (connectionError) {
loginDialog.displayError(connectionError);
}
});
}, function () {
loginDialog.close();
});
},
/* cancelCallback */ () => loginDialog.close());
}
/**

View File

@ -1,4 +1,5 @@
/* global $, APP, config, JitsiMeetJS */
import { toJid } from '../../../react/features/base/connection';
const ConnectionErrors = JitsiMeetJS.errors.connection;
@ -196,34 +197,38 @@ export default {
},
/**
* Show notification that authentication is required
* to create the conference, so he should authenticate or wait for a host.
* @param {string} roomName name of the conference
* @param {function} onAuthNow callback to invoke if
* user want to authenticate.
* 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: function (roomName, onAuthNow) {
var msg = APP.translation.generateTranslationHTML(
"dialog.WaitForHostMsg", {room: roomName}
showAuthRequiredDialog(room, onAuthNow) {
const msg = APP.translation.generateTranslationHTML(
'[html]dialog.WaitForHostMsg',
{ room }
);
var buttonTxt = APP.translation.generateTranslationHTML(
"dialog.IamHost"
const buttonTxt = APP.translation.generateTranslationHTML(
'dialog.IamHost'
);
var buttons = [{title: buttonTxt, value: "authNow"}];
const buttons = [{
title: buttonTxt,
value: 'authNow'
}];
return APP.UI.messageHandler.openDialog(
"dialog.WaitingForHost",
'dialog.WaitingForHost',
msg,
true,
buttons,
function (e, submitValue) {
// Do not close the dialog yet
(e, submitValue) => {
// Do not close the dialog yet.
e.preventDefault();
// Open login popup
// Open login popup.
if (submitValue === 'authNow') {
onAuthNow();
}

View File

@ -4,6 +4,7 @@ import React from 'react';
import { Linking } from 'react-native';
import '../../analytics';
import '../../authentication';
import { Platform } from '../../base/react';
import '../../mobile/audio-mode';
import '../../mobile/background';
@ -15,7 +16,6 @@ import '../../mobile/proximity';
import '../../mobile/wake-lock';
import { AbstractApp } from './AbstractApp';
import '../../authentication';
/**
* Root application component.

View File

@ -28,38 +28,31 @@ export const CANCEL_WAIT_FOR_OWNER = Symbol('CANCEL_WAIT_FOR_OWNER');
*/
export const STOP_WAIT_FOR_OWNER = Symbol('STOP_WAIT_FOR_OWNER');
/**
* The type of (redux) action which signals that the process of authenticating
* and upgrading the current conference user's role has been started.
*
* {
* type: UPGRADE_ROLE_STARTED,
* authConnection: JitsiAuthConnection
* }
*/
export const UPGRADE_ROLE_STARTED = Symbol('UPGRADE_ROLE_STARTED');
/**
* The type of (redux) action which informs that the authentication and role
* upgrade process has been completed successfully.
* upgrade process has finished either with success or with a specific error.
* If <tt>error</tt> is <tt>undefined</tt>, then the process succeeded;
* otherwise, it failed. Refer to
* {@link JitsiConference#authenticateAndUpgradeRole} in lib-jitsi-meet for the
* error details.
*
* {
* type: UPGRADE_ROLE_SUCCESS
* }
*/
export const UPGRADE_ROLE_SUCCESS = Symbol('UPGRADE_ROLE_SUCCESS');
/**
* The type of (redux) action which informs that the authentication and role
* upgrade process has failed with an error. Check the docs of
* {@link JitsiAuthConnection} for more details about the error structure.
*
* {
* type: UPGRADE_ROLE_SUCCESS,
* type: UPGRADE_ROLE_FINISHED,
* error: Object
* }
*/
export const UPGRADE_ROLE_FAILED = Symbol('UPGRADE_ROLE_FAILED');
export const UPGRADE_ROLE_FINISHED = Symbol('UPGRADE_ROLE_FINISHED');
/**
* The type of (redux) action which signals that the process of authenticating
* and upgrading the local participant's role has been started.
*
* {
* type: UPGRADE_ROLE_STARTED,
* thenableWithCancel: Object
* }
*/
export const UPGRADE_ROLE_STARTED = Symbol('UPGRADE_ROLE_STARTED');
/**
* The type of (redux) action that sets delayed handler which will check if

View File

@ -1,52 +1,56 @@
import { openDialog } from '../base/dialog/actions';
import { checkIfCanJoin } from '../base/conference/actions';
/* @flow */
import { checkIfCanJoin } from '../base/conference';
import { openDialog } from '../base/dialog';
import {
CANCEL_LOGIN,
CANCEL_WAIT_FOR_OWNER,
STOP_WAIT_FOR_OWNER,
UPGRADE_ROLE_FAILED,
UPGRADE_ROLE_FINISHED,
UPGRADE_ROLE_STARTED,
UPGRADE_ROLE_SUCCESS,
WAIT_FOR_OWNER
} from './actionTypes';
import { LoginDialog, WaitForOwnerDialog } from './components';
const logger = require('jitsi-meet-logger').getLogger(__filename);
/**
* Instantiates new {@link JitsiAuthConnection} and uses it to authenticate and
* upgrade role of the current conference user to moderator which will allow to
* create and join new conference on XMPP password + guest access configuration.
* See {@link LoginDialog} description for more info.
* Initiates authenticating and upgrading the role of the local participant to
* moderator which will allow to create and join a new conference on an XMPP
* password + guest access configuration. Refer to {@link LoginDialog} for more
* info.
*
* @param {string} id - XMPP user's id eg. user@domain.com.
* @param {string} userPassword - The user's password.
* @param {JitsiConference} conference - The conference for which user's role
* will be upgraded.
* @returns {function({dispatch: Function, getState: Function})}
* @param {string} id - The XMPP user's ID (e.g. user@domain.com).
* @param {string} password - The XMPP user's password.
* @param {JitsiConference} conference - The conference for which the local
* participant's role will be upgraded.
* @returns {function({ dispatch: Dispatch, getState: Function })}
*/
export function authenticateAndUpgradeRole(id, userPassword, conference) {
return (dispatch, getState) => {
const authConnection = conference.createAuthenticationConnection();
dispatch(_upgradeRoleStarted(authConnection));
export function authenticateAndUpgradeRole(
id: string,
password: string,
conference: Object) {
return (dispatch: Dispatch, getState: Function) => {
const { password: roomPassword }
= getState()['features/base/conference'];
authConnection.authenticateAndUpgradeRole({
const process
= conference.authenticateAndUpgradeRole({
id,
password: userPassword,
password,
roomPassword
})
.then(() => {
dispatch(_upgradeRoleSuccess());
})
.catch(error => {
// Lack of error means the operation was canceled, so no need to log
// that on error level.
if (error.error) {
console.error('upgradeRoleFailed', error);
});
dispatch(_upgradeRoleStarted(process));
process.then(
/* onFulfilled */ () => dispatch(_upgradeRoleFinished()),
/* onRejected */ error => {
// The lack of an error signals a cancellation.
if (error.authenticationError || error.connectionError) {
logger.error('authenticateAndUpgradeRole failed', error);
}
dispatch(_upgradeRoleFailed(error));
dispatch(_upgradeRoleFinished(error));
});
};
}
@ -78,118 +82,77 @@ export function cancelWaitForOwner() {
}
/**
* Stops waiting for conference owner and clears any pending timeout.
* Opens {@link LoginDialog} which will ask to enter username and password
* for the current conference.
*
* @protected
* @returns {Action}
*/
export function _openLoginDialog() {
return openDialog(LoginDialog);
}
/**
* Opens {@link WaitForOnwerDialog}.
*
* @protected
* @returns {Action}
*/
export function _openWaitForOwnerDialog() {
return openDialog(WaitForOwnerDialog);
}
/**
* Stops waiting for the conference owner.
*
* @returns {{
* type: STOP_WAIT_FOR_OWNER
* }}
*/
export function clearWaitForOwnerTimeout() {
export function stopWaitForOwner() {
return {
type: STOP_WAIT_FOR_OWNER
};
}
/**
* Sets a delayed "wait for owner" handler function.
*
* @param {Function} handler - The "wait for owner" handler function.
* @param {number} waitMs - The delay in milliseconds.
* Signals that the process of authenticating and upgrading the local
* participant's role has finished either with success or with a specific error.
*
* @param {Object} error - If <tt>undefined</tt>, then the process of
* authenticating and upgrading the local participant's role has succeeded;
* otherwise, it has failed with the specified error. Refer to
* {@link JitsiConference#authenticateAndUpgradeRole} in lib-jitsi-meet for the
* error details.
* @private
* @returns {{
* type: WAIT_FOR_OWNER,
* handler: Function,
* timeoutMs: number
* }}
*/
function _setWaitForOwnerTimeout(handler, waitMs) {
return {
type: WAIT_FOR_OWNER,
handler,
timeoutMs: waitMs
};
}
/**
* Displays {@link LoginDialog} which will ask to enter username and password
* for the current conference.
*
* @protected
* @returns {{
* type: OPEN_DIALOG,
* component: LoginDialog,
* props: React.PropTypes
* }}
*/
export function _showLoginDialog() {
return openDialog(LoginDialog, { });
}
/**
* Displays {@link WaitForOnwerDialog}.
*
* @protected
* @returns {{
* type: OPEN_DIALOG,
* component: WaitForOwnerDialog,
* props: React.PropTypes
* }}
*/
export function _showWaitForOwnerDialog() {
return openDialog(WaitForOwnerDialog, { });
}
/**
* Emits an error which occurred during {@link authenticateAndUpgradeRole}.
*
* @param {Object} error - Check the docs of {@link JitsiAuthConnection} in
* lib-jitsi-meet for more details about the error's structure.
*
* @private
* @returns {{
* type: UPGRADE_ROLE_FAILED,
* type: UPGRADE_ROLE_FINISHED,
* error: Object
* }}
*/
function _upgradeRoleFailed(error) {
function _upgradeRoleFinished(error) {
return {
type: UPGRADE_ROLE_FAILED,
type: UPGRADE_ROLE_FINISHED,
error
};
}
/**
* Signals that the role upgrade process has been started using given
* {@link JitsiAuthConnection} instance.
*
* @param {JitsiAuthConnection} authenticationConnection - The authentication
* connection instance that can be used to cancel the process.
* Signals that a process of authenticating and upgrading the local
* participant's role has started.
*
* @param {Object} thenableWithCancel - The process of authenticating and
* upgrading the local participant's role.
* @private
* @returns {{
* type: UPGRADE_ROLE_STARTED,
* authConnection: JitsiAuthConnection
* thenableWithCancel: Object
* }}
*/
function _upgradeRoleStarted(authenticationConnection) {
function _upgradeRoleStarted(thenableWithCancel) {
return {
type: UPGRADE_ROLE_STARTED,
authConnection: authenticationConnection
};
}
/**
* Signals that the role upgrade process has been completed successfully.
*
* @private
* @returns {{
* type: UPGRADE_ROLE_SUCCESS
* }}
*/
function _upgradeRoleSuccess() {
return {
type: UPGRADE_ROLE_SUCCESS
thenableWithCancel
};
}
@ -198,13 +161,13 @@ function _upgradeRoleSuccess() {
* start the process of "waiting for the owner" by periodically trying to join
* the room every five seconds.
*
* @returns {function({ dispatch: Function})}
* @returns {function({ dispatch: Dispatch })}
*/
export function waitForOwner() {
return dispatch => {
dispatch(
_setWaitForOwnerTimeout(
() => dispatch(checkIfCanJoin()),
5000));
};
return (dispatch: Dispatch) =>
dispatch({
type: WAIT_FOR_OWNER,
handler: () => dispatch(checkIfCanJoin()),
timeoutMs: 5000
});
}

View File

@ -1,22 +1,14 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Text, TextInput, View } from 'react-native';
import { connect as reduxConnect } from 'react-redux';
import {
Button,
Modal,
Text,
TextInput,
View
} from 'react-native';
import {
authenticateAndUpgradeRole,
cancelLogin
} from '../actions';
import {
connect,
toJid
} from '../../base/connection';
import { connect, toJid } from '../../base/connection';
import { Dialog } from '../../base/dialog';
import { translate } from '../../base/i18n';
import { JitsiConnectionErrors } from '../../base/lib-jitsi-meet';
import { authenticateAndUpgradeRole, cancelLogin } from '../actions';
import styles from './styles';
/**
@ -36,15 +28,15 @@ import styles from './styles';
* yet, Jicofo will not allow to start new conference. This will trigger
* 'CONFERENCE_FAILED' action with JitsiConferenceErrors.AUTHENTICATION_REQUIRED
* error and 'authRequired' value of 'features/base/conference' will hold
* the {@link JitsiConference} instance. If user decides to authenticate a new
* {@link JitsiAuthConnection} will be created from which separate XMPP
* connection is established and authentication is performed. In case it
* succeeds Jicofo will assign new session ID which then can be used from
* the anonymous domain connection to create and join the room. This part is
* done by {@link JitsiAuthConnection} from lib-jitsi-meet.
* the {@link JitsiConference} instance. If user decides to authenticate, a
* new/separate XMPP connection is established and authentication is performed.
* In case it succeeds, Jicofo will assign new session ID which then can be used
* from the anonymous domain connection to create and join the room. This part
* is done by {@link JitsiConference#authenticateAndUpgradeRole} in
* lib-jitsi-meet.
*
* See https://github.com/jitsi/jicofo#secure-domain for configuration
* parameters description.
* See {@link https://github.com/jitsi/jicofo#secure-domain} for a description
* of the configuration parameters.
*/
class LoginDialog extends Component {
/**
@ -57,37 +49,37 @@ class LoginDialog extends Component {
* {@link JitsiConference} that needs authentication - will hold a valid
* value in XMPP login + guest access mode.
*/
conference: React.PropTypes.object,
_conference: PropTypes.object,
/**
*
*/
configHosts: React.PropTypes.object,
_configHosts: PropTypes.object,
/**
* Indicates if the dialog should display "connecting" status message.
*/
connecting: React.PropTypes.bool,
/**
* Redux store dispatch method.
*/
dispatch: React.PropTypes.func,
_connecting: PropTypes.bool,
/**
* The error which occurred during login/authentication.
*/
error: React.PropTypes.string,
_error: PropTypes.string,
/**
* Any extra details about the error provided by lib-jitsi-meet.
*/
errorDetails: React.PropTypes.string,
_errorDetails: PropTypes.string,
/**
* Redux store dispatch method.
*/
dispatch: PropTypes.func,
/**
* Invoked to obtain translated strings.
*/
t: React.PropTypes.func
t: PropTypes.func
};
/**
@ -99,16 +91,16 @@ class LoginDialog extends Component {
constructor(props) {
super(props);
// Bind event handlers so they are only bound once for every instance.
this._onCancel = this._onCancel.bind(this);
this._onLogin = this._onLogin.bind(this);
this._onUsernameChange = this._onUsernameChange.bind(this);
this._onPasswordChange = this._onPasswordChange.bind(this);
this.state = {
username: '',
password: ''
};
// Bind event handlers so they are only bound once per instance.
this._onCancel = this._onCancel.bind(this);
this._onLogin = this._onLogin.bind(this);
this._onPasswordChange = this._onPasswordChange.bind(this);
this._onUsernameChange = this._onUsernameChange.bind(this);
}
/**
@ -119,9 +111,9 @@ class LoginDialog extends Component {
*/
render() {
const {
error,
errorDetails,
connecting,
_connecting: connecting,
_error: error,
_errorDetails: errorDetails,
t
} = this.props;
@ -132,43 +124,38 @@ class LoginDialog extends Component {
messageKey = 'dialog.incorrectPassword';
} else if (error) {
messageKey = 'dialog.connectErrorWithMsg';
messageOptions.msg = `${error} ${errorDetails}`;
}
return (
<Modal
onRequestClose = { this._onCancel }
style = { styles.outerArea }
transparent = { true } >
<View style = { styles.dialogBox }>
<Text>Username:</Text>
<Dialog
okDisabled = { connecting }
onCancel = { this._onCancel }
onSubmit = { this._onLogin }
titleKey = 'dialog.passwordRequired'>
<View style = { styles.loginDialog }>
<TextInput
onChangeText = { this._onUsernameChange }
placeholder = { 'user@domain.com' }
style = { styles.textInput }
style = { styles.loginDialogTextInput }
value = { this.state.username } />
<Text>Password:</Text>
<TextInput
onChangeText = { this._onPasswordChange }
placeholder = { t('dialog.userPassword') }
secureTextEntry = { true }
style = { styles.textInput }
style = { styles.loginDialogTextInput }
value = { this.state.password } />
<Text>
{error ? t(messageKey, messageOptions) : ''}
{connecting && !error
? t('connection.CONNECTING') : ''}
<Text style = { styles.loginDialogText }>
{
error
? t(messageKey, messageOptions)
: connecting
? t('connection.CONNECTING')
: ''
}
</Text>
<Button
disabled = { connecting }
onPress = { this._onLogin }
title = { t('dialog.Ok') } />
<Button
onPress = { this._onCancel }
title = { t('dialog.Cancel') } />
</View>
</Modal>
</Dialog>
);
}
@ -216,9 +203,9 @@ class LoginDialog extends Component {
* @returns {void}
*/
_onLogin() {
const conference = this.props.conference;
const { _conference: conference } = this.props;
const { username, password } = this.state;
const jid = toJid(username, this.props.configHosts);
const jid = toJid(username, this.props._configHosts);
// If there's a conference it means that the connection has succeeded,
// but authentication is required in order to join the room.
@ -238,42 +225,45 @@ class LoginDialog extends Component {
* @param {Object} state - The Redux state.
* @private
* @returns {{
* configHosts: Object,
* connecting: boolean,
* error: string,
* errorDetails: string,
* conference: JitsiConference
* _conference: JitsiConference,
* _configHosts: Object,
* _connecting: boolean,
* _error: string,
* _errorDetails: string
* }}
*/
function _mapStateToProps(state) {
const {
upgradeRoleError,
upgradeRoleInProgress
} = state['features/authentication'];
const { authRequired } = state['features/base/conference'];
const { hosts: configHosts } = state['features/base/config'];
const {
connecting,
error: connectionError,
errorMessage: connectionErrorMessage
} = state['features/base/connection'];
const {
authRequired
} = state['features/base/conference'];
const {
upgradeRoleError,
upgradeRoleInProgress
} = state['features/authentication'];
const error
= connectionError
|| (upgradeRoleError
&& (upgradeRoleError.connectionError
|| upgradeRoleError.authenticationError));
let error;
let errorDetails;
if (connectionError) {
error = connectionError;
errorDetails = connectionErrorMessage;
} else if (upgradeRoleError) {
error
= upgradeRoleError.connectionError
|| upgradeRoleError.authenticationError;
errorDetails = upgradeRoleError.message;
}
return {
configHosts,
connecting: Boolean(connecting) || Boolean(upgradeRoleInProgress),
error,
errorDetails:
(connectionError && connectionErrorMessage)
|| (upgradeRoleError && upgradeRoleError.message),
conference: authRequired
_conference: authRequired,
_configHosts: configHosts,
_connecting: Boolean(connecting) || Boolean(upgradeRoleInProgress),
_error: error,
_errorDetails: errorDetails
};
}

View File

@ -1,9 +1,12 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Text } from 'react-native';
import { connect } from 'react-redux';
import { Button, Modal, Text, View } from 'react-native';
import { Dialog } from '../../base/dialog';
import { translate } from '../../base/i18n';
import { _showLoginDialog, cancelWaitForOwner } from '../actions';
import { cancelWaitForOwner, _openLoginDialog } from '../actions';
import styles from './styles';
/**
@ -19,20 +22,20 @@ class WaitForOwnerDialog extends Component {
* @static
*/
static propTypes = {
/**
* Redux store dispatch function.
*/
dispatch: React.PropTypes.func,
/**
* The name of the conference room (without the domain part).
*/
roomName: React.PropTypes.string,
_room: PropTypes.string,
/**
* Redux store dispatch function.
*/
dispatch: PropTypes.func,
/**
* Invoked to obtain translated strings.
*/
t: React.PropTypes.func
t: PropTypes.func
};
/**
@ -44,8 +47,9 @@ class WaitForOwnerDialog extends Component {
constructor(props) {
super(props);
this._onLogin = this._onLogin.bind(this);
// Bind event handlers so they are only bound once per instance.
this._onCancel = this._onCancel.bind(this);
this._onLogin = this._onLogin.bind(this);
}
/**
@ -56,43 +60,25 @@ class WaitForOwnerDialog extends Component {
*/
render() {
const {
roomName,
_room: room,
t
} = this.props;
return (
<Modal
onRequestClose = { this._onCancel }
style = { styles.outerArea }
transparent = { true } >
<View style = { styles.dialogBox } >
<Text>
{ t(
'dialog.WaitForHostMsg',
{ room: roomName })
<Dialog
okTitleKey = { 'dialog.IamHost' }
onCancel = { this._onCancel }
onSubmit = { this._onLogin }
titleKey = 'dialog.WaitingForHost'>
<Text style = { styles.waitForOwnerDialog }>
{
this.renderHTML(t('dialog.WaitForHostMsg', { room }))
}
</Text>
<Button
onPress = { this._onLogin }
title = { t('dialog.IamHost') } />
<Button
onPress = { this._onCancel }
title = { t('dialog.Cancel') } />
</View>
</Modal>
</Dialog>
);
}
/**
* Called when the OK button is clicked.
*
* @private
* @returns {void}
*/
_onLogin() {
this.props.dispatch(_showLoginDialog());
}
/**
* Called when the cancel button is clicked.
*
@ -102,6 +88,33 @@ class WaitForOwnerDialog extends Component {
_onCancel() {
this.props.dispatch(cancelWaitForOwner());
}
/**
* Called when the OK button is clicked.
*
* @private
* @returns {void}
*/
_onLogin() {
this.props.dispatch(_openLoginDialog());
}
/**
* Renders a specific <tt>string</tt> which may contain HTML.
*
* @param {string} html - The <tt>string</tt> which may contain HTML to
* render.
* @returns {string}
*/
_renderHTML(html) {
if (typeof html === 'string') {
// TODO Limited styling may easily be provided by utilizing Text
// with style.
return html.replace(/<\\?b>/gi, '');
}
return html;
}
}
/**
@ -111,7 +124,7 @@ class WaitForOwnerDialog extends Component {
* @param {Object} state - The Redux state.
* @private
* @returns {{
* roomName: string
* _room: string
* }}
*/
function _mapStateToProps(state) {
@ -120,7 +133,7 @@ function _mapStateToProps(state) {
} = state['features/base/conference'];
return {
roomName: authRequired && authRequired.getName()
_room: authRequired && authRequired.getName()
};
}

View File

@ -1,23 +1,55 @@
import {
ColorPalette,
createStyleSheet
} from '../../base/styles';
import { BoxModel, createStyleSheet } from '../../base/styles';
/**
* The style common to <tt>LoginDialog</tt> and <tt>WaitForOwnerDialog</tt>.
*/
const dialog = {
marginBottom: BoxModel.margin,
marginTop: BoxModel.margin
};
/**
* The style common to <tt>Text</tt> rendered by <tt>LoginDialog</tt> and
* <tt>WaitForOwnerDialog</tt>.
*/
const text = {
};
/**
* The styles of the authentication feature.
*/
export default createStyleSheet({
outerArea: {
flex: 1
/**
* The style of <tt>LoginDialog</tt>.
*/
loginDialog: {
...dialog,
flex: 0,
flexDirection: 'column'
},
dialogBox: {
marginLeft: '10%',
marginRight: '10%',
marginTop: '10%',
backgroundColor: ColorPalette.white
/**
* The style of <tt>Text</tt> rendered by <tt>LoginDialog</tt>.
*/
loginDialogText: {
...text
},
textInput: {
height: 25,
fontSize: 16
/**
* The style of <tt>TextInput</tt> rendered by <tt>LoginDialog</tt>.
*/
loginDialogTextInput: {
// XXX Matches react-native-prompt's dialogInput because base/dialog's
// Dialog is implemented using react-native-prompt.
fontSize: 18,
height: 50
},
/**
* The style of <tt>WaitForOwnerDialog</tt>.
*/
waitForOwnerDialog: {
...dialog,
...text
}
});

View File

@ -1,74 +0,0 @@
import {
LoginDialog,
WaitForOwnerDialog
} from './components/index';
import { hideDialog } from '../base/dialog/actions';
/**
* Will clear the wait for conference owner timeout handler if any is currently
* set.
*
* @param {Object} store - The Redux store instance.
* @returns {void}
*/
export function clearExistingWaitForOwnerTimeout(store) {
const { waitForOwnerTimeoutID }
= store.getState()['features/authentication'];
if (waitForOwnerTimeoutID) {
clearTimeout(waitForOwnerTimeoutID);
}
}
/**
* Checks if {@link LoginDialog} is currently open.
*
* @param {Object|Function} getStateOrState - The Redux store instance or
* store's get state method.
* @returns {boolean}
*/
export function isLoginDialogOpened(getStateOrState) {
const state
= typeof getStateOrState === 'function'
? getStateOrState() : getStateOrState;
const dialogState = state['features/base/dialog'];
return dialogState.component && dialogState.component === LoginDialog;
}
/**
* Hides {@link LoginDialog} if it's currently displayed.
*
* @param {Object} store - The Redux store instance.
* @returns {void}
*/
export function hideLoginDialog({ dispatch, getState }) {
if (isLoginDialogOpened(getState)) {
dispatch(hideDialog());
}
}
/**
* Checks if {@link WaitForOwnerDialog} is currently open.
*
* @param {Object} store - The Redux store instance.
* @returns {boolean}
*/
export function isWaitForOwnerDialogOpened({ getState }) {
const dialogState = getState()['features/base/dialog'];
return dialogState.component
&& dialogState.component === WaitForOwnerDialog;
}
/**
* Checks if the cyclic "wait for conference owner" task is currently scheduled.
*
* @param {Object} store - The Redux store instance.
* @returns {boolean}
*/
export function isWaitingForOwner({ getState }) {
const { waitForOwnerTimeoutID } = getState()['features/authentication'];
return Boolean(waitForOwnerTimeoutID);
}

View File

@ -1,7 +1,6 @@
export * from './actions';
export * from './actionTypes';
export * from './functions';
export * from './components';
import './middleware';
import './reducer';

View File

@ -1,40 +1,33 @@
import { appNavigate } from '../app';
import {
CONFERENCE_FAILED,
CONFERENCE_JOINED,
CONFERENCE_LEFT
} from '../base/conference';
import { CONNECTION_ESTABLISHED, CONNECTION_FAILED } from '../base/connection';
import { hideDialog, isDialogOpen } from '../base/dialog';
import {
JitsiConferenceErrors,
JitsiConnectionErrors
} from '../base/lib-jitsi-meet';
import { MiddlewareRegistry } from '../base/redux';
import {
clearWaitForOwnerTimeout,
_showLoginDialog,
_showWaitForOwnerDialog,
_openLoginDialog,
_openWaitForOwnerDialog,
stopWaitForOwner,
waitForOwner
} from './actions';
import { appNavigate } from '../app/actions';
import {
CANCEL_LOGIN,
CANCEL_WAIT_FOR_OWNER,
STOP_WAIT_FOR_OWNER,
WAIT_FOR_OWNER
} from './actionTypes';
import {
CONFERENCE_FAILED,
CONFERENCE_JOINED,
CONFERENCE_LEFT
} from '../base/conference/actionTypes';
import { hideDialog } from '../base/dialog/actions';
import { CONNECTION_ESTABLISHED } from '../base/connection/actionTypes';
import { CONNECTION_FAILED } from '../base/connection';
import {
clearExistingWaitForOwnerTimeout,
hideLoginDialog,
isLoginDialogOpened,
isWaitForOwnerDialogOpened,
isWaitingForOwner
} from './functions';
import {
JitsiConferenceErrors,
JitsiConnectionErrors
} from '../base/lib-jitsi-meet';
import { LoginDialog, WaitForOwnerDialog } from './components';
/**
* Middleware that captures connection or conference failed errors and controlls
* 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.
@ -44,93 +37,121 @@ import {
*/
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case CONNECTION_FAILED: {
if (action.error === JitsiConnectionErrors.PASSWORD_REQUIRED) {
store.dispatch(_showLoginDialog());
}
break;
}
case CONNECTION_ESTABLISHED: {
hideLoginDialog(store);
break;
}
case CONFERENCE_FAILED: {
if (action.error === JitsiConferenceErrors.AUTHENTICATION_REQUIRED) {
store.dispatch(waitForOwner());
} else {
store.dispatch(clearWaitForOwnerTimeout());
}
break;
}
case CONFERENCE_JOINED: {
if (isWaitingForOwner(store)) {
store.dispatch(clearWaitForOwnerTimeout());
}
hideLoginDialog(store);
break;
}
case CONFERENCE_LEFT: {
store.dispatch(clearWaitForOwnerTimeout());
break;
}
case WAIT_FOR_OWNER: {
clearExistingWaitForOwnerTimeout(store);
const { handler, timeoutMs } = action;
const newTimeoutId = setTimeout(handler, timeoutMs);
action.waitForOwnerTimeoutID = newTimeoutId;
// The WAIT_FOR_OWNER action is cyclic and we don't want to hide
// the login dialog every few seconds...
if (!isLoginDialogOpened(store.getState())) {
store.dispatch(_showWaitForOwnerDialog());
}
break;
}
case STOP_WAIT_FOR_OWNER: {
clearExistingWaitForOwnerTimeout(store);
if (isWaitForOwnerDialogOpened(store)) {
store.dispatch(hideDialog());
}
break;
}
case CANCEL_LOGIN: {
const { upgradeRoleInProgress }
= store.getState()['features/authentication'];
if (upgradeRoleInProgress) {
upgradeRoleInProgress.cancel();
}
const waitingForOwner = isWaitingForOwner(store);
upgradeRoleInProgress && upgradeRoleInProgress.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 (!isWaitForOwnerDialogOpened(store) && !waitingForOwner) {
// Go back to app entry point
hideLoginDialog(store);
store.dispatch(appNavigate(undefined));
} else if (!isWaitForOwnerDialogOpened(store) && waitingForOwner) {
// 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);
store.dispatch(_showWaitForOwnerDialog());
store.dispatch(_openWaitForOwnerDialog());
return result;
}
// Go back to the app's entry point.
_hideLoginDialog(store);
store.dispatch(appNavigate(undefined));
}
break;
}
case CANCEL_WAIT_FOR_OWNER: {
const result = next(action);
store.dispatch(clearWaitForOwnerTimeout());
store.dispatch(stopWaitForOwner());
store.dispatch(appNavigate(undefined));
return result;
}
case CONFERENCE_FAILED:
if (action.error === JitsiConferenceErrors.AUTHENTICATION_REQUIRED) {
store.dispatch(waitForOwner());
} else {
store.dispatch(stopWaitForOwner());
}
break;
case CONFERENCE_JOINED:
if (_isWaitingForOwner(store)) {
store.dispatch(stopWaitForOwner());
}
_hideLoginDialog(store);
break;
case CONFERENCE_LEFT:
store.dispatch(stopWaitForOwner());
break;
case CONNECTION_ESTABLISHED:
_hideLoginDialog(store);
break;
case CONNECTION_FAILED:
action.error === JitsiConnectionErrors.PASSWORD_REQUIRED
&& store.dispatch(_openLoginDialog());
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);
// 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 }) {
const { waitForOwnerTimeoutID } = getState()['features/authentication'];
waitForOwnerTimeoutID && clearTimeout(waitForOwnerTimeoutID);
}
/**
* Hides {@link LoginDialog} if it's currently displayed.
*
* @param {Object} store - The redux store.
* @returns {void}
*/
function _hideLoginDialog({ dispatch }) {
dispatch(hideDialog(LoginDialog));
}
/**
* Checks if the cyclic "wait for conference owner" task is currently scheduled.
*
* @param {Object} store - The redux store.
* @returns {boolean}
*/
function _isWaitingForOwner({ getState }) {
return Boolean(getState()['features/authentication'].waitForOwnerTimeoutID);
}

View File

@ -1,43 +1,39 @@
import { assign } from '../base/redux/functions';
import { ReducerRegistry } from '../base/redux';
/* @flow */
import { assign, ReducerRegistry } from '../base/redux';
import {
CANCEL_LOGIN,
STOP_WAIT_FOR_OWNER,
UPGRADE_ROLE_FAILED, UPGRADE_ROLE_STARTED, UPGRADE_ROLE_SUCCESS,
UPGRADE_ROLE_FINISHED,
UPGRADE_ROLE_STARTED,
WAIT_FOR_OWNER
} from './actionTypes';
ReducerRegistry.register('features/authentication', (state = {}, action) => {
switch (action.type) {
case WAIT_FOR_OWNER:
return assign(state, {
waitForOwnerTimeoutID: action.waitForOwnerTimeoutID
});
case UPGRADE_ROLE_STARTED:
return assign(state, {
upgradeRoleError: undefined,
upgradeRoleInProgress: action.authConnection
});
case UPGRADE_ROLE_SUCCESS:
return assign(state, {
upgradeRoleError: undefined,
upgradeRoleInProgress: undefined
});
case UPGRADE_ROLE_FAILED:
return assign(state, {
upgradeRoleError: action.error,
upgradeRoleInProgress: undefined
});
case CANCEL_LOGIN:
return assign(state, {
upgradeRoleError: undefined,
upgradeRoleInProgress: undefined
});
case STOP_WAIT_FOR_OWNER:
return assign(state, {
waitForOwnerTimeoutID: undefined,
upgradeRoleError: undefined
upgradeRoleError: undefined,
waitForOwnerTimeoutID: undefined
});
case UPGRADE_ROLE_FINISHED:
case UPGRADE_ROLE_STARTED:
return assign(state, {
upgradeRoleError: action.error,
upgradeRoleInProgress: action.thenableWithCancel
});
case WAIT_FOR_OWNER:
return assign(state, {
waitForOwnerTimeoutID: action.waitForOwnerTimeoutID
});
}

View File

@ -298,12 +298,10 @@ export function createConference() {
*/
export function checkIfCanJoin() {
return (dispatch, getState) => {
const { password, authRequired }
const { authRequired, password }
= getState()['features/base/conference'];
if (authRequired) {
authRequired.join(password);
}
authRequired && authRequired.join(password);
};
}

View File

@ -18,12 +18,11 @@ import {
/**
* Opens new connection.
*
* @param {string} [username] - The XMPP user id eg. user@server.com.
* @param {string} [password] - The user's password.
*
* @param {string} [id] - The XMPP user's ID (e.g. user@server.com).
* @param {string} [password] - The XMPP user's password.
* @returns {Function}
*/
export function connect(username: ?string, password: ?string) {
export function connect(id: ?string, password: ?string) {
return (dispatch: Dispatch<*>, getState: Function) => {
const state = getState();
const options = _constructOptions(state);
@ -47,7 +46,7 @@ export function connect(username: ?string, password: ?string) {
_onConnectionFailed);
connection.connect({
id: username,
id,
password
});

View File

@ -53,24 +53,14 @@ export function getURLWithoutParams(url: URL): URL {
}
/**
* Convert provided id to jid if it's not jid yet.
* Converts a specific id to jid if it's not jid yet.
*
* @param {string} id - User id or jid.
* @param {Object} configHosts - The 'hosts' part of the config object.
* @returns {string} jid - A string in the form of user@server.com.
* @param {Object} configHosts - The <tt>hosts</tt> part of the <tt>config</tt>
* object.
* @returns {string} A string in the form of a JID (i.e.
* <tt>user@server.com</tt>).
*/
export function toJid(id: string, configHosts: Object): string {
if (id.indexOf('@') >= 0) {
return id;
}
let jid = id.concat('@');
if (configHosts.authdomain) {
jid += configHosts.authdomain;
} else {
jid += configHosts.domain;
}
return jid;
export function toJid(id: string, { authdomain, domain }: Object): string {
return id.indexOf('@') >= 0 ? id : `${id}@${authdomain || domain}`;
}