mirror of https://github.com/jitsi/jitsi-meet
parent
80329e8ffe
commit
141acea194
@ -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; |
||||
}); |
||||
Loading…
Reference in new issue