From c570b80d7b7c982f9c2895337d49aec2010ef447 Mon Sep 17 00:00:00 2001 From: Ilya Daynatovich Date: Mon, 12 Dec 2016 23:13:17 +0200 Subject: [PATCH 01/11] moved app initialization to react app --- app.js | 108 +----------------- modules/UI/UI.js | 14 --- react/features/app/components/AbstractApp.js | 2 +- react/features/app/components/App.web.js | 70 ++++++++---- .../{actions.js => actions.native.js} | 0 react/features/base/connection/actions.web.js | 96 ++++++++++++++++ .../{actions.js => actions.native.js} | 0 .../base/lib-jitsi-meet/actions.web.js | 61 ++++++++++ .../{functions.js => functions.native.js} | 0 .../base/lib-jitsi-meet/functions.web.js | 10 ++ .../conference/components/Conference.web.js | 48 +++++++- .../welcome/components/WelcomePage.web.js | 49 ++++++-- react/features/welcome/route.js | 24 +++- 13 files changed, 324 insertions(+), 158 deletions(-) rename react/features/base/connection/{actions.js => actions.native.js} (100%) create mode 100644 react/features/base/connection/actions.web.js rename react/features/base/lib-jitsi-meet/{actions.js => actions.native.js} (100%) create mode 100644 react/features/base/lib-jitsi-meet/actions.web.js rename react/features/base/lib-jitsi-meet/{functions.js => functions.native.js} (100%) create mode 100644 react/features/base/lib-jitsi-meet/functions.web.js diff --git a/app.js b/app.js index 978671546..076557f68 100644 --- a/app.js +++ b/app.js @@ -1,4 +1,4 @@ -/* global $, config, getRoomName, loggingConfig, JitsiMeetJS */ +/* global $, config, loggingConfig, JitsiMeetJS */ /* application specific logic */ const logger = require("jitsi-meet-logger").getLogger(__filename); @@ -23,9 +23,6 @@ const LogCollector = Logger.LogCollector; import JitsiMeetLogStorage from "./modules/util/JitsiMeetLogStorage"; import URLProcessor from "./modules/config/URLProcessor"; -import { - generateRoomWithoutSeparator -} from './react/features/base/util/roomnameGenerator'; import UI from "./modules/UI/UI"; import settings from "./modules/settings/Settings"; @@ -33,59 +30,9 @@ import conference from './conference'; import ConferenceUrl from './modules/URL/ConferenceUrl'; import API from './modules/API/API'; -import UIEvents from './service/UI/UIEvents'; import getTokenData from "./modules/tokendata/TokenData"; import translation from "./modules/translation/translation"; -const ConferenceEvents = JitsiMeetJS.events.conference; - -/** - * Tries to push history state with the following parameters: - * 'VideoChat', `Room: ${roomName}`, URL. If fail, prints the error and returns - * it. - */ -function pushHistoryState(roomName, URL) { - try { - window.history.pushState( - 'VideoChat', `Room: ${roomName}`, URL - ); - } catch (e) { - logger.warn("Push history state failed with parameters:", - 'VideoChat', `Room: ${roomName}`, URL, e); - return e; - } - return null; -} - -/** - * Replaces current history state(replaces the URL displayed by the browser). - * @param {string} newUrl the URL string which is to be displayed by the browser - * to the user. - */ -function replaceHistoryState (newUrl) { - if (window.history - && typeof window.history.replaceState === 'function') { - window.history.replaceState({}, document.title, newUrl); - } -} - -/** - * Builds and returns the room name. - */ -function buildRoomName () { - let roomName = getRoomName(); - - if(!roomName) { - let word = generateRoomWithoutSeparator(); - roomName = word.toLowerCase(); - let historyURL = window.location.href + word; - //Trying to push state with current URL + roomName - pushHistoryState(word, historyURL); - } - - return roomName; -} - /** * Adjusts the logging levels. * @private @@ -191,8 +138,6 @@ function init() { setTokenData(); // Initialize the conference URL handler APP.ConferenceUrl = new ConferenceUrl(window.location); - // Clean up the URL displayed by the browser - replaceHistoryState(APP.ConferenceUrl.getInviteUrl()); // TODO The execution of the mobile app starts from react/index.native.js. // Similarly, the execution of the Web app should start from @@ -201,54 +146,6 @@ function init() { // into the Web app, allow the execution of the Web app to start from app.js // in order to reduce the complexity of the beginning step. require('./react'); - - const isUIReady = APP.UI.start(); - if (isUIReady) { - APP.conference.init({roomName: buildRoomName()}).then(() => { - - if (APP.logCollector) { - // Start the LogCollector's periodic "store logs" task only if - // we're in the conference and not on the welcome page. This is - // determined by the value of "isUIReady" const above. - APP.logCollector.start(); - APP.logCollectorStarted = true; - // Make an attempt to flush in case a lot of logs have been - // cached, before the collector was started. - APP.logCollector.flush(); - - // This event listener will flush the logs, before - // the statistics module (CallStats) is stopped. - // - // NOTE The LogCollector is not stopped, because this event can - // be triggered multiple times during single conference - // (whenever statistics module is stopped). That includes - // the case when Jicofo terminates the single person left in the - // room. It will then restart the media session when someone - // eventually join the room which will start the stats again. - APP.conference.addConferenceListener( - ConferenceEvents.BEFORE_STATISTICS_DISPOSED, - () => { - if (APP.logCollector) { - APP.logCollector.flush(); - } - } - ); - } - - APP.UI.initConference(); - - APP.UI.addListener(UIEvents.LANG_CHANGED, language => { - APP.translation.setLanguage(language); - APP.settings.setLanguage(language); - }); - - APP.keyboardshortcut.init(); - }).catch(err => { - APP.UI.hideRingOverLay(); - APP.API.notifyConferenceLeft(APP.conference.roomName); - logger.error(err); - }); - } } /** @@ -295,9 +192,6 @@ $(document).ready(function () { URLProcessor.setConfigParametersFromUrl(); APP.init(); - - APP.translation.init(settings.getLanguage()); - APP.API.init(APP.tokenData.externalAPISettings); obtainConfigAndInit(); diff --git a/modules/UI/UI.js b/modules/UI/UI.js index 10a502cc2..b80a215bd 100644 --- a/modules/UI/UI.js +++ b/modules/UI/UI.js @@ -396,20 +396,6 @@ UI.getSharedVideoManager = function () { */ UI.start = function () { document.title = interfaceConfig.APP_NAME; - var setupWelcomePage = null; - if(config.enableWelcomePage && window.location.pathname == "/" && - Settings.isWelcomePageEnabled()) { - $("#videoconference_page").hide(); - if (!setupWelcomePage) - setupWelcomePage = require("./welcome_page/WelcomePage"); - setupWelcomePage(); - - // Return false to indicate that the UI hasn't been fully started and - // conference ready. We're still waiting for input from the user. - return false; - } - - $("#welcome_page").hide(); // Set the defaults for prompt dialogs. $.prompt.setDefaults({persistent: false}); diff --git a/react/features/app/components/AbstractApp.js b/react/features/app/components/AbstractApp.js index 4d766a2a2..7d32adec5 100644 --- a/react/features/app/components/AbstractApp.js +++ b/react/features/app/components/AbstractApp.js @@ -42,7 +42,7 @@ export class AbstractApp extends Component { * The URL, if any, with which the app was launched. */ url: React.PropTypes.string - } + }; /** * Init lib-jitsi-meet and create local participant when component is going diff --git a/react/features/app/components/App.web.js b/react/features/app/components/App.web.js index 7afd2e46a..7d646f51f 100644 --- a/react/features/app/components/App.web.js +++ b/react/features/app/components/App.web.js @@ -1,5 +1,7 @@ +/* global APP, $ */ import React from 'react'; import { Provider } from 'react-redux'; +import { compose } from 'redux'; import { browserHistory, Route, @@ -11,6 +13,7 @@ import { getDomain } from '../../base/connection'; import { RouteRegistry } from '../../base/navigator'; import { AbstractApp } from './AbstractApp'; +import settings from '../../../../modules/settings/Settings'; /** * Root application component. @@ -23,7 +26,7 @@ export class App extends AbstractApp { * * @static */ - static propTypes = AbstractApp.propTypes + static propTypes = AbstractApp.propTypes; /** * Initializes a new App instance. @@ -44,28 +47,19 @@ export class App extends AbstractApp { // Bind event handlers so they are only bound once for every instance. this._onRouteEnter = this._onRouteEnter.bind(this); this._routerCreateElement = this._routerCreateElement.bind(this); + this._getRoute = this._getRoute.bind(this); + this._getRoutes = this._getRoutes.bind(this); } /** - * Temporarily, prevents the super from dispatching Redux actions until they - * are integrated into the Web App. + * Init translation from old app. * - * @returns {void} + * @inheritdoc */ - componentWillMount() { - // FIXME Do not override the super once the dispatching of Redux actions - // is integrated into the Web App. - } + componentWillMount(...args) { + super.componentWillMount(...args); - /** - * Temporarily, prevents the super from dispatching Redux actions until they - * are integrated into the Web App. - * - * @returns {void} - */ - componentWillUnmount() { - // FIXME Do not override the super once the dispatching of Redux actions - // is integrated into the Web App. + APP.translation.init(settings.getLanguage()); } /** @@ -75,19 +69,14 @@ export class App extends AbstractApp { * @returns {ReactElement} */ render() { - const routes = RouteRegistry.getRoutes(); + return ( - { routes.map(r => - - ) } + { this._getRoutes() } ); @@ -114,6 +103,38 @@ export class App extends AbstractApp { return store.dispatch(push(path)); } + /** + * Returns routes for application. + * + * @returns {Array} + * @private + */ + _getRoutes() { + const routes = RouteRegistry.getRoutes(); + + return routes.map(this._getRoute); + } + + /** + * Method returns route for React Router. + * + * @param {Object} route - Object that describes route. + * @returns {ReactElement} + * @private + */ + _getRoute(route) { + const onEnter = route.onEnter || $.noop; + const handler = compose(this._onRouteEnter, onEnter); + + return ( + + ); + } + /** * Invoked by react-router to notify this App that a Route is about to be * rendered. @@ -122,6 +143,7 @@ export class App extends AbstractApp { * @returns {void} */ _onRouteEnter() { + // XXX The following is mandatory. Otherwise, moving back & forward // through the browser's history could leave this App on the Conference // page without a room name. diff --git a/react/features/base/connection/actions.js b/react/features/base/connection/actions.native.js similarity index 100% rename from react/features/base/connection/actions.js rename to react/features/base/connection/actions.native.js diff --git a/react/features/base/connection/actions.web.js b/react/features/base/connection/actions.web.js new file mode 100644 index 000000000..b81e479e9 --- /dev/null +++ b/react/features/base/connection/actions.web.js @@ -0,0 +1,96 @@ +/* global APP, JitsiMeetJS */ + +import { + SET_DOMAIN +} from './actionTypes'; +import './reducer'; +import UIEvents from '../../../../service/UI/UIEvents'; + +const logger = require('jitsi-meet-logger').getLogger(__filename); +const ConferenceEvents = JitsiMeetJS.events.conference; + + +/** + * Opens new connection. + * + * @returns {Promise} + */ +export function connect() { + return (dispatch, getState) => { + const state = getState(); + const room = state['features/base/conference'].room; + + // XXX For web based version we use conference initialization logic + // from the old app (at the moment of writing). + return APP.conference.init({ roomName: room }).then(() => { + if (APP.logCollector) { + // Start the LogCollector's periodic "store logs" task + APP.logCollector.start(); + APP.logCollectorStarted = true; + + // Make an attempt to flush in case a lot of logs have been + // cached, before the collector was started. + APP.logCollector.flush(); + + // This event listener will flush the logs, before + // the statistics module (CallStats) is stopped. + // + // NOTE The LogCollector is not stopped, because this event can + // be triggered multiple times during single conference + // (whenever statistics module is stopped). That includes + // the case when Jicofo terminates the single person left in the + // room. It will then restart the media session when someone + // eventually join the room which will start the stats again. + APP.conference.addConferenceListener( + ConferenceEvents.BEFORE_STATISTICS_DISPOSED, + () => { + if (APP.logCollector) { + APP.logCollector.flush(); + } + } + ); + } + + APP.UI.initConference(); + + APP.UI.addListener(UIEvents.LANG_CHANGED, language => { + APP.translation.setLanguage(language); + APP.settings.setLanguage(language); + }); + + APP.keyboardshortcut.init(); + }) + .catch(err => { + APP.UI.hideRingOverLay(); + APP.API.notifyConferenceLeft(APP.conference.roomName); + logger.error(err); + }); + }; +} + +/** + * Closes connection. + * + * @returns {Function} + */ +export function disconnect() { + // XXX For web based version we use conference + // hanging up logic from the old app. + return () => APP.conference.hangup(); +} + +/** + * Sets connection domain. + * + * @param {string} domain - Domain name. + * @returns {{ + * type: SET_DOMAIN, + * domain: string + * }} + */ +export function setDomain(domain) { + return { + type: SET_DOMAIN, + domain + }; +} diff --git a/react/features/base/lib-jitsi-meet/actions.js b/react/features/base/lib-jitsi-meet/actions.native.js similarity index 100% rename from react/features/base/lib-jitsi-meet/actions.js rename to react/features/base/lib-jitsi-meet/actions.native.js diff --git a/react/features/base/lib-jitsi-meet/actions.web.js b/react/features/base/lib-jitsi-meet/actions.web.js new file mode 100644 index 000000000..02628ea1a --- /dev/null +++ b/react/features/base/lib-jitsi-meet/actions.web.js @@ -0,0 +1,61 @@ +import { + LIB_DISPOSED, + SET_CONFIG +} from './actionTypes'; +import './middleware'; +import './reducer'; + +/** + * Disposes lib-jitsi-meet. + * + * @returns {Function} + */ +export function disposeLib() { + // XXX We're wrapping it with Promise, because: + // a) to be better aligned with initLib() method, which is async. + // b) as currently there is no implementation for it in lib-jitsi-meet, and + // there is a big chance it will be async. + // TODO Currently, lib-jitsi-meet doesn't have any functionality to + // dispose itself. + return dispatch => { + dispatch({ type: LIB_DISPOSED }); + + return Promise.resolve(); + }; +} + +/** + * Initializes lib-jitsi-meet with passed configuration. + * + * @returns {Function} + */ +export function initLib() { + return (dispatch, getState) => { + const config = getState()['features/base/lib-jitsi-meet'].config; + + if (!config) { + throw new Error('Cannot initialize lib-jitsi-meet without config'); + } + + // XXX Temporary solution. Until conference.js hasn't been moved + // to the react app we shouldn't use JitsiMeetJS from react app. + return Promise.resolve(); + }; +} + +/** + * Sets config. + * + * @param {Object} config - Config object accepted by JitsiMeetJS#init() + * method. + * @returns {{ + * type: SET_CONFIG, + * config: Object + * }} + */ +export function setConfig(config) { + return { + type: SET_CONFIG, + config + }; +} diff --git a/react/features/base/lib-jitsi-meet/functions.js b/react/features/base/lib-jitsi-meet/functions.native.js similarity index 100% rename from react/features/base/lib-jitsi-meet/functions.js rename to react/features/base/lib-jitsi-meet/functions.native.js diff --git a/react/features/base/lib-jitsi-meet/functions.web.js b/react/features/base/lib-jitsi-meet/functions.web.js new file mode 100644 index 000000000..5f0206fa6 --- /dev/null +++ b/react/features/base/lib-jitsi-meet/functions.web.js @@ -0,0 +1,10 @@ +/** + * Returns config.js file from global scope. + * We can't use version that's being used for native app + * because the old app uses config from global scope. + * + * @returns {Promise} + */ +export function loadConfig() { + return Promise.resolve(window.config); +} diff --git a/react/features/conference/components/Conference.web.js b/react/features/conference/components/Conference.web.js index cda4e0179..a9f8cd0fd 100644 --- a/react/features/conference/components/Conference.web.js +++ b/react/features/conference/components/Conference.web.js @@ -1,5 +1,12 @@ -/* global interfaceConfig */ +/* global APP, $, interfaceConfig */ import React, { Component } from 'react'; +import { connect as reactReduxConnect } from 'react-redux'; + +import { + connect, + disconnect +} from '../../base/connection'; + /** * For legacy reasons, inline style for display none. @@ -12,7 +19,42 @@ const DISPLAY_NONE_STYLE = { /** * Implements a React Component which renders initial conference layout */ -export default class Conference extends Component { +class Conference extends Component { + + /** + * Until we don't rewrite UI using react components + * we use UI.start from old app. Also method translates + * component right after it has been mounted. + * + * @inheritdoc + */ + componentDidMount() { + APP.UI.start(); + + // XXX Temporary solution until we add React translation. + APP.translation.translateElement($('#videoconference_page')); + + this.props.dispatch(connect()); + } + + /** + * Disconnect from the conference when component will be + * unmounted. + * + * @inheritdoc + */ + componentWillUnmount() { + this.props.dispatch(disconnect()); + } + + /** + * Conference component's property types. + * + * @static + */ + static propTypes = { + dispatch: React.PropTypes.func + }; /** * Initializes Conference component instance. @@ -214,3 +256,5 @@ export default class Conference extends Component { return null; } } + +export default reactReduxConnect()(Conference); diff --git a/react/features/welcome/components/WelcomePage.web.js b/react/features/welcome/components/WelcomePage.web.js index 4eaa2daac..9466bf765 100644 --- a/react/features/welcome/components/WelcomePage.web.js +++ b/react/features/welcome/components/WelcomePage.web.js @@ -1,4 +1,4 @@ -/* global APP, interfaceConfig */ +/* global interfaceConfig, APP, $ */ import React from 'react'; import { connect } from 'react-redux'; @@ -52,6 +52,9 @@ class WelcomePage extends AbstractWelcomePage { if (this.state.generateRoomnames) { this._updateRoomname(); } + + // XXX Temporary solution until we add React translation. + APP.translation.translateElement($('#welcome_page')); } /** @@ -61,12 +64,6 @@ class WelcomePage extends AbstractWelcomePage { * @returns {ReactElement|null} */ render() { - // FIXME The rendering of Conference bellow is a very quick and dirty - // temporary fix for the following issue: when the WelcomePage is - // disabled, app.js expects Conference to be rendered already and only - // then it builds a room name but the App component expects the room - // name to be built already (by looking at the window's location) in - // order to choose between WelcomePage and Conference. return (
@@ -77,7 +74,6 @@ class WelcomePage extends AbstractWelcomePage { this._renderMain() }
-
); } @@ -359,6 +355,43 @@ class WelcomePage extends AbstractWelcomePage { return null; } + /** + * Handles updating roomname. + * + * @private + * @returns {void} + **/ + _onUpdateRoomname() { + this._updateRoomname(); + } + + /** + * Event handler for changing room name input from web. + * + * @inheritdoc + * @override + * @protected + */ + _onRoomChange() { + super._onRoomChange(this.roomNameInput.value); + } + + /** + * Handles 'keydown' event and initiate joining the room if 'return' button + * was pressed. + * + * @param {Event} event - Key down event object. + * @returns {void} + * @private + */ + _onKeyDown(event) { + const RETURN_BUTTON_CODE = 13; + + if (event.keyCode === RETURN_BUTTON_CODE) { + this._onJoin(); + } + } + /** * Renders the main part of this WelcomePage. * diff --git a/react/features/welcome/route.js b/react/features/welcome/route.js index c5b2d7f29..ea7f6e5a6 100644 --- a/react/features/welcome/route.js +++ b/react/features/welcome/route.js @@ -1,11 +1,31 @@ +/* global APP */ import { RouteRegistry } from '../base/navigator'; - +import { generateRoomWithoutSeparator } from '../base/util'; import { WelcomePage } from './components'; + +/** + * Function that checks if welcome page is enabled and if it isn't + * redirects to randomly created conference. + * + * @param {Object} nextState - Next router state. + * @param {Function} replace - Function to redirect to another path. + * @returns {void} + */ +function onEnter(nextState, replace) { + if (!APP.settings.isWelcomePageEnabled()) { + const generatedRoomname = generateRoomWithoutSeparator(); + const normalizedRoomname = generatedRoomname.toLowerCase(); + + replace(`/${normalizedRoomname}`); + } +} + /** * Register route for WelcomePage. */ RouteRegistry.register({ component: WelcomePage, - path: '/' + path: '/', + onEnter }); From 3190bfa05852e46970c24e531fe6eff471c1c808 Mon Sep 17 00:00:00 2001 From: Ilya Daynatovich Date: Wed, 14 Dec 2016 12:32:36 +0200 Subject: [PATCH 02/11] Move redundant code from app.js --- app.js | 152 +----------------- react/features/app/components/App.web.js | 73 ++++++++- .../conference/components/Conference.web.js | 79 ++++++++- react/index.web.js | 40 ++++- 4 files changed, 190 insertions(+), 154 deletions(-) diff --git a/app.js b/app.js index 076557f68..684d04ce1 100644 --- a/app.js +++ b/app.js @@ -1,6 +1,4 @@ -/* global $, config, loggingConfig, JitsiMeetJS */ /* application specific logic */ -const logger = require("jitsi-meet-logger").getLogger(__filename); import "babel-polyfill"; import "jquery"; @@ -18,51 +16,13 @@ import 'aui-experimental-css'; window.toastr = require("toastr"); -const Logger = require("jitsi-meet-logger"); -const LogCollector = Logger.LogCollector; -import JitsiMeetLogStorage from "./modules/util/JitsiMeetLogStorage"; - -import URLProcessor from "./modules/config/URLProcessor"; - import UI from "./modules/UI/UI"; import settings from "./modules/settings/Settings"; import conference from './conference'; -import ConferenceUrl from './modules/URL/ConferenceUrl'; import API from './modules/API/API'; -import getTokenData from "./modules/tokendata/TokenData"; import translation from "./modules/translation/translation"; -/** - * Adjusts the logging levels. - * @private - */ -function configureLoggingLevels () { - // NOTE The library Logger is separated from the app loggers, so the levels - // have to be set in two places - - // Set default logging level - const defaultLogLevel - = loggingConfig.defaultLogLevel || JitsiMeetJS.logLevels.TRACE; - Logger.setLogLevel(defaultLogLevel); - JitsiMeetJS.setLogLevel(defaultLogLevel); - - // NOTE console was used on purpose here to go around the logging - // and always print the default logging level to the console - console.info("Default logging level set to: " + defaultLogLevel); - - // Set log level for each logger - if (loggingConfig) { - Object.keys(loggingConfig).forEach(function(loggerName) { - if ('defaultLogLevel' !== loggerName) { - const level = loggingConfig[loggerName]; - Logger.setLogLevelById(level, loggerName); - JitsiMeetJS.setLogLevelById(level, loggerName); - } - }); - } -} - const APP = { // Used by do_external_connect.js if we receive the attach data after // connect was already executed. status property can be "initialized", @@ -99,111 +59,15 @@ const APP = { */ ConferenceUrl : null, connection: null, - API, - init () { - this.initLogging(); - this.keyboardshortcut = - require("./modules/keyboardshortcut/keyboardshortcut"); - this.configFetch = require("./modules/config/HttpConfigFetch"); - this.tokenData = getTokenData(); - }, - initLogging () { - // Adjust logging level - configureLoggingLevels(); - // Create the LogCollector and register it as the global log transport. - // It is done early to capture as much logs as possible. Captured logs - // will be cached, before the JitsiMeetLogStorage gets ready (statistics - // module is initialized). - if (!this.logCollector && !loggingConfig.disableLogCollector) { - this.logCollector = new LogCollector(new JitsiMeetLogStorage()); - Logger.addGlobalTransport(this.logCollector); - JitsiMeetJS.addGlobalLogTransport(this.logCollector); - } - } + API }; -/** - * If JWT token data it will be used for local user settings - */ -function setTokenData() { - let localUser = APP.tokenData.caller; - if(localUser) { - APP.settings.setEmail((localUser.getEmail() || "").trim(), true); - APP.settings.setAvatarUrl((localUser.getAvatarUrl() || "").trim()); - APP.settings.setDisplayName((localUser.getName() || "").trim(), true); - } -} - -function init() { - setTokenData(); - // Initialize the conference URL handler - APP.ConferenceUrl = new ConferenceUrl(window.location); - - // TODO The execution of the mobile app starts from react/index.native.js. - // Similarly, the execution of the Web app should start from - // react/index.web.js for the sake of consistency and ease of understanding. - // Temporarily though because we are at the beginning of introducing React - // into the Web app, allow the execution of the Web app to start from app.js - // in order to reduce the complexity of the beginning step. - require('./react'); -} - -/** - * If we have an HTTP endpoint for getting config.json configured we're going to - * read it and override properties from config.js and interfaceConfig.js. - * If there is no endpoint we'll just continue with initialization. - * Keep in mind that if the endpoint has been configured and we fail to obtain - * the config for any reason then the conference won't start and error message - * will be displayed to the user. - */ -function obtainConfigAndInit() { - let roomName = APP.conference.roomName; - - if (config.configLocation) { - APP.configFetch.obtainConfig( - config.configLocation, roomName, - // Get config result callback - function(success, error) { - if (success) { - var now = APP.connectionTimes["configuration.fetched"] = - window.performance.now(); - logger.log("(TIME) configuration fetched:\t", now); - init(); - } else { - // Show obtain config error, - // pass the error object for report - APP.UI.messageHandler.openReportDialog( - null, "dialog.connectError", error); - } - }); - } else { - require("./modules/config/BoshAddressChoice").chooseAddress( - config, roomName); - - init(); - } -} - - -$(document).ready(function () { - var now = APP.connectionTimes["document.ready"] = window.performance.now(); - logger.log("(TIME) document ready:\t", now); - - URLProcessor.setConfigParametersFromUrl(); - - APP.init(); - APP.API.init(APP.tokenData.externalAPISettings); - - obtainConfigAndInit(); -}); - -$(window).bind('beforeunload', function () { - // Stop the LogCollector - if (APP.logCollectorStarted) { - APP.logCollector.stop(); - APP.logCollectorStarted = false; - } - APP.API.dispose(); -}); +// TODO The execution of the mobile app starts from react/index.native.js. +// Similarly, the execution of the Web app should start from +// react/index.web.js for the sake of consistency and ease of understanding. +// Temporarily though because we are at the beginning of introducing React +// into the Web app, allow the execution of the Web app to start from app.js +// in order to reduce the complexity of the beginning step. +require('./react'); module.exports = APP; diff --git a/react/features/app/components/App.web.js b/react/features/app/components/App.web.js index 7d646f51f..e4ba79a20 100644 --- a/react/features/app/components/App.web.js +++ b/react/features/app/components/App.web.js @@ -15,6 +15,15 @@ import { RouteRegistry } from '../../base/navigator'; import { AbstractApp } from './AbstractApp'; import settings from '../../../../modules/settings/Settings'; + +import URLProcessor from '../../../../modules/config/URLProcessor'; +import getTokenData from '../../../../modules/tokendata/TokenData'; +import JitsiMeetLogStorage from '../../../../modules/util/JitsiMeetLogStorage'; +import KeyboardShortcut from '../../../../modules/keyboardshortcut/keyboardshortcut'; +const Logger = require('jitsi-meet-logger'); +const LogCollector = Logger.LogCollector; + + /** * Root application component. * @@ -59,6 +68,69 @@ export class App extends AbstractApp { componentWillMount(...args) { super.componentWillMount(...args); + URLProcessor.setConfigParametersFromUrl(); + + /* APP.init BEGIN */ + + /* Init logging BEGIN */ + + // Adjust logging level + configureLoggingLevels(); + + // Create the LogCollector and register it as the global log transport. + // It is done early to capture as much logs as possible. Captured logs + // will be cached, before the JitsiMeetLogStorage gets ready (statistics + // module is initialized). + if (!APP.logCollector && !loggingConfig.disableLogCollector) { + APP.logCollector = new LogCollector(new JitsiMeetLogStorage()); + Logger.addGlobalTransport(APP.logCollector); + JitsiMeetJS.addGlobalLogTransport(APP.logCollector); + } + + /* Init logging BEGIN */ + + APP.keyboardshortcut = KeyboardShortcut; + APP.tokenData = getTokenData(); + + /* APP.init END */ + + APP.API.init(APP.tokenData.externalAPISettings); + + /** + * Adjusts the logging levels. + * + * @private + * @returns {void} + */ + function configureLoggingLevels() { + // NOTE The library Logger is separated from the app loggers, so the levels + // have to be set in two places + + // Set default logging level + const defaultLogLevel + = loggingConfig.defaultLogLevel || JitsiMeetJS.logLevels.TRACE; + + Logger.setLogLevel(defaultLogLevel); + JitsiMeetJS.setLogLevel(defaultLogLevel); + + // NOTE console was used on purpose here to go around the logging + // and always print the default logging level to the console + console.info(`Default logging level set to: ${defaultLogLevel}`); + + // Set log level for each logger + if (loggingConfig) { + Object.keys(loggingConfig).forEach(loggerName => { + if (loggerName !== 'defaultLogLevel') { + const level = loggingConfig[loggerName]; + + Logger.setLogLevelById(level, loggerName); + JitsiMeetJS.setLogLevelById(level, loggerName); + } + }); + } + } + + APP.translation.init(settings.getLanguage()); } @@ -70,7 +142,6 @@ export class App extends AbstractApp { */ render() { - return ( , - document.getElementById('react')); +/** + * Render the app when DOM tree has been loaded. + */ +document.addEventListener('DOMContentLoaded', () => { + const now = window.performance.now(); + + APP.connectionTimes['document.ready'] = now; + logger.log('(TIME) document ready:\t', now); + + // Render the main Component. + ReactDOM.render( + , + document.getElementById('react')); +}); + +/** + * Stop collecting the logs and disposing the API when + * user closes the page. + */ +window.addEventListener('beforeunload', () => { + // Stop the LogCollector + if (APP.logCollectorStarted) { + APP.logCollector.stop(); + APP.logCollectorStarted = false; + } + APP.API.dispose(); +}); From e716c1738cb037ce88b1f9634cabe57bdfcecc25 Mon Sep 17 00:00:00 2001 From: Ilya Daynatovich Date: Wed, 14 Dec 2016 12:36:42 +0200 Subject: [PATCH 03/11] Fix lint errs --- react/features/app/components/App.web.js | 8 +++- .../conference/components/Conference.web.js | 37 ++++++++++++------- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/react/features/app/components/App.web.js b/react/features/app/components/App.web.js index e4ba79a20..34cb7c242 100644 --- a/react/features/app/components/App.web.js +++ b/react/features/app/components/App.web.js @@ -1,4 +1,4 @@ -/* global APP, $ */ +/* global APP, JitsiMeetJS, loggingConfig $ */ import React from 'react'; import { Provider } from 'react-redux'; import { compose } from 'redux'; @@ -19,7 +19,10 @@ import settings from '../../../../modules/settings/Settings'; import URLProcessor from '../../../../modules/config/URLProcessor'; import getTokenData from '../../../../modules/tokendata/TokenData'; import JitsiMeetLogStorage from '../../../../modules/util/JitsiMeetLogStorage'; + +// eslint-disable-next-line max-len import KeyboardShortcut from '../../../../modules/keyboardshortcut/keyboardshortcut'; + const Logger = require('jitsi-meet-logger'); const LogCollector = Logger.LogCollector; @@ -103,7 +106,8 @@ export class App extends AbstractApp { * @returns {void} */ function configureLoggingLevels() { - // NOTE The library Logger is separated from the app loggers, so the levels + // NOTE The library Logger is separated from + // the app loggers, so the levels // have to be set in two places // Set default logging level diff --git a/react/features/conference/components/Conference.web.js b/react/features/conference/components/Conference.web.js index 10fca732b..70ce7c725 100644 --- a/react/features/conference/components/Conference.web.js +++ b/react/features/conference/components/Conference.web.js @@ -1,4 +1,5 @@ -/* global APP, $, interfaceConfig */ +/* global APP, $, interfaceConfig, config */ + import React, { Component } from 'react'; import { connect as reactReduxConnect } from 'react-redux'; @@ -10,6 +11,8 @@ import ConferenceUrl from '../../../../modules/URL/ConferenceUrl'; import HttpConfigFetch from '../../../../modules/config/HttpConfigFetch'; import BoshAddressChoice from '../../../../modules/config/BoshAddressChoice'; +const logger = require('jitsi-meet-logger').getLogger(__filename); + /** * For legacy reasons, inline style for display none. * @type {{display: string}} @@ -30,6 +33,7 @@ class Conference extends Component { * @inheritdoc */ componentDidMount() { + /** * If JWT token data it will be used for local user settings. * @@ -39,11 +43,16 @@ class Conference extends Component { const localUser = APP.tokenData.caller; if (localUser) { - APP.settings.setEmail((localUser.getEmail() || '').trim(), true); - APP.settings.setAvatarUrl((localUser.getAvatarUrl() || '').trim()); - APP.settings.setDisplayName((localUser.getName() || '').trim(), true); + const email = localUser.getEmail(); + const avatarUrl = localUser.getAvatarUrl(); + const name = localUser.getName(); + + APP.settings.setEmail((email || '').trim(), true); + APP.settings.setAvatarUrl((avatarUrl || '').trim()); + APP.settings.setDisplayName((name || '').trim(), true); } } + /** * Initialization of the app. * @@ -55,26 +64,28 @@ class Conference extends Component { // Initialize the conference URL handler APP.ConferenceUrl = new ConferenceUrl(window.location); } + /** - * If we have an HTTP endpoint for getting config.json configured we're going to - * read it and override properties from config.js and interfaceConfig.js. - * If there is no endpoint we'll just continue with initialization. - * Keep in mind that if the endpoint has been configured and we fail to obtain - * the config for any reason then the conference won't start and error message - * will be displayed to the user. + * If we have an HTTP endpoint for getting config.json configured + * we're going to read it and override properties from config.js and + * interfaceConfig.js. If there is no endpoint we'll just + * continue with initialization. + * Keep in mind that if the endpoint has been configured and we fail + * to obtain the config for any reason then the conference won't + * start and error message will be displayed to the user. * * @returns {void} */ function obtainConfigAndInit() { - const roomName = APP.conference.roomName; + const room = APP.conference.roomName; if (config.configLocation) { const configFetch = HttpConfigFetch; const location = config.configLocation; - configFetch.obtainConfig(location, roomName, obtainConfigHandler); + configFetch.obtainConfig(location, room, obtainConfigHandler); } else { - BoshAddressChoice.chooseAddress(config, roomName); + BoshAddressChoice.chooseAddress(config, room); init(); } } From f53fb3d8140d080657776d4ef09a0840ef324a2a Mon Sep 17 00:00:00 2001 From: Ilya Daynatovich Date: Wed, 14 Dec 2016 14:09:29 +0200 Subject: [PATCH 04/11] Introduced new actions and functions for app initialization --- .../app/{actions.js => actions.native.js} | 0 react/features/app/actions.web.js | 173 ++++++++++++++ react/features/app/components/App.web.js | 84 +------ .../app/{functions.js => functions.native.js} | 0 react/features/app/functions.web.js | 214 ++++++++++++++++++ 5 files changed, 391 insertions(+), 80 deletions(-) rename react/features/app/{actions.js => actions.native.js} (100%) create mode 100644 react/features/app/actions.web.js rename react/features/app/{functions.js => functions.native.js} (100%) create mode 100644 react/features/app/functions.web.js diff --git a/react/features/app/actions.js b/react/features/app/actions.native.js similarity index 100% rename from react/features/app/actions.js rename to react/features/app/actions.native.js diff --git a/react/features/app/actions.web.js b/react/features/app/actions.web.js new file mode 100644 index 000000000..2ec370379 --- /dev/null +++ b/react/features/app/actions.web.js @@ -0,0 +1,173 @@ +import { setRoom } from '../base/conference'; +import { + getDomain, + setDomain +} from '../base/connection'; +import { + loadConfig, + setConfig +} from '../base/lib-jitsi-meet'; + +import { + APP_WILL_MOUNT, + APP_WILL_UNMOUNT +} from './actionTypes'; +import { + _getRoomAndDomainFromUrlString, + _getRouteToRender, + init +} from './functions'; +import './reducer'; + + +/** + * Triggers an in-app navigation to a different route. Allows navigation to be + * abstracted between the mobile and web versions. + * + * @param {(string|undefined)} urlOrRoom - The URL or room name to which to + * navigate. + * @returns {Function} + */ +export function appNavigate(urlOrRoom) { + return (dispatch, getState) => { + const oldDomain = getDomain(getState()); + + const { domain, room } = _getRoomAndDomainFromUrlString(urlOrRoom); + + // TODO Kostiantyn Tsaregradskyi: We should probably detect if user is + // currently in a conference and ask her if she wants to close the + // current conference and start a new one with the new room name or + // domain. + + if (typeof domain === 'undefined' || oldDomain === domain) { + // If both domain and room vars became undefined, that means we're + // actually dealing with just room name and not with URL. + dispatch( + _setRoomAndNavigate( + typeof room === 'undefined' && typeof domain === 'undefined' + ? urlOrRoom + : room)); + } else if (oldDomain !== domain) { + // Update domain without waiting for config to be loaded to prevent + // race conditions when we will start to load config multiple times. + dispatch(setDomain(domain)); + + // If domain has changed, we need to load the config of the new + // domain and set it, and only after that we can navigate to + // different route. + loadConfig(`https://${domain}`) + .then( + config => configLoaded(/* err */ undefined, config), + err => configLoaded(err, /* config */ undefined)); + } + + /** + * Notifies that an attempt to load the config(uration) of domain has + * completed. + * + * @param {string|undefined} err - If the loading has failed, the error + * detailing the cause of the failure. + * @param {Object|undefined} config - If the loading has succeeded, the + * loaded config(uration). + * @returns {void} + */ + function configLoaded(err, config) { + if (err) { + // XXX The failure could be, for example, because of a + // certificate-related error. In which case the connection will + // fail later in Strophe anyway even if we use the default + // config here. + + // The function loadConfig will log the err. + return; + } + + // We set room name only here to prevent race conditions on app + // start to not make app re-render conference page for two times. + dispatch(setRoom(room)); + dispatch(setConfig(config)); + _navigate(getState()); + } + }; +} + +/** + * Temporary solution. Should dispatch actions related to + * initial settings of the app like setting log levels, + * reading the config parameters from query string etc. + * + * @returns {Function} + */ +export function appInit() { + return () => { + init(); + }; +} + +/** + * Signals that a specific App will mount (in the terms of React). + * + * @param {App} app - The App which will mount. + * @returns {{ + * type: APP_WILL_MOUNT, + * app: App + * }} + */ +export function appWillMount(app) { + return { + type: APP_WILL_MOUNT, + app + }; +} + +/** + * Signals that a specific App will unmount (in the terms of React). + * + * @param {App} app - The App which will unmount. + * @returns {{ + * type: APP_WILL_UNMOUNT, + * app: App + * }} + */ +export function appWillUnmount(app) { + return { + type: APP_WILL_UNMOUNT, + app + }; +} + +/** + * Navigates to route corresponding to current room name. + * + * @param {Object} state - Redux state. + * @private + * @returns {void} + */ +function _navigate(state) { + const app = state['features/app'].app; + const routeToRender = _getRouteToRender(state); + + app._navigate(routeToRender); +} + +/** + * Sets room and navigates to new route if needed. + * + * @param {string} newRoom - New room name. + * @private + * @returns {Function} + */ +function _setRoomAndNavigate(newRoom) { + return (dispatch, getState) => { + const oldRoom = getState()['features/base/conference'].room; + + dispatch(setRoom(newRoom)); + + const state = getState(); + const room = state['features/base/conference'].room; + + if (room !== oldRoom) { + _navigate(state); + } + }; +} diff --git a/react/features/app/components/App.web.js b/react/features/app/components/App.web.js index 34cb7c242..252812444 100644 --- a/react/features/app/components/App.web.js +++ b/react/features/app/components/App.web.js @@ -1,4 +1,4 @@ -/* global APP, JitsiMeetJS, loggingConfig $ */ +/* global $ */ import React from 'react'; import { Provider } from 'react-redux'; import { compose } from 'redux'; @@ -13,18 +13,7 @@ import { getDomain } from '../../base/connection'; import { RouteRegistry } from '../../base/navigator'; import { AbstractApp } from './AbstractApp'; -import settings from '../../../../modules/settings/Settings'; - - -import URLProcessor from '../../../../modules/config/URLProcessor'; -import getTokenData from '../../../../modules/tokendata/TokenData'; -import JitsiMeetLogStorage from '../../../../modules/util/JitsiMeetLogStorage'; - -// eslint-disable-next-line max-len -import KeyboardShortcut from '../../../../modules/keyboardshortcut/keyboardshortcut'; - -const Logger = require('jitsi-meet-logger'); -const LogCollector = Logger.LogCollector; +import { appInit } from '../actions'; /** @@ -64,78 +53,13 @@ export class App extends AbstractApp { } /** - * Init translation from old app. + * Inits the app before component will mount. * * @inheritdoc */ componentWillMount(...args) { super.componentWillMount(...args); - - URLProcessor.setConfigParametersFromUrl(); - - /* APP.init BEGIN */ - - /* Init logging BEGIN */ - - // Adjust logging level - configureLoggingLevels(); - - // Create the LogCollector and register it as the global log transport. - // It is done early to capture as much logs as possible. Captured logs - // will be cached, before the JitsiMeetLogStorage gets ready (statistics - // module is initialized). - if (!APP.logCollector && !loggingConfig.disableLogCollector) { - APP.logCollector = new LogCollector(new JitsiMeetLogStorage()); - Logger.addGlobalTransport(APP.logCollector); - JitsiMeetJS.addGlobalLogTransport(APP.logCollector); - } - - /* Init logging BEGIN */ - - APP.keyboardshortcut = KeyboardShortcut; - APP.tokenData = getTokenData(); - - /* APP.init END */ - - APP.API.init(APP.tokenData.externalAPISettings); - - /** - * Adjusts the logging levels. - * - * @private - * @returns {void} - */ - function configureLoggingLevels() { - // NOTE The library Logger is separated from - // the app loggers, so the levels - // have to be set in two places - - // Set default logging level - const defaultLogLevel - = loggingConfig.defaultLogLevel || JitsiMeetJS.logLevels.TRACE; - - Logger.setLogLevel(defaultLogLevel); - JitsiMeetJS.setLogLevel(defaultLogLevel); - - // NOTE console was used on purpose here to go around the logging - // and always print the default logging level to the console - console.info(`Default logging level set to: ${defaultLogLevel}`); - - // Set log level for each logger - if (loggingConfig) { - Object.keys(loggingConfig).forEach(loggerName => { - if (loggerName !== 'defaultLogLevel') { - const level = loggingConfig[loggerName]; - - Logger.setLogLevelById(level, loggerName); - JitsiMeetJS.setLogLevelById(level, loggerName); - } - }); - } - } - - - APP.translation.init(settings.getLanguage()); + this.props.store.dispatch(appInit()); } /** diff --git a/react/features/app/functions.js b/react/features/app/functions.native.js similarity index 100% rename from react/features/app/functions.js rename to react/features/app/functions.native.js diff --git a/react/features/app/functions.web.js b/react/features/app/functions.web.js new file mode 100644 index 000000000..5f541c74a --- /dev/null +++ b/react/features/app/functions.web.js @@ -0,0 +1,214 @@ +/* global APP, JitsiMeetJS, loggingConfig */ +import { isRoomValid } from '../base/conference'; +import { RouteRegistry } from '../base/navigator'; +import { Conference } from '../conference'; +import { WelcomePage } from '../welcome'; + +import getTokenData from '../../../modules/tokendata/TokenData'; +import settings from '../../../modules/settings/Settings'; + +import URLProcessor from '../../../modules/config/URLProcessor'; +import JitsiMeetLogStorage from '../../../modules/util/JitsiMeetLogStorage'; + +// eslint-disable-next-line max-len +import KeyboardShortcut from '../../../modules/keyboardshortcut/keyboardshortcut'; + +const Logger = require('jitsi-meet-logger'); +const LogCollector = Logger.LogCollector; + + +/** + * Gets room name and domain from URL object. + * + * @param {URL} url - URL object. + * @private + * @returns {{ + * domain: (string|undefined), + * room: (string|undefined) + * }} + */ +function _getRoomAndDomainFromUrlObject(url) { + let domain; + let room; + + if (url) { + domain = url.hostname; + room = url.pathname.substr(1); + + // Convert empty string to undefined to simplify checks. + if (room === '') { + room = undefined; + } + if (domain === '') { + domain = undefined; + } + } + + return { + domain, + room + }; +} + +/** + * Gets conference room name and connection domain from URL. + * + * @param {(string|undefined)} url - URL. + * @returns {{ + * domain: (string|undefined), + * room: (string|undefined) + * }} + */ +export function _getRoomAndDomainFromUrlString(url) { + // Rewrite the specified URL in order to handle special cases such as + // hipchat.com and enso.me which do not follow the common pattern of most + // Jitsi Meet deployments. + if (typeof url === 'string') { + // hipchat.com + let regex = /^(https?):\/\/hipchat.com\/video\/call\//gi; + let match = regex.exec(url); + + if (!match) { + // enso.me + regex = /^(https?):\/\/enso\.me\/(?:call|meeting)\//gi; + match = regex.exec(url); + } + if (match && match.length > 1) { + /* eslint-disable no-param-reassign, prefer-template */ + + url + = match[1] /* URL protocol */ + + '://enso.hipchat.me/' + + url.substring(regex.lastIndex); + + /* eslint-enable no-param-reassign, prefer-template */ + } + } + + return _getRoomAndDomainFromUrlObject(_urlStringToObject(url)); +} + +/** + * Determines which route is to be rendered in order to depict a specific Redux + * store. + * + * @param {(Object|Function)} stateOrGetState - Redux state or Regux getState() + * method. + * @returns {Route} + */ +export function _getRouteToRender(stateOrGetState) { + const state + = typeof stateOrGetState === 'function' + ? stateOrGetState() + : stateOrGetState; + const room = state['features/base/conference'].room; + const component = isRoomValid(room) ? Conference : WelcomePage; + + return RouteRegistry.getRouteByComponent(component); +} + +/** + * Parses a string into a URL (object). + * + * @param {(string|undefined)} url - The URL to parse. + * @private + * @returns {URL} + */ +function _urlStringToObject(url) { + let urlObj; + + if (url) { + try { + urlObj = new URL(url); + } catch (ex) { + // The return value will signal the failure & the logged + // exception will provide the details to the developers. + console.log(`${url} seems to be not a valid URL, but it's OK`, ex); + } + } + + return urlObj; +} + +/** + * Temporary solution. Later we'll get rid of global APP + * and set its properties in redux store. + * + * @returns {void} + */ +export function init() { + _setConfigParametersFromUrl(); + _initLogging(); + + APP.keyboardshortcut = KeyboardShortcut; + APP.tokenData = getTokenData(); + APP.API.init(APP.tokenData.externalAPISettings); + + APP.translation.init(settings.getLanguage()); +} + +/** + * Initializes logging in the app. + * + * @private + * @returns {void} + */ +function _initLogging() { + // Adjust logging level + configureLoggingLevels(); + + // Create the LogCollector and register it as the global log transport. + // It is done early to capture as much logs as possible. Captured logs + // will be cached, before the JitsiMeetLogStorage gets ready (statistics + // module is initialized). + if (!APP.logCollector && !loggingConfig.disableLogCollector) { + APP.logCollector = new LogCollector(new JitsiMeetLogStorage()); + Logger.addGlobalTransport(APP.logCollector); + JitsiMeetJS.addGlobalLogTransport(APP.logCollector); + } +} + +/** + * Adjusts the logging levels. + * + * @private + * @returns {void} + */ +function configureLoggingLevels() { + // NOTE The library Logger is separated from + // the app loggers, so the levels + // have to be set in two places + + // Set default logging level + const defaultLogLevel + = loggingConfig.defaultLogLevel || JitsiMeetJS.logLevels.TRACE; + + Logger.setLogLevel(defaultLogLevel); + JitsiMeetJS.setLogLevel(defaultLogLevel); + + // NOTE console was used on purpose here to go around the logging + // and always print the default logging level to the console + console.info(`Default logging level set to: ${defaultLogLevel}`); + + // Set log level for each logger + if (loggingConfig) { + Object.keys(loggingConfig).forEach(loggerName => { + if (loggerName !== 'defaultLogLevel') { + const level = loggingConfig[loggerName]; + + Logger.setLogLevelById(level, loggerName); + JitsiMeetJS.setLogLevelById(level, loggerName); + } + }); + } +} + +/** + * Sets config parameters from query string. + * + * @private + * @returns {void} + */ +function _setConfigParametersFromUrl() { + URLProcessor.setConfigParametersFromUrl(); +} From a99bbe67ab9ac27f043235780816046ecbbbb460 Mon Sep 17 00:00:00 2001 From: Ilya Daynatovich Date: Wed, 14 Dec 2016 15:11:48 +0200 Subject: [PATCH 05/11] Clean up Conference component --- react/features/conference/actions.js | 67 ++++++++++++++ .../conference/components/Conference.web.js | 91 +------------------ react/features/conference/functions.js | 42 +++++++++ 3 files changed, 112 insertions(+), 88 deletions(-) create mode 100644 react/features/conference/actions.js create mode 100644 react/features/conference/functions.js diff --git a/react/features/conference/actions.js b/react/features/conference/actions.js new file mode 100644 index 000000000..49160adde --- /dev/null +++ b/react/features/conference/actions.js @@ -0,0 +1,67 @@ +/* global APP, config */ +import ConferenceUrl from '../../../modules/URL/ConferenceUrl'; +import BoshAddressChoice from '../../../modules/config/BoshAddressChoice'; +import { obtainConfig, setTokenData } from './functions'; +const logger = require('jitsi-meet-logger').getLogger(__filename); + +/** + * If we have an HTTP endpoint for getting config.json configured + * we're going to read it and override properties from config.js and + * interfaceConfig.js. If there is no endpoint we'll just + * continue with initialization. + * Keep in mind that if the endpoint has been configured and we fail + * to obtain the config for any reason then the conference won't + * start and error message will be displayed to the user. + * + * @returns {Function} + */ +export function obtainConfigAndInit() { + return () => { + const room = APP.conference.roomName; + + if (config.configLocation) { + const location = config.configLocation; + + obtainConfig(location, room) + .then(_obtainConfigHandler) + .then(_initConference) + .catch(err => { + // Show obtain config error, + // pass the error object for report + APP.UI.messageHandler.openReportDialog( + null, 'dialog.connectError', err); + }); + } else { + BoshAddressChoice.chooseAddress(config, room); + _initConference(); + } + }; +} + +/** + * Obtain config handler. + * + * @returns {Promise} + * @private + */ +function _obtainConfigHandler() { + const now = window.performance.now(); + + APP.connectionTimes['configuration.fetched'] = now; + logger.log('(TIME) configuration fetched:\t', now); + + return Promise.resolve(); +} + +/** + * Initialization of the app. + * + * @returns {void} + * @private + */ +function _initConference() { + setTokenData(); + + // Initialize the conference URL handler + APP.ConferenceUrl = new ConferenceUrl(window.location); +} diff --git a/react/features/conference/components/Conference.web.js b/react/features/conference/components/Conference.web.js index 70ce7c725..942e5e21f 100644 --- a/react/features/conference/components/Conference.web.js +++ b/react/features/conference/components/Conference.web.js @@ -1,4 +1,4 @@ -/* global APP, $, interfaceConfig, config */ +/* global APP, $, interfaceConfig */ import React, { Component } from 'react'; import { connect as reactReduxConnect } from 'react-redux'; @@ -7,11 +7,7 @@ import { connect, disconnect } from '../../base/connection'; -import ConferenceUrl from '../../../../modules/URL/ConferenceUrl'; -import HttpConfigFetch from '../../../../modules/config/HttpConfigFetch'; -import BoshAddressChoice from '../../../../modules/config/BoshAddressChoice'; - -const logger = require('jitsi-meet-logger').getLogger(__filename); +import { obtainConfigAndInit } from '../actions'; /** * For legacy reasons, inline style for display none. @@ -33,88 +29,7 @@ class Conference extends Component { * @inheritdoc */ componentDidMount() { - - /** - * If JWT token data it will be used for local user settings. - * - * @returns {void} - */ - function setTokenData() { - const localUser = APP.tokenData.caller; - - if (localUser) { - const email = localUser.getEmail(); - const avatarUrl = localUser.getAvatarUrl(); - const name = localUser.getName(); - - APP.settings.setEmail((email || '').trim(), true); - APP.settings.setAvatarUrl((avatarUrl || '').trim()); - APP.settings.setDisplayName((name || '').trim(), true); - } - } - - /** - * Initialization of the app. - * - * @returns {void} - */ - function init() { - setTokenData(); - - // Initialize the conference URL handler - APP.ConferenceUrl = new ConferenceUrl(window.location); - } - - /** - * If we have an HTTP endpoint for getting config.json configured - * we're going to read it and override properties from config.js and - * interfaceConfig.js. If there is no endpoint we'll just - * continue with initialization. - * Keep in mind that if the endpoint has been configured and we fail - * to obtain the config for any reason then the conference won't - * start and error message will be displayed to the user. - * - * @returns {void} - */ - function obtainConfigAndInit() { - const room = APP.conference.roomName; - - if (config.configLocation) { - const configFetch = HttpConfigFetch; - const location = config.configLocation; - - configFetch.obtainConfig(location, room, obtainConfigHandler); - } else { - BoshAddressChoice.chooseAddress(config, room); - init(); - } - } - - /** - * Obtain config handler. - * - * @param {boolean} success - Equals to true if - * config has been obtained w/o errors. - * @param {Object} error - Error object if there is error occured - * while fetching config. - * @returns {void} - */ - function obtainConfigHandler(success, error) { - if (success) { - const now = window.performance.now(); - - APP.connectionTimes['configuration.fetched'] = now; - logger.log('(TIME) configuration fetched:\t', now); - init(); - } else { - // Show obtain config error, - // pass the error object for report - APP.UI.messageHandler.openReportDialog( - null, 'dialog.connectError', error); - } - } - - obtainConfigAndInit(); + this.props.dispatch(obtainConfigAndInit()); APP.UI.start(); // XXX Temporary solution until we add React translation. diff --git a/react/features/conference/functions.js b/react/features/conference/functions.js new file mode 100644 index 000000000..6018773d9 --- /dev/null +++ b/react/features/conference/functions.js @@ -0,0 +1,42 @@ +/* global APP */ +import HttpConfigFetch from '../../../modules/config/HttpConfigFetch'; + +/** + * Promise wrapper on obtain config method. + * When HttpConfigFetch will be moved to React app + * it's better to use load config instead. + * + * @param {string} location - URL of the domain. + * @param {string} room - Room name. + * @returns {Promise} + */ +export function obtainConfig(location, room) { + return new Promise((resolve, reject) => { + HttpConfigFetch.obtainConfig(location, room, (success, error) => { + if (success) { + resolve(); + } else { + reject(error); + } + }); + }); +} + +/** + * If JWT token data it will be used for local user settings. + * + * @returns {void} + */ +export function setTokenData() { + const localUser = APP.tokenData.caller; + + if (localUser) { + const email = localUser.getEmail(); + const avatarUrl = localUser.getAvatarUrl(); + const name = localUser.getName(); + + APP.settings.setEmail((email || '').trim(), true); + APP.settings.setAvatarUrl((avatarUrl || '').trim()); + APP.settings.setDisplayName((name || '').trim(), true); + } +} From 58b5e1748f1f4164b7cd75ee9e88f9ccf09ef6a1 Mon Sep 17 00:00:00 2001 From: Ilya Daynatovich Date: Fri, 16 Dec 2016 15:42:40 +0200 Subject: [PATCH 06/11] editions after rebase --- modules/UI/welcome_page/WelcomePage.js | 30 ------------- .../welcome/components/WelcomePage.web.js | 42 ------------------- 2 files changed, 72 deletions(-) delete mode 100644 modules/UI/welcome_page/WelcomePage.js diff --git a/modules/UI/welcome_page/WelcomePage.js b/modules/UI/welcome_page/WelcomePage.js deleted file mode 100644 index 6777ff23a..000000000 --- a/modules/UI/welcome_page/WelcomePage.js +++ /dev/null @@ -1,30 +0,0 @@ -/* global $ */ - -function enterRoom() { - const $enterRoomField = $("#enter_room_field"); - - var val = $enterRoomField.val(); - if(!val) { - val = $enterRoomField.data("room-name"); - } - if (val) { - window.location.pathname = "/" + val; - } -} - -function setupWelcomePage() { - // XXX: We left only going to conference page here because transitions via - // React Router isn't implemented yet. - - $("#enter_room_button").click(function() { - enterRoom(); - }); - - $("#enter_room_field").keydown(function (event) { - if (event.keyCode === 13 /* enter */) { - enterRoom(); - } - }); -} - -module.exports = setupWelcomePage; diff --git a/react/features/welcome/components/WelcomePage.web.js b/react/features/welcome/components/WelcomePage.web.js index 9466bf765..404d21802 100644 --- a/react/features/welcome/components/WelcomePage.web.js +++ b/react/features/welcome/components/WelcomePage.web.js @@ -3,8 +3,6 @@ import React from 'react'; import { connect } from 'react-redux'; -import { Conference } from '../../conference'; - import { AbstractWelcomePage, mapStateToProps } from './AbstractWelcomePage'; /** @@ -128,19 +126,6 @@ class WelcomePage extends AbstractWelcomePage { }); } - /** - * Overrides the super in order to prevent the dispatching of the Redux - * action SET_ROOM. - * - * @override - * @protected - * @returns {null} - */ - _onJoin() { - // Don't call the super implementation and thus prevent the dispatching - // of the Redux action SET_ROOM. - } - /** * Handles 'keydown' event to initiate joining the room when the * 'Enter/Return' button is pressed. @@ -365,33 +350,6 @@ class WelcomePage extends AbstractWelcomePage { this._updateRoomname(); } - /** - * Event handler for changing room name input from web. - * - * @inheritdoc - * @override - * @protected - */ - _onRoomChange() { - super._onRoomChange(this.roomNameInput.value); - } - - /** - * Handles 'keydown' event and initiate joining the room if 'return' button - * was pressed. - * - * @param {Event} event - Key down event object. - * @returns {void} - * @private - */ - _onKeyDown(event) { - const RETURN_BUTTON_CODE = 13; - - if (event.keyCode === RETURN_BUTTON_CODE) { - this._onJoin(); - } - } - /** * Renders the main part of this WelcomePage. * From ed1aa700d088ce72ea25eef1c42c31950250975c Mon Sep 17 00:00:00 2001 From: Ilya Daynatovich Date: Fri, 16 Dec 2016 16:19:31 +0200 Subject: [PATCH 07/11] remove asterisk symbol from jsdoc --- react/features/welcome/components/WelcomePage.web.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react/features/welcome/components/WelcomePage.web.js b/react/features/welcome/components/WelcomePage.web.js index 404d21802..40cadf451 100644 --- a/react/features/welcome/components/WelcomePage.web.js +++ b/react/features/welcome/components/WelcomePage.web.js @@ -345,7 +345,7 @@ class WelcomePage extends AbstractWelcomePage { * * @private * @returns {void} - **/ + */ _onUpdateRoomname() { this._updateRoomname(); } From ad9bdf4dd2258b6dba5bff180bc035487502cd68 Mon Sep 17 00:00:00 2001 From: Ilya Daynatovich Date: Wed, 4 Jan 2017 19:01:25 +0200 Subject: [PATCH 08/11] Fix url params stripping --- .../conference/components/Conference.web.js | 2 - react/features/conference/functions.js | 68 ++++++++++++++++++- react/features/conference/route.js | 8 ++- 3 files changed, 74 insertions(+), 4 deletions(-) diff --git a/react/features/conference/components/Conference.web.js b/react/features/conference/components/Conference.web.js index c2470999f..ab8b5a9fe 100644 --- a/react/features/conference/components/Conference.web.js +++ b/react/features/conference/components/Conference.web.js @@ -6,7 +6,6 @@ import { connect, disconnect } from '../../base/connection'; -import { obtainConfigAndInit } from '../actions'; /** * For legacy reasons, inline style for display none. @@ -28,7 +27,6 @@ class Conference extends Component { * @inheritdoc */ componentDidMount() { - this.props.dispatch(obtainConfigAndInit()); APP.UI.start(); // XXX Temporary solution until we add React translation. diff --git a/react/features/conference/functions.js b/react/features/conference/functions.js index 6018773d9..97e5f010c 100644 --- a/react/features/conference/functions.js +++ b/react/features/conference/functions.js @@ -1,5 +1,71 @@ -/* global APP */ +/* global APP, config */ import HttpConfigFetch from '../../../modules/config/HttpConfigFetch'; +import ConferenceUrl from '../../../modules/URL/ConferenceUrl'; +import BoshAddressChoice from '../../../modules/config/BoshAddressChoice'; +const logger = require('jitsi-meet-logger').getLogger(__filename); + +/** + * If we have an HTTP endpoint for getting config.json configured + * we're going to read it and override properties from config.js and + * interfaceConfig.js. If there is no endpoint we'll just + * continue with initialization. + * Keep in mind that if the endpoint has been configured and we fail + * to obtain the config for any reason then the conference won't + * start and error message will be displayed to the user. + * + * @returns {Function} + */ +export function obtainConfigAndInit() { + // Skip initialization if conference is already initialized + if (!APP.ConferenceUrl) { + const room = APP.conference.roomName; + + if (config.configLocation) { + const location = config.configLocation; + + obtainConfig(location, room) + .then(_obtainConfigHandler) + .then(_initConference) + .catch(err => { + // Show obtain config error, + // pass the error object for report + APP.UI.messageHandler.openReportDialog( + null, 'dialog.connectError', err); + }); + } else { + BoshAddressChoice.chooseAddress(config, room); + _initConference(); + } + } +} + +/** + * Obtain config handler. + * + * @returns {Promise} + * @private + */ +function _obtainConfigHandler() { + const now = window.performance.now(); + + APP.connectionTimes['configuration.fetched'] = now; + logger.log('(TIME) configuration fetched:\t', now); + + return Promise.resolve(); +} + +/** + * Initialization of the app. + * + * @returns {void} + * @private + */ +function _initConference() { + setTokenData(); + + // Initialize the conference URL handler + APP.ConferenceUrl = new ConferenceUrl(window.location); +} /** * Promise wrapper on obtain config method. diff --git a/react/features/conference/route.js b/react/features/conference/route.js index 9efac31b9..35250efa6 100644 --- a/react/features/conference/route.js +++ b/react/features/conference/route.js @@ -1,11 +1,17 @@ import { RouteRegistry } from '../base/navigator'; import { Conference } from './components'; +import { obtainConfigAndInit } from './functions'; /** * Register route for Conference (page). */ RouteRegistry.register({ component: Conference, - path: '/:room' + path: '/:room', + onEnter: () => { + // XXX: If config or jwt are set by hash or query parameters + // Getting raw URL before stripping it. + obtainConfigAndInit(); + } }); From 2f01746c55d200ecde119781eab65320f06dc62a Mon Sep 17 00:00:00 2001 From: Lyubomir Marinov Date: Tue, 10 Jan 2017 13:06:18 -0600 Subject: [PATCH 09/11] Remove duplication, simplify, comply with coding style --- app.js | 10 +- .../app/{actions.web.js => actions.js} | 23 ++- react/features/app/actions.native.js | 158 ------------------ react/features/app/components/AbstractApp.js | 2 +- react/features/app/components/App.web.js | 78 +++++---- .../{actions.native.js => actions.js} | 8 + .../base/lib-jitsi-meet/actions.web.js | 61 ------- .../{functions.native.js => functions.js} | 9 + .../base/lib-jitsi-meet/functions.web.js | 10 -- .../conference/components/Conference.web.js | 2 +- react/features/conference/route.js | 4 +- .../welcome/components/WelcomePage.web.js | 12 +- react/features/welcome/route.js | 4 +- 13 files changed, 77 insertions(+), 304 deletions(-) rename react/features/app/{actions.web.js => actions.js} (99%) delete mode 100644 react/features/app/actions.native.js rename react/features/base/lib-jitsi-meet/{actions.native.js => actions.js} (88%) delete mode 100644 react/features/base/lib-jitsi-meet/actions.web.js rename react/features/base/lib-jitsi-meet/{functions.native.js => functions.js} (72%) delete mode 100644 react/features/base/lib-jitsi-meet/functions.web.js diff --git a/app.js b/app.js index 684d04ce1..044c8c0fc 100644 --- a/app.js +++ b/app.js @@ -63,11 +63,11 @@ const APP = { }; // TODO The execution of the mobile app starts from react/index.native.js. -// Similarly, the execution of the Web app should start from -// react/index.web.js for the sake of consistency and ease of understanding. -// Temporarily though because we are at the beginning of introducing React -// into the Web app, allow the execution of the Web app to start from app.js -// in order to reduce the complexity of the beginning step. +// Similarly, the execution of the Web app should start from react/index.web.js +// for the sake of consistency and ease of understanding. Temporarily though +// because we are at the beginning of introducing React into the Web app, allow +// the execution of the Web app to start from app.js in order to reduce the +// complexity of the beginning step. require('./react'); module.exports = APP; diff --git a/react/features/app/actions.web.js b/react/features/app/actions.js similarity index 99% rename from react/features/app/actions.web.js rename to react/features/app/actions.js index 2ec370379..96cd27549 100644 --- a/react/features/app/actions.web.js +++ b/react/features/app/actions.js @@ -19,6 +19,16 @@ import { } from './functions'; import './reducer'; +/** + * Temporary solution. Should dispatch actions related to + * initial settings of the app like setting log levels, + * reading the config parameters from query string etc. + * + * @returns {Function} + */ +export function appInit() { + return () => init(); +} /** * Triggers an in-app navigation to a different route. Allows navigation to be @@ -91,19 +101,6 @@ export function appNavigate(urlOrRoom) { }; } -/** - * Temporary solution. Should dispatch actions related to - * initial settings of the app like setting log levels, - * reading the config parameters from query string etc. - * - * @returns {Function} - */ -export function appInit() { - return () => { - init(); - }; -} - /** * Signals that a specific App will mount (in the terms of React). * diff --git a/react/features/app/actions.native.js b/react/features/app/actions.native.js deleted file mode 100644 index ef0566ddd..000000000 --- a/react/features/app/actions.native.js +++ /dev/null @@ -1,158 +0,0 @@ -import { setRoom } from '../base/conference'; -import { - getDomain, - setDomain -} from '../base/connection'; -import { - loadConfig, - setConfig -} from '../base/lib-jitsi-meet'; - -import { - APP_WILL_MOUNT, - APP_WILL_UNMOUNT -} from './actionTypes'; -import { - _getRoomAndDomainFromUrlString, - _getRouteToRender -} from './functions'; -import './reducer'; - -/** - * Triggers an in-app navigation to a different route. Allows navigation to be - * abstracted between the mobile and web versions. - * - * @param {(string|undefined)} urlOrRoom - The URL or room name to which to - * navigate. - * @returns {Function} - */ -export function appNavigate(urlOrRoom) { - return (dispatch, getState) => { - const oldDomain = getDomain(getState()); - - const { domain, room } = _getRoomAndDomainFromUrlString(urlOrRoom); - - // TODO Kostiantyn Tsaregradskyi: We should probably detect if user is - // currently in a conference and ask her if she wants to close the - // current conference and start a new one with the new room name or - // domain. - - if (typeof domain === 'undefined' || oldDomain === domain) { - // If both domain and room vars became undefined, that means we're - // actually dealing with just room name and not with URL. - dispatch( - _setRoomAndNavigate( - typeof room === 'undefined' && typeof domain === 'undefined' - ? urlOrRoom - : room)); - } else if (oldDomain !== domain) { - // Update domain without waiting for config to be loaded to prevent - // race conditions when we will start to load config multiple times. - dispatch(setDomain(domain)); - - // If domain has changed, we need to load the config of the new - // domain and set it, and only after that we can navigate to - // different route. - loadConfig(`https://${domain}`) - .then( - config => configLoaded(/* err */ undefined, config), - err => configLoaded(err, /* config */ undefined)); - } - - /** - * Notifies that an attempt to load the config(uration) of domain has - * completed. - * - * @param {string|undefined} err - If the loading has failed, the error - * detailing the cause of the failure. - * @param {Object|undefined} config - If the loading has succeeded, the - * loaded config(uration). - * @returns {void} - */ - function configLoaded(err, config) { - if (err) { - // XXX The failure could be, for example, because of a - // certificate-related error. In which case the connection will - // fail later in Strophe anyway even if we use the default - // config here. - - // The function loadConfig will log the err. - return; - } - - // We set room name only here to prevent race conditions on app - // start to not make app re-render conference page for two times. - dispatch(setRoom(room)); - dispatch(setConfig(config)); - _navigate(getState()); - } - }; -} - -/** - * Signals that a specific App will mount (in the terms of React). - * - * @param {App} app - The App which will mount. - * @returns {{ - * type: APP_WILL_MOUNT, - * app: App - * }} - */ -export function appWillMount(app) { - return { - type: APP_WILL_MOUNT, - app - }; -} - -/** - * Signals that a specific App will unmount (in the terms of React). - * - * @param {App} app - The App which will unmount. - * @returns {{ - * type: APP_WILL_UNMOUNT, - * app: App - * }} - */ -export function appWillUnmount(app) { - return { - type: APP_WILL_UNMOUNT, - app - }; -} - -/** - * Navigates to route corresponding to current room name. - * - * @param {Object} state - Redux state. - * @private - * @returns {void} - */ -function _navigate(state) { - const app = state['features/app'].app; - const routeToRender = _getRouteToRender(state); - - app._navigate(routeToRender); -} - -/** - * Sets room and navigates to new route if needed. - * - * @param {string} newRoom - New room name. - * @private - * @returns {Function} - */ -function _setRoomAndNavigate(newRoom) { - return (dispatch, getState) => { - const oldRoom = getState()['features/base/conference'].room; - - dispatch(setRoom(newRoom)); - - const state = getState(); - const room = state['features/base/conference'].room; - - if (room !== oldRoom) { - _navigate(state); - } - }; -} diff --git a/react/features/app/components/AbstractApp.js b/react/features/app/components/AbstractApp.js index 7d32adec5..4d766a2a2 100644 --- a/react/features/app/components/AbstractApp.js +++ b/react/features/app/components/AbstractApp.js @@ -42,7 +42,7 @@ export class AbstractApp extends Component { * The URL, if any, with which the app was launched. */ url: React.PropTypes.string - }; + } /** * Init lib-jitsi-meet and create local participant when component is going diff --git a/react/features/app/components/App.web.js b/react/features/app/components/App.web.js index 252812444..3d8de06a1 100644 --- a/react/features/app/components/App.web.js +++ b/react/features/app/components/App.web.js @@ -1,20 +1,19 @@ /* global $ */ import React from 'react'; import { Provider } from 'react-redux'; -import { compose } from 'redux'; import { browserHistory, Route, Router } from 'react-router'; import { push, syncHistoryWithStore } from 'react-router-redux'; +import { compose } from 'redux'; import { getDomain } from '../../base/connection'; import { RouteRegistry } from '../../base/navigator'; -import { AbstractApp } from './AbstractApp'; import { appInit } from '../actions'; - +import { AbstractApp } from './AbstractApp'; /** * Root application component. @@ -27,7 +26,7 @@ export class App extends AbstractApp { * * @static */ - static propTypes = AbstractApp.propTypes; + static propTypes = AbstractApp.propTypes /** * Initializes a new App instance. @@ -46,10 +45,10 @@ export class App extends AbstractApp { this.history = syncHistoryWithStore(browserHistory, props.store); // Bind event handlers so they are only bound once for every instance. - this._onRouteEnter = this._onRouteEnter.bind(this); - this._routerCreateElement = this._routerCreateElement.bind(this); this._getRoute = this._getRoute.bind(this); this._getRoutes = this._getRoutes.bind(this); + this._onRouteEnter = this._onRouteEnter.bind(this); + this._routerCreateElement = this._routerCreateElement.bind(this); } /** @@ -59,6 +58,7 @@ export class App extends AbstractApp { */ componentWillMount(...args) { super.componentWillMount(...args); + this.props.store.dispatch(appInit()); } @@ -69,7 +69,6 @@ export class App extends AbstractApp { * @returns {ReactElement} */ render() { - return ( + ); + } + + /** + * Returns routes for application. + * + * @returns {Array} + * @private + */ + _getRoutes() { + const routes = RouteRegistry.getRoutes(); + + return routes.map(this._getRoute); + } + /** * Navigates to a specific Route (via platform-specific means). * @@ -102,38 +133,6 @@ export class App extends AbstractApp { return store.dispatch(push(path)); } - /** - * Returns routes for application. - * - * @returns {Array} - * @private - */ - _getRoutes() { - const routes = RouteRegistry.getRoutes(); - - return routes.map(this._getRoute); - } - - /** - * Method returns route for React Router. - * - * @param {Object} route - Object that describes route. - * @returns {ReactElement} - * @private - */ - _getRoute(route) { - const onEnter = route.onEnter || $.noop; - const handler = compose(this._onRouteEnter, onEnter); - - return ( - - ); - } - /** * Invoked by react-router to notify this App that a Route is about to be * rendered. @@ -142,7 +141,6 @@ export class App extends AbstractApp { * @returns {void} */ _onRouteEnter() { - // XXX The following is mandatory. Otherwise, moving back & forward // through the browser's history could leave this App on the Conference // page without a room name. diff --git a/react/features/base/lib-jitsi-meet/actions.native.js b/react/features/base/lib-jitsi-meet/actions.js similarity index 88% rename from react/features/base/lib-jitsi-meet/actions.native.js rename to react/features/base/lib-jitsi-meet/actions.js index aa97ae36d..ddb69594d 100644 --- a/react/features/base/lib-jitsi-meet/actions.native.js +++ b/react/features/base/lib-jitsi-meet/actions.js @@ -1,3 +1,5 @@ +import React from 'react'; + import JitsiMeetJS from './'; import { LIB_DISPOSED, @@ -40,6 +42,12 @@ export function initLib() { throw new Error('Cannot initialize lib-jitsi-meet without config'); } + if (!React.View) { + // XXX Temporarily until conference.js is moved to the React app we + // shouldn't use JitsiMeetJS from the React app. + return Promise.resolve(); + } + return JitsiMeetJS.init(config) .then(() => dispatch({ type: LIB_INITIALIZED })) .catch(error => { diff --git a/react/features/base/lib-jitsi-meet/actions.web.js b/react/features/base/lib-jitsi-meet/actions.web.js deleted file mode 100644 index 02628ea1a..000000000 --- a/react/features/base/lib-jitsi-meet/actions.web.js +++ /dev/null @@ -1,61 +0,0 @@ -import { - LIB_DISPOSED, - SET_CONFIG -} from './actionTypes'; -import './middleware'; -import './reducer'; - -/** - * Disposes lib-jitsi-meet. - * - * @returns {Function} - */ -export function disposeLib() { - // XXX We're wrapping it with Promise, because: - // a) to be better aligned with initLib() method, which is async. - // b) as currently there is no implementation for it in lib-jitsi-meet, and - // there is a big chance it will be async. - // TODO Currently, lib-jitsi-meet doesn't have any functionality to - // dispose itself. - return dispatch => { - dispatch({ type: LIB_DISPOSED }); - - return Promise.resolve(); - }; -} - -/** - * Initializes lib-jitsi-meet with passed configuration. - * - * @returns {Function} - */ -export function initLib() { - return (dispatch, getState) => { - const config = getState()['features/base/lib-jitsi-meet'].config; - - if (!config) { - throw new Error('Cannot initialize lib-jitsi-meet without config'); - } - - // XXX Temporary solution. Until conference.js hasn't been moved - // to the react app we shouldn't use JitsiMeetJS from react app. - return Promise.resolve(); - }; -} - -/** - * Sets config. - * - * @param {Object} config - Config object accepted by JitsiMeetJS#init() - * method. - * @returns {{ - * type: SET_CONFIG, - * config: Object - * }} - */ -export function setConfig(config) { - return { - type: SET_CONFIG, - config - }; -} diff --git a/react/features/base/lib-jitsi-meet/functions.native.js b/react/features/base/lib-jitsi-meet/functions.js similarity index 72% rename from react/features/base/lib-jitsi-meet/functions.native.js rename to react/features/base/lib-jitsi-meet/functions.js index 27438b603..543d64922 100644 --- a/react/features/base/lib-jitsi-meet/functions.native.js +++ b/react/features/base/lib-jitsi-meet/functions.js @@ -1,3 +1,5 @@ +import React from 'react'; + import { loadScript } from '../../base/util'; /** @@ -8,6 +10,13 @@ import { loadScript } from '../../base/util'; * @returns {Promise} */ export function loadConfig(host, path = '/config.js') { + if (!React.View) { + // Returns config.js file from global scope. We can't use the version + // that's being used for the React Native app because the old/current + // Web app uses config from the global scope. + return Promise.resolve(window.config); + } + return loadScript(new URL(path, host).toString()) .then(() => { const config = window.config; diff --git a/react/features/base/lib-jitsi-meet/functions.web.js b/react/features/base/lib-jitsi-meet/functions.web.js deleted file mode 100644 index 5f0206fa6..000000000 --- a/react/features/base/lib-jitsi-meet/functions.web.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Returns config.js file from global scope. - * We can't use version that's being used for native app - * because the old app uses config from global scope. - * - * @returns {Promise} - */ -export function loadConfig() { - return Promise.resolve(window.config); -} diff --git a/react/features/conference/components/Conference.web.js b/react/features/conference/components/Conference.web.js index ab8b5a9fe..f930e342c 100644 --- a/react/features/conference/components/Conference.web.js +++ b/react/features/conference/components/Conference.web.js @@ -1,4 +1,4 @@ -/* global APP, $, interfaceConfig */ +/* global $, APP, interfaceConfig */ import React, { Component } from 'react'; import { connect as reactReduxConnect } from 'react-redux'; diff --git a/react/features/conference/route.js b/react/features/conference/route.js index 35250efa6..86555aa88 100644 --- a/react/features/conference/route.js +++ b/react/features/conference/route.js @@ -8,10 +8,10 @@ import { obtainConfigAndInit } from './functions'; */ RouteRegistry.register({ component: Conference, - path: '/:room', onEnter: () => { // XXX: If config or jwt are set by hash or query parameters // Getting raw URL before stripping it. obtainConfigAndInit(); - } + }, + path: '/:room' }); diff --git a/react/features/welcome/components/WelcomePage.web.js b/react/features/welcome/components/WelcomePage.web.js index 40cadf451..9b287c8db 100644 --- a/react/features/welcome/components/WelcomePage.web.js +++ b/react/features/welcome/components/WelcomePage.web.js @@ -1,4 +1,4 @@ -/* global interfaceConfig, APP, $ */ +/* global $, APP, interfaceConfig */ import React from 'react'; import { connect } from 'react-redux'; @@ -340,16 +340,6 @@ class WelcomePage extends AbstractWelcomePage { return null; } - /** - * Handles updating roomname. - * - * @private - * @returns {void} - */ - _onUpdateRoomname() { - this._updateRoomname(); - } - /** * Renders the main part of this WelcomePage. * diff --git a/react/features/welcome/route.js b/react/features/welcome/route.js index ea7f6e5a6..6556fc144 100644 --- a/react/features/welcome/route.js +++ b/react/features/welcome/route.js @@ -26,6 +26,6 @@ function onEnter(nextState, replace) { */ RouteRegistry.register({ component: WelcomePage, - path: '/', - onEnter + onEnter, + path: '/' }); From 0912dbf130ed500b5a5ac44623bee60fdc7fc3d7 Mon Sep 17 00:00:00 2001 From: Lyubomir Marinov Date: Tue, 10 Jan 2017 15:55:31 -0600 Subject: [PATCH 10/11] Remove duplication, simplify, comply with coding style --- modules/UI/UI.js | 1 + react/features/app/actions.js | 21 +- react/features/app/components/App.web.js | 88 ++++----- react/features/app/functions.native.js | 4 +- react/features/app/functions.web.js | 182 +++--------------- react/features/base/connection/actions.web.js | 16 +- react/features/base/lib-jitsi-meet/actions.js | 8 +- .../features/base/lib-jitsi-meet/functions.js | 10 +- react/features/conference/actions.js | 67 ------- .../conference/components/Conference.web.js | 25 ++- react/features/conference/functions.js | 108 ----------- react/features/conference/route.js | 115 ++++++++++- react/features/welcome/route.js | 37 ++-- react/index.native.js | 4 +- react/index.web.js | 17 +- 15 files changed, 245 insertions(+), 458 deletions(-) delete mode 100644 react/features/conference/actions.js delete mode 100644 react/features/conference/functions.js diff --git a/modules/UI/UI.js b/modules/UI/UI.js index b80a215bd..75752268f 100644 --- a/modules/UI/UI.js +++ b/modules/UI/UI.js @@ -1,4 +1,5 @@ /* global APP, JitsiMeetJS, $, config, interfaceConfig, toastr */ + const logger = require("jitsi-meet-logger").getLogger(__filename); var UI = {}; diff --git a/react/features/app/actions.js b/react/features/app/actions.js index 96cd27549..efbbe6cad 100644 --- a/react/features/app/actions.js +++ b/react/features/app/actions.js @@ -1,17 +1,8 @@ import { setRoom } from '../base/conference'; -import { - getDomain, - setDomain -} from '../base/connection'; -import { - loadConfig, - setConfig -} from '../base/lib-jitsi-meet'; +import { getDomain, setDomain } from '../base/connection'; +import { loadConfig, setConfig } from '../base/lib-jitsi-meet'; -import { - APP_WILL_MOUNT, - APP_WILL_UNMOUNT -} from './actionTypes'; +import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from './actionTypes'; import { _getRoomAndDomainFromUrlString, _getRouteToRender, @@ -20,9 +11,9 @@ import { import './reducer'; /** - * Temporary solution. Should dispatch actions related to - * initial settings of the app like setting log levels, - * reading the config parameters from query string etc. + * Temporary solution. Should dispatch actions related to initial settings of + * the app like setting log levels, reading the config parameters from query + * string etc. * * @returns {Function} */ diff --git a/react/features/app/components/App.web.js b/react/features/app/components/App.web.js index 3d8de06a1..942c4884a 100644 --- a/react/features/app/components/App.web.js +++ b/react/features/app/components/App.web.js @@ -1,13 +1,7 @@ -/* global $ */ import React from 'react'; import { Provider } from 'react-redux'; -import { - browserHistory, - Route, - Router -} from 'react-router'; +import { browserHistory, Route, Router } from 'react-router'; import { push, syncHistoryWithStore } from 'react-router-redux'; -import { compose } from 'redux'; import { getDomain } from '../../base/connection'; import { RouteRegistry } from '../../base/navigator'; @@ -45,9 +39,6 @@ export class App extends AbstractApp { this.history = syncHistoryWithStore(browserHistory, props.store); // Bind event handlers so they are only bound once for every instance. - this._getRoute = this._getRoute.bind(this); - this._getRoutes = this._getRoutes.bind(this); - this._onRouteEnter = this._onRouteEnter.bind(this); this._routerCreateElement = this._routerCreateElement.bind(this); } @@ -74,44 +65,14 @@ export class App extends AbstractApp { - { this._getRoutes() } + { + this._renderRoutes() + } ); } - /** - * Method returns route for React Router. - * - * @param {Object} route - Object that describes route. - * @returns {ReactElement} - * @private - */ - _getRoute(route) { - const onEnter = route.onEnter || $.noop; - const handler = compose(this._onRouteEnter, onEnter); - - return ( - - ); - } - - /** - * Returns routes for application. - * - * @returns {Array} - * @private - */ - _getRoutes() { - const routes = RouteRegistry.getRoutes(); - - return routes.map(this._getRoute); - } - /** * Navigates to a specific Route (via platform-specific means). * @@ -137,10 +98,18 @@ export class App extends AbstractApp { * Invoked by react-router to notify this App that a Route is about to be * rendered. * + * @param {Route} route - The Route that is about to be rendered. * @private * @returns {void} */ - _onRouteEnter() { + _onRouteEnter(route, ...args) { + // Notify the route that it is about to be entered. + const onEnter = route.onEnter; + + if (typeof onEnter === 'function') { + onEnter(...args); + } + // XXX The following is mandatory. Otherwise, moving back & forward // through the browser's history could leave this App on the Conference // page without a room name. @@ -159,6 +128,37 @@ export class App extends AbstractApp { this._openURL(url); } + /** + * Renders a specific Route (for the purposes of the Router of this App). + * + * @param {Object} route - The Route to render. + * @returns {ReactElement} + * @private + */ + _renderRoute(route) { + const onEnter = (...args) => { + this._onRouteEnter(route, ...args); + }; + + return ( + + ); + } + + /** + * Renders the Routes of the Router of this App. + * + * @returns {Array.} + * @private + */ + _renderRoutes() { + return RouteRegistry.getRoutes().map(this._renderRoute, this); + } + /** * Create a ReactElement from the specified component and props on behalf of * the associated Router. diff --git a/react/features/app/functions.native.js b/react/features/app/functions.native.js index 1c5a6f230..fb67dd1e1 100644 --- a/react/features/app/functions.native.js +++ b/react/features/app/functions.native.js @@ -64,8 +64,8 @@ export function _getRoomAndDomainFromUrlString(url) { url = match[1] /* URL protocol */ - + '://enso.hipchat.me/' - + url.substring(regex.lastIndex); + + '://enso.hipchat.me/' + + url.substring(regex.lastIndex); /* eslint-enable no-param-reassign, prefer-template */ } diff --git a/react/features/app/functions.web.js b/react/features/app/functions.web.js index 5f541c74a..ec4bbee3a 100644 --- a/react/features/app/functions.web.js +++ b/react/features/app/functions.web.js @@ -1,143 +1,24 @@ /* global APP, JitsiMeetJS, loggingConfig */ -import { isRoomValid } from '../base/conference'; -import { RouteRegistry } from '../base/navigator'; -import { Conference } from '../conference'; -import { WelcomePage } from '../welcome'; - -import getTokenData from '../../../modules/tokendata/TokenData'; -import settings from '../../../modules/settings/Settings'; import URLProcessor from '../../../modules/config/URLProcessor'; +import KeyboardShortcut + from '../../../modules/keyboardshortcut/keyboardshortcut'; +import settings from '../../../modules/settings/Settings'; +import getTokenData from '../../../modules/tokendata/TokenData'; import JitsiMeetLogStorage from '../../../modules/util/JitsiMeetLogStorage'; -// eslint-disable-next-line max-len -import KeyboardShortcut from '../../../modules/keyboardshortcut/keyboardshortcut'; - const Logger = require('jitsi-meet-logger'); -const LogCollector = Logger.LogCollector; +export * from './functions.native'; /** - * Gets room name and domain from URL object. - * - * @param {URL} url - URL object. - * @private - * @returns {{ - * domain: (string|undefined), - * room: (string|undefined) - * }} - */ -function _getRoomAndDomainFromUrlObject(url) { - let domain; - let room; - - if (url) { - domain = url.hostname; - room = url.pathname.substr(1); - - // Convert empty string to undefined to simplify checks. - if (room === '') { - room = undefined; - } - if (domain === '') { - domain = undefined; - } - } - - return { - domain, - room - }; -} - -/** - * Gets conference room name and connection domain from URL. - * - * @param {(string|undefined)} url - URL. - * @returns {{ - * domain: (string|undefined), - * room: (string|undefined) - * }} - */ -export function _getRoomAndDomainFromUrlString(url) { - // Rewrite the specified URL in order to handle special cases such as - // hipchat.com and enso.me which do not follow the common pattern of most - // Jitsi Meet deployments. - if (typeof url === 'string') { - // hipchat.com - let regex = /^(https?):\/\/hipchat.com\/video\/call\//gi; - let match = regex.exec(url); - - if (!match) { - // enso.me - regex = /^(https?):\/\/enso\.me\/(?:call|meeting)\//gi; - match = regex.exec(url); - } - if (match && match.length > 1) { - /* eslint-disable no-param-reassign, prefer-template */ - - url - = match[1] /* URL protocol */ - + '://enso.hipchat.me/' - + url.substring(regex.lastIndex); - - /* eslint-enable no-param-reassign, prefer-template */ - } - } - - return _getRoomAndDomainFromUrlObject(_urlStringToObject(url)); -} - -/** - * Determines which route is to be rendered in order to depict a specific Redux - * store. - * - * @param {(Object|Function)} stateOrGetState - Redux state or Regux getState() - * method. - * @returns {Route} - */ -export function _getRouteToRender(stateOrGetState) { - const state - = typeof stateOrGetState === 'function' - ? stateOrGetState() - : stateOrGetState; - const room = state['features/base/conference'].room; - const component = isRoomValid(room) ? Conference : WelcomePage; - - return RouteRegistry.getRouteByComponent(component); -} - -/** - * Parses a string into a URL (object). - * - * @param {(string|undefined)} url - The URL to parse. - * @private - * @returns {URL} - */ -function _urlStringToObject(url) { - let urlObj; - - if (url) { - try { - urlObj = new URL(url); - } catch (ex) { - // The return value will signal the failure & the logged - // exception will provide the details to the developers. - console.log(`${url} seems to be not a valid URL, but it's OK`, ex); - } - } - - return urlObj; -} - -/** - * Temporary solution. Later we'll get rid of global APP - * and set its properties in redux store. + * Temporary solution. Later we'll get rid of global APP and set its properties + * in redux store. * * @returns {void} */ export function init() { - _setConfigParametersFromUrl(); + URLProcessor.setConfigParametersFromUrl(); _initLogging(); APP.keyboardshortcut = KeyboardShortcut; @@ -147,36 +28,14 @@ export function init() { APP.translation.init(settings.getLanguage()); } -/** - * Initializes logging in the app. - * - * @private - * @returns {void} - */ -function _initLogging() { - // Adjust logging level - configureLoggingLevels(); - - // Create the LogCollector and register it as the global log transport. - // It is done early to capture as much logs as possible. Captured logs - // will be cached, before the JitsiMeetLogStorage gets ready (statistics - // module is initialized). - if (!APP.logCollector && !loggingConfig.disableLogCollector) { - APP.logCollector = new LogCollector(new JitsiMeetLogStorage()); - Logger.addGlobalTransport(APP.logCollector); - JitsiMeetJS.addGlobalLogTransport(APP.logCollector); - } -} - /** * Adjusts the logging levels. * * @private * @returns {void} */ -function configureLoggingLevels() { - // NOTE The library Logger is separated from - // the app loggers, so the levels +function _configureLoggingLevels() { + // NOTE The library Logger is separated from the app loggers, so the levels // have to be set in two places // Set default logging level @@ -186,8 +45,8 @@ function configureLoggingLevels() { Logger.setLogLevel(defaultLogLevel); JitsiMeetJS.setLogLevel(defaultLogLevel); - // NOTE console was used on purpose here to go around the logging - // and always print the default logging level to the console + // NOTE console was used on purpose here to go around the logging and always + // print the default logging level to the console console.info(`Default logging level set to: ${defaultLogLevel}`); // Set log level for each logger @@ -204,11 +63,22 @@ function configureLoggingLevels() { } /** - * Sets config parameters from query string. + * Initializes logging in the app. * * @private * @returns {void} */ -function _setConfigParametersFromUrl() { - URLProcessor.setConfigParametersFromUrl(); +function _initLogging() { + // Adjust logging level + _configureLoggingLevels(); + + // Create the LogCollector and register it as the global log transport. It + // is done early to capture as much logs as possible. Captured logs will be + // cached, before the JitsiMeetLogStorage gets ready (statistics module is + // initialized). + if (!APP.logCollector && !loggingConfig.disableLogCollector) { + APP.logCollector = new Logger.LogCollector(new JitsiMeetLogStorage()); + Logger.addGlobalTransport(APP.logCollector); + JitsiMeetJS.addGlobalLogTransport(APP.logCollector); + } } diff --git a/react/features/base/connection/actions.web.js b/react/features/base/connection/actions.web.js index b81e479e9..e8a65f638 100644 --- a/react/features/base/connection/actions.web.js +++ b/react/features/base/connection/actions.web.js @@ -1,14 +1,12 @@ /* global APP, JitsiMeetJS */ -import { - SET_DOMAIN -} from './actionTypes'; -import './reducer'; import UIEvents from '../../../../service/UI/UIEvents'; -const logger = require('jitsi-meet-logger').getLogger(__filename); -const ConferenceEvents = JitsiMeetJS.events.conference; +import { SET_DOMAIN } from './actionTypes'; +import './reducer'; +const JitsiConferenceEvents = JitsiMeetJS.events.conference; +const logger = require('jitsi-meet-logger').getLogger(__filename); /** * Opens new connection. @@ -42,7 +40,7 @@ export function connect() { // room. It will then restart the media session when someone // eventually join the room which will start the stats again. APP.conference.addConferenceListener( - ConferenceEvents.BEFORE_STATISTICS_DISPOSED, + JitsiConferenceEvents.BEFORE_STATISTICS_DISPOSED, () => { if (APP.logCollector) { APP.logCollector.flush(); @@ -74,8 +72,8 @@ export function connect() { * @returns {Function} */ export function disconnect() { - // XXX For web based version we use conference - // hanging up logic from the old app. + // XXX For web based version we use conference hanging up logic from the old + // app. return () => APP.conference.hangup(); } diff --git a/react/features/base/lib-jitsi-meet/actions.js b/react/features/base/lib-jitsi-meet/actions.js index ddb69594d..ef9bb8105 100644 --- a/react/features/base/lib-jitsi-meet/actions.js +++ b/react/features/base/lib-jitsi-meet/actions.js @@ -1,5 +1,3 @@ -import React from 'react'; - import JitsiMeetJS from './'; import { LIB_DISPOSED, @@ -42,9 +40,9 @@ export function initLib() { throw new Error('Cannot initialize lib-jitsi-meet without config'); } - if (!React.View) { - // XXX Temporarily until conference.js is moved to the React app we - // shouldn't use JitsiMeetJS from the React app. + // XXX Temporarily until conference.js is moved to the React app we + // shouldn't use JitsiMeetJS from the React app. + if (typeof APP !== 'undefined') { return Promise.resolve(); } diff --git a/react/features/base/lib-jitsi-meet/functions.js b/react/features/base/lib-jitsi-meet/functions.js index 543d64922..03ab8910e 100644 --- a/react/features/base/lib-jitsi-meet/functions.js +++ b/react/features/base/lib-jitsi-meet/functions.js @@ -1,5 +1,3 @@ -import React from 'react'; - import { loadScript } from '../../base/util'; /** @@ -10,10 +8,10 @@ import { loadScript } from '../../base/util'; * @returns {Promise} */ export function loadConfig(host, path = '/config.js') { - if (!React.View) { - // Returns config.js file from global scope. We can't use the version - // that's being used for the React Native app because the old/current - // Web app uses config from the global scope. + // Returns config.js file from global scope. We can't use the version that's + // being used for the React Native app because the old/current Web app uses + // config from the global scope. + if (typeof APP !== 'undefined') { return Promise.resolve(window.config); } diff --git a/react/features/conference/actions.js b/react/features/conference/actions.js deleted file mode 100644 index 49160adde..000000000 --- a/react/features/conference/actions.js +++ /dev/null @@ -1,67 +0,0 @@ -/* global APP, config */ -import ConferenceUrl from '../../../modules/URL/ConferenceUrl'; -import BoshAddressChoice from '../../../modules/config/BoshAddressChoice'; -import { obtainConfig, setTokenData } from './functions'; -const logger = require('jitsi-meet-logger').getLogger(__filename); - -/** - * If we have an HTTP endpoint for getting config.json configured - * we're going to read it and override properties from config.js and - * interfaceConfig.js. If there is no endpoint we'll just - * continue with initialization. - * Keep in mind that if the endpoint has been configured and we fail - * to obtain the config for any reason then the conference won't - * start and error message will be displayed to the user. - * - * @returns {Function} - */ -export function obtainConfigAndInit() { - return () => { - const room = APP.conference.roomName; - - if (config.configLocation) { - const location = config.configLocation; - - obtainConfig(location, room) - .then(_obtainConfigHandler) - .then(_initConference) - .catch(err => { - // Show obtain config error, - // pass the error object for report - APP.UI.messageHandler.openReportDialog( - null, 'dialog.connectError', err); - }); - } else { - BoshAddressChoice.chooseAddress(config, room); - _initConference(); - } - }; -} - -/** - * Obtain config handler. - * - * @returns {Promise} - * @private - */ -function _obtainConfigHandler() { - const now = window.performance.now(); - - APP.connectionTimes['configuration.fetched'] = now; - logger.log('(TIME) configuration fetched:\t', now); - - return Promise.resolve(); -} - -/** - * Initialization of the app. - * - * @returns {void} - * @private - */ -function _initConference() { - setTokenData(); - - // Initialize the conference URL handler - APP.ConferenceUrl = new ConferenceUrl(window.location); -} diff --git a/react/features/conference/components/Conference.web.js b/react/features/conference/components/Conference.web.js index f930e342c..93bd85606 100644 --- a/react/features/conference/components/Conference.web.js +++ b/react/features/conference/components/Conference.web.js @@ -1,11 +1,9 @@ /* global $, APP, interfaceConfig */ + import React, { Component } from 'react'; import { connect as reactReduxConnect } from 'react-redux'; -import { - connect, - disconnect -} from '../../base/connection'; +import { connect, disconnect } from '../../base/connection'; /** * For legacy reasons, inline style for display none. @@ -19,6 +17,16 @@ const DISPLAY_NONE_STYLE = { * Implements a React Component which renders initial conference layout */ class Conference extends Component { + + /** + * Conference component's property types. + * + * @static + */ + static propTypes = { + dispatch: React.PropTypes.func + } + /** * Until we don't rewrite UI using react components * we use UI.start from old app. Also method translates @@ -45,15 +53,6 @@ class Conference extends Component { this.props.dispatch(disconnect()); } - /** - * Conference component's property types. - * - * @static - */ - static propTypes = { - dispatch: React.PropTypes.func - }; - /** * Initializes Conference component instance. * diff --git a/react/features/conference/functions.js b/react/features/conference/functions.js deleted file mode 100644 index 97e5f010c..000000000 --- a/react/features/conference/functions.js +++ /dev/null @@ -1,108 +0,0 @@ -/* global APP, config */ -import HttpConfigFetch from '../../../modules/config/HttpConfigFetch'; -import ConferenceUrl from '../../../modules/URL/ConferenceUrl'; -import BoshAddressChoice from '../../../modules/config/BoshAddressChoice'; -const logger = require('jitsi-meet-logger').getLogger(__filename); - -/** - * If we have an HTTP endpoint for getting config.json configured - * we're going to read it and override properties from config.js and - * interfaceConfig.js. If there is no endpoint we'll just - * continue with initialization. - * Keep in mind that if the endpoint has been configured and we fail - * to obtain the config for any reason then the conference won't - * start and error message will be displayed to the user. - * - * @returns {Function} - */ -export function obtainConfigAndInit() { - // Skip initialization if conference is already initialized - if (!APP.ConferenceUrl) { - const room = APP.conference.roomName; - - if (config.configLocation) { - const location = config.configLocation; - - obtainConfig(location, room) - .then(_obtainConfigHandler) - .then(_initConference) - .catch(err => { - // Show obtain config error, - // pass the error object for report - APP.UI.messageHandler.openReportDialog( - null, 'dialog.connectError', err); - }); - } else { - BoshAddressChoice.chooseAddress(config, room); - _initConference(); - } - } -} - -/** - * Obtain config handler. - * - * @returns {Promise} - * @private - */ -function _obtainConfigHandler() { - const now = window.performance.now(); - - APP.connectionTimes['configuration.fetched'] = now; - logger.log('(TIME) configuration fetched:\t', now); - - return Promise.resolve(); -} - -/** - * Initialization of the app. - * - * @returns {void} - * @private - */ -function _initConference() { - setTokenData(); - - // Initialize the conference URL handler - APP.ConferenceUrl = new ConferenceUrl(window.location); -} - -/** - * Promise wrapper on obtain config method. - * When HttpConfigFetch will be moved to React app - * it's better to use load config instead. - * - * @param {string} location - URL of the domain. - * @param {string} room - Room name. - * @returns {Promise} - */ -export function obtainConfig(location, room) { - return new Promise((resolve, reject) => { - HttpConfigFetch.obtainConfig(location, room, (success, error) => { - if (success) { - resolve(); - } else { - reject(error); - } - }); - }); -} - -/** - * If JWT token data it will be used for local user settings. - * - * @returns {void} - */ -export function setTokenData() { - const localUser = APP.tokenData.caller; - - if (localUser) { - const email = localUser.getEmail(); - const avatarUrl = localUser.getAvatarUrl(); - const name = localUser.getName(); - - APP.settings.setEmail((email || '').trim(), true); - APP.settings.setAvatarUrl((avatarUrl || '').trim()); - APP.settings.setDisplayName((name || '').trim(), true); - } -} diff --git a/react/features/conference/route.js b/react/features/conference/route.js index 86555aa88..5cfed1311 100644 --- a/react/features/conference/route.js +++ b/react/features/conference/route.js @@ -1,7 +1,14 @@ +/* global APP, config */ + +import BoshAddressChoice from '../../../modules/config/BoshAddressChoice'; +import HttpConfigFetch from '../../../modules/config/HttpConfigFetch'; +import ConferenceUrl from '../../../modules/URL/ConferenceUrl'; + import { RouteRegistry } from '../base/navigator'; import { Conference } from './components'; -import { obtainConfigAndInit } from './functions'; + +const logger = require('jitsi-meet-logger').getLogger(__filename); /** * Register route for Conference (page). @@ -9,9 +16,111 @@ import { obtainConfigAndInit } from './functions'; RouteRegistry.register({ component: Conference, onEnter: () => { - // XXX: If config or jwt are set by hash or query parameters + // XXX If config or jwt are set by hash or query parameters // Getting raw URL before stripping it. - obtainConfigAndInit(); + _obtainConfigAndInit(); }, path: '/:room' }); + +/** + * Initialization of the app. + * + * @private + * @returns {void} + */ +function _initConference() { + _setTokenData(); + + // Initialize the conference URL handler + APP.ConferenceUrl = new ConferenceUrl(window.location); +} + +/** + * Promise wrapper on obtain config method. When HttpConfigFetch will be moved + * to React app it's better to use load config instead. + * + * @param {string} location - URL of the domain. + * @param {string} room - Room name. + * @private + * @returns {Promise} + */ +function _obtainConfig(location, room) { + return new Promise((resolve, reject) => { + HttpConfigFetch.obtainConfig(location, room, (success, error) => { + if (success) { + resolve(); + } else { + reject(error); + } + }); + }); +} + +/** + * If we have an HTTP endpoint for getting config.json configured we're going to + * read it and override properties from config.js and interfaceConfig.js. If + * there is no endpoint we'll just continue with initialization. Keep in mind + * that if the endpoint has been configured and we fail to obtain the config for + * any reason then the conference won't start and error message will be + * displayed to the user. + * + * @private + * @returns {void} + */ +function _obtainConfigAndInit() { + // Skip initialization if conference is initialized already. + if (typeof APP !== 'undefined' && !APP.ConferenceUrl) { + const location = config.configLocation; + const room = APP.conference.roomName; + + if (location) { + _obtainConfig(location, room) + .then(() => { + _obtainConfigHandler(); + _initConference(); + }) + .catch(err => { + // Show obtain config error. + APP.UI.messageHandler.openReportDialog( + null, 'dialog.connectError', err); + }); + } else { + BoshAddressChoice.chooseAddress(config, room); + _initConference(); + } + } +} + +/** + * Obtain config handler. + * + * @private + * @returns {Promise} + */ +function _obtainConfigHandler() { + const now = window.performance.now(); + + APP.connectionTimes['configuration.fetched'] = now; + logger.log('(TIME) configuration fetched:\t', now); +} + +/** + * If JWT token data it will be used for local user settings. + * + * @private + * @returns {void} + */ +function _setTokenData() { + const localUser = APP.tokenData.caller; + + if (localUser) { + const email = localUser.getEmail(); + const avatarUrl = localUser.getAvatarUrl(); + const name = localUser.getName(); + + APP.settings.setEmail((email || '').trim(), true); + APP.settings.setAvatarUrl((avatarUrl || '').trim()); + APP.settings.setDisplayName((name || '').trim(), true); + } +} diff --git a/react/features/welcome/route.js b/react/features/welcome/route.js index 6556fc144..3e0922448 100644 --- a/react/features/welcome/route.js +++ b/react/features/welcome/route.js @@ -1,26 +1,10 @@ /* global APP */ + import { RouteRegistry } from '../base/navigator'; import { generateRoomWithoutSeparator } from '../base/util'; + import { WelcomePage } from './components'; - -/** - * Function that checks if welcome page is enabled and if it isn't - * redirects to randomly created conference. - * - * @param {Object} nextState - Next router state. - * @param {Function} replace - Function to redirect to another path. - * @returns {void} - */ -function onEnter(nextState, replace) { - if (!APP.settings.isWelcomePageEnabled()) { - const generatedRoomname = generateRoomWithoutSeparator(); - const normalizedRoomname = generatedRoomname.toLowerCase(); - - replace(`/${normalizedRoomname}`); - } -} - /** * Register route for WelcomePage. */ @@ -29,3 +13,20 @@ RouteRegistry.register({ onEnter, path: '/' }); + +/** + * If the Welcome page/screen is disabled, generates a (random) room (name) so + * that the Welcome page/screen is skipped and the Conference page/screen is + * presented instead. + * + * @param {Object} nextState - The next Router state. + * @param {Function} replace - The function to redirect to another path. + * @returns {void} + */ +function onEnter(nextState, replace) { + if (typeof APP !== 'undefined' && !APP.settings.isWelcomePageEnabled()) { + const room = generateRoomWithoutSeparator(); + + replace(`/${room}`); + } +} diff --git a/react/index.native.js b/react/index.native.js index 7df47f219..1f983045d 100644 --- a/react/index.native.js +++ b/react/index.native.js @@ -61,7 +61,9 @@ class Root extends Component { // XXX Start with an empty URL if getting the initial URL fails; // otherwise, nothing will be rendered. - this.setState({ url: null }); + if (this.state.url !== null) { + this.setState({ url: null }); + } }); } diff --git a/react/index.web.js b/react/index.web.js index b9090ad2a..4a18ed22e 100644 --- a/react/index.web.js +++ b/react/index.web.js @@ -1,20 +1,15 @@ /* global APP */ + import React from 'react'; import ReactDOM from 'react-dom'; import { browserHistory } from 'react-router'; -import { - routerMiddleware, - routerReducer -} from 'react-router-redux'; +import { routerMiddleware, routerReducer } from 'react-router-redux'; import { compose, createStore } from 'redux'; import Thunk from 'redux-thunk'; import config from './config'; import { App } from './features/app'; -import { - MiddlewareRegistry, - ReducerRegistry -} from './features/base/redux'; +import { MiddlewareRegistry, ReducerRegistry } from './features/base/redux'; const logger = require('jitsi-meet-logger').getLogger(__filename); @@ -49,7 +44,7 @@ if (typeof window === 'object' const store = createStore(reducer, middleware); /** - * Render the app when DOM tree has been loaded. + * Renders the app when the DOM tree has been loaded. */ document.addEventListener('DOMContentLoaded', () => { const now = window.performance.now(); @@ -67,8 +62,8 @@ document.addEventListener('DOMContentLoaded', () => { }); /** - * Stop collecting the logs and disposing the API when - * user closes the page. + * Stops collecting the logs and disposing the API when the user closes the + * page. */ window.addEventListener('beforeunload', () => { // Stop the LogCollector From 1f995cffe9878d43663e7aaa5d1e7913cbc18ca8 Mon Sep 17 00:00:00 2001 From: Lyubomir Marinov Date: Thu, 12 Jan 2017 11:01:12 -0600 Subject: [PATCH 11/11] eslint 3.13.1, react 15.4.1, react-redux 5.0.2 --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 48a1d9be5..e2024d222 100644 --- a/package.json +++ b/package.json @@ -32,13 +32,13 @@ "jws": "*", "lib-jitsi-meet": "jitsi/lib-jitsi-meet", "postis": "^2.2.0", - "react": "15.4.1", - "react-dom": "15.4.1", + "react": "15.4.2", + "react-dom": "15.4.2", "react-native": "0.39.2", "react-native-prompt": "^1.0.0", "react-native-vector-icons": "^3.0.0", "react-native-webrtc": "jitsi/react-native-webrtc", - "react-redux": "^5.0.1", + "react-redux": "^5.0.2", "react-router": "^3.0.0", "react-router-redux": "^4.0.7", "redux": "^3.5.2", @@ -60,7 +60,7 @@ "babel-preset-stage-1": "^6.16.0", "clean-css": "*", "css-loader": "*", - "eslint": "^3.12.2", + "eslint": "^3.13.1", "eslint-plugin-jsdoc": "*", "eslint-plugin-react": "*", "eslint-plugin-react-native": "^2.2.1",