/* global APP, JitsiMeetJS, config */ import { jitsiLocalStorage } from '@jitsi/js-utils'; import Logger from '@jitsi/logger'; import { redirectToTokenAuthService } from './modules/UI/authentication/AuthHandler'; import { LoginDialog } from './react/features/authentication/components'; import { isTokenAuthEnabled } from './react/features/authentication/functions'; import { connectionEstablished, connectionFailed, constructOptions } from './react/features/base/connection/actions'; import { openDialog } from './react/features/base/dialog/actions'; import { setJWT } from './react/features/base/jwt'; import { JitsiConnectionErrors, JitsiConnectionEvents } from './react/features/base/lib-jitsi-meet'; import { isFatalJitsiConnectionError } from './react/features/base/lib-jitsi-meet/functions'; import { getCustomerDetails } from './react/features/jaas/actions.any'; import { isVpaasMeeting, getJaasJWT } from './react/features/jaas/functions'; import { setPrejoinDisplayNameRequired, setPrejoinPageVisibility } from './react/features/prejoin/actions'; const logger = Logger.getLogger(__filename); /** * The feature announced so we can distinguish jibri participants. * * @type {string} */ export const DISCO_JIBRI_FEATURE = 'http://jitsi.org/protocol/jibri'; /** * Checks if we have data to use attach instead of connect. If we have the data * executes attach otherwise check if we have to wait for the data. If we have * to wait for the attach data we are setting handler to APP.connect.handler * which is going to be called when the attach data is received otherwise * executes connect. * * @param {string} [id] user id * @param {string} [password] password * @param {string} [roomName] the name of the conference. */ function checkForAttachParametersAndConnect(id, password, connection) { if (window.XMPPAttachInfo) { APP.connect.status = 'connecting'; // When connection optimization is not deployed or enabled the default // value will be window.XMPPAttachInfo.status = "error" // If the connection optimization is deployed and enabled and there is // a failure the value will be window.XMPPAttachInfo.status = "error" if (window.XMPPAttachInfo.status === 'error') { connection.connect({ id, password }); return; } const attachOptions = window.XMPPAttachInfo.data; if (attachOptions) { connection.attach(attachOptions); delete window.XMPPAttachInfo.data; } else { connection.connect({ id, password }); } } else { APP.connect.status = 'ready'; APP.connect.handler = checkForAttachParametersAndConnect.bind( null, id, password, connection); } } /** * Try to open connection using provided credentials. * @param {string} [id] * @param {string} [password] * @returns {Promise} connection if * everything is ok, else error. */ export async function connect(id, password) { const state = APP.store.getState(); let { jwt } = state['features/base/jwt']; const { iAmRecorder, iAmSipGateway } = state['features/base/config']; if (!iAmRecorder && !iAmSipGateway && isVpaasMeeting(state)) { await APP.store.dispatch(getCustomerDetails()); if (!jwt) { jwt = await getJaasJWT(state); APP.store.dispatch(setJWT(jwt)); } } const connection = new JitsiMeetJS.JitsiConnection(null, jwt, constructOptions(state)); if (config.iAmRecorder) { connection.addFeature(DISCO_JIBRI_FEATURE); } return new Promise((resolve, reject) => { connection.addEventListener( JitsiConnectionEvents.CONNECTION_ESTABLISHED, handleConnectionEstablished); connection.addEventListener( JitsiConnectionEvents.CONNECTION_FAILED, handleConnectionFailed); connection.addEventListener( JitsiConnectionEvents.CONNECTION_FAILED, connectionFailedHandler); connection.addEventListener( JitsiConnectionEvents.DISPLAY_NAME_REQUIRED, displayNameRequiredHandler ); /* eslint-disable max-params */ /** * */ function connectionFailedHandler(error, message, credentials, details) { /* eslint-enable max-params */ APP.store.dispatch( connectionFailed( connection, { credentials, details, message, name: error })); if (isFatalJitsiConnectionError(error)) { connection.removeEventListener( JitsiConnectionEvents.CONNECTION_FAILED, connectionFailedHandler); } } /** * */ function unsubscribe() { connection.removeEventListener( JitsiConnectionEvents.CONNECTION_ESTABLISHED, handleConnectionEstablished); connection.removeEventListener( JitsiConnectionEvents.CONNECTION_FAILED, handleConnectionFailed); } /** * */ function handleConnectionEstablished() { APP.store.dispatch(connectionEstablished(connection, Date.now())); unsubscribe(); resolve(connection); } /** * */ function handleConnectionFailed(err) { unsubscribe(); logger.error('CONNECTION FAILED:', err); reject(err); } /** * Marks the display name for the prejoin screen as required. * This can happen if a user tries to join a room with lobby enabled. */ function displayNameRequiredHandler() { APP.store.dispatch(setPrejoinDisplayNameRequired()); } checkForAttachParametersAndConnect(id, password, connection); }); } /** * Open JitsiConnection using provided credentials. * If retry option is true it will show auth dialog on PASSWORD_REQUIRED error. * * @param {object} options * @param {string} [options.id] * @param {string} [options.password] * @param {string} [options.roomName] * @param {boolean} [retry] if we should show auth dialog * on PASSWORD_REQUIRED error. * * @returns {Promise} */ export function openConnection({ id, password, retry, roomName }) { const usernameOverride = jitsiLocalStorage.getItem('xmpp_username_override'); const passwordOverride = jitsiLocalStorage.getItem('xmpp_password_override'); if (usernameOverride && usernameOverride.length > 0) { id = usernameOverride; // eslint-disable-line no-param-reassign } if (passwordOverride && passwordOverride.length > 0) { password = passwordOverride; // eslint-disable-line no-param-reassign } return connect(id, password, roomName).catch(err => { if (retry) { const { jwt } = APP.store.getState()['features/base/jwt']; if (err === JitsiConnectionErrors.PASSWORD_REQUIRED && !jwt) { return requestAuth(roomName); } } throw err; }); } /** * Show Authentication Dialog and try to connect with new credentials. * If failed to connect because of PASSWORD_REQUIRED error * then ask for password again. * @param {string} [roomName] name of the conference room * * @returns {Promise} */ function requestAuth(roomName) { const config = APP.store.getState()['features/base/config']; if (isTokenAuthEnabled(config)) { // This Promise never resolves as user gets redirected to another URL return new Promise(() => redirectToTokenAuthService(roomName)); } return new Promise(resolve => { const onSuccess = connection => { resolve(connection); }; APP.store.dispatch(setPrejoinPageVisibility(false)); APP.store.dispatch( openDialog(LoginDialog, { onSuccess, roomName }) ); }); }