core: refactor routing
Unfortunately, as the Jitsi Meet development evolved the routing mechanism became more complex and thre logic ended up spread across multiple parts of the codebase, which made it hard to follow and extend. This change aims to fix that by rewriting the routing logic and centralizing it in (pretty much) a single place, with no implicit inter-dependencies. In order to arrive there, however, some extra changes were needed, which were not caught early enough and are thus part of this change: - JitsiMeetJS initialization is now synchronous: there is nothing async about it, and the only async requirement (Temasys support) was lifted. See [0]. - WebRTC support can be detected early: building on top of the above, WebRTC support can now be detected immediately, so take advantage of this to simplify how we handle unsupported browsers. See [0]. The new router takes decissions based on the Redux state at the time of invocation. A route can be represented by either a component or a URl reference, with the latter taking precedence. On mobile, obviously, there is no concept of URL reference so routing is based solely on components. [0]: https://github.com/jitsi/lib-jitsi-meet/pull/779
This commit is contained in:
parent
5aee082bf9
commit
155e02bbfb
|
@ -2320,45 +2320,39 @@ export default {
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_initDeviceList() {
|
_initDeviceList() {
|
||||||
JitsiMeetJS.mediaDevices.isDeviceListAvailable()
|
if (JitsiMeetJS.mediaDevices.isDeviceListAvailable()
|
||||||
.then(isDeviceListAvailable => {
|
&& JitsiMeetJS.mediaDevices.isDeviceChangeAvailable()) {
|
||||||
if (isDeviceListAvailable
|
JitsiMeetJS.mediaDevices.enumerateDevices(devices => {
|
||||||
&& JitsiMeetJS.mediaDevices.isDeviceChangeAvailable()) {
|
// Ugly way to synchronize real device IDs with local
|
||||||
JitsiMeetJS.mediaDevices.enumerateDevices(devices => {
|
// storage and settings menu. This is a workaround until
|
||||||
// Ugly way to synchronize real device IDs with local
|
// getConstraints() method will be implemented
|
||||||
// storage and settings menu. This is a workaround until
|
// in browsers.
|
||||||
// getConstraints() method will be implemented
|
const { dispatch } = APP.store;
|
||||||
// in browsers.
|
|
||||||
const { dispatch } = APP.store;
|
|
||||||
|
|
||||||
if (this.localAudio) {
|
if (this.localAudio) {
|
||||||
dispatch(updateSettings({
|
dispatch(updateSettings({
|
||||||
micDeviceId: this.localAudio.getDeviceId()
|
micDeviceId: this.localAudio.getDeviceId()
|
||||||
}));
|
}));
|
||||||
}
|
|
||||||
|
|
||||||
if (this.localVideo) {
|
|
||||||
dispatch(updateSettings({
|
|
||||||
cameraDeviceId: this.localVideo.getDeviceId()
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
mediaDeviceHelper.setCurrentMediaDevices(devices);
|
|
||||||
APP.UI.onAvailableDevicesChanged(devices);
|
|
||||||
APP.store.dispatch(updateDeviceList(devices));
|
|
||||||
});
|
|
||||||
|
|
||||||
this.deviceChangeListener = devices =>
|
|
||||||
window.setTimeout(
|
|
||||||
() => this._onDeviceListChanged(devices), 0);
|
|
||||||
JitsiMeetJS.mediaDevices.addEventListener(
|
|
||||||
JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
|
|
||||||
this.deviceChangeListener);
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.catch(error => {
|
if (this.localVideo) {
|
||||||
logger.warn(`Error getting device list: ${error}`);
|
dispatch(updateSettings({
|
||||||
|
cameraDeviceId: this.localVideo.getDeviceId()
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaDeviceHelper.setCurrentMediaDevices(devices);
|
||||||
|
APP.UI.onAvailableDevicesChanged(devices);
|
||||||
|
APP.store.dispatch(updateDeviceList(devices));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.deviceChangeListener = devices =>
|
||||||
|
window.setTimeout(
|
||||||
|
() => this._onDeviceListChanged(devices), 0);
|
||||||
|
JitsiMeetJS.mediaDevices.addEventListener(
|
||||||
|
JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
|
||||||
|
this.deviceChangeListener);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The modules stores information about the URL used to start the conference and
|
|
||||||
* provides utility methods for dealing with conference URL and reloads.
|
|
||||||
*/
|
|
||||||
export default class ConferenceUrl {
|
|
||||||
/**
|
|
||||||
* Initializes the module.
|
|
||||||
*
|
|
||||||
* @param location an object which stores provides the info about conference
|
|
||||||
* URL(would be 'window.location' for the Web app). The params below are
|
|
||||||
* described based on the following example URL:
|
|
||||||
*
|
|
||||||
* https://example.com:8888/SomeConference1245?opt=1#somehash
|
|
||||||
*
|
|
||||||
* @param location.href full URL with all parameters, would be the whole URL
|
|
||||||
* from the example string above.
|
|
||||||
* @param location.host the host part of the URL, 'example.com' from
|
|
||||||
* the sample URL above.
|
|
||||||
* @param location.pathname the path part of the URL, would be
|
|
||||||
* '/SomeConference1245' from the example above.
|
|
||||||
* @param location.protocol the protocol part of the URL, would be 'https:'
|
|
||||||
* from the sample URL.
|
|
||||||
*/
|
|
||||||
constructor(location) {
|
|
||||||
logger.info(`Stored original conference URL: ${location.href}`);
|
|
||||||
logger.info(
|
|
||||||
`Conference URL for invites: ${location.protocol}//${
|
|
||||||
location.host}${location.pathname}`);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -77,32 +77,25 @@ function _appNavigateToMandatoryLocation(
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
function loadConfigSettled(error, config) {
|
function loadConfigSettled(error, config) {
|
||||||
let promise;
|
|
||||||
|
|
||||||
// Due to the asynchronous nature of the loading, the specified config
|
// Due to the asynchronous nature of the loading, the specified config
|
||||||
// may or may not be required by the time the notification arrives.
|
// may or may not be required by the time the notification arrives.
|
||||||
// If we receive the config for a location we are no longer interested
|
// If we receive the config for a location we are no longer interested
|
||||||
// in, "ignore" it - deliver it to the external API, for example, but do
|
// in, "ignore" it - deliver it to the external API, for example, but do
|
||||||
// not proceed with the appNavigate procedure/process.
|
// not proceed with the appNavigate procedure/process.
|
||||||
if (getState()['features/base/config'].locationURL === locationURL) {
|
if (getState()['features/base/config'].locationURL === locationURL) {
|
||||||
promise = dispatch(setLocationURL(locationURL));
|
dispatch(setLocationURL(locationURL));
|
||||||
|
dispatch(setConfig(config));
|
||||||
} else {
|
} else {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
error || (error = new Error('Config no longer needed!'));
|
error || (error = new Error('Config no longer needed!'));
|
||||||
promise = Promise.resolve();
|
|
||||||
|
// XXX The failure could be, for example, because of a
|
||||||
|
// certificate-related error. In which case the connection will
|
||||||
|
// fail later in Strophe anyway.
|
||||||
|
dispatch(loadConfigError(error, locationURL));
|
||||||
|
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return promise.then(() => {
|
|
||||||
if (error) {
|
|
||||||
// XXX The failure could be, for example, because of a
|
|
||||||
// certificate-related error. In which case the connection will
|
|
||||||
// fail later in Strophe anyway.
|
|
||||||
dispatch(loadConfigError(error, locationURL));
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return dispatch(setConfig(config));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/* global APP */
|
/* global APP */
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component, Fragment } from 'react';
|
import React, { Component, Fragment } from 'react';
|
||||||
import { I18nextProvider } from 'react-i18next';
|
import { I18nextProvider } from 'react-i18next';
|
||||||
|
@ -9,7 +10,6 @@ import Thunk from 'redux-thunk';
|
||||||
|
|
||||||
import { i18next } from '../../base/i18n';
|
import { i18next } from '../../base/i18n';
|
||||||
import { localParticipantLeft } from '../../base/participants';
|
import { localParticipantLeft } from '../../base/participants';
|
||||||
import { RouteRegistry } from '../../base/react';
|
|
||||||
import {
|
import {
|
||||||
MiddlewareRegistry,
|
MiddlewareRegistry,
|
||||||
ReducerRegistry,
|
ReducerRegistry,
|
||||||
|
@ -19,7 +19,6 @@ import { SoundCollection } from '../../base/sounds';
|
||||||
import { PersistenceRegistry } from '../../base/storage';
|
import { PersistenceRegistry } from '../../base/storage';
|
||||||
import { toURLString } from '../../base/util';
|
import { toURLString } from '../../base/util';
|
||||||
import { OverlayContainer } from '../../overlay';
|
import { OverlayContainer } from '../../overlay';
|
||||||
import { BlankPage } from '../../welcome';
|
|
||||||
|
|
||||||
import { appNavigate, appWillMount, appWillUnmount } from '../actions';
|
import { appNavigate, appWillMount, appWillUnmount } from '../actions';
|
||||||
|
|
||||||
|
@ -84,7 +83,7 @@ export class AbstractApp extends Component {
|
||||||
*
|
*
|
||||||
* @type {Route}
|
* @type {Route}
|
||||||
*/
|
*/
|
||||||
route: undefined,
|
route: {},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The state of the »possible« async initialization of
|
* The state of the »possible« async initialization of
|
||||||
|
@ -114,7 +113,6 @@ export class AbstractApp extends Component {
|
||||||
.catch(() => { /* AbstractApp should always initialize! */ })
|
.catch(() => { /* AbstractApp should always initialize! */ })
|
||||||
.then(() =>
|
.then(() =>
|
||||||
this.setState({
|
this.setState({
|
||||||
route: undefined,
|
|
||||||
store: this._maybeCreateStore(props)
|
store: this._maybeCreateStore(props)
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -242,7 +240,7 @@ export class AbstractApp extends Component {
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { appAsyncInitialized, route } = this.state;
|
const { appAsyncInitialized, route } = this.state;
|
||||||
const component = (route && route.component) || BlankPage;
|
const { component } = route;
|
||||||
|
|
||||||
if (appAsyncInitialized && component) {
|
if (appAsyncInitialized && component) {
|
||||||
return (
|
return (
|
||||||
|
@ -421,33 +419,16 @@ export class AbstractApp extends Component {
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
_navigate(route) {
|
_navigate(route) {
|
||||||
if (RouteRegistry.areRoutesEqual(this.state.route, route)) {
|
if (_.isEqual(route, this.state.route)) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
let nextState = {
|
if (route.href) {
|
||||||
route
|
// This navigation requires loading a new URL in the browser.
|
||||||
};
|
window.location.href = route.href;
|
||||||
|
|
||||||
// The Web App was using react-router so it utilized react-router's
|
return Promise.resolve();
|
||||||
// onEnter. During the removal of react-router, modifications were
|
}
|
||||||
// minimized by preserving the onEnter interface:
|
|
||||||
// (1) Router would provide its nextState to the Route's onEnter. As the
|
|
||||||
// role of Router is now this AbstractApp and we use redux, provide the
|
|
||||||
// redux store instead.
|
|
||||||
// (2) A replace function would be provided to the Route in case it
|
|
||||||
// chose to redirect to another path.
|
|
||||||
route && this._onRouteEnter(route, this._getStore(), pathname => {
|
|
||||||
if (pathname) {
|
|
||||||
this._openURL(pathname);
|
|
||||||
|
|
||||||
// Do not proceed with the route because it chose to redirect to
|
|
||||||
// another path.
|
|
||||||
nextState = undefined;
|
|
||||||
} else {
|
|
||||||
nextState.route = undefined;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// XXX React's setState is asynchronous which means that the value of
|
// XXX React's setState is asynchronous which means that the value of
|
||||||
// this.state.route above may not even be correct. If the check is
|
// this.state.route above may not even be correct. If the check is
|
||||||
|
@ -455,28 +436,10 @@ export class AbstractApp extends Component {
|
||||||
// expected route. In order to mitigate the problem, _navigate was
|
// expected route. In order to mitigate the problem, _navigate was
|
||||||
// changed to return a Promise.
|
// changed to return a Promise.
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
if (nextState) {
|
this.setState({ route }, resolve);
|
||||||
this.setState(nextState, resolve);
|
|
||||||
} else {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies this {@code App} that a specific Route is about to be rendered.
|
|
||||||
*
|
|
||||||
* @param {Route} route - The Route that is about to be rendered.
|
|
||||||
* @private
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
_onRouteEnter(route, ...args) {
|
|
||||||
// Notify the route that it is about to be entered.
|
|
||||||
const { onEnter } = route;
|
|
||||||
|
|
||||||
typeof onEnter === 'function' && onEnter(...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Navigates this {@code AbstractApp} to (i.e. opens) a specific URL.
|
* Navigates this {@code AbstractApp} to (i.e. opens) a specific URL.
|
||||||
*
|
*
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { AtlasKitThemeProvider } from '@atlaskit/theme';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import '../../base/responsive-ui';
|
import '../../base/responsive-ui';
|
||||||
import { getLocationContextRoot } from '../../base/util';
|
|
||||||
import '../../chat';
|
import '../../chat';
|
||||||
import '../../room-lock';
|
import '../../room-lock';
|
||||||
import '../../video-layout';
|
import '../../video-layout';
|
||||||
|
@ -22,27 +21,6 @@ export class App extends AbstractApp {
|
||||||
*/
|
*/
|
||||||
static propTypes = AbstractApp.propTypes;
|
static propTypes = AbstractApp.propTypes;
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a new App instance.
|
|
||||||
*
|
|
||||||
* @param {Object} props - The read-only React Component props with which
|
|
||||||
* the new instance is to be initialized.
|
|
||||||
*/
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
...this.state,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The context root of window.location i.e. this Web App.
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
windowLocationContextRoot: this._getWindowLocationContextRoot()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overrides the parent method to inject {@link AtlasKitThemeProvider} as
|
* Overrides the parent method to inject {@link AtlasKitThemeProvider} as
|
||||||
* the top most component.
|
* the top most component.
|
||||||
|
@ -66,79 +44,4 @@ export class App extends AbstractApp {
|
||||||
getWindowLocation() {
|
getWindowLocation() {
|
||||||
return window.location;
|
return window.location;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the context root of this Web App from window.location.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @returns {string} The context root of window.location i.e. this Web App.
|
|
||||||
*/
|
|
||||||
_getWindowLocationContextRoot() {
|
|
||||||
return getLocationContextRoot(this.getWindowLocation());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Navigates to a specific Route (via platform-specific means).
|
|
||||||
*
|
|
||||||
* @param {Route} route - The Route to which to navigate.
|
|
||||||
* @protected
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
_navigate(route) {
|
|
||||||
let path;
|
|
||||||
|
|
||||||
if (route) {
|
|
||||||
path = route.path;
|
|
||||||
|
|
||||||
const store = this._getStore();
|
|
||||||
|
|
||||||
// The syntax :room bellow is defined by react-router. It "matches a
|
|
||||||
// URL segment up to the next /, ?, or #. The matched string is
|
|
||||||
// called a param."
|
|
||||||
path
|
|
||||||
= path.replace(
|
|
||||||
/:room/g,
|
|
||||||
store.getState()['features/base/conference'].room);
|
|
||||||
path = this._routePath2WindowLocationPathname(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Navigate to the specified Route.
|
|
||||||
const windowLocation = this.getWindowLocation();
|
|
||||||
let promise;
|
|
||||||
|
|
||||||
if (!route || windowLocation.pathname === path) {
|
|
||||||
// The browser is at the specified path already and what remains is
|
|
||||||
// to make this App instance aware of the route to be rendered at
|
|
||||||
// the current location.
|
|
||||||
|
|
||||||
// XXX Refer to the super's _navigate for an explanation why a
|
|
||||||
// Promise is returned.
|
|
||||||
promise = super._navigate(route);
|
|
||||||
} else {
|
|
||||||
// The browser must go to the specified location. Once the specified
|
|
||||||
// location becomes current, the App will be made aware of the route
|
|
||||||
// to be rendered at it.
|
|
||||||
windowLocation.pathname = path;
|
|
||||||
}
|
|
||||||
|
|
||||||
return promise || Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a specific Route path to a window.location.pathname.
|
|
||||||
*
|
|
||||||
* @param {string} path - A Route path to be converted to/represeted as a
|
|
||||||
* window.location.pathname.
|
|
||||||
* @private
|
|
||||||
* @returns {string} A window.location.pathname-compatible representation of
|
|
||||||
* the specified Route path.
|
|
||||||
*/
|
|
||||||
_routePath2WindowLocationPathname(path) {
|
|
||||||
let pathname = this.state.windowLocationContextRoot;
|
|
||||||
|
|
||||||
pathname.endsWith('/') || (pathname += '/');
|
|
||||||
pathname += path.startsWith('/') ? path.substring(1) : path;
|
|
||||||
|
|
||||||
return pathname;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import { NativeModules } from 'react-native';
|
import { NativeModules } from 'react-native';
|
||||||
|
|
||||||
export * from './functions.any';
|
export * from './functions.any';
|
||||||
export * from './getRouteToRender';
|
export * from './router';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns application name.
|
* Returns application name.
|
||||||
|
|
|
@ -1,89 +1,9 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import { toState } from '../base/redux';
|
|
||||||
import { getDeepLinkingPage } from '../deep-linking';
|
|
||||||
import { UnsupportedDesktopBrowser } from '../unsupported-browser';
|
|
||||||
|
|
||||||
import {
|
|
||||||
// eslint-disable-next-line camelcase
|
|
||||||
_getRouteToRender as _super_getRouteToRender
|
|
||||||
} from './getRouteToRender';
|
|
||||||
|
|
||||||
declare var APP: Object;
|
|
||||||
declare var interfaceConfig: Object;
|
|
||||||
declare var loggingConfig: Object;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Array of rules defining whether we should {@link _interceptComponent} to
|
|
||||||
* render.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param {Object} state - Object containing current redux state.
|
|
||||||
* @returns {Promise<ReactElement>|void}
|
|
||||||
* @type {Function[]}
|
|
||||||
*/
|
|
||||||
const _INTERCEPT_COMPONENT_RULES = [
|
|
||||||
getDeepLinkingPage,
|
|
||||||
state => {
|
|
||||||
const { webRTCReady } = state['features/base/lib-jitsi-meet'];
|
|
||||||
|
|
||||||
if (webRTCReady === false) {
|
|
||||||
return Promise.resolve(UnsupportedDesktopBrowser);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
export * from './functions.any';
|
export * from './functions.any';
|
||||||
|
export * from './router';
|
||||||
|
|
||||||
/**
|
declare var interfaceConfig: Object;
|
||||||
* Determines which route is to be rendered in order to depict a specific redux
|
|
||||||
* store.
|
|
||||||
*
|
|
||||||
* @param {(Object|Function)} stateOrGetState - The redux state or
|
|
||||||
* {@link getState} function.
|
|
||||||
* @returns {Promise<Route>}
|
|
||||||
*/
|
|
||||||
export function _getRouteToRender(stateOrGetState: Object | Function): Object {
|
|
||||||
const route = _super_getRouteToRender(stateOrGetState);
|
|
||||||
|
|
||||||
// Intercepts route components if any of component interceptor rules is
|
|
||||||
// satisfied.
|
|
||||||
return _interceptComponent(stateOrGetState, route.component).then(
|
|
||||||
(component: React$Element<*>) => {
|
|
||||||
route.component = component;
|
|
||||||
|
|
||||||
return route;
|
|
||||||
}, () => Promise.resolve(route));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Intercepts route components based on a {@link _INTERCEPT_COMPONENT_RULES}.
|
|
||||||
*
|
|
||||||
* @param {Object|Function} stateOrGetState - The redux state or
|
|
||||||
* {@link getState} function.
|
|
||||||
* @param {ReactElement} component - Current route component to render.
|
|
||||||
* @private
|
|
||||||
* @returns {Promise<ReactElement>} If any of the pre-defined rules is
|
|
||||||
* satisfied, returns intercepted component.
|
|
||||||
*/
|
|
||||||
function _interceptComponent(
|
|
||||||
stateOrGetState: Object | Function,
|
|
||||||
component: React$Element<*>) {
|
|
||||||
const state = toState(stateOrGetState);
|
|
||||||
|
|
||||||
const promises = [];
|
|
||||||
|
|
||||||
_INTERCEPT_COMPONENT_RULES.forEach(rule => {
|
|
||||||
promises.push(rule(state));
|
|
||||||
});
|
|
||||||
|
|
||||||
return Promise.all(promises).then(
|
|
||||||
results =>
|
|
||||||
results.find(result => typeof result !== 'undefined') || component,
|
|
||||||
() => Promise.resolve(component));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns application name.
|
* Returns application name.
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
/* @flow */
|
|
||||||
|
|
||||||
import { isRoomValid } from '../base/conference';
|
|
||||||
import { RouteRegistry } from '../base/react';
|
|
||||||
import { toState } from '../base/redux';
|
|
||||||
import { Conference } from '../conference';
|
|
||||||
import { WelcomePage } from '../welcome';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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: Object | Function) {
|
|
||||||
const { room } = toState(stateOrGetState)['features/base/conference'];
|
|
||||||
const component = isRoomValid(room) ? Conference : WelcomePage;
|
|
||||||
|
|
||||||
return RouteRegistry.getRouteByComponent(component);
|
|
||||||
}
|
|
|
@ -3,21 +3,17 @@
|
||||||
import { SET_ROOM } from '../base/conference';
|
import { SET_ROOM } from '../base/conference';
|
||||||
import {
|
import {
|
||||||
CONNECTION_ESTABLISHED,
|
CONNECTION_ESTABLISHED,
|
||||||
getURLWithoutParams,
|
getURLWithoutParams
|
||||||
SET_LOCATION_URL
|
|
||||||
} from '../base/connection';
|
} from '../base/connection';
|
||||||
import { MiddlewareRegistry } from '../base/redux';
|
import { MiddlewareRegistry } from '../base/redux';
|
||||||
|
|
||||||
import { _getRouteToRender } from './functions';
|
import { getRouteToRender } from './router';
|
||||||
|
|
||||||
MiddlewareRegistry.register(store => next => action => {
|
MiddlewareRegistry.register(store => next => action => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case CONNECTION_ESTABLISHED:
|
case CONNECTION_ESTABLISHED:
|
||||||
return _connectionEstablished(store, next, action);
|
return _connectionEstablished(store, next, action);
|
||||||
|
|
||||||
case SET_LOCATION_URL:
|
|
||||||
return _setLocationURL(store, next, action);
|
|
||||||
|
|
||||||
case SET_ROOM:
|
case SET_ROOM:
|
||||||
return _setRoom(store, next, action);
|
return _setRoom(store, next, action);
|
||||||
}
|
}
|
||||||
|
@ -77,40 +73,8 @@ function _connectionEstablished(store, next, action) {
|
||||||
function _navigate({ getState }) {
|
function _navigate({ getState }) {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const { app } = state['features/app'];
|
const { app } = state['features/app'];
|
||||||
const routeToRender = _getRouteToRender(state);
|
|
||||||
|
|
||||||
// XXX Web changed _getRouteToRender to return Promsie instead of Route.
|
getRouteToRender(state).then(route => app._navigate(route));
|
||||||
// Unfortunately, the commit left mobile to return Route.
|
|
||||||
let routeToRenderPromise;
|
|
||||||
|
|
||||||
if (routeToRender && typeof routeToRender.then === 'function') {
|
|
||||||
routeToRenderPromise = routeToRender;
|
|
||||||
}
|
|
||||||
if (!routeToRenderPromise) {
|
|
||||||
routeToRenderPromise = Promise.resolve(routeToRender);
|
|
||||||
}
|
|
||||||
|
|
||||||
routeToRenderPromise.then(app._navigate.bind(app));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies the feature app that the action {@link SET_LOCATION_URL} is being
|
|
||||||
* dispatched within a specific redux {@code store}.
|
|
||||||
*
|
|
||||||
* @param {Store} store - The redux store in which the specified {@code action}
|
|
||||||
* is being dispatched.
|
|
||||||
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
|
|
||||||
* specified {@code action} to the specified {@code store}.
|
|
||||||
* @param {Action} action - The redux action, {@code SET_LOCATION_URL}, which is
|
|
||||||
* being dispatched in the specified {@code store}.
|
|
||||||
* @private
|
|
||||||
* @returns {Object} The new state that is the result of the reduction of the
|
|
||||||
* specified {@code action}.
|
|
||||||
*/
|
|
||||||
function _setLocationURL({ getState }, next, action) {
|
|
||||||
return (
|
|
||||||
getState()['features/app'].app._navigate(undefined)
|
|
||||||
.then(() => next(action)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
// @flow
|
||||||
|
import type { Component } from 'react';
|
||||||
|
|
||||||
|
import { isRoomValid } from '../base/conference';
|
||||||
|
import JitsiMeetJS from '../base/lib-jitsi-meet';
|
||||||
|
import { Platform } from '../base/react';
|
||||||
|
import { toState } from '../base/redux';
|
||||||
|
import { Conference } from '../conference';
|
||||||
|
import { getDeepLinkingPage } from '../deep-linking';
|
||||||
|
import { UnsupportedDesktopBrowser } from '../unsupported-browser';
|
||||||
|
import {
|
||||||
|
BlankPage,
|
||||||
|
WelcomePage,
|
||||||
|
generateRoomWithoutSeparator,
|
||||||
|
isWelcomePageAppEnabled,
|
||||||
|
isWelcomePageUserEnabled
|
||||||
|
} from '../welcome';
|
||||||
|
|
||||||
|
declare var interfaceConfig: Object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Object describing application route.
|
||||||
|
*
|
||||||
|
* @typedef {Object} Route
|
||||||
|
* @property {Component} component - React Component constructor.
|
||||||
|
* @property {string|undefined} href - New location, in case navigation involves
|
||||||
|
* a location change.
|
||||||
|
*/
|
||||||
|
export type Route = {
|
||||||
|
component: Class<Component<*>>,
|
||||||
|
href: ?string
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines which route is to be rendered in order to depict a specific Redux
|
||||||
|
* store.
|
||||||
|
*
|
||||||
|
* @param {(Object|Function)} stateful - Redux state or Regux getState()
|
||||||
|
* method.
|
||||||
|
* @returns {Promise<Route>}
|
||||||
|
*/
|
||||||
|
export function getRouteToRender(stateful: Object | Function): Promise<Route> {
|
||||||
|
const state = toState(stateful);
|
||||||
|
const { room } = state['features/base/conference'];
|
||||||
|
const isMobileApp = navigator.product === 'ReactNative';
|
||||||
|
const isMobileBrowser
|
||||||
|
= !isMobileApp && (Platform.OS === 'android' || Platform.OS === 'ios');
|
||||||
|
const route: Route = {
|
||||||
|
component: BlankPage,
|
||||||
|
href: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
// First check if the current endpoint supports WebRTC. We are
|
||||||
|
// intentionally not performing the check for mobile browsers because:
|
||||||
|
// - the welcome page is mobile ready
|
||||||
|
// - if the URL points to a conference, getDeepLinkingPage will take
|
||||||
|
// care of it
|
||||||
|
if (!isMobileBrowser && !JitsiMeetJS.isWebRtcSupported()) {
|
||||||
|
route.component = UnsupportedDesktopBrowser;
|
||||||
|
resolve(route);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRoomValid(room)) {
|
||||||
|
if (isMobileApp) {
|
||||||
|
route.component = Conference;
|
||||||
|
resolve(route);
|
||||||
|
} else {
|
||||||
|
// Update the location if it doesn't match. This happens when a
|
||||||
|
// room is joined from the welcome page. The reason for doing
|
||||||
|
// this instead of using the history API is that we want to
|
||||||
|
// load the config.js which takes the room into account.
|
||||||
|
const { locationURL } = state['features/base/connection'];
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-negated-condition
|
||||||
|
if (window.location.href !== locationURL.href) {
|
||||||
|
route.href = locationURL.href;
|
||||||
|
resolve(route);
|
||||||
|
} else {
|
||||||
|
// Maybe show deep-linking, otherwise go to Conference.
|
||||||
|
getDeepLinkingPage(state).then(component => {
|
||||||
|
route.component = component || Conference;
|
||||||
|
resolve(route);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isWelcomePageUserEnabled(state)) {
|
||||||
|
// Web: if the welcome page is disabled, go directly to a
|
||||||
|
// random room.
|
||||||
|
|
||||||
|
let href = window.location.href;
|
||||||
|
|
||||||
|
href.endsWith('/') || (href += '/');
|
||||||
|
route.href = href + generateRoomWithoutSeparator();
|
||||||
|
} else if (isWelcomePageAppEnabled(state)) {
|
||||||
|
// Mobile: only go to the welcome page if enabled.
|
||||||
|
|
||||||
|
route.component = WelcomePage;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(route);
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import { CONNECTION_WILL_CONNECT } from '../connection';
|
import { CONNECTION_WILL_CONNECT, SET_LOCATION_URL } from '../connection';
|
||||||
import { JitsiConferenceErrors } from '../lib-jitsi-meet';
|
import { JitsiConferenceErrors } from '../lib-jitsi-meet';
|
||||||
import { assign, ReducerRegistry, set } from '../redux';
|
import { assign, ReducerRegistry, set } from '../redux';
|
||||||
import { LOCKED_LOCALLY, LOCKED_REMOTELY } from '../../room-lock';
|
import { LOCKED_LOCALLY, LOCKED_REMOTELY } from '../../room-lock';
|
||||||
|
@ -66,6 +66,9 @@ ReducerRegistry.register('features/base/conference', (state = {}, action) => {
|
||||||
case SET_FOLLOW_ME:
|
case SET_FOLLOW_ME:
|
||||||
return set(state, 'followMeEnabled', action.enabled);
|
return set(state, 'followMeEnabled', action.enabled);
|
||||||
|
|
||||||
|
case SET_LOCATION_URL:
|
||||||
|
return set(state, 'room', undefined);
|
||||||
|
|
||||||
case SET_PASSWORD:
|
case SET_PASSWORD:
|
||||||
return _setPassword(state, action);
|
return _setPassword(state, action);
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
import type { Dispatch } from 'redux';
|
import type { Dispatch } from 'redux';
|
||||||
|
|
||||||
import { libInitError, WEBRTC_NOT_SUPPORTED } from '../lib-jitsi-meet';
|
|
||||||
|
|
||||||
declare var APP: Object;
|
declare var APP: Object;
|
||||||
declare var config: Object;
|
declare var config: Object;
|
||||||
|
|
||||||
|
@ -26,24 +24,14 @@ export function connect() {
|
||||||
|
|
||||||
// XXX Lib-jitsi-meet does not accept uppercase letters.
|
// XXX Lib-jitsi-meet does not accept uppercase letters.
|
||||||
const room = state['features/base/conference'].room.toLowerCase();
|
const room = state['features/base/conference'].room.toLowerCase();
|
||||||
const { initPromise } = state['features/base/lib-jitsi-meet'];
|
|
||||||
|
|
||||||
// XXX For web based version we use conference initialization logic
|
// XXX For web based version we use conference initialization logic
|
||||||
// from the old app (at the moment of writing).
|
// from the old app (at the moment of writing).
|
||||||
return initPromise.then(() => APP.conference.init({
|
return APP.conference.init({
|
||||||
roomName: room
|
roomName: room
|
||||||
})).catch(error => {
|
}).catch(error => {
|
||||||
APP.API.notifyConferenceLeft(APP.conference.roomName);
|
APP.API.notifyConferenceLeft(APP.conference.roomName);
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
||||||
// TODO The following are in fact Errors raised by
|
|
||||||
// JitsiMeetJS.init() which should be taken care of in
|
|
||||||
// features/base/lib-jitsi-meet but we are not there yet on the
|
|
||||||
// Web at the time of this writing.
|
|
||||||
switch (error.name) {
|
|
||||||
case WEBRTC_NOT_SUPPORTED:
|
|
||||||
dispatch(libInitError(error));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,16 +27,6 @@ export const LIB_DID_INIT = Symbol('LIB_DID_INIT');
|
||||||
*/
|
*/
|
||||||
export const LIB_INIT_ERROR = Symbol('LIB_INIT_ERROR');
|
export const LIB_INIT_ERROR = Symbol('LIB_INIT_ERROR');
|
||||||
|
|
||||||
/**
|
|
||||||
* Action to dispatch the promise returned by JitsiMeetJS.init.
|
|
||||||
*
|
|
||||||
* {
|
|
||||||
* type: LIB_INIT_PROMISE_CREATED,
|
|
||||||
* initPromise: Promise
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
export const LIB_INIT_PROMISE_CREATED = Symbol('LIB_INIT_PROMISE_CREATED');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of Redux action which signals that {@link JitsiMeetJS} will be
|
* The type of Redux action which signals that {@link JitsiMeetJS} will be
|
||||||
* disposed.
|
* disposed.
|
||||||
|
@ -56,13 +46,3 @@ export const LIB_WILL_DISPOSE = Symbol('LIB_WILL_DISPOSE');
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
export const LIB_WILL_INIT = Symbol('LIB_WILL_INIT');
|
export const LIB_WILL_INIT = Symbol('LIB_WILL_INIT');
|
||||||
|
|
||||||
/**
|
|
||||||
* The type of Redux action which indicates whether WebRTC is ready.
|
|
||||||
*
|
|
||||||
* {
|
|
||||||
* type: SET_WEBRTC_READY,
|
|
||||||
* webRTCReady: boolean | Promise
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
export const SET_WEBRTC_READY = Symbol('SET_WEBRTC_READY');
|
|
||||||
|
|
|
@ -7,10 +7,8 @@ import {
|
||||||
LIB_DID_DISPOSE,
|
LIB_DID_DISPOSE,
|
||||||
LIB_DID_INIT,
|
LIB_DID_INIT,
|
||||||
LIB_INIT_ERROR,
|
LIB_INIT_ERROR,
|
||||||
LIB_INIT_PROMISE_CREATED,
|
|
||||||
LIB_WILL_DISPOSE,
|
LIB_WILL_DISPOSE,
|
||||||
LIB_WILL_INIT,
|
LIB_WILL_INIT
|
||||||
SET_WEBRTC_READY
|
|
||||||
} from './actionTypes';
|
} from './actionTypes';
|
||||||
import { isAnalyticsEnabled } from './functions';
|
import { isAnalyticsEnabled } from './functions';
|
||||||
|
|
||||||
|
@ -38,7 +36,7 @@ export function disposeLib() {
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
export function initLib() {
|
export function initLib() {
|
||||||
return (dispatch: Dispatch<*>, getState: Function): Promise<void> => {
|
return (dispatch: Dispatch<*>, getState: Function): void => {
|
||||||
const config = getState()['features/base/config'];
|
const config = getState()['features/base/config'];
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
|
@ -47,30 +45,15 @@ export function initLib() {
|
||||||
|
|
||||||
dispatch({ type: LIB_WILL_INIT });
|
dispatch({ type: LIB_WILL_INIT });
|
||||||
|
|
||||||
const initPromise = JitsiMeetJS.init({
|
try {
|
||||||
enableAnalyticsLogging: isAnalyticsEnabled(getState),
|
JitsiMeetJS.init({
|
||||||
...config
|
enableAnalyticsLogging: isAnalyticsEnabled(getState),
|
||||||
});
|
...config
|
||||||
|
});
|
||||||
dispatch({
|
dispatch({ type: LIB_DID_INIT });
|
||||||
type: LIB_INIT_PROMISE_CREATED,
|
} catch (error) {
|
||||||
initPromise
|
dispatch(libInitError(error));
|
||||||
});
|
}
|
||||||
|
|
||||||
return (
|
|
||||||
initPromise
|
|
||||||
.then(() => dispatch({ type: LIB_DID_INIT }))
|
|
||||||
.catch(error => {
|
|
||||||
// TODO: See the comment in the connect action in
|
|
||||||
// base/connection/actions.web.js.
|
|
||||||
if (typeof APP === 'undefined') {
|
|
||||||
dispatch(libInitError(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Handle LIB_INIT_ERROR error somewhere instead.
|
|
||||||
console.error('lib-jitsi-meet failed to init:', error);
|
|
||||||
throw error;
|
|
||||||
}));
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,22 +72,3 @@ export function libInitError(error: Error) {
|
||||||
error
|
error
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the indicator which determines whether WebRTC is ready.
|
|
||||||
*
|
|
||||||
* @param {boolean} webRTCReady - The indicator which determines
|
|
||||||
* whether WebRTC is ready.
|
|
||||||
* @returns {Function}
|
|
||||||
*/
|
|
||||||
export function setWebRTCReady(webRTCReady: boolean) {
|
|
||||||
return (dispatch: Function, getState: Function) => {
|
|
||||||
if (getState()['features/base/lib-jitsi-meet'].webRTCReady
|
|
||||||
!== webRTCReady) {
|
|
||||||
dispatch({
|
|
||||||
type: SET_WEBRTC_READY,
|
|
||||||
webRTCReady
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
/**
|
|
||||||
* The name of the Error thrown by {@link JitsiMeetJS.init()} which indicates
|
|
||||||
* that WebRTC is not supported by the execution environment.
|
|
||||||
*/
|
|
||||||
export const WEBRTC_NOT_SUPPORTED = 'WEBRTC_NOT_SUPPORTED';
|
|
|
@ -24,7 +24,6 @@ export const JitsiTrackEvents = JitsiMeetJS.events.track;
|
||||||
|
|
||||||
export * from './actions';
|
export * from './actions';
|
||||||
export * from './actionTypes';
|
export * from './actionTypes';
|
||||||
export * from './constants';
|
|
||||||
export * from './functions';
|
export * from './functions';
|
||||||
|
|
||||||
import './middleware';
|
import './middleware';
|
||||||
|
|
|
@ -6,9 +6,8 @@ import { PARTICIPANT_LEFT } from '../participants';
|
||||||
import { MiddlewareRegistry } from '../redux';
|
import { MiddlewareRegistry } from '../redux';
|
||||||
|
|
||||||
import JitsiMeetJS from './_';
|
import JitsiMeetJS from './_';
|
||||||
import { disposeLib, initLib, setWebRTCReady } from './actions';
|
import { disposeLib, initLib } from './actions';
|
||||||
import { LIB_DID_INIT, LIB_INIT_ERROR, LIB_WILL_INIT } from './actionTypes';
|
import { LIB_WILL_INIT } from './actionTypes';
|
||||||
import { WEBRTC_NOT_SUPPORTED } from './constants';
|
|
||||||
|
|
||||||
declare var APP: Object;
|
declare var APP: Object;
|
||||||
|
|
||||||
|
@ -31,18 +30,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
_setErrorHandlers();
|
_setErrorHandlers();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case LIB_DID_INIT:
|
|
||||||
// FIXME: The web version doesn't need this action during initialization
|
|
||||||
// because it is still using the old logic from conference.js. We still
|
|
||||||
// have to reactify the old logic from conference.js and then maybe
|
|
||||||
// we'll need this action for web too.
|
|
||||||
if (typeof APP === 'undefined') {
|
|
||||||
store.dispatch(setWebRTCReady(true));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LIB_INIT_ERROR:
|
|
||||||
return _libInitError(store, next, action);
|
|
||||||
|
|
||||||
case PARTICIPANT_LEFT:
|
case PARTICIPANT_LEFT:
|
||||||
action.participant.local && store.dispatch(disposeLib());
|
action.participant.local && store.dispatch(disposeLib());
|
||||||
|
@ -55,32 +42,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
return next(action);
|
return next(action);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies the feature base/lib-jitsi-meet that the action LIB_INIT_ERROR 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 LIB_INIT_ERROR 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 _libInitError(store, next, action) {
|
|
||||||
const nextState = next(action);
|
|
||||||
|
|
||||||
const { error } = action;
|
|
||||||
|
|
||||||
if (error && error.name === WEBRTC_NOT_SUPPORTED) {
|
|
||||||
store.dispatch(setWebRTCReady(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
return nextState;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies the feature base/lib-jitsi-meet that the action SET_CONFIG is being
|
* Notifies the feature base/lib-jitsi-meet that the action SET_CONFIG is being
|
||||||
* dispatched within a specific Redux store.
|
* dispatched within a specific Redux store.
|
||||||
|
|
|
@ -5,9 +5,7 @@ import { ReducerRegistry } from '../redux';
|
||||||
import {
|
import {
|
||||||
LIB_DID_DISPOSE,
|
LIB_DID_DISPOSE,
|
||||||
LIB_DID_INIT,
|
LIB_DID_INIT,
|
||||||
LIB_INIT_ERROR,
|
LIB_INIT_ERROR
|
||||||
LIB_INIT_PROMISE_CREATED,
|
|
||||||
SET_WEBRTC_READY
|
|
||||||
} from './actionTypes';
|
} from './actionTypes';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,20 +33,7 @@ ReducerRegistry.register(
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
initError: action.error,
|
initError: action.error,
|
||||||
initialized: false,
|
initialized: false
|
||||||
initPromise: undefined
|
|
||||||
};
|
|
||||||
|
|
||||||
case LIB_INIT_PROMISE_CREATED:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
initPromise: action.initPromise
|
|
||||||
};
|
|
||||||
|
|
||||||
case SET_WEBRTC_READY:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
webRTCReady: action.webRTCReady
|
|
||||||
};
|
};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -1,119 +0,0 @@
|
||||||
/* @flow */
|
|
||||||
|
|
||||||
import { Component } from 'react';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object describing application route.
|
|
||||||
*
|
|
||||||
* @typedef {Object} Route
|
|
||||||
* @property {Component} component - React Component constructor.
|
|
||||||
* @property {string} path - URL route, required for web routing.
|
|
||||||
*/
|
|
||||||
type Route = {
|
|
||||||
component: Class<Component<*>>, // eslint-disable-line no-undef
|
|
||||||
path: string
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A registry for Navigator routes, allowing features to register themselves
|
|
||||||
* without needing to create additional inter-feature dependencies.
|
|
||||||
*/
|
|
||||||
class RouteRegistry {
|
|
||||||
_elements: Array<Route>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a new RouteRegistry instance.
|
|
||||||
*/
|
|
||||||
constructor() {
|
|
||||||
/**
|
|
||||||
* The set of registered routes.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @type {Route[]}
|
|
||||||
*/
|
|
||||||
this._elements = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether two specific Routes are equal i.e. they describe one
|
|
||||||
* and the same abstract route.
|
|
||||||
*
|
|
||||||
* @param {Object} a - The Route to compare to b.
|
|
||||||
* @param {Object} b - The Route to compare to a.
|
|
||||||
* @returns {boolean} True if the specified a and b describe one and the
|
|
||||||
* same abstract route; otherwise, false.
|
|
||||||
*/
|
|
||||||
areRoutesEqual(a: Route, b: Route) {
|
|
||||||
if (a === b) { // reflexive
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!a) {
|
|
||||||
return !b;
|
|
||||||
}
|
|
||||||
if (!b) {
|
|
||||||
return !a;
|
|
||||||
}
|
|
||||||
|
|
||||||
const aKeys = Object.keys(a);
|
|
||||||
const bKeys = Object.keys(b);
|
|
||||||
|
|
||||||
return (
|
|
||||||
aKeys.length === bKeys.length /* symmetric */
|
|
||||||
&& aKeys.every(key => a[key] === b[key]));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all registered routes.
|
|
||||||
*
|
|
||||||
* @returns {Route[]}
|
|
||||||
*/
|
|
||||||
getRoutes() {
|
|
||||||
// We use the destructuring operator to 'clone' the route object to
|
|
||||||
// prevent modifications from outside (e.g. React Native's Navigator
|
|
||||||
// extends it with additional properties).
|
|
||||||
return this._elements.map(r => {
|
|
||||||
return { ...r };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/* eslint-disable no-undef */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns registered route by name if any.
|
|
||||||
*
|
|
||||||
* @param {Component} component - The React Component (class) of the route
|
|
||||||
* to retrieve.
|
|
||||||
* @returns {Route|null}
|
|
||||||
*/
|
|
||||||
getRouteByComponent(component: Class<Component<*>>) {
|
|
||||||
|
|
||||||
/* eslint-enable no-undef */
|
|
||||||
|
|
||||||
const route = this._elements.find(r => r.component === component);
|
|
||||||
|
|
||||||
// We use destructuring operator to 'clone' route object to prevent
|
|
||||||
// modifications from outside (e.g. React Native's Navigator extends
|
|
||||||
// it with some additional properties).
|
|
||||||
return route ? { ...route } : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a route to this registry.
|
|
||||||
*
|
|
||||||
* @param {Route} route - Route definition object.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
register(route: Route) {
|
|
||||||
if (this._elements.includes(route)) {
|
|
||||||
throw new Error(
|
|
||||||
`Route ${String(route.component)} is registered already!`);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._elements.push(route);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The public singleton instance of the RouteRegistry class.
|
|
||||||
*/
|
|
||||||
export default new RouteRegistry();
|
|
|
@ -1,3 +1,2 @@
|
||||||
export * from './components';
|
export * from './components';
|
||||||
export { default as Platform } from './Platform';
|
export { default as Platform } from './Platform';
|
||||||
export { default as RouteRegistry } from './RouteRegistry';
|
|
||||||
|
|
|
@ -37,6 +37,13 @@ type Props = {
|
||||||
*/
|
*/
|
||||||
_connecting: boolean,
|
_connecting: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current conference's full URL.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_locationURL: URL,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The handler which dispatches the (redux) action connect.
|
* The handler which dispatches the (redux) action connect.
|
||||||
*
|
*
|
||||||
|
@ -77,6 +84,13 @@ type Props = {
|
||||||
*/
|
*/
|
||||||
_reducedUI: boolean,
|
_reducedUI: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current conference room name.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_room: string,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The handler which dispatches the (redux) action setToolboxVisible to
|
* The handler which dispatches the (redux) action setToolboxVisible to
|
||||||
* show/hide the Toolbox.
|
* show/hide the Toolbox.
|
||||||
|
@ -163,15 +177,30 @@ class Conference extends Component<Props> {
|
||||||
* participant count.
|
* participant count.
|
||||||
*
|
*
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
* @param {Object} nextProps - The read-only React {@code Component} props
|
* @param {Props} nextProps - The read-only React {@code Component} props
|
||||||
* that this instance will receive.
|
* that this instance will receive.
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
componentWillReceiveProps({ _participantCount: newParticipantCount }) {
|
componentWillReceiveProps(nextProps: Props) {
|
||||||
const {
|
const {
|
||||||
|
_locationURL: oldLocationURL,
|
||||||
|
_onConnect,
|
||||||
|
_onDisconnect,
|
||||||
_participantCount: oldParticipantCount,
|
_participantCount: oldParticipantCount,
|
||||||
|
_room: oldRoom,
|
||||||
_setToolboxVisible
|
_setToolboxVisible
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
const {
|
||||||
|
_locationURL: newLocationURL,
|
||||||
|
_participantCount: newParticipantCount,
|
||||||
|
_room: newRoom
|
||||||
|
} = nextProps;
|
||||||
|
|
||||||
|
// If the location URL changes we need to reconnect.
|
||||||
|
oldLocationURL !== newLocationURL && _onDisconnect();
|
||||||
|
|
||||||
|
// Start the connection process when there is a (valid) room.
|
||||||
|
oldRoom !== newRoom && newRoom && _onConnect();
|
||||||
|
|
||||||
if (oldParticipantCount === 1) {
|
if (oldParticipantCount === 1) {
|
||||||
newParticipantCount > 1 && _setToolboxVisible(false);
|
newParticipantCount > 1 && _setToolboxVisible(false);
|
||||||
|
@ -389,15 +418,23 @@ function _mapDispatchToProps(dispatch) {
|
||||||
* @private
|
* @private
|
||||||
* @returns {{
|
* @returns {{
|
||||||
* _connecting: boolean,
|
* _connecting: boolean,
|
||||||
|
* _locationURL: URL,
|
||||||
* _participantCount: number,
|
* _participantCount: number,
|
||||||
* _reducedUI: boolean,
|
* _reducedUI: boolean,
|
||||||
|
* _room: string,
|
||||||
* _toolboxVisible: boolean,
|
* _toolboxVisible: boolean,
|
||||||
* _toolboxAlwaysVisible: boolean
|
* _toolboxAlwaysVisible: boolean
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
function _mapStateToProps(state) {
|
function _mapStateToProps(state) {
|
||||||
const { connecting, connection } = state['features/base/connection'];
|
const { connecting, connection, locationURL }
|
||||||
const { conference, joining, leaving } = state['features/base/conference'];
|
= state['features/base/connection'];
|
||||||
|
const {
|
||||||
|
conference,
|
||||||
|
joining,
|
||||||
|
leaving,
|
||||||
|
room
|
||||||
|
} = state['features/base/conference'];
|
||||||
const { reducedUI } = state['features/base/responsive-ui'];
|
const { reducedUI } = state['features/base/responsive-ui'];
|
||||||
const { alwaysVisible, visible } = state['features/toolbox'];
|
const { alwaysVisible, visible } = state['features/toolbox'];
|
||||||
|
|
||||||
|
@ -425,6 +462,14 @@ function _mapStateToProps(state) {
|
||||||
*/
|
*/
|
||||||
_connecting: Boolean(connecting_),
|
_connecting: Boolean(connecting_),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current conference's full URL.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @type {URL}
|
||||||
|
*/
|
||||||
|
_locationURL: locationURL,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of participants in the conference.
|
* The number of participants in the conference.
|
||||||
*
|
*
|
||||||
|
@ -442,6 +487,14 @@ function _mapStateToProps(state) {
|
||||||
*/
|
*/
|
||||||
_reducedUI: reducedUI,
|
_reducedUI: reducedUI,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current conference room name.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
_room: room,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The indicator which determines whether the Toolbox is visible.
|
* The indicator which determines whether the Toolbox is visible.
|
||||||
*
|
*
|
||||||
|
|
|
@ -4,6 +4,7 @@ import _ from 'lodash';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect as reactReduxConnect } from 'react-redux';
|
import { connect as reactReduxConnect } from 'react-redux';
|
||||||
|
|
||||||
|
import { obtainConfig } from '../../base/config';
|
||||||
import { connect, disconnect } from '../../base/connection';
|
import { connect, disconnect } from '../../base/connection';
|
||||||
import { DialogContainer } from '../../base/dialog';
|
import { DialogContainer } from '../../base/dialog';
|
||||||
import { translate } from '../../base/i18n';
|
import { translate } from '../../base/i18n';
|
||||||
|
@ -22,8 +23,29 @@ import {
|
||||||
import { maybeShowSuboptimalExperienceNotification } from '../functions';
|
import { maybeShowSuboptimalExperienceNotification } from '../functions';
|
||||||
|
|
||||||
declare var APP: Object;
|
declare var APP: Object;
|
||||||
|
declare var config: Object;
|
||||||
declare var interfaceConfig: Object;
|
declare var interfaceConfig: Object;
|
||||||
|
|
||||||
|
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 from which the config is to be
|
||||||
|
* obtained.
|
||||||
|
* @param {string} room - Room name.
|
||||||
|
* @private
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
function _obtainConfig(location: string, room: string) {
|
||||||
|
return new Promise((resolve, reject) =>
|
||||||
|
obtainConfig(location, room, (success, error) => {
|
||||||
|
success ? resolve() : reject(error);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DOM events for when full screen mode has changed. Different browsers need
|
* DOM events for when full screen mode has changed. Different browsers need
|
||||||
* different vendor prefixes.
|
* different vendor prefixes.
|
||||||
|
@ -47,6 +69,11 @@ type Props = {
|
||||||
*/
|
*/
|
||||||
_iAmRecorder: boolean,
|
_iAmRecorder: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conference room name.
|
||||||
|
*/
|
||||||
|
_room: string,
|
||||||
|
|
||||||
dispatch: Function,
|
dispatch: Function,
|
||||||
t: Function
|
t: Function
|
||||||
}
|
}
|
||||||
|
@ -84,29 +111,35 @@ class Conference extends Component<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Until we don't rewrite UI using react components
|
* Start the connection and get the UI ready for the conference.
|
||||||
* we use UI.start from old app. Also method translates
|
|
||||||
* component right after it has been mounted.
|
|
||||||
*
|
*
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
APP.UI.start();
|
const { configLocation } = config;
|
||||||
|
|
||||||
APP.UI.registerListeners();
|
if (configLocation) {
|
||||||
APP.UI.bindEvents();
|
_obtainConfig(configLocation, this.props._room)
|
||||||
|
.then(() => {
|
||||||
|
const now = window.performance.now();
|
||||||
|
|
||||||
FULL_SCREEN_EVENTS.forEach(name =>
|
APP.connectionTimes['configuration.fetched'] = now;
|
||||||
document.addEventListener(name, this._onFullScreenChange));
|
logger.log('(TIME) configuration fetched:\t', now);
|
||||||
|
|
||||||
const { dispatch, t } = this.props;
|
this._start();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
logger.log(err);
|
||||||
|
|
||||||
dispatch(connect());
|
// Show obtain config error.
|
||||||
|
APP.UI.messageHandler.showError({
|
||||||
maybeShowSuboptimalExperienceNotification(dispatch, t);
|
titleKey: 'connection.CONNFAIL',
|
||||||
|
descriptionKey: 'dialog.connectError'
|
||||||
interfaceConfig.filmStripOnly
|
});
|
||||||
&& dispatch(setToolboxAlwaysVisible(true));
|
});
|
||||||
|
} else {
|
||||||
|
this._start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -185,6 +218,32 @@ class Conference extends Component<Props> {
|
||||||
_onShowToolbar() {
|
_onShowToolbar() {
|
||||||
this.props.dispatch(showToolbox());
|
this.props.dispatch(showToolbox());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
_start() {
|
||||||
|
APP.UI.start();
|
||||||
|
|
||||||
|
APP.UI.registerListeners();
|
||||||
|
APP.UI.bindEvents();
|
||||||
|
|
||||||
|
FULL_SCREEN_EVENTS.forEach(name =>
|
||||||
|
document.addEventListener(name, this._onFullScreenChange));
|
||||||
|
|
||||||
|
const { dispatch, t } = this.props;
|
||||||
|
|
||||||
|
dispatch(connect());
|
||||||
|
|
||||||
|
maybeShowSuboptimalExperienceNotification(dispatch, t);
|
||||||
|
|
||||||
|
interfaceConfig.filmStripOnly
|
||||||
|
&& dispatch(setToolboxAlwaysVisible(true));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -198,6 +257,7 @@ class Conference extends Component<Props> {
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
function _mapStateToProps(state) {
|
function _mapStateToProps(state) {
|
||||||
|
const { room } = state['features/base/conference'];
|
||||||
const { iAmRecorder } = state['features/base/config'];
|
const { iAmRecorder } = state['features/base/config'];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -206,7 +266,12 @@ function _mapStateToProps(state) {
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_iAmRecorder: iAmRecorder
|
_iAmRecorder: iAmRecorder,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conference room name.
|
||||||
|
*/
|
||||||
|
_room: room
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
export * from './components';
|
export * from './components';
|
||||||
|
|
||||||
import './middleware';
|
import './middleware';
|
||||||
import './route';
|
|
||||||
|
|
|
@ -1,105 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import ConferenceUrl from '../../../modules/URL/ConferenceUrl';
|
|
||||||
|
|
||||||
import { obtainConfig } from '../base/config';
|
|
||||||
import { RouteRegistry } from '../base/react';
|
|
||||||
|
|
||||||
import { Conference } from './components';
|
|
||||||
|
|
||||||
declare var APP: Object;
|
|
||||||
declare var config: Object;
|
|
||||||
|
|
||||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register route for Conference (page).
|
|
||||||
*/
|
|
||||||
RouteRegistry.register({
|
|
||||||
component: Conference,
|
|
||||||
onEnter: () => {
|
|
||||||
// XXX If config or jwt are set by hash or query parameters
|
|
||||||
// Getting raw URL before stripping it.
|
|
||||||
_obtainConfigAndInit();
|
|
||||||
},
|
|
||||||
path: '/:room'
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialization of the app.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
function _initConference() {
|
|
||||||
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 from which the config is to be
|
|
||||||
* obtained.
|
|
||||||
* @param {string} room - Room name.
|
|
||||||
* @private
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
function _obtainConfig(location: string, room: string) {
|
|
||||||
return new Promise((resolve, reject) =>
|
|
||||||
obtainConfig(location, room, (success, error) => {
|
|
||||||
success ? resolve() : 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 => {
|
|
||||||
logger.log(err);
|
|
||||||
|
|
||||||
// Show obtain config error.
|
|
||||||
APP.UI.messageHandler.showError({
|
|
||||||
titleKey: 'connection.CONNFAIL',
|
|
||||||
descriptionKey: 'dialog.connectError'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
_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);
|
|
||||||
}
|
|
|
@ -94,10 +94,7 @@ function _processRequest(dispatch, getState, request, responseCallback) { // esl
|
||||||
|
|
||||||
switch (request.name) {
|
switch (request.name) {
|
||||||
case 'isDeviceListAvailable':
|
case 'isDeviceListAvailable':
|
||||||
JitsiMeetJS.mediaDevices.isDeviceListAvailable()
|
responseCallback(JitsiMeetJS.mediaDevices.isDeviceListAvailable());
|
||||||
.then(isDeviceListAvailable =>
|
|
||||||
responseCallback(isDeviceListAvailable))
|
|
||||||
.catch(e => responseCallback(null, e));
|
|
||||||
break;
|
break;
|
||||||
case 'isDeviceChangeAvailable':
|
case 'isDeviceChangeAvailable':
|
||||||
responseCallback(
|
responseCallback(
|
||||||
|
|
|
@ -8,9 +8,8 @@ import DeviceSelectionPopup from './DeviceSelectionPopup';
|
||||||
let deviceSelectionPopup;
|
let deviceSelectionPopup;
|
||||||
|
|
||||||
window.init = i18next => {
|
window.init = i18next => {
|
||||||
JitsiMeetJS.init({}).then(() => {
|
JitsiMeetJS.init();
|
||||||
deviceSelectionPopup = new DeviceSelectionPopup(i18next);
|
deviceSelectionPopup = new DeviceSelectionPopup(i18next);
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('beforeunload', () => deviceSelectionPopup.close());
|
window.addEventListener('beforeunload', () => deviceSelectionPopup.close());
|
||||||
|
|
|
@ -1,3 +1 @@
|
||||||
export * from './components';
|
export * from './components';
|
||||||
|
|
||||||
import './middleware';
|
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import { appNavigate } from '../app';
|
|
||||||
import { SET_WEBRTC_READY } from '../base/lib-jitsi-meet';
|
|
||||||
import { MiddlewareRegistry } from '../base/redux';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Middleware that dispatches appNavigate when WebRTC readiness changes.
|
|
||||||
*
|
|
||||||
* @param {Store} store - The Redux store.
|
|
||||||
* @returns {Function}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
MiddlewareRegistry.register(store => next => action => {
|
|
||||||
switch (action.type) {
|
|
||||||
case SET_WEBRTC_READY:
|
|
||||||
return _setWebRTCReady(store, next, action);
|
|
||||||
}
|
|
||||||
|
|
||||||
return next(action);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies the feature unsupported-browser that the action SET_WEBRTC_READY 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_WEBRTC_READY 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 _setWebRTCReady({ dispatch, getState }, next, action) {
|
|
||||||
const result = next(action);
|
|
||||||
|
|
||||||
// FIXME The feature unsupported-browser needs to notify the app that it may
|
|
||||||
// need to render a different Component at its current location because the
|
|
||||||
// execution enviroment has changed. The current location is not necessarily
|
|
||||||
// available through window.location (e.g. on mobile) but the following
|
|
||||||
// works at the time of this writing.
|
|
||||||
const windowLocation = getState()['features/app'].app.getWindowLocation();
|
|
||||||
|
|
||||||
if (windowLocation) {
|
|
||||||
const { href } = windowLocation;
|
|
||||||
|
|
||||||
href && dispatch(appNavigate(href));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
|
@ -1,38 +1,27 @@
|
||||||
/* @flow */
|
// @flow
|
||||||
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import type { Dispatch } from 'redux';
|
||||||
|
|
||||||
import { destroyLocalTracks } from '../../base/tracks';
|
import { destroyLocalTracks } from '../../base/tracks';
|
||||||
import { NetworkActivityIndicator } from '../../mobile/network-activity';
|
import { NetworkActivityIndicator } from '../../mobile/network-activity';
|
||||||
|
|
||||||
import { isWelcomePageAppEnabled } from '../functions';
|
|
||||||
import LocalVideoTrackUnderlay from './LocalVideoTrackUnderlay';
|
import LocalVideoTrackUnderlay from './LocalVideoTrackUnderlay';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code BlankPage} React {@code Component}'s prop types.
|
||||||
|
*/
|
||||||
|
type Props = {
|
||||||
|
dispatch: Dispatch<*>
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The React {@code Component} displayed by {@code AbstractApp} when it has no
|
* The React {@code Component} displayed by {@code AbstractApp} when it has no
|
||||||
* {@code Route} to render. Renders a progress indicator when there are ongoing
|
* {@code Route} to render. Renders a progress indicator when there are ongoing
|
||||||
* network requests.
|
* network requests.
|
||||||
*/
|
*/
|
||||||
class BlankPage extends Component<*> {
|
class BlankPage extends Component<Props> {
|
||||||
/**
|
|
||||||
* {@code BlankPage} React {@code Component}'s prop types.
|
|
||||||
*
|
|
||||||
* @static
|
|
||||||
*/
|
|
||||||
static propTypes = {
|
|
||||||
/**
|
|
||||||
* The indicator which determines whether {@code WelcomePage} is (to
|
|
||||||
* be) rendered.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_welcomePageEnabled: PropTypes.bool,
|
|
||||||
|
|
||||||
dispatch: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destroys the local tracks (if any) since no media is desired when this
|
* Destroys the local tracks (if any) since no media is desired when this
|
||||||
* component is rendered.
|
* component is rendered.
|
||||||
|
@ -41,8 +30,7 @@ class BlankPage extends Component<*> {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.props._welcomePageEnabled
|
this.props.dispatch(destroyLocalTracks());
|
||||||
|| this.props.dispatch(destroyLocalTracks());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -60,20 +48,4 @@ class BlankPage extends Component<*> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export default connect()(BlankPage);
|
||||||
* Maps (parts of) the redux state to the React {@code Component} props of
|
|
||||||
* {@code BlankPage}.
|
|
||||||
*
|
|
||||||
* @param {Object} state - The redux state.
|
|
||||||
* @private
|
|
||||||
* @returns {{
|
|
||||||
* _welcomePageEnabled: boolean
|
|
||||||
* }}
|
|
||||||
*/
|
|
||||||
function _mapStateToProps(state) {
|
|
||||||
return {
|
|
||||||
_welcomePageEnabled: isWelcomePageAppEnabled(state)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(_mapStateToProps)(BlankPage);
|
|
||||||
|
|
|
@ -19,8 +19,6 @@ export * from './roomnameGenerator';
|
||||||
* {@code true}; otherwise, {@code false}.
|
* {@code true}; otherwise, {@code false}.
|
||||||
*/
|
*/
|
||||||
export function isWelcomePageAppEnabled(stateful: Function | Object) {
|
export function isWelcomePageAppEnabled(stateful: Function | Object) {
|
||||||
let b;
|
|
||||||
|
|
||||||
if (navigator.product === 'ReactNative') {
|
if (navigator.product === 'ReactNative') {
|
||||||
// We introduced the welcomePageEnabled prop on App in Jitsi Meet SDK
|
// We introduced the welcomePageEnabled prop on App in Jitsi Meet SDK
|
||||||
// for Android and iOS. There isn't a strong reason not to introduce it
|
// for Android and iOS. There isn't a strong reason not to introduce it
|
||||||
|
@ -29,12 +27,10 @@ export function isWelcomePageAppEnabled(stateful: Function | Object) {
|
||||||
// - Enabling/disabling the Welcome page on Web historically
|
// - Enabling/disabling the Welcome page on Web historically
|
||||||
// automatically redirects to a random room and that does not make sense
|
// automatically redirects to a random room and that does not make sense
|
||||||
// on mobile (right now).
|
// on mobile (right now).
|
||||||
b = Boolean(getAppProp(stateful, 'welcomePageEnabled'));
|
return Boolean(getAppProp(stateful, 'welcomePageEnabled'));
|
||||||
} else {
|
|
||||||
b = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return b;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -2,4 +2,3 @@ export * from './components';
|
||||||
export * from './functions';
|
export * from './functions';
|
||||||
|
|
||||||
import './reducer';
|
import './reducer';
|
||||||
import './route';
|
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
/* @flow */
|
|
||||||
|
|
||||||
import { RouteRegistry } from '../base/react';
|
|
||||||
|
|
||||||
import { WelcomePage } from './components';
|
|
||||||
import {
|
|
||||||
generateRoomWithoutSeparator,
|
|
||||||
isWelcomePageAppEnabled,
|
|
||||||
isWelcomePageUserEnabled
|
|
||||||
} from './functions';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register route for {@code WelcomePage}.
|
|
||||||
*/
|
|
||||||
RouteRegistry.register({
|
|
||||||
component: WelcomePage,
|
|
||||||
onEnter,
|
|
||||||
path: '/'
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Skips the {@code WelcomePage} if it is disabled (by the app or the user).
|
|
||||||
*
|
|
||||||
* @param {Object} store - The redux store.
|
|
||||||
* @param {Function} replace - The function to redirect to another path.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
function onEnter({ getState }, replace) {
|
|
||||||
if (isWelcomePageAppEnabled(getState)) {
|
|
||||||
isWelcomePageUserEnabled(getState)
|
|
||||||
|| replace(`/${generateRoomWithoutSeparator()}`);
|
|
||||||
} else {
|
|
||||||
replace(undefined);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue