From 67d7d4fc1484038ed72356c33b1edf56c1a47540 Mon Sep 17 00:00:00 2001 From: paweldomas Date: Tue, 26 Jun 2018 14:18:29 +0200 Subject: [PATCH] feat(RN): add a fatal error state which is a catch all Adds a fatal error state on which will depend whether or not the reload screen is to be displayed. It is to happen when a relevant fatal error action is not claimed by any feature for error recovery (the recoverable flag is not set). --- react/features/overlay/actionTypes.js | 12 ++++++++ react/features/overlay/actions.js | 22 +++++++++++++++ .../components/AbstractPageReloadOverlay.js | 28 ++++++++++++++++--- .../components/PageReloadOverlay.native.js | 4 +-- react/features/overlay/index.js | 1 + react/features/overlay/middleware.js | 27 ++++++++++++++++++ react/features/overlay/reducer.js | 20 ++++++++++++- 7 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 react/features/overlay/middleware.js diff --git a/react/features/overlay/actionTypes.js b/react/features/overlay/actionTypes.js index 55baf662c..521844027 100644 --- a/react/features/overlay/actionTypes.js +++ b/react/features/overlay/actionTypes.js @@ -21,3 +21,15 @@ export const MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED * @public */ export const SUSPEND_DETECTED = Symbol('SUSPEND_DETECTED'); + +/** + * Adjust the state of the fatal error which shows/hides the reload screen. See + * action methods's description for more info about each of the fields. + * + * { + * type: SET_FATAL_ERROR, + * fatalError: ?Object + * } + * @public + */ +export const SET_FATAL_ERROR = Symbol('SET_FATAL_ERROR'); diff --git a/react/features/overlay/actions.js b/react/features/overlay/actions.js index 6ea7020cd..b8a15529f 100644 --- a/react/features/overlay/actions.js +++ b/react/features/overlay/actions.js @@ -3,6 +3,7 @@ import { toURLString } from '../base/util'; import { MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED, + SET_FATAL_ERROR, SUSPEND_DETECTED } from './actionTypes'; @@ -37,6 +38,8 @@ export function mediaPermissionPromptVisibilityChanged(isVisible, browser) { */ export function _reloadNow() { return (dispatch, getState) => { + dispatch(setFatalError(undefined)); + const { locationURL } = getState()['features/base/connection']; logger.info(`Reloading the conference using URL: ${locationURL}`); @@ -62,3 +65,22 @@ export function suspendDetected() { type: SUSPEND_DETECTED }; } + +/** + * The action indicates that an unrecoverable error has occurred and the reload + * screen will be displayed or hidden. + * + * @param {Object} fatalError - A critical error which was not claimed by any + * feature for error recovery (the recoverable flag was not set). If + * {@code undefined} then any fatal error currently stored will be discarded. + * @returns {{ + * type: SET_FATAL_ERROR, + * fatalError: ?Error + * }} + */ +export function setFatalError(fatalError) { + return { + type: SET_FATAL_ERROR, + fatalError + }; +} diff --git a/react/features/overlay/components/AbstractPageReloadOverlay.js b/react/features/overlay/components/AbstractPageReloadOverlay.js index d78d3d0d5..f161a2fa4 100644 --- a/react/features/overlay/components/AbstractPageReloadOverlay.js +++ b/react/features/overlay/components/AbstractPageReloadOverlay.js @@ -77,6 +77,25 @@ export default class AbstractPageReloadOverlay extends Component<*, *> { * {@code false}, otherwise. */ static needsRender(state: Object) { + // FIXME web does not rely on the 'recoverable' flag set on an error + // action, but on a predefined list of fatal errors. Because of that + // the value of 'fatalError' which relies on the flag should not be used + // on web yet (until conference/connection and their errors handling is + // not unified). + return typeof APP === 'undefined' + ? Boolean(state['features/overlay'].fatalError) + : this.needsRenderWeb(state); + } + + /** + * Determines whether this overlay needs to be rendered (according to a + * specific redux state). Called by {@link OverlayContainer}. + * + * @param {Object} state - The redux state. + * @returns {boolean} - If this overlay needs to be rendered, {@code true}; + * {@code false}, otherwise. + */ + static needsRenderWeb(state: Object) { const conferenceError = state['features/base/conference'].error; const configError = state['features/base/config'].error; const connectionError = state['features/base/connection'].error; @@ -273,13 +292,14 @@ export default class AbstractPageReloadOverlay extends Component<*, *> { * }} */ export function abstractMapStateToProps(state: Object) { - const { error: conferenceError } = state['features/base/conference']; const { error: configError } = state['features/base/config']; const { error: connectionError } = state['features/base/connection']; + const { fatalError } = state['features/overlay']; return { - details: connectionError ? connectionError.details : undefined, - isNetworkFailure: Boolean(configError || connectionError), - reason: (configError || connectionError || conferenceError).message + details: fatalError && fatalError.details, + isNetworkFailure: + fatalError === configError || fatalError === connectionError, + reason: fatalError && fatalError.message }; } diff --git a/react/features/overlay/components/PageReloadOverlay.native.js b/react/features/overlay/components/PageReloadOverlay.native.js index 7a43b86d8..6f3d02a66 100644 --- a/react/features/overlay/components/PageReloadOverlay.native.js +++ b/react/features/overlay/components/PageReloadOverlay.native.js @@ -6,10 +6,9 @@ import { appNavigate } from '../../app'; import { translate } from '../../base/i18n'; import { LoadingIndicator } from '../../base/react'; -import { _reloadNow } from '../actions'; - import AbstractPageReloadOverlay, { abstractMapStateToProps } from './AbstractPageReloadOverlay'; +import { _reloadNow, setFatalError } from '../actions'; import OverlayFrame from './OverlayFrame'; import { pageReloadOverlay as styles } from './styles'; @@ -42,6 +41,7 @@ class PageReloadOverlay extends AbstractPageReloadOverlay { */ _onCancel() { clearInterval(this._interval); + this.props.dispatch(setFatalError(undefined)); this.props.dispatch(appNavigate(undefined)); } diff --git a/react/features/overlay/index.js b/react/features/overlay/index.js index 582e1f9dd..7f0ef0251 100644 --- a/react/features/overlay/index.js +++ b/react/features/overlay/index.js @@ -1,4 +1,5 @@ export * from './actions'; export * from './components'; +import './middleware'; import './reducer'; diff --git a/react/features/overlay/middleware.js b/react/features/overlay/middleware.js new file mode 100644 index 000000000..d0615eecd --- /dev/null +++ b/react/features/overlay/middleware.js @@ -0,0 +1,27 @@ +// @flow + +import { StateListenerRegistry } from '../base/redux'; + +import { setFatalError } from './actions'; + +declare var APP: Object; + +/** + * State listener which emits the {@code fatalErrorOccurred} action which works + * as a catch all for critical errors which have not been claimed by any other + * feature for error recovery (the recoverable flag is not set). + */ +StateListenerRegistry.register( + /* selector */ state => { + const { error: conferenceError } = state['features/base/conference']; + const { error: configError } = state['features/base/config']; + const { error: connectionError } = state['features/base/connection']; + + return configError || connectionError || conferenceError; + }, + /* listener */ (error, { dispatch }) => { + error + && typeof error.recoverable === 'undefined' + && dispatch(setFatalError(error)); + } +); diff --git a/react/features/overlay/reducer.js b/react/features/overlay/reducer.js index 634ca34c3..d15eb5003 100644 --- a/react/features/overlay/reducer.js +++ b/react/features/overlay/reducer.js @@ -4,6 +4,7 @@ import { assign, ReducerRegistry, set } from '../base/redux'; import { MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED, + SET_FATAL_ERROR, SUSPEND_DETECTED } from './actionTypes'; @@ -12,11 +13,14 @@ import { * * FIXME: these pieces of state should probably be in a different place. */ -ReducerRegistry.register('features/overlay', (state = {}, action) => { +ReducerRegistry.register('features/overlay', (state = { }, action) => { switch (action.type) { case MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED: return _mediaPermissionPromptVisibilityChanged(state, action); + case SET_FATAL_ERROR: + return _setFatalError(state, action); + case SUSPEND_DETECTED: return _suspendDetected(state); } @@ -54,3 +58,17 @@ function _mediaPermissionPromptVisibilityChanged( function _suspendDetected(state) { return set(state, 'suspendDetected', true); } + +/** + * Reduces a specific redux action {@code SET_FATAL_ERROR} of the feature + * overlay. + * + * @param {Object} state - The redux state of the feature overlay. + * @param {Error} fatalError - If the value is set it indicates that a fatal + * error has occurred and that the reload screen is to be displayed. + * @returns {Object} + * @private + */ +function _setFatalError(state, { fatalError }) { + return set(state, 'fatalError', fatalError); +}