Skip to content

Commit

Permalink
feat(ios): add native-side init API for startup crashes (#1056)
Browse files Browse the repository at this point in the history
Jira ID: MOB-13246
  • Loading branch information
a7medev authored and HeshamMegid committed Nov 14, 2023
1 parent f880e94 commit d8e4b0d
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 73 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased](https://github.com/Instabug/Instabug-React-Native/compare/v12.1.0...dev)

### Added

- Add an iOS-side init API which allows capturing crashes that happen early in the app lifecycle and before the JavaScript code has started ([#1056](https://github.com/Instabug/Instabug-React-Native/pull/1056)).

### Changed

- Bump Instabug iOS SDK to v12.2.0 ([#1053](https://github.com/Instabug/Instabug-React-Native/pull/1053)). [See release notes](https://github.com/instabug/instabug-ios/releases/tag/12.2.0).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
CC3DF8932A1DFC9A003E9914 /* InstabugSurveysTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3DF88B2A1DFC99003E9914 /* InstabugSurveysTests.m */; };
CC3DF8942A1DFC9A003E9914 /* InstabugAPMTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3DF88C2A1DFC99003E9914 /* InstabugAPMTests.m */; };
CC3DF8952A1DFC9A003E9914 /* IBGConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3DF88D2A1DFC9A003E9914 /* IBGConstants.m */; };
CCF1E4092B022CF20024802D /* RNInstabugTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCF1E4082B022CF20024802D /* RNInstabugTests.m */; };
CD36F4707EA1F435D2CC7A15 /* libPods-InstabugExample-InstabugTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AF7A6E02D40E0CEEA833CC4 /* libPods-InstabugExample-InstabugTests.a */; };
F7BF47401EF3A435254C97BB /* libPods-InstabugExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BAED0D0441A708AE2390E153 /* libPods-InstabugExample.a */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -59,6 +60,7 @@
CC3DF88B2A1DFC99003E9914 /* InstabugSurveysTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InstabugSurveysTests.m; sourceTree = "<group>"; };
CC3DF88C2A1DFC99003E9914 /* InstabugAPMTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InstabugAPMTests.m; sourceTree = "<group>"; };
CC3DF88D2A1DFC9A003E9914 /* IBGConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IBGConstants.m; sourceTree = "<group>"; };
CCF1E4082B022CF20024802D /* RNInstabugTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNInstabugTests.m; sourceTree = "<group>"; };
DBCB1B1D023646D84146C91E /* Pods-InstabugExample-InstabugTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InstabugExample-InstabugTests.release.xcconfig"; path = "Target Support Files/Pods-InstabugExample-InstabugTests/Pods-InstabugExample-InstabugTests.release.xcconfig"; sourceTree = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
Expand Down Expand Up @@ -97,6 +99,7 @@
CC3DF8872A1DFC99003E9914 /* InstabugSampleTests.m */,
CC3DF88B2A1DFC99003E9914 /* InstabugSurveysTests.m */,
00E356F01AD99517003FC87E /* Supporting Files */,
CCF1E4082B022CF20024802D /* RNInstabugTests.m */,
);
path = InstabugTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -447,6 +450,7 @@
CC3DF8932A1DFC9A003E9914 /* InstabugSurveysTests.m in Sources */,
20E556262AC55766007416B1 /* InstabugSessionReplayTests.m in Sources */,
CC3DF88F2A1DFC9A003E9914 /* InstabugBugReportingTests.m in Sources */,
CCF1E4092B022CF20024802D /* RNInstabugTests.m in Sources */,
CC3DF88E2A1DFC9A003E9914 /* InstabugCrashReportingTests.m in Sources */,
CC3DF8942A1DFC9A003E9914 /* InstabugAPMTests.m in Sources */,
CC3DF8922A1DFC9A003E9914 /* InstabugRepliesTests.m in Sources */,
Expand Down
14 changes: 4 additions & 10 deletions examples/default/ios/InstabugTests/InstabugSampleTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#import "InstabugReactBridge.h"
#import <Instabug/IBGTypes.h>
#import "IBGConstants.h"
#import "RNInstabug.h"

@protocol InstabugCPTestProtocol <NSObject>
/**
Expand All @@ -36,6 +37,7 @@ - (void)setEnabled:(BOOL)isEnabled;

@interface InstabugSampleTests : XCTestCase
@property (nonatomic, retain) InstabugReactBridge *instabugBridge;
@property (nonatomic, retain) id mRNInstabug;
@end

@implementation InstabugSampleTests
Expand All @@ -44,6 +46,7 @@ @implementation InstabugSampleTests
- (void)setUp {
// Put setup code here. This method is called before the invocation of each test method in the class.
self.instabugBridge = [[InstabugReactBridge alloc] init];
self.mRNInstabug = OCMClassMock([RNInstabug class]);
}

/*
Expand All @@ -62,23 +65,14 @@ - (void)testSetEnabled {
}

- (void)testInit {
id<InstabugCPTestProtocol> mock = OCMClassMock([Instabug class]);
IBGInvocationEvent floatingButtonInvocationEvent = IBGInvocationEventFloatingButton;
NSString *appToken = @"app_token";
NSArray *invocationEvents = [NSArray arrayWithObjects:[NSNumber numberWithInteger:floatingButtonInvocationEvent], nil];
IBGSDKDebugLogsLevel sdkDebugLogsLevel = IBGSDKDebugLogsLevelDebug;

XCTestExpectation *expectation = [self expectationWithDescription:@"Testing [Instabug init]"];

OCMStub([mock startWithToken:appToken invocationEvents:floatingButtonInvocationEvent]);
[self.instabugBridge init:appToken invocationEvents:invocationEvents debugLogsLevel:sdkDebugLogsLevel];

[[NSRunLoop mainRunLoop] performBlock:^{
OCMVerify([mock startWithToken:appToken invocationEvents:floatingButtonInvocationEvent]);
[expectation fulfill];
}];

[self waitForExpectationsWithTimeout:EXPECTATION_TIMEOUT handler:nil];
OCMVerify([self.mRNInstabug initWithToken:appToken invocationEvents:floatingButtonInvocationEvent debugLogsLevel:sdkDebugLogsLevel]);
}

- (void)testSetUserData {
Expand Down
71 changes: 71 additions & 0 deletions examples/default/ios/InstabugTests/RNInstabugTests.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#import <XCTest/XCTest.h>
#import "OCMock/OCMock.h"
#import "Instabug/Instabug.h"
#import "Instabug/IBGSurvey.h"
#import <Instabug/IBGTypes.h>
#import "RNInstabug.h"
#import "RNInstabug/Instabug+CP.h"
#import "RNInstabug/IBGNetworkLogger+CP.h"
#import "IBGConstants.h"

@interface RNInstabugTests : XCTestCase

@property (nonatomic, strong) id mInstabug;
@property (nonatomic, strong) id mIBGNetworkLogger;

@end

// Expose the `reset` API on RNInstabug to allow multiple calls to `initWithToken`.
@interface RNInstabug (Test)
+ (void)reset;
@end

@implementation RNInstabugTests

- (void)setUp {
self.mInstabug = OCMClassMock([Instabug class]);
self.mIBGNetworkLogger = OCMClassMock([IBGNetworkLogger class]);

[RNInstabug reset];
}

- (void)testInitWithoutLogsLevel {
NSString *token = @"app-token";
IBGInvocationEvent invocationEvents = IBGInvocationEventFloatingButton | IBGInvocationEventShake;

[RNInstabug initWithToken:token invocationEvents:invocationEvents];

OCMVerify([self.mInstabug startWithToken:token invocationEvents:invocationEvents]);
OCMVerify([self.mInstabug setCurrentPlatform:IBGPlatformReactNative]);
OCMVerify([self.mIBGNetworkLogger disableAutomaticCapturingOfNetworkLogs]);
OCMVerify([self.mIBGNetworkLogger setEnabled:YES]);
}

- (void)testInitStartsOnce {
NSString *token = @"app-token";
IBGInvocationEvent invocationEvents = IBGInvocationEventFloatingButton | IBGInvocationEventShake;

// Call init twice to check that only 1 call to native methods happens.
[RNInstabug initWithToken:token invocationEvents:invocationEvents];
[RNInstabug initWithToken:token invocationEvents:invocationEvents];

OCMVerify(times(1), [self.mInstabug startWithToken:token invocationEvents:invocationEvents]);
OCMVerify(times(1), [self.mInstabug setCurrentPlatform:IBGPlatformReactNative]);
OCMVerify(times(1), [self.mIBGNetworkLogger disableAutomaticCapturingOfNetworkLogs]);
OCMVerify(times(1), [self.mIBGNetworkLogger setEnabled:YES]);
}

- (void)testInitWithLogsLevel {
NSString *token = @"app-token";
IBGInvocationEvent invocationEvents = IBGInvocationEventFloatingButton | IBGInvocationEventShake;
IBGSDKDebugLogsLevel debugLogsLevel = IBGSDKDebugLogsLevelDebug;

[RNInstabug initWithToken:token invocationEvents:invocationEvents debugLogsLevel:debugLogsLevel];

OCMVerify([self.mInstabug startWithToken:token invocationEvents:invocationEvents]);
OCMVerify([self.mInstabug setCurrentPlatform:IBGPlatformReactNative]);
OCMVerify([self.mIBGNetworkLogger disableAutomaticCapturingOfNetworkLogs]);
OCMVerify([self.mIBGNetworkLogger setEnabled:YES]);
}

@end
66 changes: 3 additions & 63 deletions ios/RNInstabug/InstabugReactBridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@
#import <Instabug/IBGLog.h>
#import <Instabug/IBGAPM.h>
#import <asl.h>
#import <React/RCTLog.h>
#import <os/log.h>
#import <Instabug/IBGTypes.h>
#import <React/RCTUIManager.h>
#import "Util/IBGNetworkLogger+CP.h"
#import "RNInstabug.h"

@interface Instabug (PrivateWillSendAPI)
+ (void)setWillSendReportHandler_private:(void(^)(IBGReport *report, void(^reportCompletionHandler)(IBGReport *)))willSendReportHandler_private;
Expand All @@ -40,31 +38,13 @@ - (dispatch_queue_t)methodQueue {
}

RCT_EXPORT_METHOD(init:(NSString *)token invocationEvents:(NSArray*)invocationEventsArray debugLogsLevel:(IBGSDKDebugLogsLevel)sdkDebugLogsLevel) {
SEL setPrivateApiSEL = NSSelectorFromString(@"setCurrentPlatform:");
if ([[Instabug class] respondsToSelector:setPrivateApiSEL]) {
NSInteger *platform = IBGPlatformReactNative;
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[[Instabug class] methodSignatureForSelector:setPrivateApiSEL]];
[inv setSelector:setPrivateApiSEL];
[inv setTarget:[Instabug class]];
[inv setArgument:&(platform) atIndex:2];
[inv invoke];
}
IBGInvocationEvent invocationEvents = 0;
NSLog(@"invocation events: %ld",(long)invocationEvents);

for (NSNumber *boxedValue in invocationEventsArray) {
invocationEvents |= [boxedValue intValue];
}
[IBGNetworkLogger disableAutomaticCapturingOfNetworkLogs];
[Instabug startWithToken:token invocationEvents:invocationEvents];
[Instabug setSdkDebugLogsLevel:sdkDebugLogsLevel];

RCTAddLogFunction(InstabugReactLogFunction);
RCTSetLogThreshold(RCTLogLevelInfo);

IBGNetworkLogger.enabled = YES;

// Temporarily disabling APM hot launches
IBGAPM.hotAppLaunchEnabled = NO;
[RNInstabug initWithToken:token invocationEvents:invocationEvents debugLogsLevel:sdkDebugLogsLevel];
}

RCT_EXPORT_METHOD(setReproStepsConfig:(IBGUserStepsMode)bugMode :(IBGUserStepsMode)crashMode:(IBGUserStepsMode)sessionReplayMode) {
Expand Down Expand Up @@ -415,44 +395,4 @@ + (BOOL)iOSVersionIsLessThan:(NSString *)iOSVersion {
return [iOSVersion compare:[UIDevice currentDevice].systemVersion options:NSNumericSearch] == NSOrderedDescending;
};

// Note: This function is used to bridge IBGNSLog with RCTLogFunction.
// This log function should not be used externally and is only an implementation detail.
void RNIBGLog(IBGLogLevel logLevel, NSString *format, ...) {
va_list arg_list;
va_start(arg_list, format);
IBGNSLogWithLevel(format, arg_list, logLevel);
va_end(arg_list);
}

RCTLogFunction InstabugReactLogFunction = ^(
RCTLogLevel level,
__unused RCTLogSource source,
NSString *fileName,
NSNumber *lineNumber,
NSString *message
)
{
NSString *formatString = @"Instabug - REACT LOG: %@";
NSString *log = RCTFormatLog([NSDate date], level, fileName, lineNumber, message);

switch(level) {
case RCTLogLevelTrace:
RNIBGLog(IBGLogLevelVerbose, formatString, log);
break;
case RCTLogLevelInfo:
RNIBGLog(IBGLogLevelInfo, formatString, log);
break;
case RCTLogLevelWarning:
RNIBGLog(IBGLogLevelWarning, formatString, log);
break;
case RCTLogLevelError:
RNIBGLog(IBGLogLevelError, formatString, log);
break;
case RCTLogLevelFatal:
RNIBGLog(IBGLogLevelError, formatString, log);
break;
}
};


@end
13 changes: 13 additions & 0 deletions ios/RNInstabug/RNInstabug.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#ifndef RNInstabug_h
#define RNInstabug_h

#import <Instabug/Instabug.h>

@interface RNInstabug : NSObject

+ (void)initWithToken:(NSString *)token invocationEvents:(IBGInvocationEvent)invocationEvents debugLogsLevel:(IBGSDKDebugLogsLevel)debugLogsLevel;
+ (void)initWithToken:(NSString *)token invocationEvents:(IBGInvocationEvent)invocationEvents;

@end

#endif /* RNInstabug_h */
90 changes: 90 additions & 0 deletions ios/RNInstabug/RNInstabug.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#import <Instabug/Instabug.h>
#import <React/RCTLog.h>
#import "RNInstabug.h"
#import "Util/IBGNetworkLogger+CP.h"
#import "Util/Instabug+CP.h"

@implementation RNInstabug

static BOOL didInit = NO;

/// Resets `didInit` allowing re-initialization, it should not be added to the header file and is there for testing purposes.
+ (void)reset {
didInit = NO;
}

+ (void)initWithToken:(NSString *)token invocationEvents:(IBGInvocationEvent)invocationEvents {
// Initialization is performed only once to avoid unexpected behavior.
if (didInit) {
NSLog(@"IBG-RN: Skipped iOS SDK re-initialization");
return;
}

didInit = YES;

[Instabug setCurrentPlatform:IBGPlatformReactNative];

// Disable automatic network logging in the iOS SDK to avoid duplicate network logs coming
// from both the iOS and React Native SDKs
[IBGNetworkLogger disableAutomaticCapturingOfNetworkLogs];

[Instabug startWithToken:token invocationEvents:invocationEvents];

// Setup automatic capturing of JavaScript console logs
RCTAddLogFunction(InstabugReactLogFunction);
RCTSetLogThreshold(RCTLogLevelInfo);

// Even though automatic network logging is disabled in the iOS SDK, the network logger itself
// is still needed since network logs captured by the React Native SDK need to be logged through it
IBGNetworkLogger.enabled = YES;

// Temporarily disabling APM hot launches
IBGAPM.hotAppLaunchEnabled = NO;
}

+ (void)initWithToken:(NSString *)token
invocationEvents:(IBGInvocationEvent)invocationEvents
debugLogsLevel:(IBGSDKDebugLogsLevel)debugLogsLevel {
[Instabug setSdkDebugLogsLevel:debugLogsLevel];
[self initWithToken:token invocationEvents:invocationEvents];
}

// Note: This function is used to bridge IBGNSLog with RCTLogFunction.
// This log function should not be used externally and is only an implementation detail.
void RNIBGLog(IBGLogLevel logLevel, NSString *format, ...) {
va_list arg_list;
va_start(arg_list, format);
IBGNSLogWithLevel(format, arg_list, logLevel);
va_end(arg_list);
}

RCTLogFunction InstabugReactLogFunction = ^(RCTLogLevel level,
__unused RCTLogSource source,
NSString *fileName,
NSNumber *lineNumber,
NSString *message)
{
NSString *formatString = @"Instabug - REACT LOG: %@";
NSString *log = RCTFormatLog([NSDate date], level, fileName, lineNumber, message);

switch(level) {
case RCTLogLevelTrace:
RNIBGLog(IBGLogLevelVerbose, formatString, log);
break;
case RCTLogLevelInfo:
RNIBGLog(IBGLogLevelInfo, formatString, log);
break;
case RCTLogLevelWarning:
RNIBGLog(IBGLogLevelWarning, formatString, log);
break;
case RCTLogLevelError:
RNIBGLog(IBGLogLevelError, formatString, log);
break;
case RCTLogLevelFatal:
RNIBGLog(IBGLogLevelError, formatString, log);
break;
}
};

@end

12 changes: 12 additions & 0 deletions ios/RNInstabug/Util/Instabug+CP.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#import <Instabug/Instabug.h>
#import <Instabug/IBGTypes.h>

NS_ASSUME_NONNULL_BEGIN

@interface Instabug (CP)

+ (void)setCurrentPlatform:(IBGPlatform)platform;

@end

NS_ASSUME_NONNULL_END

0 comments on commit d8e4b0d

Please sign in to comment.