[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;
|
package org.jitsi.meet;
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import com.crashlytics.android.Crashlytics;
|
|
||||||
import com.facebook.react.ReactActivity;
|
import com.facebook.react.ReactActivity;
|
||||||
import com.facebook.react.ReactActivityDelegate;
|
import com.facebook.react.ReactActivityDelegate;
|
||||||
import com.facebook.react.ReactRootView;
|
import com.facebook.react.ReactRootView;
|
||||||
import io.fabric.sdk.android.Fabric;
|
|
||||||
|
|
||||||
public class MainActivity extends ReactActivity {
|
public class MainActivity extends ReactActivity {
|
||||||
/**
|
/**
|
||||||
|
@ -46,11 +43,4 @@ public class MainActivity extends ReactActivity {
|
||||||
protected String getMainComponentName() {
|
protected String getMainComponentName() {
|
||||||
return "App";
|
return "App";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
Fabric.with(this, new Crashlytics());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +1,60 @@
|
||||||
package org.jitsi.meet;
|
package org.jitsi.meet;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
|
import com.crashlytics.android.Crashlytics;
|
||||||
import com.facebook.react.ReactApplication;
|
import com.facebook.react.ReactApplication;
|
||||||
import com.facebook.react.ReactInstanceManager;
|
|
||||||
import com.facebook.react.ReactNativeHost;
|
import com.facebook.react.ReactNativeHost;
|
||||||
import com.facebook.react.ReactPackage;
|
import com.facebook.react.ReactPackage;
|
||||||
import com.facebook.react.shell.MainReactPackage;
|
|
||||||
import com.oblador.vectoricons.VectorIconsPackage;
|
import io.fabric.sdk.android.Fabric;
|
||||||
import com.oney.WebRTCModule.WebRTCModulePackage;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class MainApplication extends Application implements ReactApplication {
|
public class MainApplication extends Application implements ReactApplication {
|
||||||
|
|
||||||
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
|
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean getUseDeveloperSupport() {
|
protected boolean getUseDeveloperSupport() {
|
||||||
return BuildConfig.DEBUG;
|
return BuildConfig.DEBUG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected List<ReactPackage> getPackages() {
|
protected List<ReactPackage> getPackages() {
|
||||||
return Arrays.<ReactPackage>asList(
|
return Arrays.<ReactPackage>asList(
|
||||||
new MainReactPackage(),
|
new com.facebook.react.shell.MainReactPackage(),
|
||||||
new VectorIconsPackage(),
|
new com.oblador.vectoricons.VectorIconsPackage(),
|
||||||
new WebRTCModulePackage()
|
new com.oney.WebRTCModule.WebRTCModulePackage()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public ReactNativeHost getReactNativeHost() {
|
public ReactNativeHost getReactNativeHost() {
|
||||||
return mReactNativeHost;
|
return mReactNativeHost;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
|
||||||
|
if (!getReactNativeHost()
|
||||||
|
.getReactInstanceManager()
|
||||||
|
.getDevSupportManager()
|
||||||
|
.getDevSupportEnabled()) {
|
||||||
|
Fabric.with(this, new Crashlytics());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,10 +10,35 @@
|
||||||
#import "AppDelegate.h"
|
#import "AppDelegate.h"
|
||||||
#import <Crashlytics/Crashlytics.h>
|
#import <Crashlytics/Crashlytics.h>
|
||||||
#import <Fabric/Fabric.h>
|
#import <Fabric/Fabric.h>
|
||||||
|
#import "RCTAssert.h"
|
||||||
#import "RCTBundleURLProvider.h"
|
#import "RCTBundleURLProvider.h"
|
||||||
#import "RCTLinkingManager.h"
|
#import "RCTLinkingManager.h"
|
||||||
#import "RCTRootView.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
|
@implementation AppDelegate
|
||||||
|
|
||||||
// https://facebook.github.io/react-native/docs/linking.html
|
// https://facebook.github.io/react-native/docs/linking.html
|
||||||
|
@ -29,8 +54,18 @@ continueUserActivity:(NSUserActivity *)userActivity
|
||||||
- (BOOL)application:(UIApplication *)application
|
- (BOOL)application:(UIApplication *)application
|
||||||
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||||
{
|
{
|
||||||
|
#if !DEBUG
|
||||||
[Fabric with:@[[Crashlytics class]]];
|
[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
|
NSURL *jsCodeLocation
|
||||||
= [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios"
|
= [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios"
|
||||||
fallbackResource:nil];
|
fallbackResource:nil];
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
/* global __DEV__ */
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Linking, Navigator } from 'react-native';
|
import { Linking, Navigator, Platform } from 'react-native';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
import { _getRouteToRender } from '../functions';
|
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.
|
// Bind event handlers so they are only bound once for every instance.
|
||||||
this._navigatorRenderScene = this._navigatorRenderScene.bind(this);
|
this._navigatorRenderScene = this._navigatorRenderScene.bind(this);
|
||||||
this._onLinkingURL = this._onLinkingURL.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 });
|
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
|
* Renders the scene identified by a specific route in the Navigator of this
|
||||||
* instance.
|
* instance.
|
||||||
|
@ -133,3 +179,29 @@ export class App extends AbstractApp {
|
||||||
this._openURL(event.url);
|
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