Merge branch 'BeatC-moving-conference-init-to-react-1' of https://github.com/BeatC/jitsi-meet into BeatC-BeatC-moving-conference-init-to-react-1
This commit is contained in:
commit
b67994235e
258
app.js
258
app.js
|
@ -1,6 +1,4 @@
|
||||||
/* global $, config, getRoomName, loggingConfig, JitsiMeetJS */
|
|
||||||
/* application specific logic */
|
/* application specific logic */
|
||||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
|
||||||
|
|
||||||
import "babel-polyfill";
|
import "babel-polyfill";
|
||||||
import "jquery";
|
import "jquery";
|
||||||
|
@ -18,104 +16,13 @@ import 'aui-experimental-css';
|
||||||
|
|
||||||
window.toastr = require("toastr");
|
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 {
|
|
||||||
generateRoomWithoutSeparator
|
|
||||||
} from './react/features/base/util/roomnameGenerator';
|
|
||||||
|
|
||||||
import UI from "./modules/UI/UI";
|
import UI from "./modules/UI/UI";
|
||||||
import settings from "./modules/settings/Settings";
|
import settings from "./modules/settings/Settings";
|
||||||
import conference from './conference';
|
import conference from './conference';
|
||||||
import ConferenceUrl from './modules/URL/ConferenceUrl';
|
|
||||||
import API from './modules/API/API';
|
import API from './modules/API/API';
|
||||||
|
|
||||||
import UIEvents from './service/UI/UIEvents';
|
|
||||||
import getTokenData from "./modules/tokendata/TokenData";
|
|
||||||
import translation from "./modules/translation/translation";
|
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
|
|
||||||
*/
|
|
||||||
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 = {
|
const APP = {
|
||||||
// Used by do_external_connect.js if we receive the attach data after
|
// Used by do_external_connect.js if we receive the attach data after
|
||||||
// connect was already executed. status property can be "initialized",
|
// connect was already executed. status property can be "initialized",
|
||||||
|
@ -152,164 +59,15 @@ const APP = {
|
||||||
*/
|
*/
|
||||||
ConferenceUrl : null,
|
ConferenceUrl : null,
|
||||||
connection: null,
|
connection: null,
|
||||||
API,
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
// TODO The execution of the mobile app starts from react/index.native.js.
|
||||||
* If JWT token data it will be used for local user settings
|
// Similarly, the execution of the Web app should start from
|
||||||
*/
|
// react/index.web.js for the sake of consistency and ease of understanding.
|
||||||
function setTokenData() {
|
// Temporarily though because we are at the beginning of introducing React
|
||||||
let localUser = APP.tokenData.caller;
|
// into the Web app, allow the execution of the Web app to start from app.js
|
||||||
if(localUser) {
|
// in order to reduce the complexity of the beginning step.
|
||||||
APP.settings.setEmail((localUser.getEmail() || "").trim(), true);
|
require('./react');
|
||||||
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);
|
|
||||||
// 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
|
|
||||||
// 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');
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.translation.init(settings.getLanguage());
|
|
||||||
|
|
||||||
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();
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = APP;
|
module.exports = APP;
|
||||||
|
|
|
@ -396,20 +396,6 @@ UI.getSharedVideoManager = function () {
|
||||||
*/
|
*/
|
||||||
UI.start = function () {
|
UI.start = function () {
|
||||||
document.title = interfaceConfig.APP_NAME;
|
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.
|
// Set the defaults for prompt dialogs.
|
||||||
$.prompt.setDefaults({persistent: false});
|
$.prompt.setDefaults({persistent: false});
|
||||||
|
|
|
@ -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;
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -42,7 +42,7 @@ export class AbstractApp extends Component {
|
||||||
* The URL, if any, with which the app was launched.
|
* The URL, if any, with which the app was launched.
|
||||||
*/
|
*/
|
||||||
url: React.PropTypes.string
|
url: React.PropTypes.string
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Init lib-jitsi-meet and create local participant when component is going
|
* Init lib-jitsi-meet and create local participant when component is going
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
/* global $ */
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
|
import { compose } from 'redux';
|
||||||
import {
|
import {
|
||||||
browserHistory,
|
browserHistory,
|
||||||
Route,
|
Route,
|
||||||
|
@ -11,6 +13,8 @@ import { getDomain } from '../../base/connection';
|
||||||
import { RouteRegistry } from '../../base/navigator';
|
import { RouteRegistry } from '../../base/navigator';
|
||||||
|
|
||||||
import { AbstractApp } from './AbstractApp';
|
import { AbstractApp } from './AbstractApp';
|
||||||
|
import { appInit } from '../actions';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Root application component.
|
* Root application component.
|
||||||
|
@ -23,7 +27,7 @@ export class App extends AbstractApp {
|
||||||
*
|
*
|
||||||
* @static
|
* @static
|
||||||
*/
|
*/
|
||||||
static propTypes = AbstractApp.propTypes
|
static propTypes = AbstractApp.propTypes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a new App instance.
|
* Initializes a new App instance.
|
||||||
|
@ -44,28 +48,18 @@ export class App extends AbstractApp {
|
||||||
// Bind event handlers so they are only bound once for every instance.
|
// Bind event handlers so they are only bound once for every instance.
|
||||||
this._onRouteEnter = this._onRouteEnter.bind(this);
|
this._onRouteEnter = this._onRouteEnter.bind(this);
|
||||||
this._routerCreateElement = this._routerCreateElement.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
|
* Inits the app before component will mount.
|
||||||
* are integrated into the Web App.
|
|
||||||
*
|
*
|
||||||
* @returns {void}
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
componentWillMount() {
|
componentWillMount(...args) {
|
||||||
// FIXME Do not override the super once the dispatching of Redux actions
|
super.componentWillMount(...args);
|
||||||
// is integrated into the Web App.
|
this.props.store.dispatch(appInit());
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -75,19 +69,13 @@ export class App extends AbstractApp {
|
||||||
* @returns {ReactElement}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const routes = RouteRegistry.getRoutes();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Provider store = { this.props.store }>
|
<Provider store = { this.props.store }>
|
||||||
<Router
|
<Router
|
||||||
createElement = { this._routerCreateElement }
|
createElement = { this._routerCreateElement }
|
||||||
history = { this.history }>
|
history = { this.history }>
|
||||||
{ routes.map(r =>
|
{ this._getRoutes() }
|
||||||
<Route
|
|
||||||
component = { r.component }
|
|
||||||
key = { r.component }
|
|
||||||
path = { r.path } />
|
|
||||||
) }
|
|
||||||
</Router>
|
</Router>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
|
@ -114,6 +102,38 @@ export class App extends AbstractApp {
|
||||||
return store.dispatch(push(path));
|
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 (
|
||||||
|
<Route
|
||||||
|
component = { route.component }
|
||||||
|
key = { route.component }
|
||||||
|
onEnter = { handler }
|
||||||
|
path = { route.path } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked by react-router to notify this App that a Route is about to be
|
* Invoked by react-router to notify this App that a Route is about to be
|
||||||
* rendered.
|
* rendered.
|
||||||
|
@ -122,6 +142,7 @@ export class App extends AbstractApp {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onRouteEnter() {
|
_onRouteEnter() {
|
||||||
|
|
||||||
// XXX The following is mandatory. Otherwise, moving back & forward
|
// XXX The following is mandatory. Otherwise, moving back & forward
|
||||||
// through the browser's history could leave this App on the Conference
|
// through the browser's history could leave this App on the Conference
|
||||||
// page without a room name.
|
// page without a room name.
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -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<JitsiConnection>}
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
|
@ -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
|
||||||
|
};
|
||||||
|
}
|
|
@ -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<Object>}
|
||||||
|
*/
|
||||||
|
export function loadConfig() {
|
||||||
|
return Promise.resolve(window.config);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -1,5 +1,11 @@
|
||||||
/* global interfaceConfig, APP */
|
/* global APP, $, interfaceConfig */
|
||||||
import React, { Component } from 'react';
|
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.
|
* For legacy reasons, inline style for display none.
|
||||||
|
@ -12,7 +18,41 @@ const DISPLAY_NONE_STYLE = {
|
||||||
/**
|
/**
|
||||||
* Implements a React Component which renders initial conference layout
|
* 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.
|
* Initializes Conference component instance.
|
||||||
|
@ -220,3 +260,5 @@ export default class Conference extends Component {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default reactReduxConnect()(Conference);
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
/* 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,17 @@
|
||||||
import { RouteRegistry } from '../base/navigator';
|
import { RouteRegistry } from '../base/navigator';
|
||||||
|
|
||||||
import { Conference } from './components';
|
import { Conference } from './components';
|
||||||
|
import { obtainConfigAndInit } from './functions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register route for Conference (page).
|
* Register route for Conference (page).
|
||||||
*/
|
*/
|
||||||
RouteRegistry.register({
|
RouteRegistry.register({
|
||||||
component: Conference,
|
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();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
/* global APP, interfaceConfig */
|
/* global interfaceConfig, APP, $ */
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { Conference } from '../../conference';
|
|
||||||
|
|
||||||
import { AbstractWelcomePage, mapStateToProps } from './AbstractWelcomePage';
|
import { AbstractWelcomePage, mapStateToProps } from './AbstractWelcomePage';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -52,6 +50,9 @@ class WelcomePage extends AbstractWelcomePage {
|
||||||
if (this.state.generateRoomnames) {
|
if (this.state.generateRoomnames) {
|
||||||
this._updateRoomname();
|
this._updateRoomname();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// XXX Temporary solution until we add React translation.
|
||||||
|
APP.translation.translateElement($('#welcome_page'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -61,12 +62,6 @@ class WelcomePage extends AbstractWelcomePage {
|
||||||
* @returns {ReactElement|null}
|
* @returns {ReactElement|null}
|
||||||
*/
|
*/
|
||||||
render() {
|
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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div id = 'welcome_page'>
|
<div id = 'welcome_page'>
|
||||||
|
@ -77,7 +72,6 @@ class WelcomePage extends AbstractWelcomePage {
|
||||||
this._renderMain()
|
this._renderMain()
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<Conference />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -132,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
|
* Handles 'keydown' event to initiate joining the room when the
|
||||||
* 'Enter/Return' button is pressed.
|
* 'Enter/Return' button is pressed.
|
||||||
|
@ -359,6 +340,16 @@ class WelcomePage extends AbstractWelcomePage {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles updating roomname.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onUpdateRoomname() {
|
||||||
|
this._updateRoomname();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the main part of this WelcomePage.
|
* Renders the main part of this WelcomePage.
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,11 +1,31 @@
|
||||||
|
/* global APP */
|
||||||
import { RouteRegistry } from '../base/navigator';
|
import { RouteRegistry } from '../base/navigator';
|
||||||
|
import { generateRoomWithoutSeparator } from '../base/util';
|
||||||
import { WelcomePage } from './components';
|
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.
|
* Register route for WelcomePage.
|
||||||
*/
|
*/
|
||||||
RouteRegistry.register({
|
RouteRegistry.register({
|
||||||
component: WelcomePage,
|
component: WelcomePage,
|
||||||
path: '/'
|
path: '/',
|
||||||
|
onEnter
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* global APP */
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { browserHistory } from 'react-router';
|
import { browserHistory } from 'react-router';
|
||||||
|
@ -15,6 +16,8 @@ import {
|
||||||
ReducerRegistry
|
ReducerRegistry
|
||||||
} from './features/base/redux';
|
} from './features/base/redux';
|
||||||
|
|
||||||
|
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||||
|
|
||||||
// Create combined reducer from all reducers in registry + routerReducer from
|
// Create combined reducer from all reducers in registry + routerReducer from
|
||||||
// 'react-router-redux' module (stores location updates from history).
|
// 'react-router-redux' module (stores location updates from history).
|
||||||
// @see https://github.com/reactjs/react-router-redux#routerreducer.
|
// @see https://github.com/reactjs/react-router-redux#routerreducer.
|
||||||
|
@ -45,10 +48,33 @@ if (typeof window === 'object'
|
||||||
// Create Redux store with our reducer and middleware.
|
// Create Redux store with our reducer and middleware.
|
||||||
const store = createStore(reducer, middleware);
|
const store = createStore(reducer, middleware);
|
||||||
|
|
||||||
// Render the main Component.
|
/**
|
||||||
ReactDOM.render(
|
* Render the app when DOM tree has been loaded.
|
||||||
<App
|
*/
|
||||||
config = { config }
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
store = { store }
|
const now = window.performance.now();
|
||||||
url = { window.location.toString() } />,
|
|
||||||
document.getElementById('react'));
|
APP.connectionTimes['document.ready'] = now;
|
||||||
|
logger.log('(TIME) document ready:\t', now);
|
||||||
|
|
||||||
|
// Render the main Component.
|
||||||
|
ReactDOM.render(
|
||||||
|
<App
|
||||||
|
config = { config }
|
||||||
|
store = { store }
|
||||||
|
url = { window.location.toString() } />,
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue