Browse Source

[RN] Prevent unhandled JS errors from killing the process in Release

master
Lyubomir Marinov 8 years ago
parent
commit
4fef8a3b79

+ 0
- 10
android/app/src/main/java/org/jitsi/meet/MainActivity.java View File

1
 package org.jitsi.meet;
1
 package org.jitsi.meet;
2
 
2
 
3
-import android.os.Bundle;
4
-import com.crashlytics.android.Crashlytics;
5
 import com.facebook.react.ReactActivity;
3
 import com.facebook.react.ReactActivity;
6
 import com.facebook.react.ReactActivityDelegate;
4
 import com.facebook.react.ReactActivityDelegate;
7
 import com.facebook.react.ReactRootView;
5
 import com.facebook.react.ReactRootView;
8
-import io.fabric.sdk.android.Fabric;
9
 
6
 
10
 public class MainActivity extends ReactActivity {
7
 public class MainActivity extends ReactActivity {
11
     /**
8
     /**
46
     protected String getMainComponentName() {
43
     protected String getMainComponentName() {
47
         return "App";
44
         return "App";
48
     }
45
     }
49
-
50
-    @Override
51
-    protected void onCreate(Bundle savedInstanceState) {
52
-        super.onCreate(savedInstanceState);
53
-
54
-        Fabric.with(this, new Crashlytics());
55
-    }
56
 }
46
 }

+ 41
- 20
android/app/src/main/java/org/jitsi/meet/MainApplication.java View File

1
 package org.jitsi.meet;
1
 package org.jitsi.meet;
2
 
2
 
3
 import android.app.Application;
3
 import android.app.Application;
4
-import android.util.Log;
5
 
4
 
5
+import com.crashlytics.android.Crashlytics;
6
 import com.facebook.react.ReactApplication;
6
 import com.facebook.react.ReactApplication;
7
-import com.facebook.react.ReactInstanceManager;
8
 import com.facebook.react.ReactNativeHost;
7
 import com.facebook.react.ReactNativeHost;
9
 import com.facebook.react.ReactPackage;
8
 import com.facebook.react.ReactPackage;
10
-import com.facebook.react.shell.MainReactPackage;
11
-import com.oblador.vectoricons.VectorIconsPackage;
12
-import com.oney.WebRTCModule.WebRTCModulePackage;
9
+
10
+import io.fabric.sdk.android.Fabric;
13
 
11
 
14
 import java.util.Arrays;
12
 import java.util.Arrays;
15
 import java.util.List;
13
 import java.util.List;
16
 
14
 
17
 public class MainApplication extends Application implements ReactApplication {
15
 public class MainApplication extends Application implements ReactApplication {
16
+    private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
17
+        /**
18
+         * {@inheritDoc}
19
+         */
20
+        @Override
21
+        protected boolean getUseDeveloperSupport() {
22
+            return BuildConfig.DEBUG;
23
+        }
24
+
25
+        /**
26
+         * {@inheritDoc}
27
+         */
28
+        @Override
29
+        protected List<ReactPackage> getPackages() {
30
+            return Arrays.<ReactPackage>asList(
31
+                new com.facebook.react.shell.MainReactPackage(),
32
+                new com.oblador.vectoricons.VectorIconsPackage(),
33
+                new com.oney.WebRTCModule.WebRTCModulePackage()
34
+            );
35
+        }
36
+    };
18
 
37
 
19
-  private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
38
+    /**
39
+     * {@inheritDoc}
40
+     */
20
     @Override
41
     @Override
21
-    protected boolean getUseDeveloperSupport() {
22
-      return BuildConfig.DEBUG;
42
+    public ReactNativeHost getReactNativeHost() {
43
+        return mReactNativeHost;
23
     }
44
     }
24
 
45
 
46
+    /**
47
+     * {@inheritDoc}
48
+     */
25
     @Override
49
     @Override
26
-    protected List<ReactPackage> getPackages() {
27
-      return Arrays.<ReactPackage>asList(
28
-          new MainReactPackage(),
29
-          new VectorIconsPackage(),
30
-          new WebRTCModulePackage()
31
-      );
32
-    }
33
-  };
50
+    public void onCreate() {
51
+        super.onCreate();
34
 
52
 
35
-  @Override
36
-  public ReactNativeHost getReactNativeHost() {
37
-      return mReactNativeHost;
38
-  }
53
+        if (!getReactNativeHost()
54
+                .getReactInstanceManager()
55
+                    .getDevSupportManager()
56
+                        .getDevSupportEnabled()) {
57
+            Fabric.with(this, new Crashlytics());
58
+        }
59
+    }
39
 }
60
 }

+ 35
- 0
ios/app/AppDelegate.m View File

10
 #import "AppDelegate.h"
10
 #import "AppDelegate.h"
11
 #import <Crashlytics/Crashlytics.h>
11
 #import <Crashlytics/Crashlytics.h>
12
 #import <Fabric/Fabric.h>
12
 #import <Fabric/Fabric.h>
13
+#import "RCTAssert.h"
13
 #import "RCTBundleURLProvider.h"
14
 #import "RCTBundleURLProvider.h"
14
 #import "RCTLinkingManager.h"
15
 #import "RCTLinkingManager.h"
15
 #import "RCTRootView.h"
16
 #import "RCTRootView.h"
16
 
17
 
18
+/**
19
+ * A <tt>RCTFatalHandler</tt> implementation which swallows JavaScript errors.
20
+ * In the Release configuration, React Native will (intentionally) raise an
21
+ * unhandled NSException for an unhandled JavaScript error. This will
22
+ * effectively kill the application. <tt>_RCTFatal</tt> is suitable to be in
23
+ * accord with the Web i.e. not kill the application.
24
+ */
25
+RCTFatalHandler _RCTFatal = ^(NSError *error) {
26
+  id jsStackTrace = error.userInfo[RCTJSStackTraceKey];
27
+  @try {
28
+    NSString *name
29
+      = [NSString stringWithFormat:@"%@: %@",
30
+                  RCTFatalExceptionName,
31
+                  error.localizedDescription];
32
+    NSString *message
33
+      = RCTFormatError(error.localizedDescription, jsStackTrace, 75);
34
+    [NSException raise:name format:@"%@", message];
35
+  } @catch (NSException *e) {
36
+    if (!jsStackTrace) {
37
+      @throw;
38
+    }
39
+  }
40
+};
41
+
17
 @implementation AppDelegate
42
 @implementation AppDelegate
18
 
43
 
19
 // https://facebook.github.io/react-native/docs/linking.html
44
 // https://facebook.github.io/react-native/docs/linking.html
29
 - (BOOL)application:(UIApplication *)application
54
 - (BOOL)application:(UIApplication *)application
30
 didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
55
 didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
31
 {
56
 {
57
+#if !DEBUG
32
   [Fabric with:@[[Crashlytics class]]];
58
   [Fabric with:@[[Crashlytics class]]];
33
 
59
 
60
+  // In the Release configuration, React Native will (intentionally) raise an
61
+  // unhandled NSException for an unhandled JavaScript error. This will
62
+  // effectively kill the application. In accord with the Web, do not kill the
63
+  // application.
64
+  if (!RCTGetFatalHandler()) {
65
+    RCTSetFatalHandler(_RCTFatal);
66
+  }
67
+#endif
68
+
34
   NSURL *jsCodeLocation
69
   NSURL *jsCodeLocation
35
     = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios"
70
     = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios"
36
                                                      fallbackResource:nil];
71
                                                      fallbackResource:nil];

+ 2
- 2
ios/app/POSIX.m View File

53
     if (0 != err) {
53
     if (0 != err) {
54
         NSError *error
54
         NSError *error
55
             = [NSError errorWithDomain:NSPOSIXErrorDomain
55
             = [NSError errorWithDomain:NSPOSIXErrorDomain
56
-                  code:err
57
-                  userInfo:nil];
56
+                                  code:err
57
+                              userInfo:nil];
58
 
58
 
59
         reject(rejectCode, error.localizedDescription, error);
59
         reject(rejectCode, error.localizedDescription, error);
60
     }
60
     }

+ 73
- 1
react/features/app/components/App.native.js View File

1
+/* global __DEV__ */
2
+
1
 import React from 'react';
3
 import React from 'react';
2
-import { Linking, Navigator } from 'react-native';
4
+import { Linking, Navigator, Platform } from 'react-native';
3
 import { Provider } from 'react-redux';
5
 import { Provider } from 'react-redux';
4
 
6
 
5
 import { _getRouteToRender } from '../functions';
7
 import { _getRouteToRender } from '../functions';
30
         // Bind event handlers so they are only bound once for every instance.
32
         // Bind event handlers so they are only bound once for every instance.
31
         this._navigatorRenderScene = this._navigatorRenderScene.bind(this);
33
         this._navigatorRenderScene = this._navigatorRenderScene.bind(this);
32
         this._onLinkingURL = this._onLinkingURL.bind(this);
34
         this._onLinkingURL = this._onLinkingURL.bind(this);
35
+
36
+        // In the Release configuration, React Native will (intentionally) throw
37
+        // an unhandled JavascriptException for an unhandled JavaScript error.
38
+        // This will effectively kill the application. In accord with the Web,
39
+        // do not kill the application.
40
+        this._maybeDisableExceptionsManager();
33
     }
41
     }
34
 
42
 
35
     /**
43
     /**
101
         navigator && navigator.replace({ ...route });
109
         navigator && navigator.replace({ ...route });
102
     }
110
     }
103
 
111
 
112
+    /**
113
+     * Attempts to disable the use of React Native
114
+     * {@link ExceptionsManager#handleException} on platforms and in
115
+     * configurations on/in which the use of the method in questions has been
116
+     * determined to be undesirable. For example, React Native will
117
+     * (intentionally) throw an unhandled JavascriptException for an
118
+     * unhandled JavaScript error in the Release configuration. This will
119
+     * effectively kill the application. In accord with the Web, do not kill the
120
+     * application.
121
+     *
122
+     * @private
123
+     * @returns {void}
124
+     */
125
+    _maybeDisableExceptionsManager() {
126
+        if (__DEV__) {
127
+            // As mentioned above, only the Release configuration was observed
128
+            // to suffer.
129
+            return;
130
+        }
131
+        if (Platform.OS !== 'android') {
132
+            // A solution based on RTCSetFatalHandler was implemented on iOS and
133
+            // it is preferred because it is at a later step of the
134
+            // error/exception handling and it is specific to fatal
135
+            // errors/exceptions which were observed to kill the application.
136
+            // The solution implemented bellow was tested on Android only so it
137
+            // is considered safest to use it there only.
138
+            return;
139
+        }
140
+
141
+        const oldHandler = global.ErrorUtils.getGlobalHandler();
142
+        const newHandler = _handleException;
143
+
144
+        if (!oldHandler || oldHandler !== newHandler) {
145
+            newHandler.next = oldHandler;
146
+            global.ErrorUtils.setGlobalHandler(newHandler);
147
+        }
148
+    }
149
+
104
     /**
150
     /**
105
      * Renders the scene identified by a specific route in the Navigator of this
151
      * Renders the scene identified by a specific route in the Navigator of this
106
      * instance.
152
      * instance.
133
         this._openURL(event.url);
179
         this._openURL(event.url);
134
     }
180
     }
135
 }
181
 }
182
+
183
+/**
184
+ * Handles a (possibly unhandled) JavaScript error by preventing React Native
185
+ * from converting a fatal error into an unhandled native exception which will
186
+ * kill the application.
187
+ *
188
+ * @param {Error} error - The (possibly unhandled) JavaScript error to handle.
189
+ * @param {boolean} fatal - True if the specified error is fatal; otherwise,
190
+ * false.
191
+ * @private
192
+ * @returns {void}
193
+ */
194
+function _handleException(error, fatal) {
195
+    if (fatal) {
196
+        // In the Release configuration, React Native will (intentionally) throw
197
+        // an unhandled JavascriptException for an unhandled JavaScript error.
198
+        // This will effectively kill the application. In accord with the Web,
199
+        // do not kill the application.
200
+        console.error(error);
201
+    } else {
202
+        // Forward to the next globalHandler of ErrorUtils.
203
+        const next = _handleException.next;
204
+
205
+        typeof next === 'function' && next(error, fatal);
206
+    }
207
+}

Loading…
Cancel
Save