diff --git a/app.js b/app.js index 0d82d480c..5bd759360 100644 --- a/app.js +++ b/app.js @@ -15,15 +15,25 @@ import 'aui-experimental-css'; window.toastr = require("toastr"); -import UI from "./modules/UI/UI"; -import settings from "./modules/settings/Settings"; import conference from './conference'; import API from './modules/API'; - -import translation from "./modules/translation/translation"; +import keyboardshortcut from './modules/keyboardshortcut/keyboardshortcut'; import remoteControl from "./modules/remotecontrol/RemoteControl"; +import settings from "./modules/settings/Settings"; +import translation from "./modules/translation/translation"; +import UI from "./modules/UI/UI"; const APP = { + API, + conference, + + /** + * After the APP has been initialized provides utility methods for dealing + * with the conference room URL(address). + * @type ConferenceUrl + */ + ConferenceUrl: null, + // Used by do_external_connect.js if we receive the attach data after // connect was already executed. status property can be "initialized", // "ready" or "connecting". We are interested in "ready" status only which @@ -34,33 +44,29 @@ const APP = { status: "initialized", handler: null }, + connection: null, + // Used for automated performance tests connectionTimes: { "index.loaded": window.indexLoadedTime }, - UI, - settings, - conference, - translation, + keyboardshortcut, + /** * The log collector which captures JS console logs for this app. * @type {LogCollector} */ logCollector: null, + /** * Indicates if the log collector has been started (it will not be started * if the welcome page is displayed). */ logCollectorStarted : false, - /** - * After the APP has been initialized provides utility methods for dealing - * with the conference room URL(address). - * @type ConferenceUrl - */ - ConferenceUrl : null, - connection: null, - API, - remoteControl + remoteControl, + settings, + translation, + UI }; // TODO The execution of the mobile app starts from react/index.native.js. @@ -69,6 +75,6 @@ const APP = { // 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'); +import './react'; module.exports = APP; diff --git a/modules/API/API.js b/modules/API/API.js index a429e435c..1a884a35c 100644 --- a/modules/API/API.js +++ b/modules/API/API.js @@ -1,4 +1,5 @@ import * as JitsiMeetConferenceEvents from '../../ConferenceEvents'; +import { parseJWTFromURLParams } from '../../react/features/jwt'; import { getJitsiMeetTransport } from '../transport'; import { API_ID } from './constants'; @@ -74,7 +75,15 @@ function onDesktopSharingEnabledChanged(enabled = false) { * @returns {boolean} */ function shouldBeEnabled() { - return typeof API_ID === 'number'; + return ( + typeof API_ID === 'number' + + // XXX Enable the API when a JSON Web Token (JWT) is specified in + // the location/URL because then it is very likely that the Jitsi + // Meet (Web) app is being used by an external/wrapping (Web) app + // and, consequently, the latter will need to communicate with the + // former. (The described logic is merely a heuristic though.) + || parseJWTFromURLParams()); } /** @@ -102,12 +111,10 @@ class API { * sends a message to the external application that API is initialized. * * @param {Object} options - Optional parameters. - * @param {boolean} options.forceEnable - True to forcefully enable the - * module. * @returns {void} */ - init({ forceEnable } = {}) { - if (!shouldBeEnabled() && !forceEnable) { + init() { + if (!shouldBeEnabled()) { return; } diff --git a/modules/keyboardshortcut/keyboardshortcut.js b/modules/keyboardshortcut/keyboardshortcut.js index cd50e94c8..b1b4a26e0 100644 --- a/modules/keyboardshortcut/keyboardshortcut.js +++ b/modules/keyboardshortcut/keyboardshortcut.js @@ -1,8 +1,6 @@ /* global APP, $, JitsiMeetJS, interfaceConfig */ -import { - toggleDialog -} from '../../react/features/base/dialog'; +import { toggleDialog } from '../../react/features/base/dialog'; import { SpeakerStats } from '../../react/features/speaker-stats'; /** @@ -17,7 +15,6 @@ let keyboardShortcutDialog = null; * triggered _only_ with a shortcut. */ function initGlobalShortcuts() { - KeyboardShortcut.registerShortcut("ESCAPE", null, function() { showKeyboardShortcutsPanel(false); }); @@ -57,19 +54,16 @@ function initGlobalShortcuts() { */ function showKeyboardShortcutsPanel(show) { if (show - && !APP.UI.messageHandler.isDialogOpened() - && keyboardShortcutDialog === null) { - + && !APP.UI.messageHandler.isDialogOpened() + && keyboardShortcutDialog === null) { let msg = $('#keyboard-shortcuts').html(); let buttons = { Close: true }; keyboardShortcutDialog = APP.UI.messageHandler.openDialog( 'keyboardShortcuts.keyboardShortcuts', msg, true, buttons); - } else { - if (keyboardShortcutDialog !== null) { - keyboardShortcutDialog.close(); - keyboardShortcutDialog = null; - } + } else if (keyboardShortcutDialog !== null) { + keyboardShortcutDialog.close(); + keyboardShortcutDialog = null; } } diff --git a/react/features/app/actions.js b/react/features/app/actions.js index 1ba255111..faa42bbc7 100644 --- a/react/features/app/actions.js +++ b/react/features/app/actions.js @@ -4,23 +4,9 @@ import { setConfig } from '../base/config'; import { loadConfig } from '../base/lib-jitsi-meet'; import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from './actionTypes'; -import { - _getRouteToRender, - _parseURIString, - init -} from './functions'; +import { _getRouteToRender, _parseURIString } from './functions'; -/** - * 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 (dispatch: Dispatch<*>, getState: Function) => - init(getState()); -} +declare var APP: Object; /** * Triggers an in-app navigation to a specific route. Allows navigation to be @@ -28,7 +14,7 @@ export function appInit() { * * @param {(string|undefined)} uri - The URI to which to navigate. It may be a * full URL with an HTTP(S) scheme, a full or partial URI with the app-specific - * sheme, or a mere room name. + * scheme, or a mere room name. * @returns {Function} */ export function appNavigate(uri: ?string) { @@ -180,9 +166,20 @@ function _appNavigateToOptionalLocation( * }} */ export function appWillMount(app) { - return { - type: APP_WILL_MOUNT, - app + return (dispatch: Dispatch<*>) => { + dispatch({ + type: APP_WILL_MOUNT, + app + }); + + // TODO There was a redux action creator appInit which I did not like + // because we already had the redux action creator appWillMount and, + // respectively, the redux action APP_WILL_MOUNT. So I set out to remove + // appInit and managed to move everything it was doing but the + // following. Which is not extremely bad because we haven't moved the + // API module into its own feature yet so we're bound to work on that in + // the future. + typeof APP === 'object' && APP.API.init(); }; } @@ -205,7 +202,7 @@ export function appWillUnmount(app) { /** * Loads config.js from a specific host. * - * @param {Object} location - The loction URI which specifies the host to load + * @param {Object} location - The location URI which specifies the host to load * the config.js from. * @returns {Promise} */ @@ -224,9 +221,9 @@ function _loadConfig(location: Object) { } /** - * Navigates to a route in accord with a specific Redux state. + * Navigates to a route in accord with a specific redux state. * - * @param {Object} state - The Redux state which determines/identifies the route + * @param {Object} state - The redux state which determines/identifies the route * to navigate to. * @private * @returns {void} diff --git a/react/features/app/components/AbstractApp.js b/react/features/app/components/AbstractApp.js index 1170da7e0..161ce95fc 100644 --- a/react/features/app/components/AbstractApp.js +++ b/react/features/app/components/AbstractApp.js @@ -84,7 +84,7 @@ export class AbstractApp extends Component { // business logic in the React Component (i.e. UI) AbstractApp now. let localParticipant; - if (typeof APP !== 'undefined') { + if (typeof APP === 'object') { localParticipant = { avatarID: APP.settings.getAvatarId(), avatarURL: APP.settings.getAvatarUrl(), diff --git a/react/features/app/components/App.web.js b/react/features/app/components/App.web.js index 62f1abbe9..0488f4d23 100644 --- a/react/features/app/components/App.web.js +++ b/react/features/app/components/App.web.js @@ -1,4 +1,3 @@ -import { appInit } from '../actions'; import { AbstractApp } from './AbstractApp'; import { getLocationContextRoot } from '../functions'; @@ -38,17 +37,6 @@ export class App extends AbstractApp { }; } - /** - * Inits the app before component will mount. - * - * @inheritdoc - */ - componentWillMount(...args) { - super.componentWillMount(...args); - - this._getStore().dispatch(appInit()); - } - /** * Gets a Location object from the window with information about the current * location of the document. diff --git a/react/features/app/functions.web.js b/react/features/app/functions.web.js index ede39654f..cd11288a0 100644 --- a/react/features/app/functions.web.js +++ b/react/features/app/functions.web.js @@ -1,9 +1,6 @@ /* @flow */ -import Logger from 'jitsi-meet-logger'; - import { isRoomValid } from '../base/conference'; -import JitsiMeetJS from '../base/lib-jitsi-meet'; import { Platform, RouteRegistry } from '../base/react'; import { Conference } from '../conference'; import { @@ -14,10 +11,6 @@ import { } from '../unsupported-browser'; import { WelcomePage } from '../welcome'; -import KeyboardShortcut - from '../../../modules/keyboardshortcut/keyboardshortcut'; -import JitsiMeetLogStorage from '../../../modules/util/JitsiMeetLogStorage'; - declare var APP: Object; declare var interfaceConfig: Object; declare var loggingConfig: Object; @@ -106,46 +99,6 @@ export function _getRouteToRender(stateOrGetState: Object | Function) { return route; } -/** - * Temporary solution. Later we'll get rid of global APP and set its properties - * in redux store. - * - * @param {Object} state - Snapshot of current state of redux store. - * @returns {void} - */ -export function init(state: Object) { - _initLogging(); - - APP.keyboardshortcut = KeyboardShortcut; - - const { jwt } = state['features/jwt']; - - // Force enable the API if jwt token is passed because most probably - // jitsi meet is displayed inside of wrapper that will need to communicate - // with jitsi meet. - APP.API.init(jwt ? { forceEnable: true } : undefined); - - APP.translation.init(); -} - -/** - * Initializes logging in the app. - * - * @private - * @returns {void} - */ -function _initLogging() { - // 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); - } -} - /** * Intercepts route components based on a {@link _INTERCEPT_COMPONENT_RULES}. * diff --git a/react/features/app/index.js b/react/features/app/index.js index cb41009ea..5d564613e 100644 --- a/react/features/app/index.js +++ b/react/features/app/index.js @@ -3,9 +3,4 @@ export * from './actionTypes'; export * from './components'; export * from './functions'; -// We need to import the jwt module in order to register the reducer and -// middleware, because the module is not used outside of this feature. -import '../jwt'; - import './reducer'; - diff --git a/react/features/base/config/parseURLParams.js b/react/features/base/config/parseURLParams.js index 11951a911..afc43f090 100644 --- a/react/features/base/config/parseURLParams.js +++ b/react/features/base/config/parseURLParams.js @@ -3,7 +3,7 @@ /** * Parses the parameters from the URL and returns them as a JS object. * - * @param {string} url - URL to parse. + * @param {string} url - The URL to parse. * @param {boolean} dontParse - If false or undefined some transformations * (for parsing the value as JSON) are going to be executed. * @param {string} source - Values - "hash"/"search" if "search" the parameters diff --git a/react/features/base/i18n/index.js b/react/features/base/i18n/index.js index 4fb07bd59..49b25f87a 100644 --- a/react/features/base/i18n/index.js +++ b/react/features/base/i18n/index.js @@ -4,3 +4,5 @@ export * from './functions'; // TODO Eventually (e.g. when the non-React Web app is rewritten into React), it // should not be necessary to export i18next. export { default as i18next } from './i18next'; + +import './middleware'; diff --git a/react/features/base/i18n/middleware.js b/react/features/base/i18n/middleware.js new file mode 100644 index 000000000..349e90036 --- /dev/null +++ b/react/features/base/i18n/middleware.js @@ -0,0 +1,48 @@ +/* @flow */ + +import { SET_CONFIG } from '../config'; +import { MiddlewareRegistry } from '../redux'; + +declare var APP: Object; + +/** + * The redux middleware of the feature base/i18n. + * + * @param {Store} store - The redux store. + * @returns {Function} + * @private + */ +MiddlewareRegistry.register(store => next => action => { + switch (action.type) { + case SET_CONFIG: + return _setConfig(store, next, action); + } + + return next(action); +}); + +/** + * Notifies the feature base/i18n that the action SET_CONFIG is being dispatched + * within a specific redux store. + * + * @param {Store} store - The redux store in which the specified action is being + * dispatched. + * @param {Dispatch} next - The redux dispatch function to dispatch the + * specified action to the specified store. + * @param {Action} action - The redux action SET_CONFIG which is being + * dispatched in the specified store. + * @private + * @returns {Object} The new state that is the result of the reduction of the + * specified action. + */ +function _setConfig({ dispatch, getState }, next, action) { + const oldValue = getState()['features/base/config']; + const result = next(action); + const newValue = getState()['features/base/config']; + + if (oldValue !== newValue && typeof APP === 'object') { + APP.translation.init(); + } + + return result; +} diff --git a/react/features/base/logging/middleware.js b/react/features/base/logging/middleware.js index 82b50855a..0507f6af7 100644 --- a/react/features/base/logging/middleware.js +++ b/react/features/base/logging/middleware.js @@ -6,6 +6,8 @@ import { APP_WILL_MOUNT } from '../../app'; import JitsiMeetJS, { LIB_WILL_INIT } from '../lib-jitsi-meet'; import { MiddlewareRegistry } from '../redux'; +import JitsiMeetLogStorage from '../../../../modules/util/JitsiMeetLogStorage'; + import { SET_LOGGING_CONFIG } from './actionTypes'; declare var APP: Object; @@ -60,6 +62,28 @@ function _appWillMount({ getState }, next, action) { return next(action); } +/** + * Initializes logging in the app. + * + * @param {Object} loggingConfig - The configuration with which logging is to be + * initialized. + * @private + * @returns {void} + */ +function _initLogging(loggingConfig) { + // 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 (typeof APP === 'object' + && !APP.logCollector + && !loggingConfig.disableLogCollector) { + APP.logCollector = new Logger.LogCollector(new JitsiMeetLogStorage()); + Logger.addGlobalTransport(APP.logCollector); + JitsiMeetJS.addGlobalLogTransport(APP.logCollector); + } +} + /** * Notifies the feature base/logging that the action {@link LIB_WILL_INIT} is * being dispatched within a specific Redux {@code store}. @@ -102,6 +126,8 @@ function _setLoggingConfig({ getState }, next, action) { if (oldValue !== newValue) { _setLogLevels(Logger, newValue); _setLogLevels(JitsiMeetJS, newValue); + + _initLogging(newValue); } return result; diff --git a/react/features/jwt/functions.js b/react/features/jwt/functions.js new file mode 100644 index 000000000..8a88a41ce --- /dev/null +++ b/react/features/jwt/functions.js @@ -0,0 +1,16 @@ +/* @flow */ + +import { parseURLParams } from '../base/config'; + +/** + * Retrieves the JSON Web Token (JWT), if any, defined by a specific + * {@link URL}. + * + * @param {URL} url - The {@code URL} to parse and retrieve the JSON Web Token + * (JWT), if any, from. + * @returns {string} The JSON Web Token (JWT), if any, defined by the specified + * {@code url}; otherwise, {@code undefined}. + */ +export function parseJWTFromURLParams(url: URL = window.location) { + return parseURLParams(url, true, 'search').jwt; +} diff --git a/react/features/jwt/index.js b/react/features/jwt/index.js index dbf9c8839..eea8d7393 100644 --- a/react/features/jwt/index.js +++ b/react/features/jwt/index.js @@ -1,4 +1,5 @@ export * from './actions'; +export * from './functions'; import './middleware'; import './reducer'; diff --git a/react/features/jwt/middleware.js b/react/features/jwt/middleware.js index fc3f37925..79f0ce72e 100644 --- a/react/features/jwt/middleware.js +++ b/react/features/jwt/middleware.js @@ -1,11 +1,12 @@ import jwtDecode from 'jwt-decode'; -import { parseURLParams, SET_CONFIG } from '../base/config'; +import { SET_CONFIG } from '../base/config'; import { SET_LOCATION_URL } from '../base/connection'; import { MiddlewareRegistry } from '../base/redux'; import { setJWT } from './actions'; import { SET_JWT } from './actionTypes'; +import { parseJWTFromURLParams } from './functions'; /** * Middleware to parse token data upon setting a new room URL. @@ -55,7 +56,7 @@ function _setConfigOrLocationURL({ dispatch, getState }, next, action) { let jwt; if (locationURL) { - jwt = parseURLParams(locationURL, true, 'search').jwt; + jwt = parseJWTFromURLParams(locationURL); } dispatch(setJWT(jwt));