[RN] Handle config loading errors

They will be stored in redux and the PageReloadOverlay will be displayed.

Note that this commit also introduces a subtle (and yet important!) change:
the location URL is now always set, regardless of the configuration loading or
not. This is needed in order for the retry logic to pick it up.
This commit is contained in:
Saúl Ibarra Corretgé 2017-11-29 15:15:57 +01:00 committed by Lyubo Marinov
parent 87a87eebb9
commit c05c8e0f1e
5 changed files with 67 additions and 18 deletions

View File

@ -1,7 +1,7 @@
/* @flow */ /* @flow */
import { setRoom } from '../base/conference'; import { setRoom } from '../base/conference';
import { loadConfigError, setConfig } from '../base/config'; import { configWillLoad, loadConfigError, setConfig } from '../base/config';
import { setLocationURL } from '../base/connection'; import { setLocationURL } from '../base/connection';
import { loadConfig } from '../base/lib-jitsi-meet'; import { loadConfig } from '../base/lib-jitsi-meet';
import { parseURIString } from '../base/util'; import { parseURIString } from '../base/util';
@ -44,6 +44,8 @@ function _appNavigateToMandatoryLocation(
): Promise<void> { ): Promise<void> {
const { room } = newLocation; const { room } = newLocation;
dispatch(configWillLoad(newLocation));
return ( return (
_loadConfig(newLocation) _loadConfig(newLocation)
.then( .then(
@ -67,23 +69,20 @@ function _appNavigateToMandatoryLocation(
// config may or may not be required by the time the notification // config may or may not be required by the time the notification
// arrives. // arrives.
const promise
= dispatch(setLocationURL(new URL(newLocation.toString())));
if (error) { if (error) {
// XXX The failure could be, for example, because of a // XXX The failure could be, for example, because of a
// certificate-related error. In which case the connection will // certificate-related error. In which case the connection will
// fail later in Strophe anyway. // fail later in Strophe anyway.
dispatch(loadConfigError(error, newLocation)); return promise.then(() => {
dispatch(loadConfigError(error, newLocation));
// Cannot go to a room if its configuration failed to load.
if (room) {
dispatch(appNavigate(undefined));
throw error; throw error;
} });
} }
return ( return promise.then(() => dispatch(setConfig(config)));
dispatch(setLocationURL(new URL(newLocation.toString())))
.then(() => dispatch(setConfig(config))));
} }
} }

View File

@ -1,3 +1,14 @@
/**
* The redux action which signals that a configuration will be loaded for a
* specific locationURL.
*
* {
* type: CONFIG_WILL_LOAD,
* locationURL: string | URL
* }
*/
export const CONFIG_WILL_LOAD = Symbol('CONFIG_WILL_LOAD');
/** /**
* The redux action which signals that a configuration could not be loaded due * The redux action which signals that a configuration could not be loaded due
* to a specific error. * to a specific error.

View File

@ -1,6 +1,27 @@
/* @flow */ /* @flow */
import { LOAD_CONFIG_ERROR, SET_CONFIG } from './actionTypes'; import {
CONFIG_WILL_LOAD,
LOAD_CONFIG_ERROR,
SET_CONFIG
} from './actionTypes';
/**
* Signals that the configuration for a specific locationURL will be loaded now.
*
* @param {string|URL} locationURL - The URL of the location which necessitated
* the loading of a configuration.
* @returns {{
* type: CONFIG_WILL_LOAD,
* locationURL
* }}
*/
export function configWillLoad(locationURL: string | URL) {
return {
type: CONFIG_WILL_LOAD,
locationURL
};
}
/** /**
* Signals that a configuration could not be loaded due to a specific error. * Signals that a configuration could not be loaded due to a specific error.

View File

@ -4,7 +4,11 @@ import _ from 'lodash';
import { equals, ReducerRegistry, set } from '../redux'; import { equals, ReducerRegistry, set } from '../redux';
import { SET_CONFIG } from './actionTypes'; import {
CONFIG_WILL_LOAD,
LOAD_CONFIG_ERROR,
SET_CONFIG
} from './actionTypes';
/** /**
* The initial state of the feature base/config when executing in a * The initial state of the feature base/config when executing in a
@ -47,6 +51,16 @@ ReducerRegistry.register(
'features/base/config', 'features/base/config',
(state = _getInitialState(), action) => { (state = _getInitialState(), action) => {
switch (action.type) { switch (action.type) {
case CONFIG_WILL_LOAD:
return {
error: undefined
};
case LOAD_CONFIG_ERROR:
return {
error: action.error
};
case SET_CONFIG: case SET_CONFIG:
return _setConfig(state, action); return _setConfig(state, action);
@ -81,20 +95,21 @@ function _getInitialState() {
* @returns {Object} The new state of the feature base/lib-jitsi-meet after the * @returns {Object} The new state of the feature base/lib-jitsi-meet after the
* reduction of the specified action. * reduction of the specified action.
*/ */
function _setConfig(state, action) { function _setConfig(state, { config }) {
let { config } = action;
// The mobile app bundles jitsi-meet and lib-jitsi-meet at build time and // The mobile app bundles jitsi-meet and lib-jitsi-meet at build time and
// does not download them at runtime from the deployment on which it will // does not download them at runtime from the deployment on which it will
// join a conference. The downloading is planned for implementation in the // join a conference. The downloading is planned for implementation in the
// future (later rather than sooner) but is not implemented yet at the time // future (later rather than sooner) but is not implemented yet at the time
// of this writing and, consequently, we must provide legacy support in the // of this writing and, consequently, we must provide legacy support in the
// meantime. // meantime.
// eslint-disable-next-line no-param-reassign
config = _translateLegacyConfig(config); config = _translateLegacyConfig(config);
const newState = _.merge( const newState = _.merge(
{}, {},
config, config,
{ error: undefined },
// The config of _getInitialState() is meant to override the config // The config of _getInitialState() is meant to override the config
// downloaded from the Jitsi Meet deployment because the former contains // downloaded from the Jitsi Meet deployment because the former contains

View File

@ -66,12 +66,14 @@ export default class AbstractPageReloadOverlay extends Component<*, *> {
*/ */
static needsRender(state) { static needsRender(state) {
const conferenceError = state['features/base/conference'].error; const conferenceError = state['features/base/conference'].error;
const configError = state['features/base/config'].error;
const connectionError = state['features/base/connection'].error; const connectionError = state['features/base/connection'].error;
return ( return (
(connectionError && isFatalJitsiConnectionError(connectionError)) (connectionError && isFatalJitsiConnectionError(connectionError))
|| (conferenceError || (conferenceError
&& isFatalJitsiConferenceError(conferenceError)) && isFatalJitsiConferenceError(conferenceError))
|| configError
); );
} }
@ -253,10 +255,11 @@ export default class AbstractPageReloadOverlay extends Component<*, *> {
*/ */
export function abstractMapStateToProps(state: Object) { export function abstractMapStateToProps(state: Object) {
const conferenceError = state['features/base/conference'].error; const conferenceError = state['features/base/conference'].error;
const configError = state['features/base/config'].error;
const connectionError = state['features/base/connection'].error; const connectionError = state['features/base/connection'].error;
return { return {
isNetworkFailure: Boolean(connectionError), isNetworkFailure: Boolean(configError || connectionError),
reason: (connectionError || conferenceError).message reason: (configError || connectionError || conferenceError).message
}; };
} }