diff --git a/app.js b/app.js index 4b6327650..97bd80b8e 100644 --- a/app.js +++ b/app.js @@ -17,6 +17,11 @@ import URLProcessor from "./modules/config/URLProcessor"; import RoomnameGenerator from './modules/util/RoomnameGenerator'; import CQEvents from './service/connectionquality/CQEvents'; import UIEvents from './service/UI/UIEvents'; +import LoginDialog from './modules/UI/authentication/LoginDialog'; +import UIUtil from './modules/UI/util/UIUtil'; + +import {openConnection} from './modules/connection'; +import AuthHandler from './modules/AuthHandler'; import createRoomLocker from './modules/RoomLocker'; @@ -42,7 +47,7 @@ function buildRoomName () { location ~ ^/([a-zA-Z0-9]+)$ { rewrite ^/(.*)$ / break; } - */ + */ if (path.length > 1) { roomName = path.substr(1).toLowerCase(); } else { @@ -104,62 +109,9 @@ const APP = { const ConnectionEvents = JitsiMeetJS.events.connection; const ConnectionErrors = JitsiMeetJS.errors.connection; -function connect() { - let connection = new JitsiMeetJS.JitsiConnection(null, null, { - hosts: config.hosts, - bosh: config.bosh, - clientNode: config.clientNode - }); - return new Promise(function (resolve, reject) { - let handlers = {}; - - function unsubscribe () { - Object.keys(handlers).forEach(function (event) { - connection.removeEventListener(event, handlers[event]); - }); - } - - handlers[ConnectionEvents.CONNECTION_ESTABLISHED] = function () { - console.log('CONNECTED'); - unsubscribe(); - resolve(connection); - }; - - function listenForFailure (event) { - handlers[event] = function (...args) { - console.error(`CONNECTION FAILED: ${event}`, ...args); - - unsubscribe(); - reject([event, ...args]); - }; - } - - listenForFailure(ConnectionEvents.CONNECTION_FAILED); - listenForFailure(ConnectionErrors.PASSWORD_REQUIRED); - listenForFailure(ConnectionErrors.CONNECTION_ERROR); - listenForFailure(ConnectionErrors.OTHER_ERRORS); - - // install event listeners - Object.keys(handlers).forEach(function (event) { - connection.addEventListener(event, handlers[event]); - }); - - connection.connect(); - }).catch(function (err) { - if (err[0] === ConnectionErrors.PASSWORD_REQUIRED) { - // FIXME ask for password and try again - return connect(); - } - console.error('FAILED TO CONNECT', err); - APP.UI.notifyConnectionFailed(err[1]); - - throw new Error(err[0]); - }); -} - -var ConferenceEvents = JitsiMeetJS.events.conference; -var ConferenceErrors = JitsiMeetJS.errors.conference; +const ConferenceEvents = JitsiMeetJS.events.conference; +const ConferenceErrors = JitsiMeetJS.errors.conference; function initConference(localTracks, connection) { let room = connection.initJitsiConference(APP.conference.roomName, { openSctp: config.openSctp, @@ -378,10 +330,6 @@ function initConference(localTracks, connection) { APP.UI.changeDisplayName(APP.conference.localId, nickname); }); - room.on(ConferenceErrors.CONNECTION_ERROR, function () { - // FIXME handle - }); - APP.UI.addListener( UIEvents.START_MUTED_CHANGED, function (startAudioMuted, startVideoMuted) { @@ -461,7 +409,7 @@ function initConference(localTracks, connection) { }); APP.UI.addListener(UIEvents.AUTH_CLICKED, function () { - // FIXME handle + AuthHandler.authenticate(room); }); APP.UI.addListener(UIEvents.SELECTED_ENDPOINT, function (id) { @@ -477,22 +425,67 @@ function initConference(localTracks, connection) { }); return new Promise(function (resolve, reject) { - room.on(ConferenceEvents.CONFERENCE_JOINED, resolve); + room.on(ConferenceEvents.CONFERENCE_JOINED, handleConferenceJoined); + room.on(ConferenceEvents.CONFERENCE_FAILED, onConferenceFailed); - room.on(ConferenceErrors.PASSWORD_REQUIRED, function () { - APP.UI.markRoomLocked(true); - roomLocker.requirePassword().then(function () { - room.join(roomLocker.password); - }); - }); + let password; + let reconnectTimeout; - // FIXME handle errors here + function unsubscribe() { + room.off( + ConferenceEvents.CONFERENCE_JOINED, handleConferenceJoined + ); + room.off( + ConferenceEvents.CONFERENCE_FAILED, onConferenceFailed + ); + if (reconnectTimeout) { + clearTimeout(reconnectTimeout); + } + AuthHandler.closeAuth(); + } - room.join(); - }).catch(function (err) { - // FIXME notify that we cannot conenct to the room + function handleConferenceJoined() { + unsubscribe(); + resolve(); + } - throw new Error(err[0]); + function handleConferenceFailed(err) { + unsubscribe(); + reject(err); + } + + function onConferenceFailed(err, msg = '') { + console.error('CONFERENCE FAILED:', err, msg); + switch (err) { + // room is locked by the password + case ConferenceErrors.PASSWORD_REQUIRED: + APP.UI.markRoomLocked(true); + roomLocker.requirePassword().then(function () { + room.join(roomLocker.password); + }); + break; + + case ConferenceErrors.CONNECTION_ERROR: + APP.UI.notifyConnectionFailed(msg); + break; + + // not enough rights to create conference + case ConferenceErrors.AUTHENTICATION_REQUIRED: + // schedule reconnect to check if someone else created the room + reconnectTimeout = setTimeout(function () { + room.join(password); + }, 5000); + + // notify user that auth is required + AuthHandler.requireAuth(APP.conference.roomName); + break; + + default: + handleConferenceFailed(err); + } + } + + room.join(password); }); } @@ -505,9 +498,22 @@ function createLocalTracks () { }); } +function connect() { + return openConnection({retry: true}).catch(function (err) { + if (err === ConnectionErrors.PASSWORD_REQUIRED) { + APP.UI.notifyTokenAuthFailed(); + } else { + APP.UI.notifyConnectionFailed(err); + } + throw err; + }); +} + function init() { APP.UI.start(); + JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.TRACE); + JitsiMeetJS.init().then(function () { return Promise.all([createLocalTracks(), connect()]); }).then(function ([tracks, connection]) { diff --git a/lib-jitsi-meet.js b/lib-jitsi-meet.js index ccd575b05..bf382d4bb 100644 --- a/lib-jitsi-meet.js +++ b/lib-jitsi-meet.js @@ -1,6 +1,6 @@ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.JitsiMeetJS = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o${passRequiredMsg} + + + `; +} function Dialog(successCallback, cancelCallback) { - var message = '

'; - message += APP.translation.translateString("dialog.passwordRequired"); - message += '

' + - ''; - - var okButton = APP.translation.generateTranslationHTML("dialog.Ok"); - - var cancelButton = APP.translation.generateTranslationHTML("dialog.Cancel"); - - var states = { + const states = { login: { - html: message, - buttons: [ - { title: okButton, value: true}, - { title: cancelButton, value: false} - ], + html: getPasswordInputHtml(), + buttons: [{ + title: APP.translation.generateTranslationHTML("dialog.Ok"), + value: true + }, { + title: APP.translation.generateTranslationHTML("dialog.Cancel"), + value: false + }], focus: ':input:first', submit: function (e, v, m, f) { e.preventDefault(); if (v) { - var jid = f.username; - var password = f.password; + let jid = f.username; + let password = f.password; if (jid && password) { connDialog.goToState('connecting'); successCallback(jid, password); @@ -55,22 +54,20 @@ function Dialog(successCallback, cancelCallback) { finished: { title: APP.translation.translateString('dialog.error'), html: '
', - buttons: [ - { - title: APP.translation.translateString('dialog.retry'), - value: 'retry' - }, - { - title: APP.translation.translateString('dialog.Cancel'), - value: 'cancel' - }, - ], + buttons: [{ + title: APP.translation.translateString('dialog.retry'), + value: 'retry' + }, { + title: APP.translation.generateTranslationHTML("dialog.Cancel"), + value: false + }], defaultButton: 0, submit: function (e, v, m, f) { e.preventDefault(); if (v === 'retry') { connDialog.goToState('login'); } else { + // User cancelled cancelCallback(); } } @@ -104,23 +101,9 @@ function Dialog(successCallback, cancelCallback) { }; } -var LoginDialog = { +const LoginDialog = { - /** - * Displays login prompt used to establish new XMPP connection. Given - * callback(Strophe.Connection, Strophe.Status) function will be - * called when we connect successfully(status === CONNECTED) or when we fail - * to do so. On connection failure program can call Dialog.close() method in - * order to cancel or do nothing to let the user retry. - * @param callback function(Strophe.Connection, Strophe.Status) - * called when we either fail to connect or succeed(check - * Strophe.Status). - * @param obtainSession true if we want to send ConferenceIQ to - * Jicofo in order to create session-id after the connection is - * established. - * @returns {Dialog} - */ - show: function (successCallback, cancelCallback) { + showAuthDialog: function (successCallback, cancelCallback) { return new Dialog(successCallback, cancelCallback); }, @@ -156,10 +139,10 @@ var LoginDialog = { msg, true, buttons, - function (onSubmitEvent, submitValue) { + function (e, submitValue) { // Do not close the dialog yet - onSubmitEvent.preventDefault(); + e.preventDefault(); // Open login popup if (submitValue === 'authNow') { @@ -170,4 +153,4 @@ var LoginDialog = { } }; -module.exports = LoginDialog; +export default LoginDialog; diff --git a/modules/UI/util/UIUtil.js b/modules/UI/util/UIUtil.js index fec900d2a..bee0149a5 100644 --- a/modules/UI/util/UIUtil.js +++ b/modules/UI/util/UIUtil.js @@ -114,7 +114,11 @@ import PanelToggler from "../side_pannels/SidePanelToggler"; .filter(function (item) { return item; }) .join(','); $(selector).hide(); - } + }, + + redirect (url) { + window.location.href = url; + } }; export default UIUtil; diff --git a/modules/connection.js b/modules/connection.js new file mode 100644 index 000000000..24b00a226 --- /dev/null +++ b/modules/connection.js @@ -0,0 +1,83 @@ +/* global APP, JitsiMeetJS, config */ + +import LoginDialog from './UI/authentication/LoginDialog'; + +const ConnectionEvents = JitsiMeetJS.events.connection; +const ConnectionErrors = JitsiMeetJS.errors.connection; + +export function openConnection({retry, id, password}) { + let connection = new JitsiMeetJS.JitsiConnection(null, null, { + hosts: config.hosts, + bosh: config.bosh, + clientNode: config.clientNode + }); + + return new Promise(function (resolve, reject) { + connection.addEventListener( + ConnectionEvents.CONNECTION_ESTABLISHED, handleConnectionEstablished + ); + connection.addEventListener( + ConnectionEvents.CONNECTION_FAILED, onConnectionFailed + ); + let authDialog; + + function unsubscribe() { + connection.removeEventListener( + ConnectionEvents.CONNECTION_ESTABLISHED, + handleConnectionEstablished + ); + connection.removeEventListener( + ConnectionEvents.CONNECTION_FAILED, onConnectionFailed + ); + if (authDialog) { + authDialog.close(); + } + } + + function handleConnectionEstablished() { + unsubscribe(); + resolve(connection); + } + + function handleConnectionFailed(err) { + unsubscribe(); + reject(err); + } + + function onConnectionFailed (err) { + console.error("CONNECTION FAILED:", err); + + if (!retry) { + handleConnectionFailed(err); + return; + } + + // retry only if auth failed + if (err !== ConnectionErrors.PASSWORD_REQUIRED) { + handleConnectionFailed(err); + return; + } + + // do not retry if token is not valid + if (config.token) { + handleConnectionFailed(err); + return; + } + + // ask for password and try again + + if (authDialog) { + authDialog.displayError(err); + return; + } + + authDialog = LoginDialog.showAuthDialog( + function (id, password) { + connection.connect({id, password}); + } + ); + } + + connection.connect(id, password); + }); +}