[RN] Prevent unhandled JS errors from killing the process in Release
This commit is contained in:
parent
29001c3ab0
commit
4fef8a3b79
|
@ -1,11 +1,8 @@
|
|||
package org.jitsi.meet;
|
||||
|
||||
import android.os.Bundle;
|
||||
import com.crashlytics.android.Crashlytics;
|
||||
import com.facebook.react.ReactActivity;
|
||||
import com.facebook.react.ReactActivityDelegate;
|
||||
import com.facebook.react.ReactRootView;
|
||||
import io.fabric.sdk.android.Fabric;
|
||||
|
||||
public class MainActivity extends ReactActivity {
|
||||
/**
|
||||
|
@ -46,11 +43,4 @@ public class MainActivity extends ReactActivity {
|
|||
protected String getMainComponentName() {
|
||||
return "App";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Fabric.with(this, new Crashlytics());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,39 +1,60 @@
|
|||
package org.jitsi.meet;
|
||||
|
||||
import android.app.Application;
|
||||
import android.util.Log;
|
||||
|
||||
import com.crashlytics.android.Crashlytics;
|
||||
import com.facebook.react.ReactApplication;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.ReactNativeHost;
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.shell.MainReactPackage;
|
||||
import com.oblador.vectoricons.VectorIconsPackage;
|
||||
import com.oney.WebRTCModule.WebRTCModulePackage;
|
||||
|
||||
import io.fabric.sdk.android.Fabric;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class MainApplication extends Application implements ReactApplication {
|
||||
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected boolean getUseDeveloperSupport() {
|
||||
return BuildConfig.DEBUG;
|
||||
}
|
||||
|
||||
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected List<ReactPackage> getPackages() {
|
||||
return Arrays.<ReactPackage>asList(
|
||||
new com.facebook.react.shell.MainReactPackage(),
|
||||
new com.oblador.vectoricons.VectorIconsPackage(),
|
||||
new com.oney.WebRTCModule.WebRTCModulePackage()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected boolean getUseDeveloperSupport() {
|
||||
return BuildConfig.DEBUG;
|
||||
public ReactNativeHost getReactNativeHost() {
|
||||
return mReactNativeHost;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected List<ReactPackage> getPackages() {
|
||||
return Arrays.<ReactPackage>asList(
|
||||
new MainReactPackage(),
|
||||
new VectorIconsPackage(),
|
||||
new WebRTCModulePackage()
|
||||
);
|
||||
}
|
||||
};
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
@Override
|
||||
public ReactNativeHost getReactNativeHost() {
|
||||
return mReactNativeHost;
|
||||
}
|
||||
if (!getReactNativeHost()
|
||||
.getReactInstanceManager()
|
||||
.getDevSupportManager()
|
||||
.getDevSupportEnabled()) {
|
||||
Fabric.with(this, new Crashlytics());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,10 +10,35 @@
|
|||
#import "AppDelegate.h"
|
||||
#import <Crashlytics/Crashlytics.h>
|
||||
#import <Fabric/Fabric.h>
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTBundleURLProvider.h"
|
||||
#import "RCTLinkingManager.h"
|
||||
#import "RCTRootView.h"
|
||||
|
||||
/**
|
||||
* A <tt>RCTFatalHandler</tt> implementation which swallows JavaScript errors.
|
||||
* In the Release configuration, React Native will (intentionally) raise an
|
||||
* unhandled NSException for an unhandled JavaScript error. This will
|
||||
* effectively kill the application. <tt>_RCTFatal</tt> is suitable to be in
|
||||
* accord with the Web i.e. not kill the application.
|
||||
*/
|
||||
RCTFatalHandler _RCTFatal = ^(NSError *error) {
|
||||
id jsStackTrace = error.userInfo[RCTJSStackTraceKey];
|
||||
@try {
|
||||
NSString *name
|
||||
= [NSString stringWithFormat:@"%@: %@",
|
||||
RCTFatalExceptionName,
|
||||
error.localizedDescription];
|
||||
NSString *message
|
||||
= RCTFormatError(error.localizedDescription, jsStackTrace, 75);
|
||||
[NSException raise:name format:@"%@", message];
|
||||
} @catch (NSException *e) {
|
||||
if (!jsStackTrace) {
|
||||
@throw;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
// https://facebook.github.io/react-native/docs/linking.html
|
||||
|
@ -29,8 +54,18 @@ continueUserActivity:(NSUserActivity *)userActivity
|
|||
- (BOOL)application:(UIApplication *)application
|
||||
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
#if !DEBUG
|
||||
[Fabric with:@[[Crashlytics class]]];
|
||||
|
||||
// In the Release configuration, React Native will (intentionally) raise an
|
||||
// unhandled NSException for an unhandled JavaScript error. This will
|
||||
// effectively kill the application. In accord with the Web, do not kill the
|
||||
// application.
|
||||
if (!RCTGetFatalHandler()) {
|
||||
RCTSetFatalHandler(_RCTFatal);
|
||||
}
|
||||
#endif
|
||||
|
||||
NSURL *jsCodeLocation
|
||||
= [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios"
|
||||
fallbackResource:nil];
|
||||
|
|
|
@ -53,8 +53,8 @@ RCT_EXPORT_METHOD(getaddrinfo:(NSString *)hostname
|
|||
if (0 != err) {
|
||||
NSError *error
|
||||
= [NSError errorWithDomain:NSPOSIXErrorDomain
|
||||
code:err
|
||||
userInfo:nil];
|
||||
code:err
|
||||
userInfo:nil];
|
||||
|
||||
reject(rejectCode, error.localizedDescription, error);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
/* global __DEV__ */
|
||||
|
||||
import React from 'react';
|
||||
import { Linking, Navigator } from 'react-native';
|
||||
import { Linking, Navigator, Platform } from 'react-native';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { _getRouteToRender } from '../functions';
|
||||
|
@ -30,6 +32,12 @@ export class App extends AbstractApp {
|
|||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._navigatorRenderScene = this._navigatorRenderScene.bind(this);
|
||||
this._onLinkingURL = this._onLinkingURL.bind(this);
|
||||
|
||||
// In the Release configuration, React Native will (intentionally) throw
|
||||
// an unhandled JavascriptException for an unhandled JavaScript error.
|
||||
// This will effectively kill the application. In accord with the Web,
|
||||
// do not kill the application.
|
||||
this._maybeDisableExceptionsManager();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -101,6 +109,44 @@ export class App extends AbstractApp {
|
|||
navigator && navigator.replace({ ...route });
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 JavascriptException for an
|
||||
* unhandled JavaScript error in the Release configuration. This will
|
||||
* effectively kill the application. In accord with the Web, do not kill the
|
||||
* application.
|
||||
*
|
||||
* @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 application.
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the scene identified by a specific route in the Navigator of this
|
||||
* instance.
|
||||
|
@ -133,3 +179,29 @@ export class App extends AbstractApp {
|
|||
this._openURL(event.url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a (possibly unhandled) JavaScript error by preventing React Native
|
||||
* from converting a fatal error into an unhandled native exception which will
|
||||
* kill the application.
|
||||
*
|
||||
* @param {Error} error - The (possibly unhandled) JavaScript error to handle.
|
||||
* @param {boolean} fatal - True if the specified error is fatal; otherwise,
|
||||
* 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 application. In accord with the Web,
|
||||
// do not kill the application.
|
||||
console.error(error);
|
||||
} else {
|
||||
// Forward to the next globalHandler of ErrorUtils.
|
||||
const next = _handleException.next;
|
||||
|
||||
typeof next === 'function' && next(error, fatal);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue