Fix the initialization of the (external) API

The counterpart of the external API in the Jitsi Meet Web app uses the
search URL param jwt to heuristically detect that the Web app is very
likely embedded (as an iframe) and, consequently, needs to forcefully
enable itself. It was looking at whether there was a JSON Web Token
(JWT) but that logic got broken when the JWT support was rewritten
because the check started happening before the search URL param jwt was
parsed.
This commit is contained in:
Lyubo Marinov 2017-05-26 17:11:33 -05:00 committed by hristoterezov
parent 69f8cf7836
commit 320e67baa1
15 changed files with 160 additions and 126 deletions

42
app.js
View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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<Object>}
*/
@ -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}

View File

@ -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(),

View File

@ -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.

View File

@ -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}.
*

View File

@ -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';

View File

@ -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

View File

@ -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';

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}

View File

@ -1,4 +1,5 @@
export * from './actions';
export * from './functions';
import './middleware';
import './reducer';

View File

@ -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));