Skip to content

Commit b0ab65a

Browse files
feat: enhance non-fatals support (#1194)
* add non fatal api --------- Co-authored-by: Ahmed Mahmoud <[email protected]>
1 parent d27317e commit b0ab65a

27 files changed

+450
-77
lines changed

CHANGELOG.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
- Bump Instabug iOS SDK to v13.1.0 ([#1227](https://github.com/Instabug/Instabug-React-Native/pull/1227)). [See release notes](https://github.com/Instabug/Instabug-iOS/releases/tag/13.1.0).
88
- Bump Instabug android SDK to v13.1.1 ([#1228](https://github.com/Instabug/Instabug-React-Native/pull/1228)). [See release notes](https://github.com/Instabug/android/releases/tag/v13.1.0).
99

10+
### Added
11+
12+
- Add support for passing a grouping fingerprint, error level, and user attributes to the `CrashReporting.reportError` non-fatals API ([#1194](https://github.com/Instabug/Instabug-React-Native/pull/1194)).
13+
1014
## [13.0.5](https://github.com/Instabug/Instabug-React-Native/compare/v13.0.4...v13.0.5) (May 18, 2024)
1115

1216
### Changed
@@ -33,7 +37,7 @@
3337
- Bump Instabug iOS SDK to v13.0.0 ([#1189](https://github.com/Instabug/Instabug-React-Native/pull/1189)). [See release notes](https://github.com/instabug/instabug-ios/releases/tag/13.0.0).
3438
- Bump Instabug Android SDK to v13.0.0 ([#1188](https://github.com/Instabug/Instabug-React-Native/pull/1188)). [See release notes](https://github.com/Instabug/android/releases/tag/v13.0.0).
3539

36-
## [12.9.0](https://github.com/Instabug/Instabug-React-Native/compare/v12.8.0...v12.9.0) (April 2, 2024)
40+
## [12.9.0](https://github.com/Instabug/Instabug-React-Native/compare/v12.8.0...dev)(April 2, 2024)
3741

3842
### Added
3943

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

+9
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import com.instabug.bug.BugReporting;
66
import com.instabug.bug.invocation.Option;
7+
import com.instabug.crash.models.IBGNonFatalException;
78
import com.instabug.featuresrequest.ActionType;
89
import com.instabug.library.InstabugColorTheme;
910
import com.instabug.library.InstabugCustomTextPlaceHolder.Key;
@@ -54,11 +55,19 @@ static Map<String, Object> getAll() {
5455
putAll(extendedBugReportStates);
5556
putAll(reproModes);
5657
putAll(sdkLogLevels);
58+
putAll(nonFatalExceptionLevel);
5759
putAll(locales);
5860
putAll(placeholders);
5961
}};
6062
}
6163

64+
public static ArgsMap<IBGNonFatalException.Level> nonFatalExceptionLevel = new ArgsMap<IBGNonFatalException.Level>() {{
65+
put("nonFatalErrorLevelCritical", IBGNonFatalException.Level.CRITICAL);
66+
put("nonFatalErrorLevelError", IBGNonFatalException.Level.ERROR);
67+
put("nonFatalErrorLevelWarning", IBGNonFatalException.Level.WARNING);
68+
put("nonFatalErrorLevelInfo", IBGNonFatalException.Level.INFO);
69+
}};
70+
6271
static ArgsMap<InstabugInvocationEvent> invocationEvents = new ArgsMap<InstabugInvocationEvent>() {{
6372
put("invocationEventNone", InstabugInvocationEvent.NONE);
6473
put("invocationEventShake", InstabugInvocationEvent.SHAKE);

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

+58-29
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,24 @@
22

33
import static com.instabug.reactlibrary.utils.InstabugUtil.getMethod;
44

5+
import androidx.annotation.NonNull;
6+
57
import com.facebook.react.bridge.Promise;
68
import com.facebook.react.bridge.ReactApplicationContext;
79
import com.facebook.react.bridge.ReactContextBaseJavaModule;
810
import com.facebook.react.bridge.ReactMethod;
11+
import com.facebook.react.bridge.ReadableMap;
912
import com.instabug.crash.CrashReporting;
13+
import com.instabug.crash.models.IBGNonFatalException;
1014
import com.instabug.library.Feature;
1115
import com.instabug.reactlibrary.utils.MainThreadHandler;
1216

1317
import org.json.JSONObject;
1418

1519
import java.lang.reflect.InvocationTargetException;
1620
import java.lang.reflect.Method;
21+
import java.util.HashMap;
22+
import java.util.Map;
1723

1824
import javax.annotation.Nonnull;
1925
import javax.annotation.Nullable;
@@ -57,8 +63,8 @@ public void run() {
5763
* Send unhandled JS error object
5864
*
5965
* @param exceptionObject Exception object to be sent to Instabug's servers
60-
* @param promise This makes sure that the RN side crashes the app only after the Android SDK
61-
* finishes processing/handling the crash.
66+
* @param promise This makes sure that the RN side crashes the app only after the Android SDK
67+
* finishes processing/handling the crash.
6268
*/
6369
@ReactMethod
6470
public void sendJSCrash(final String exceptionObject, final Promise promise) {
@@ -79,41 +85,64 @@ public void run() {
7985
* Send handled JS error object
8086
*
8187
* @param exceptionObject Exception object to be sent to Instabug's servers
88+
* @param userAttributes (Optional) extra user attributes attached to the crash
89+
* @param fingerprint (Optional) key used to customize how crashes are grouped together
90+
* @param level different severity levels for errors
8291
*/
8392
@ReactMethod
84-
public void sendHandledJSCrash(final String exceptionObject) {
93+
public void sendHandledJSCrash(final String exceptionObject, @Nullable ReadableMap userAttributes, @Nullable String fingerprint, @Nullable String level) {
8594
try {
8695
JSONObject jsonObject = new JSONObject(exceptionObject);
87-
sendJSCrashByReflection(jsonObject, true, null);
88-
} catch (Exception e) {
96+
MainThreadHandler.runOnMainThread(new Runnable() {
97+
@Override
98+
public void run() {
99+
try {
100+
Method method = getMethod(Class.forName("com.instabug.crash.CrashReporting"), "reportException", JSONObject.class, boolean.class,
101+
Map.class, JSONObject.class, IBGNonFatalException.Level.class);
102+
if (method != null) {
103+
IBGNonFatalException.Level nonFatalExceptionLevel = ArgsRegistry.nonFatalExceptionLevel.getOrDefault(level, IBGNonFatalException.Level.ERROR);
104+
Map<String, Object> userAttributesMap = userAttributes == null ? null : userAttributes.toHashMap();
105+
JSONObject fingerprintObj = fingerprint == null ? null : CrashReporting.getFingerprintObject(fingerprint);
106+
107+
method.invoke(null, jsonObject, true, userAttributesMap, fingerprintObj, nonFatalExceptionLevel);
108+
109+
RNInstabugReactnativeModule.clearCurrentReport();
110+
}
111+
} catch (ClassNotFoundException | IllegalAccessException |
112+
InvocationTargetException e) {
113+
e.printStackTrace();
114+
}
115+
}
116+
});
117+
} catch (Throwable e) {
89118
e.printStackTrace();
90119
}
91120
}
92121

93-
private void sendJSCrashByReflection(final JSONObject exceptionObject, final boolean isHandled, @Nullable final Runnable onComplete) {
94-
MainThreadHandler.runOnMainThread(new Runnable() {
95-
@Override
96-
public void run() {
97-
try {
98-
Method method = getMethod(Class.forName("com.instabug.crash.CrashReporting"), "reportException", JSONObject.class, boolean.class);
99-
if (method != null) {
100-
method.invoke(null, exceptionObject, isHandled);
101-
RNInstabugReactnativeModule.clearCurrentReport();
102-
}
103-
} catch (ClassNotFoundException e) {
104-
e.printStackTrace();
105-
} catch (IllegalAccessException e) {
106-
e.printStackTrace();
107-
} catch (InvocationTargetException e) {
108-
e.printStackTrace();
109-
} finally {
110-
if (onComplete != null) {
111-
onComplete.run();
112-
}
113-
}
114-
}
115-
});
116-
}
122+
private void sendJSCrashByReflection(final JSONObject exceptionObject, final boolean isHandled, @Nullable final Runnable onComplete) {
123+
MainThreadHandler.runOnMainThread(new Runnable() {
124+
@Override
125+
public void run() {
126+
try {
127+
Method method = getMethod(Class.forName("com.instabug.crash.CrashReporting"), "reportException", JSONObject.class, boolean.class);
128+
if (method != null) {
129+
method.invoke(null, exceptionObject, isHandled);
130+
RNInstabugReactnativeModule.clearCurrentReport();
131+
}
132+
} catch (ClassNotFoundException e) {
133+
e.printStackTrace();
134+
} catch (IllegalAccessException e) {
135+
e.printStackTrace();
136+
} catch (InvocationTargetException e) {
137+
e.printStackTrace();
138+
} finally {
139+
if (onComplete != null) {
140+
onComplete.run();
141+
}
142+
}
143+
}
144+
});
145+
}
117146

118147
/**
119148
* Enables and disables capturing native C++ NDK crash reporting.

android/src/test/java/com/instabug/reactlibrary/RNInstabugCrashReportingModuleTest.java

+27
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
package com.instabug.reactlibrary;
22

3+
import static com.instabug.crash.CrashReporting.getFingerprintObject;
4+
import static com.instabug.reactlibrary.util.GlobalMocks.reflected;
5+
import static org.mockito.AdditionalMatchers.cmpEq;
36
import static org.mockito.ArgumentMatchers.any;
7+
import static org.mockito.ArgumentMatchers.eq;
8+
import static org.mockito.ArgumentMatchers.same;
49
import static org.mockito.Mockito.mock;
510
import static org.mockito.Mockito.mockStatic;
611

712
import android.os.Looper;
813

914
import com.instabug.crash.CrashReporting;
15+
import com.instabug.crash.models.IBGNonFatalException;
1016
import com.instabug.library.Feature;
1117
import com.instabug.reactlibrary.util.GlobalMocks;
18+
import com.instabug.reactlibrary.util.MockReflected;
1219
import com.instabug.reactlibrary.utils.MainThreadHandler;
1320

21+
import org.json.JSONException;
22+
import org.json.JSONObject;
1423
import org.junit.After;
1524
import org.junit.Before;
1625
import org.junit.Test;
@@ -19,6 +28,9 @@
1928
import org.mockito.invocation.InvocationOnMock;
2029
import org.mockito.stubbing.Answer;
2130

31+
import java.util.HashMap;
32+
import java.util.Map;
33+
2234

2335
public class RNInstabugCrashReportingModuleTest {
2436
private final RNInstabugCrashReportingModule rnModule = new RNInstabugCrashReportingModule(null);
@@ -38,6 +50,7 @@ public void mockMainThreadHandler() throws Exception {
3850
// Mock Looper class
3951
Looper mockMainThreadLooper = mock(Looper.class);
4052
Mockito.when(Looper.getMainLooper()).thenReturn(mockMainThreadLooper);
53+
GlobalMocks.setUp();
4154

4255

4356
// Override runOnMainThread
@@ -58,6 +71,8 @@ public void tearDown() {
5871
mockLooper.close();
5972
mockMainThreadHandler.close();
6073
mockCrashReporting.close();
74+
GlobalMocks.close();
75+
6176
}
6277

6378
/********Crashes*********/
@@ -80,6 +95,18 @@ public void testSetNDKCrashesEnabledGivenFalse() {
8095
mockCrashReporting.verify(() -> CrashReporting.setNDKCrashesState(Feature.State.DISABLED));
8196
}
8297

98+
@Test
99+
public void testSendNonFatalError() {
100+
String jsonCrash = "{}";
101+
boolean isHandled = true;
102+
String fingerPrint = "test";
103+
String level = ArgsRegistry.nonFatalExceptionLevel.keySet().iterator().next();
104+
JSONObject expectedFingerprint = getFingerprintObject(fingerPrint);
105+
IBGNonFatalException.Level expectedLevel = ArgsRegistry.nonFatalExceptionLevel.get(level);
106+
rnModule.sendHandledJSCrash(jsonCrash, null, fingerPrint, level);
107+
reflected.verify(() -> MockReflected.reportException(any(JSONObject.class), eq(isHandled), eq(null), eq(expectedFingerprint), eq(expectedLevel)));
108+
}
109+
83110
@Test
84111
public void givenString$sendHandledJSCrash_whenQuery_thenShouldCallNativeApiWithArgs() throws Exception {
85112
// JSONObject json = mock(JSONObject.class);

android/src/test/java/com/instabug/reactlibrary/util/GlobalMocks.java

+10
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
import android.util.Log;
66

7+
import com.instabug.crash.models.IBGNonFatalException;
78
import com.instabug.reactlibrary.utils.InstabugUtil;
89

10+
import org.json.JSONObject;
911
import org.mockito.MockedStatic;
1012

1113
import java.lang.reflect.Method;
@@ -37,6 +39,14 @@ public static void setUp() throws NoSuchMethodException {
3739
reflection
3840
.when(() -> InstabugUtil.getMethod(Class.forName("com.instabug.library.util.InstabugDeprecationLogger"), "setBaseUrl", String.class))
3941
.thenReturn(mSetBaseUrl);
42+
43+
// reportException mock
44+
Method mCrashReportException = MockReflected.class.getDeclaredMethod("reportException", JSONObject.class, boolean.class, java.util.Map.class, JSONObject.class, IBGNonFatalException.Level.class);
45+
mCrashReportException.setAccessible(true);
46+
reflection
47+
.when(() -> InstabugUtil.getMethod(Class.forName("com.instabug.crash.CrashReporting"), "reportException", JSONObject.class,
48+
boolean.class, java.util.Map.class, JSONObject.class, IBGNonFatalException.Level.class))
49+
.thenReturn(mCrashReportException);
4050
}
4151

4252
public static void close() {

android/src/test/java/com/instabug/reactlibrary/util/MockReflected.java

+11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
package com.instabug.reactlibrary.util;
22

3+
import com.instabug.crash.models.IBGNonFatalException;
4+
5+
import org.json.JSONObject;
6+
7+
import java.util.Map;
8+
39
/**
410
* Includes fake implementations of methods called by reflection.
511
* Used to verify whether or not a private methods was called.
@@ -16,4 +22,9 @@ public static void setCurrentPlatform(int platform) {}
1622
* Instabug.util.InstabugDeprecationLogger.setBaseUrl
1723
*/
1824
public static void setBaseUrl(String baseUrl) {}
25+
/**
26+
* CrashReporting.reportException
27+
*/
28+
public static void reportException(JSONObject exception, boolean isHandled, Map userAttributes, JSONObject fingerPrint, IBGNonFatalException.Level level) {}
29+
1930
}

examples/default/ios/InstabugExample.xcodeproj/project.pbxproj

+10
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
9A3D962AB03F97E25566779F /* Pods-InstabugExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InstabugExample.debug.xcconfig"; path = "Target Support Files/Pods-InstabugExample/Pods-InstabugExample.debug.xcconfig"; sourceTree = "<group>"; };
5353
BAED0D0441A708AE2390E153 /* libPods-InstabugExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-InstabugExample.a"; sourceTree = BUILT_PRODUCTS_DIR; };
5454
BD54B44E2DF85672BB2D4DEE /* Pods-InstabugExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InstabugExample.release.xcconfig"; path = "Target Support Files/Pods-InstabugExample/Pods-InstabugExample.release.xcconfig"; sourceTree = "<group>"; };
55+
BE3328762BDACE030078249A /* IBGCrashReporting+CP.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "IBGCrashReporting+CP.h"; sourceTree = "<group>"; };
5556
C3C8C24386310A3120006604 /* CrashReportingExampleModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CrashReportingExampleModule.m; sourceTree = "<group>"; };
5657
C3C8C784EADC037C5A752B94 /* CrashReportingExampleModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CrashReportingExampleModule.h; sourceTree = "<group>"; };
5758
CC3DF8852A1DFC99003E9914 /* InstabugCrashReportingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InstabugCrashReportingTests.m; sourceTree = "<group>"; };
@@ -91,6 +92,7 @@
9192
00E356EF1AD99517003FC87E /* InstabugTests */ = {
9293
isa = PBXGroup;
9394
children = (
95+
BE3328752BDACE030078249A /* Util */,
9496
CC3DF8892A1DFC99003E9914 /* IBGConstants.h */,
9597
CC3DF88D2A1DFC9A003E9914 /* IBGConstants.m */,
9698
CC3DF88C2A1DFC99003E9914 /* InstabugAPMTests.m */,
@@ -181,6 +183,14 @@
181183
path = Pods;
182184
sourceTree = "<group>";
183185
};
186+
BE3328752BDACE030078249A /* Util */ = {
187+
isa = PBXGroup;
188+
children = (
189+
BE3328762BDACE030078249A /* IBGCrashReporting+CP.h */,
190+
);
191+
path = Util;
192+
sourceTree = "<group>";
193+
};
184194
C3C8C1DDCEA91410F27A3683 /* native */ = {
185195
isa = PBXGroup;
186196
children = (

examples/default/ios/InstabugTests/InstabugCrashReportingTests.m

+24
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
#import <XCTest/XCTest.h>
22
#import "Instabug/Instabug.h"
33
#import "InstabugCrashReportingBridge.h"
4+
#import "OCMock/OCMock.h"
5+
#import "Util/IBGCrashReporting+CP.h"
46

57
@interface InstabugCrashReportingTests : XCTestCase
68
@property (nonatomic, retain) InstabugCrashReportingBridge *bridge;
9+
@property (nonatomic, strong) id mCrashReporting;
10+
711
@end
812

913
@implementation InstabugCrashReportingTests
1014

1115
- (void)setUp {
1216
self.bridge = [[InstabugCrashReportingBridge alloc] init];
17+
self.mCrashReporting = OCMClassMock([IBGCrashReporting class]);
18+
1319
}
1420

1521
- (void)testSetEnabled {
@@ -20,4 +26,22 @@ - (void)testSetEnabled {
2026
XCTAssertFalse(IBGCrashReporting.enabled);
2127
}
2228

29+
- (void)testSendNonFatalErrorJsonCrash {
30+
NSDictionary<NSString *,NSString * > *jsonCrash = @{};
31+
NSString *fingerPrint = @"fingerprint";
32+
RCTPromiseResolveBlock resolve = ^(id result) {};
33+
RCTPromiseRejectBlock reject = ^(NSString *code, NSString *message, NSError *error) {};
34+
NSDictionary *userAttributes = @{ @"key" : @"value", };
35+
IBGNonFatalLevel ibgNonFatalLevel = IBGNonFatalLevelInfo;
36+
37+
38+
[self.bridge sendHandledJSCrash:jsonCrash userAttributes:userAttributes fingerprint:fingerPrint nonFatalExceptionLevel:ibgNonFatalLevel resolver:resolve rejecter:reject];
39+
40+
OCMVerify([self.mCrashReporting cp_reportNonFatalCrashWithStackTrace:jsonCrash
41+
level:IBGNonFatalLevelInfo
42+
groupingString:fingerPrint
43+
userAttributes:userAttributes
44+
]);
45+
}
46+
2347
@end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#import <Instabug/Instabug.h>
2+
3+
4+
@interface IBGCrashReporting (CP)
5+
6+
+ (void)cp_reportFatalCrashWithStackTrace:(NSDictionary*)stackTrace;
7+
8+
+ (void)cp_reportNonFatalCrashWithStackTrace:(NSDictionary*)stackTrace
9+
level:(IBGNonFatalLevel)level
10+
groupingString:(NSString *)groupingString
11+
userAttributes:(NSDictionary<NSString *, NSString*> *)userAttributes;
12+
@end
13+

0 commit comments

Comments
 (0)