[RN] Support XMPP authentication
This commit is contained in:
parent
80329e8ffe
commit
141acea194
|
@ -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 <b>__room__ </b> has not yet started. If you are the host then please authenticate. Otherwise, please wait for the host to arrive.",
|
||||
"WaitForHostMsg": "The conference '__room__' 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",
|
||||
|
|
|
@ -140,44 +140,35 @@ function initJWTTokenListener(room) {
|
|||
* @param {string} [lockPassword] password to use if the conference is locked
|
||||
*/
|
||||
function doXmppAuth (room, lockPassword) {
|
||||
let loginDialog = LoginDialog.showAuthDialog(function (id, password) {
|
||||
// auth "on the fly":
|
||||
// 1. open new connection with proper id and password
|
||||
// 2. connect to the room
|
||||
// (this will store sessionId in the localStorage)
|
||||
// 3. close new connection
|
||||
// 4. reallocate focus in current room
|
||||
openConnection({id, password, roomName: room.getName()}).then(
|
||||
function (connection) {
|
||||
// open room
|
||||
let newRoom = connection.initJitsiConference(
|
||||
room.getName(), APP.conference._getConferenceOptions()
|
||||
);
|
||||
|
||||
loginDialog.displayConnectionStatus('connection.FETCH_SESSION_ID');
|
||||
|
||||
newRoom.room.moderator.authenticate().then(function () {
|
||||
connection.disconnect();
|
||||
const loginDialog = LoginDialog.showAuthDialog(function (id, password) {
|
||||
const authConnection = room.createAuthenticationConnection();
|
||||
|
||||
authConnection.authenticateAndUpgradeRole({
|
||||
id,
|
||||
password,
|
||||
roomPassword: lockPassword,
|
||||
onLoginSuccessful: () => { /* Called when XMPP login succeeds */
|
||||
loginDialog.displayConnectionStatus(
|
||||
'connection.FETCH_SESSION_ID');
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
loginDialog.displayConnectionStatus(
|
||||
'connection.GOT_SESSION_ID');
|
||||
|
||||
// authenticate conference on the fly
|
||||
room.join(lockPassword);
|
||||
|
||||
loginDialog.close();
|
||||
}).catch(function (error, code) {
|
||||
connection.disconnect();
|
||||
|
||||
logger.error('Auth on the fly failed', error);
|
||||
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error('authenticateAndUpgradeRole failed', error);
|
||||
if (error.authenticationError) {
|
||||
loginDialog.displayError(
|
||||
'connection.GET_SESSION_ID_ERROR', {code: code});
|
||||
'connection.GET_SESSION_ID_ERROR', {
|
||||
msg: error.authenticationError
|
||||
});
|
||||
}, function (err) {
|
||||
loginDialog.displayError(err);
|
||||
} else {
|
||||
loginDialog.displayError(error.connectionError);
|
||||
}
|
||||
});
|
||||
}, function () { // user canceled
|
||||
}, function () {
|
||||
loginDialog.close();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
/* global $, APP, config, JitsiMeetJS */
|
||||
import { toJid } from '../../../react/features/base/connection';
|
||||
|
||||
const ConnectionErrors = JitsiMeetJS.errors.connection;
|
||||
|
||||
/**
|
||||
|
@ -19,26 +21,6 @@ function getPasswordInputHtml() {
|
|||
data-i18n="[placeholder]dialog.userPassword">`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert provided id to jid if it's not jid yet.
|
||||
* @param {string} id user id or jid
|
||||
* @returns {string} jid
|
||||
*/
|
||||
function toJid(id) {
|
||||
if (id.indexOf("@") >= 0) {
|
||||
return id;
|
||||
}
|
||||
|
||||
let jid = id.concat('@');
|
||||
if (config.hosts.authdomain) {
|
||||
jid += config.hosts.authdomain;
|
||||
} else {
|
||||
jid += config.hosts.domain;
|
||||
}
|
||||
|
||||
return jid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate cancel button config for the dialog.
|
||||
* @returns {Object}
|
||||
|
@ -90,7 +72,7 @@ function LoginDialog(successCallback, cancelCallback) {
|
|||
let password = f.password;
|
||||
if (jid && password) {
|
||||
connDialog.goToState('connecting');
|
||||
successCallback(toJid(jid), password);
|
||||
successCallback(toJid(jid, config.hosts), password);
|
||||
}
|
||||
} else {
|
||||
// User cancelled
|
||||
|
@ -223,7 +205,7 @@ export default {
|
|||
*/
|
||||
showAuthRequiredDialog: function (roomName, onAuthNow) {
|
||||
var msg = APP.translation.generateTranslationHTML(
|
||||
"[html]dialog.WaitForHostMsg", {room: roomName}
|
||||
"dialog.WaitForHostMsg", {room: roomName}
|
||||
);
|
||||
|
||||
var buttonTxt = APP.translation.generateTranslationHTML(
|
||||
|
|
|
@ -15,6 +15,7 @@ import '../../mobile/proximity';
|
|||
import '../../mobile/wake-lock';
|
||||
|
||||
import { AbstractApp } from './AbstractApp';
|
||||
import '../../authentication';
|
||||
|
||||
/**
|
||||
* Root application component.
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* The type of (redux) action which signals that {@link LoginDialog} has been
|
||||
* canceled.
|
||||
*
|
||||
* {
|
||||
* type: CANCEL_LOGIN
|
||||
* }
|
||||
*/
|
||||
export const CANCEL_LOGIN = Symbol('CANCEL_LOGIN');
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals that the {@link WaitForOwnerDialog}
|
||||
* has been canceled.
|
||||
*
|
||||
* {
|
||||
* type: CANCEL_WAIT_FOR_OWNER
|
||||
* }
|
||||
*/
|
||||
export const CANCEL_WAIT_FOR_OWNER = Symbol('CANCEL_WAIT_FOR_OWNER');
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals that the cyclic operation of waiting
|
||||
* for conference owner has been aborted.
|
||||
*
|
||||
* {
|
||||
* type: STOP_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.
|
||||
*
|
||||
* {
|
||||
* 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,
|
||||
* error: Object
|
||||
* }
|
||||
*/
|
||||
export const UPGRADE_ROLE_FAILED = Symbol('UPGRADE_ROLE_FAILED');
|
||||
|
||||
/**
|
||||
* The type of (redux) action that sets delayed handler which will check if
|
||||
* the conference has been created and it's now possible to join from anonymous
|
||||
* connection.
|
||||
*
|
||||
* {
|
||||
* type: WAIT_FOR_OWNER,
|
||||
* handler: Function,
|
||||
* timeoutMs: number
|
||||
* }
|
||||
*/
|
||||
export const WAIT_FOR_OWNER = Symbol('WAIT_FOR_OWNER');
|
|
@ -0,0 +1,210 @@
|
|||
import { openDialog } from '../base/dialog/actions';
|
||||
import { checkIfCanJoin } from '../base/conference/actions';
|
||||
import {
|
||||
CANCEL_LOGIN,
|
||||
CANCEL_WAIT_FOR_OWNER,
|
||||
STOP_WAIT_FOR_OWNER,
|
||||
UPGRADE_ROLE_FAILED,
|
||||
UPGRADE_ROLE_STARTED,
|
||||
UPGRADE_ROLE_SUCCESS,
|
||||
WAIT_FOR_OWNER
|
||||
} from './actionTypes';
|
||||
import { LoginDialog, WaitForOwnerDialog } from './components';
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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})}
|
||||
*/
|
||||
export function authenticateAndUpgradeRole(id, userPassword, conference) {
|
||||
return (dispatch, getState) => {
|
||||
const authConnection = conference.createAuthenticationConnection();
|
||||
|
||||
dispatch(_upgradeRoleStarted(authConnection));
|
||||
|
||||
const { password: roomPassword }
|
||||
= getState()['features/base/conference'];
|
||||
|
||||
authConnection.authenticateAndUpgradeRole({
|
||||
id,
|
||||
password: userPassword,
|
||||
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(_upgradeRoleFailed(error));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels {@ink LoginDialog}.
|
||||
*
|
||||
* @returns {{
|
||||
* type: CANCEL_LOGIN
|
||||
* }}
|
||||
*/
|
||||
export function cancelLogin() {
|
||||
return {
|
||||
type: CANCEL_LOGIN
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels {@link WaitForOwnerDialog}. Will navigate back to the welcome page.
|
||||
*
|
||||
* @returns {{
|
||||
* type: CANCEL_WAIT_FOR_OWNER
|
||||
* }}
|
||||
*/
|
||||
export function cancelWaitForOwner() {
|
||||
return {
|
||||
type: CANCEL_WAIT_FOR_OWNER
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops waiting for conference owner and clears any pending timeout.
|
||||
*
|
||||
* @returns {{
|
||||
* type: STOP_WAIT_FOR_OWNER
|
||||
* }}
|
||||
*/
|
||||
export function clearWaitForOwnerTimeout() {
|
||||
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.
|
||||
*
|
||||
* @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,
|
||||
* error: Object
|
||||
* }}
|
||||
*/
|
||||
function _upgradeRoleFailed(error) {
|
||||
return {
|
||||
type: UPGRADE_ROLE_FAILED,
|
||||
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.
|
||||
*
|
||||
* @private
|
||||
* @returns {{
|
||||
* type: UPGRADE_ROLE_STARTED,
|
||||
* authConnection: JitsiAuthConnection
|
||||
* }}
|
||||
*/
|
||||
function _upgradeRoleStarted(authenticationConnection) {
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when Jicofo rejects to create the room for anonymous user. Will
|
||||
* start the process of "waiting for the owner" by periodically trying to join
|
||||
* the room every five seconds.
|
||||
*
|
||||
* @returns {function({ dispatch: Function})}
|
||||
*/
|
||||
export function waitForOwner() {
|
||||
return dispatch => {
|
||||
dispatch(
|
||||
_setWaitForOwnerTimeout(
|
||||
() => dispatch(checkIfCanJoin()),
|
||||
5000));
|
||||
};
|
||||
}
|
|
@ -0,0 +1,280 @@
|
|||
import React, { Component } from 'react';
|
||||
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 { translate } from '../../base/i18n';
|
||||
import { JitsiConnectionErrors } from '../../base/lib-jitsi-meet';
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* Dialog asks user for username and password.
|
||||
*
|
||||
* First authentication configuration that it will deal with is the main XMPP
|
||||
* domain (config.hosts.domain) with password authentication. A LoginDialog
|
||||
* will be opened after 'CONNECTION_FAILED' action with
|
||||
* 'JitsiConnectionErrors.PASSWORD_REQUIRED' error. After username and password
|
||||
* are entered a new 'connect' action from 'features/base/connection' will be
|
||||
* triggered which will result in new XMPP connection. The conference will start
|
||||
* if the credentials are correct.
|
||||
*
|
||||
* The second setup is the main XMPP domain with password plus guest domain with
|
||||
* anonymous access configured under 'config.hosts.anonymousdomain'. In such
|
||||
* case user connects from the anonymous domain, but if the room does not exist
|
||||
* 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.
|
||||
*
|
||||
* See https://github.com/jitsi/jicofo#secure-domain for configuration
|
||||
* parameters description.
|
||||
*/
|
||||
class LoginDialog extends Component {
|
||||
/**
|
||||
* LoginDialog component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
/**
|
||||
* {@link JitsiConference} that needs authentication - will hold a valid
|
||||
* value in XMPP login + guest access mode.
|
||||
*/
|
||||
conference: React.PropTypes.object,
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
configHosts: React.PropTypes.object,
|
||||
|
||||
/**
|
||||
* Indicates if the dialog should display "connecting" status message.
|
||||
*/
|
||||
connecting: React.PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Redux store dispatch method.
|
||||
*/
|
||||
dispatch: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* The error which occurred during login/authentication.
|
||||
*/
|
||||
error: React.PropTypes.string,
|
||||
|
||||
/**
|
||||
* Any extra details about the error provided by lib-jitsi-meet.
|
||||
*/
|
||||
errorDetails: React.PropTypes.string,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: React.PropTypes.func
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new LoginDialog instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
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: ''
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
error,
|
||||
errorDetails,
|
||||
connecting,
|
||||
t
|
||||
} = this.props;
|
||||
|
||||
let messageKey = '';
|
||||
const messageOptions = { };
|
||||
|
||||
if (error === JitsiConnectionErrors.PASSWORD_REQUIRED) {
|
||||
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>
|
||||
<TextInput
|
||||
onChangeText = { this._onUsernameChange }
|
||||
placeholder = { 'user@domain.com' }
|
||||
style = { styles.textInput }
|
||||
value = { this.state.username } />
|
||||
<Text>Password:</Text>
|
||||
<TextInput
|
||||
onChangeText = { this._onPasswordChange }
|
||||
placeholder = { t('dialog.userPassword') }
|
||||
secureTextEntry = { true }
|
||||
style = { styles.textInput }
|
||||
value = { this.state.password } />
|
||||
<Text>
|
||||
{error ? t(messageKey, messageOptions) : ''}
|
||||
{connecting && !error
|
||||
? t('connection.CONNECTING') : ''}
|
||||
</Text>
|
||||
<Button
|
||||
disabled = { connecting }
|
||||
onPress = { this._onLogin }
|
||||
title = { t('dialog.Ok') } />
|
||||
<Button
|
||||
onPress = { this._onCancel }
|
||||
title = { t('dialog.Cancel') } />
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when user edits the username.
|
||||
*
|
||||
* @param {string} text - A new username value entered by user.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
_onUsernameChange(text) {
|
||||
this.setState({
|
||||
username: text
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when user edits the password.
|
||||
*
|
||||
* @param {string} text - A new password value entered by user.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
_onPasswordChange(text) {
|
||||
this.setState({
|
||||
password: text
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies this LoginDialog that it has been dismissed by cancel.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onCancel() {
|
||||
this.props.dispatch(cancelLogin());
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies this LoginDialog that the login button (OK) has been pressed by
|
||||
* the user.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onLogin() {
|
||||
const conference = this.props.conference;
|
||||
const { username, password } = this.state;
|
||||
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.
|
||||
if (conference) {
|
||||
this.props.dispatch(
|
||||
authenticateAndUpgradeRole(jid, password, conference));
|
||||
} else {
|
||||
this.props.dispatch(connect(jid, password));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated props for the
|
||||
* {@code LoginDialog} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* configHosts: Object,
|
||||
* connecting: boolean,
|
||||
* error: string,
|
||||
* errorDetails: string,
|
||||
* conference: JitsiConference
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
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));
|
||||
|
||||
return {
|
||||
configHosts,
|
||||
connecting: Boolean(connecting) || Boolean(upgradeRoleInProgress),
|
||||
error,
|
||||
errorDetails:
|
||||
(connectionError && connectionErrorMessage)
|
||||
|| (upgradeRoleError && upgradeRoleError.message),
|
||||
conference: authRequired
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(reduxConnect(_mapStateToProps)(LoginDialog));
|
|
@ -0,0 +1,127 @@
|
|||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Button, Modal, Text, View } from 'react-native';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
import { _showLoginDialog, cancelWaitForOwner } from '../actions';
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* The dialog is display in XMPP password + guest access configuration, after
|
||||
* user connects from anonymous domain and the conference does not exist yet.
|
||||
*
|
||||
* See {@link LoginDialog} description for more details.
|
||||
*/
|
||||
class WaitForOwnerDialog extends Component {
|
||||
/**
|
||||
* WaitForOwnerDialog component's property types.
|
||||
*
|
||||
* @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,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: React.PropTypes.func
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new WaitForWonderDialog instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._onLogin = this._onLogin.bind(this);
|
||||
this._onCancel = this._onCancel.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
roomName,
|
||||
t
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
onRequestClose = { this._onCancel }
|
||||
style = { styles.outerArea }
|
||||
transparent = { true } >
|
||||
<View style = { styles.dialogBox } >
|
||||
<Text>
|
||||
{ t(
|
||||
'dialog.WaitForHostMsg',
|
||||
{ room: roomName })
|
||||
}
|
||||
</Text>
|
||||
<Button
|
||||
onPress = { this._onLogin }
|
||||
title = { t('dialog.IamHost') } />
|
||||
<Button
|
||||
onPress = { this._onCancel }
|
||||
title = { t('dialog.Cancel') } />
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the OK button is clicked.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onLogin() {
|
||||
this.props.dispatch(_showLoginDialog());
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the cancel button is clicked.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onCancel() {
|
||||
this.props.dispatch(cancelWaitForOwner());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated props for the
|
||||
* {@code WaitForOwnerDialog} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* roomName: string
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const {
|
||||
authRequired
|
||||
} = state['features/base/conference'];
|
||||
|
||||
return {
|
||||
roomName: authRequired && authRequired.getName()
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(WaitForOwnerDialog));
|
|
@ -0,0 +1,2 @@
|
|||
export { default as LoginDialog } from './LoginDialog';
|
||||
export { default as WaitForOwnerDialog } from './WaitForOwnerDialog';
|
|
@ -0,0 +1,23 @@
|
|||
import {
|
||||
ColorPalette,
|
||||
createStyleSheet
|
||||
} from '../../base/styles';
|
||||
|
||||
/**
|
||||
* The styles of the authentication feature.
|
||||
*/
|
||||
export default createStyleSheet({
|
||||
outerArea: {
|
||||
flex: 1
|
||||
},
|
||||
dialogBox: {
|
||||
marginLeft: '10%',
|
||||
marginRight: '10%',
|
||||
marginTop: '10%',
|
||||
backgroundColor: ColorPalette.white
|
||||
},
|
||||
textInput: {
|
||||
height: 25,
|
||||
fontSize: 16
|
||||
}
|
||||
});
|
|
@ -0,0 +1,74 @@
|
|||
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);
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './functions';
|
||||
|
||||
import './middleware';
|
||||
|
||||
import './reducer';
|
|
@ -0,0 +1,136 @@
|
|||
import { MiddlewareRegistry } from '../base/redux';
|
||||
|
||||
import {
|
||||
clearWaitForOwnerTimeout,
|
||||
_showLoginDialog,
|
||||
_showWaitForOwnerDialog,
|
||||
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';
|
||||
|
||||
/**
|
||||
* Middleware that captures connection or conference failed errors and controlls
|
||||
* {@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 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);
|
||||
|
||||
// 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) {
|
||||
// Instead of hiding show the new one.
|
||||
const result = next(action);
|
||||
|
||||
store.dispatch(_showWaitForOwnerDialog());
|
||||
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CANCEL_WAIT_FOR_OWNER: {
|
||||
const result = next(action);
|
||||
|
||||
store.dispatch(clearWaitForOwnerTimeout());
|
||||
store.dispatch(appNavigate(undefined));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
|
@ -0,0 +1,45 @@
|
|||
import { assign } from '../base/redux/functions';
|
||||
import { ReducerRegistry } from '../base/redux';
|
||||
|
||||
import {
|
||||
CANCEL_LOGIN,
|
||||
STOP_WAIT_FOR_OWNER,
|
||||
UPGRADE_ROLE_FAILED, UPGRADE_ROLE_STARTED, UPGRADE_ROLE_SUCCESS,
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
|
@ -288,6 +288,25 @@ export function createConference() {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Will try to join the conference again in case it failed earlier with
|
||||
* {@link JitsiConferenceErrors.AUTHENTICATION_REQUIRED}. It means that Jicofo
|
||||
* did not allow to create new room from anonymous domain, but it can be tried
|
||||
* again later in case authenticated user created it in the meantime.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function checkIfCanJoin() {
|
||||
return (dispatch, getState) => {
|
||||
const { password, authRequired }
|
||||
= getState()['features/base/conference'];
|
||||
|
||||
if (authRequired) {
|
||||
authRequired.join(password);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals the data channel with the bridge has successfully opened.
|
||||
*
|
||||
|
|
|
@ -84,7 +84,13 @@ function _conferenceFailed(state, { conference, error }) {
|
|||
? conference
|
||||
: undefined;
|
||||
|
||||
const authRequired
|
||||
= JitsiConferenceErrors.AUTHENTICATION_REQUIRED === error
|
||||
? conference
|
||||
: undefined;
|
||||
|
||||
return assign(state, {
|
||||
authRequired,
|
||||
conference: undefined,
|
||||
joining: undefined,
|
||||
leaving: undefined,
|
||||
|
@ -126,6 +132,8 @@ function _conferenceJoined(state, { conference }) {
|
|||
const locked = conference.room.locked ? LOCKED_REMOTELY : undefined;
|
||||
|
||||
return assign(state, {
|
||||
authRequired: undefined,
|
||||
|
||||
/**
|
||||
* The JitsiConference instance represented by the Redux state of the
|
||||
* feature base/conference.
|
||||
|
@ -170,6 +178,7 @@ function _conferenceLeft(state, { conference }) {
|
|||
}
|
||||
|
||||
return assign(state, {
|
||||
authRequired: undefined,
|
||||
conference: undefined,
|
||||
joining: undefined,
|
||||
leaving: undefined,
|
||||
|
@ -209,6 +218,7 @@ function _conferenceWillLeave(state, { conference }) {
|
|||
}
|
||||
|
||||
return assign(state, {
|
||||
authRequired: undefined,
|
||||
joining: undefined,
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,9 +18,12 @@ import {
|
|||
/**
|
||||
* Opens new connection.
|
||||
*
|
||||
* @param {string} [username] - The XMPP user id eg. user@server.com.
|
||||
* @param {string} [password] - The user's password.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function connect() {
|
||||
export function connect(username: ?string, password: ?string) {
|
||||
return (dispatch: Dispatch<*>, getState: Function) => {
|
||||
const state = getState();
|
||||
const options = _constructOptions(state);
|
||||
|
@ -43,7 +46,10 @@ export function connect() {
|
|||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
_onConnectionFailed);
|
||||
|
||||
connection.connect();
|
||||
connection.connect({
|
||||
id: username,
|
||||
password
|
||||
});
|
||||
|
||||
/**
|
||||
* Dispatches CONNECTION_DISCONNECTED action when connection is
|
||||
|
|
|
@ -51,3 +51,26 @@ export function getURLWithoutParams(url: URL): URL {
|
|||
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert provided 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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue