Przeglądaj źródła

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

master
Lyubomir Marinov 8 lat temu
rodzic
commit
4fef8a3b79

+ 0
- 10
android/app/src/main/java/org/jitsi/meet/MainActivity.java Wyświetl plik

@@ -1,11 +1,8 @@
1 1
 package org.jitsi.meet;
2 2
 
3
-import android.os.Bundle;
4
-import com.crashlytics.android.Crashlytics;
5 3
 import com.facebook.react.ReactActivity;
6 4
 import com.facebook.react.ReactActivityDelegate;
7 5
 import com.facebook.react.ReactRootView;
8
-import io.fabric.sdk.android.Fabric;
9 6
 
10 7
 public class MainActivity extends ReactActivity {
11 8
     /**
@@ -46,11 +43,4 @@ public class MainActivity extends ReactActivity {
46 43
     protected String getMainComponentName() {
47 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 Wyświetl plik

@@ -1,39 +1,60 @@
1 1
 package org.jitsi.meet;
2 2
 
3 3
 import android.app.Application;
4
-import android.util.Log;
5 4
 
5
+import com.crashlytics.android.Crashlytics;
6 6
 import com.facebook.react.ReactApplication;
7
-import com.facebook.react.ReactInstanceManager;
8 7
 import com.facebook.react.ReactNativeHost;
9 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 12
 import java.util.Arrays;
15 13
 import java.util.List;
16 14
 
17 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 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 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 Wyświetl plik

@@ -10,10 +10,35 @@
10 10
 #import "AppDelegate.h"
11 11
 #import <Crashlytics/Crashlytics.h>
12 12
 #import <Fabric/Fabric.h>
13
+#import "RCTAssert.h"
13 14
 #import "RCTBundleURLProvider.h"
14 15
 #import "RCTLinkingManager.h"
15 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 42
 @implementation AppDelegate
18 43
 
19 44
 // https://facebook.github.io/react-native/docs/linking.html
@@ -29,8 +54,18 @@ continueUserActivity:(NSUserActivity *)userActivity
29 54
 - (BOOL)application:(UIApplication *)application
30 55
 didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
31 56
 {
57
+#if !DEBUG
32 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 69
   NSURL *jsCodeLocation
35 70
     = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios"
36 71
                                                      fallbackResource:nil];

+ 2
- 2
ios/app/POSIX.m Wyświetl plik

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

+ 73
- 1
react/features/app/components/App.native.js Wyświetl plik

@@ -1,5 +1,7 @@
1
+/* global __DEV__ */
2
+
1 3
 import React from 'react';
2
-import { Linking, Navigator } from 'react-native';
4
+import { Linking, Navigator, Platform } from 'react-native';
3 5
 import { Provider } from 'react-redux';
4 6
 
5 7
 import { _getRouteToRender } from '../functions';
@@ -30,6 +32,12 @@ export class App extends AbstractApp {
30 32
         // Bind event handlers so they are only bound once for every instance.
31 33
         this._navigatorRenderScene = this._navigatorRenderScene.bind(this);
32 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,6 +109,44 @@ export class App extends AbstractApp {
101 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 151
      * Renders the scene identified by a specific route in the Navigator of this
106 152
      * instance.
@@ -133,3 +179,29 @@ export class App extends AbstractApp {
133 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
+}

Ładowanie…
Anuluj
Zapisz