Skip to content

Commit 5b9ec8e

Browse files
committed
✨ React Native JS Crash Reporter
1 parent fec5cf4 commit 5b9ec8e

File tree

5 files changed

+187
-2
lines changed

5 files changed

+187
-2
lines changed

android/src/main/java/com/instabug/reactlibrary/RNInstabugReactnativeModule.java

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@
3939
import com.instabug.reactlibrary.utils.ArrayUtil;
4040
import com.instabug.reactlibrary.utils.MapUtil;
4141

42+
import org.json.JSONException;
43+
import org.json.JSONObject;
44+
45+
import java.lang.reflect.InvocationTargetException;
46+
import java.lang.reflect.Method;
4247
import java.util.ArrayList;
4348
import java.util.Arrays;
4449
import java.util.HashMap;
@@ -368,6 +373,82 @@ public void setFileAttachment(String fileUri, String fileNameWithExtension) {
368373
}
369374
}
370375

376+
/**
377+
* Send unhandled JS error object
378+
*
379+
* @param exceptionObject Exception object to be sent to Instabug's servers
380+
*/
381+
@ReactMethod
382+
public void sendJSCrash(String exceptionObject) {
383+
try {
384+
sendJSCrashByReflection(exceptionObject, false);
385+
} catch (Exception e) {
386+
e.printStackTrace();
387+
}
388+
}
389+
390+
/**
391+
* Send handled JS error object
392+
*
393+
* @param exceptionObject Exception object to be sent to Instabug's servers
394+
*/
395+
@ReactMethod
396+
public void sendHandledJSCrash(String exceptionObject) {
397+
try {
398+
sendJSCrashByReflection(exceptionObject, true);
399+
} catch (Exception e) {
400+
e.printStackTrace();
401+
}
402+
}
403+
404+
/**
405+
* Sets whether crash reporting feature is Enabled or Disabled
406+
*
407+
* @param isEnabled Exception object to be sent to Instabug's servers
408+
*/
409+
@ReactMethod
410+
public void setCrashReportingEnabled(boolean isEnabled) {
411+
try {
412+
if(isEnabled) {
413+
Instabug.setCrashReportingState(Feature.State.ENABLED);
414+
} else {
415+
Instabug.setCrashReportingState(Feature.State.DISABLED);
416+
}
417+
} catch (Exception e) {
418+
e.printStackTrace();
419+
}
420+
}
421+
422+
private void sendJSCrashByReflection(String exceptionObject, boolean isHandled) {
423+
try {
424+
JSONObject newJSONObject = new JSONObject(exceptionObject);
425+
Method method = getMethod(Class.forName("com.instabug.crash.InstabugCrash"), "reportException");
426+
if (method != null) {
427+
method.invoke(null, newJSONObject, isHandled);
428+
}
429+
} catch (ClassNotFoundException e) {
430+
e.printStackTrace();
431+
} catch (InvocationTargetException e) {
432+
e.printStackTrace();
433+
} catch (IllegalAccessException e) {
434+
e.printStackTrace();
435+
} catch (JSONException e) {
436+
e.printStackTrace();
437+
}
438+
439+
}
440+
441+
public static Method getMethod(Class clazz, String methodName) {
442+
final Method[] methods = clazz.getDeclaredMethods();
443+
for (Method method : methods) {
444+
if (method.getName().equals(methodName)) {
445+
method.setAccessible(true);
446+
return method;
447+
}
448+
}
449+
return null;
450+
}
451+
371452
/**
372453
* If your app already acquires the user's email address and you provide it to this method,
373454
* Instabug will pre-fill the user email in reports.

android/src/main/java/com/instabug/reactlibrary/RNInstabugReactnativePackage.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public RNInstabugReactnativePackage(String androidApplicationToken, Application
5656

5757
mInstabug = new Instabug.Builder(this.androidApplication, this.mAndroidApplicationToken)
5858
.setInvocationEvent(this.invocationEvent)
59-
.setCrashReportingState(Feature.State.DISABLED)
59+
.setCrashReportingState(Feature.State.ENABLED)
6060
.setReproStepsState(State.DISABLED)
6161
.build();
6262

index.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import {NativeModules, NativeAppEventEmitter, DeviceEventEmitter, Platform} from "react-native";
22
let {Instabug} = NativeModules;
3+
import InstabugUtils from './utils/InstabugUtils.js';
4+
5+
InstabugUtils.captureJsErrors();
36

47
/**
58
* Instabug
@@ -101,6 +104,14 @@ module.exports = {
101104
Instabug.setUserStepsEnabled(isUserStepsEnabled);
102105
},
103106

107+
/**
108+
* Report un-caught exceptions to Instabug dashboard
109+
* We don't send exceptions from __DEV__, since it's way too noisy!
110+
*/
111+
setCrashReportingEnabled: function(enableCrashReporter){
112+
Instabug.setCrashReportingEnabled(enableCrashReporter);
113+
},
114+
104115
/**
105116
* Sets a block of code to be executed before sending each report.
106117
* This block is executed in the background before sending each report. Could
@@ -899,6 +910,26 @@ module.exports = {
899910
}
900911
},
901912

913+
/**
914+
* Send handled JS error object
915+
*
916+
* @param errorObject Error object to be sent to Instabug's servers
917+
*/
918+
reportJSException: function(errorObject) {
919+
let jsStackTrace = InstabugUtils.parseErrorStack(errorObject);
920+
var jsonObject = {
921+
message: errorObject.name + " - " + errorObject.message,
922+
os: Platform.OS,
923+
platform: 'react_native',
924+
exception: jsStackTrace
925+
}
926+
if(Platform.OS === 'android') {
927+
Instabug.sendHandledJSCrash(JSON.stringify(jsonObject));
928+
} else {
929+
Instabug.sendHandledJSCrash(jsonObject);
930+
}
931+
},
932+
902933
/**
903934
* Sets the default position at which the Instabug screen recording button will be shown.
904935
* Different orientations are already handled.

ios/RNInstabug/InstabugReactBridge.m

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,11 @@ - (dispatch_queue_t)methodQueue {
3434
[Instabug startWithToken:token invocationEvent:invocationEvent];
3535
RCTAddLogFunction(InstabugReactLogFunction);
3636
RCTSetLogThreshold(RCTLogLevelInfo);
37-
[Instabug setCrashReportingEnabled:NO];
3837
[Instabug setNetworkLoggingEnabled:NO];
38+
SEL setCrossPlatformSEL = NSSelectorFromString(@"setCrossPlatform:");
39+
if ([[Instabug class] respondsToSelector:setCrossPlatformSEL]) {
40+
[[Instabug class] performSelector:setCrossPlatformSEL withObject:@(true)];
41+
}
3942
}
4043

4144
RCT_EXPORT_METHOD(invoke) {
@@ -58,6 +61,20 @@ - (dispatch_queue_t)methodQueue {
5861
[Instabug setFileAttachment:fileLocation];
5962
}
6063

64+
RCT_EXPORT_METHOD(sendJSCrash:(NSDictionary *)stackTrace) {
65+
SEL reportCrashWithStackTraceSEL = NSSelectorFromString(@"reportCrashWithStackTrace:handled:");
66+
if ([[Instabug class] respondsToSelector:reportCrashWithStackTraceSEL]) {
67+
[[Instabug class] performSelector:reportCrashWithStackTraceSEL withObject:stackTrace withObject:@(false)];
68+
}
69+
}
70+
71+
RCT_EXPORT_METHOD(sendHandledJSCrash:(NSDictionary *)stackTrace) {
72+
SEL reportCrashWithStackTraceSEL = NSSelectorFromString(@"reportCrashWithStackTrace:handled:");
73+
if ([[Instabug class] respondsToSelector:reportCrashWithStackTraceSEL]) {
74+
[[Instabug class] performSelector:reportCrashWithStackTraceSEL withObject:stackTrace withObject:@(true)];
75+
}
76+
}
77+
6178
RCT_EXPORT_METHOD(setUserData:(NSString *)userData) {
6279
[Instabug setUserData:userData];
6380
}
@@ -78,6 +95,14 @@ - (dispatch_queue_t)methodQueue {
7895
[Instabug setUserStepsEnabled:isUserStepsEnabled];
7996
}
8097

98+
RCT_EXPORT_METHOD(setCrashReportingEnabled:(BOOL)enabledCrashReporter) {
99+
if(enabledCrashReporter) {
100+
[Instabug setCrashReportingEnabled:YES];
101+
} else {
102+
[Instabug setCrashReportingEnabled:NO];
103+
}
104+
}
105+
81106
RCT_EXPORT_METHOD(setAutoScreenRecordingEnabled:(BOOL)enabled) {
82107
[Instabug setAutoScreenRecordingEnabled:enabled];
83108
}

utils/InstabugUtils.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
'use strict';
2+
import {NativeModules, Platform} from 'react-native';
3+
let {Instabug} = NativeModules;
4+
import parseErrorStackLib from '../../react-native/Libraries/Core/Devtools/parseErrorStack.js';
5+
6+
let parseErrorStack = (error) => {
7+
return parseErrorStackLib(error);
8+
};
9+
10+
const originalHandler = global.ErrorUtils.getGlobalHandler();
11+
12+
let init = () => {
13+
if (__DEV__) {
14+
return;
15+
}
16+
17+
function errorHandler(e, isFatal) {
18+
let jsStackTrace = parseErrorStackLib(e);
19+
20+
//JSON object to be sent to the native SDK
21+
var jsonObject = {
22+
message: e.name + " - " + e.message,
23+
os: Platform.OS,
24+
platform: 'react_native',
25+
exception: jsStackTrace
26+
}
27+
if(Platform.OS === 'android') {
28+
Instabug.sendJSCrash(JSON.stringify(jsonObject));
29+
} else {
30+
Instabug.sendJSCrash(jsonObject);
31+
}
32+
if (originalHandler) {
33+
if (Platform.OS === 'ios') {
34+
originalHandler(e, isFatal);
35+
} else {
36+
setTimeout(() => {
37+
originalHandler(e, isFatal);
38+
}, 500);
39+
}
40+
}
41+
}
42+
global.ErrorUtils.setGlobalHandler(errorHandler);
43+
};
44+
45+
module.exports = {
46+
parseErrorStack: parseErrorStack,
47+
captureJsErrors: init
48+
};

0 commit comments

Comments
 (0)