Up until now we relied on implicit loading of middlewares and reducers, through having imports in each feature's index.js. This leads to many complex import cycles which result in (sometimes) hard to fix bugs in addition to (often) breaking mobile because a web-only feature gets imported on mobile too, thanks to the implicit loading. This PR changes that to make the process explicit. Both middlewares and reducers are imported in a single place, the app entrypoint. They have been divided into 3 categories: any, web and native, which represent each of the platforms respectively. Ideally no feature should have an index.js exporting actions, action types and components, but that's a larger ordeal, so this is just the first step in getting there. In order to both set example and avoid large cycles the app feature has been refactored to not have an idex.js itself.
207 lines
6.3 KiB
JavaScript
207 lines
6.3 KiB
JavaScript
// @flow
|
|
|
|
import React from 'react';
|
|
|
|
import { setColorScheme } from '../../base/color-scheme';
|
|
import { DialogContainer } from '../../base/dialog';
|
|
import { CALL_INTEGRATION_ENABLED, updateFlags } from '../../base/flags';
|
|
import { Platform } from '../../base/react';
|
|
import { DimensionsDetector, clientResized } from '../../base/responsive-ui';
|
|
import { updateSettings } from '../../base/settings';
|
|
import logger from '../logger';
|
|
|
|
import { AbstractApp } from './AbstractApp';
|
|
import type { Props as AbstractAppProps } from './AbstractApp';
|
|
|
|
// Register middlewares and reducers.
|
|
import '../middlewares';
|
|
import '../reducers';
|
|
|
|
declare var __DEV__;
|
|
|
|
/**
|
|
* The type of React {@code Component} props of {@link App}.
|
|
*/
|
|
type Props = AbstractAppProps & {
|
|
|
|
/**
|
|
* An object of colors that override the default colors of the app/sdk.
|
|
*/
|
|
colorScheme: ?Object,
|
|
|
|
/**
|
|
* Identifier for this app on the native side.
|
|
*/
|
|
externalAPIScope: string,
|
|
|
|
/**
|
|
* An object with the feature flags.
|
|
*/
|
|
flags: Object,
|
|
|
|
/**
|
|
* An object with user information (display name, email, avatar URL).
|
|
*/
|
|
userInfo: ?Object
|
|
};
|
|
|
|
/**
|
|
* Root app {@code Component} on mobile/React Native.
|
|
*
|
|
* @extends AbstractApp
|
|
*/
|
|
export class App extends AbstractApp {
|
|
_init: Promise<*>;
|
|
|
|
/**
|
|
* Initializes a new {@code App} instance.
|
|
*
|
|
* @param {Props} props - The read-only React {@code Component} props with
|
|
* which the new instance is to be initialized.
|
|
*/
|
|
constructor(props: Props) {
|
|
super(props);
|
|
|
|
// In the Release configuration, React Native will (intentionally) throw
|
|
// an unhandled JavascriptException for an unhandled JavaScript error.
|
|
// This will effectively kill the app. In accord with the Web, do not
|
|
// kill the app.
|
|
this._maybeDisableExceptionsManager();
|
|
|
|
// Bind event handler so it is only bound once per instance.
|
|
this._onDimensionsChanged = this._onDimensionsChanged.bind(this);
|
|
}
|
|
|
|
/**
|
|
* Initializes the color scheme.
|
|
*
|
|
* @inheritdoc
|
|
*
|
|
* @returns {void}
|
|
*/
|
|
componentDidMount() {
|
|
super.componentDidMount();
|
|
|
|
this._init.then(() => {
|
|
// We set these early enough so then we avoid any unnecessary re-renders.
|
|
const { dispatch } = this.state.store;
|
|
|
|
dispatch(setColorScheme(this.props.colorScheme));
|
|
dispatch(updateFlags(this.props.flags));
|
|
dispatch(updateSettings(this.props.userInfo || {}));
|
|
|
|
// Update settings with feature-flag.
|
|
const callIntegrationEnabled = this.props.flags[CALL_INTEGRATION_ENABLED];
|
|
|
|
if (typeof callIntegrationEnabled !== 'undefined') {
|
|
dispatch(updateSettings({ disableCallIntegration: !callIntegrationEnabled }));
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Overrides the parent method to inject {@link DimensionsDetector} as
|
|
* the top most component.
|
|
*
|
|
* @override
|
|
*/
|
|
_createMainElement(component, props) {
|
|
return (
|
|
<DimensionsDetector
|
|
onDimensionsChanged = { this._onDimensionsChanged }>
|
|
{ super._createMainElement(component, props) }
|
|
</DimensionsDetector>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Attempts to disable the use of React Native
|
|
* {@link ExceptionsManager#handleException} on platforms and in
|
|
* configurations on/in which the use of the method in questions has been
|
|
* determined to be undesirable. For example, React Native will
|
|
* (intentionally) throw an unhandled {@code JavascriptException} for an
|
|
* unhandled JavaScript error in the Release configuration. This will
|
|
* effectively kill the app. In accord with the Web, do not kill the app.
|
|
*
|
|
* @private
|
|
* @returns {void}
|
|
*/
|
|
_maybeDisableExceptionsManager() {
|
|
if (__DEV__) {
|
|
// As mentioned above, only the Release configuration was observed
|
|
// to suffer.
|
|
return;
|
|
}
|
|
if (Platform.OS !== 'android') {
|
|
// A solution based on RTCSetFatalHandler was implemented on iOS and
|
|
// it is preferred because it is at a later step of the
|
|
// error/exception handling and it is specific to fatal
|
|
// errors/exceptions which were observed to kill the app. The
|
|
// solution implemented bellow was tested on Android only so it is
|
|
// considered safest to use it there only.
|
|
return;
|
|
}
|
|
|
|
const oldHandler = global.ErrorUtils.getGlobalHandler();
|
|
const newHandler = _handleException;
|
|
|
|
if (!oldHandler || oldHandler !== newHandler) {
|
|
newHandler.next = oldHandler;
|
|
global.ErrorUtils.setGlobalHandler(newHandler);
|
|
}
|
|
}
|
|
|
|
_onDimensionsChanged: (width: number, height: number) => void;
|
|
|
|
/**
|
|
* Updates the known available size for the app to occupy.
|
|
*
|
|
* @param {number} width - The component's current width.
|
|
* @param {number} height - The component's current height.
|
|
* @private
|
|
* @returns {void}
|
|
*/
|
|
_onDimensionsChanged(width: number, height: number) {
|
|
const { dispatch } = this.state.store;
|
|
|
|
dispatch(clientResized(width, height));
|
|
}
|
|
|
|
/**
|
|
* Renders the platform specific dialog container.
|
|
*
|
|
* @returns {React$Element}
|
|
*/
|
|
_renderDialogContainer() {
|
|
return (
|
|
<DialogContainer />
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles a (possibly unhandled) JavaScript error by preventing React Native
|
|
* from converting a fatal error into an unhandled native exception which will
|
|
* kill the app.
|
|
*
|
|
* @param {Error} error - The (possibly unhandled) JavaScript error to handle.
|
|
* @param {boolean} fatal - If the specified error is fatal, {@code true};
|
|
* otherwise, {@code false}.
|
|
* @private
|
|
* @returns {void}
|
|
*/
|
|
function _handleException(error, fatal) {
|
|
if (fatal) {
|
|
// In the Release configuration, React Native will (intentionally) throw
|
|
// an unhandled JavascriptException for an unhandled JavaScript error.
|
|
// This will effectively kill the app. In accord with the Web, do not
|
|
// kill the app.
|
|
logger.error(error);
|
|
} else {
|
|
// Forward to the next globalHandler of ErrorUtils.
|
|
const { next } = _handleException;
|
|
|
|
typeof next === 'function' && next(error, fatal);
|
|
}
|
|
}
|