From 432829833125e783e058648c8bd5c22178e5a740 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 14 Jan 2025 13:47:44 +0100 Subject: [PATCH 01/21] chore: Initialize `RNSentryTimeToDisplay` during native module `init` on iOS --- CHANGELOG.md | 4 ++++ packages/core/ios/RNSentry.mm | 10 ++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dd716d64a..f46face18c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,10 @@ - Rename `navigation.processing` span to more expressive `Navigation dispatch to screen A mounted/navigation cancelled` ([#4423](https://github.com/getsentry/sentry-react-native/pull/4423)) - Add RN SDK package to `sdk.packages` for Cocoa ([#4381](https://github.com/getsentry/sentry-react-native/pull/4381)) +### Internal + +- Initialize `RNSentryTimeToDisplay` during native module `init` on iOS ([#4443](https://github.com/getsentry/sentry-react-native/pull/4443)) + ### Dependencies - Bump CLI from v2.39.1 to v2.40.0 ([#4412](https://github.com/getsentry/sentry-react-native/pull/4412)) diff --git a/packages/core/ios/RNSentry.mm b/packages/core/ios/RNSentry.mm index d22852df6a..79ff76d0ae 100644 --- a/packages/core/ios/RNSentry.mm +++ b/packages/core/ios/RNSentry.mm @@ -78,6 +78,14 @@ + (BOOL)requiresMainQueueSetup return YES; } +- (instancetype)init +{ + if (self = [super init]) { + _timeToDisplay = [[RNSentryTimeToDisplay alloc] init]; + } + return self; +} + RCT_EXPORT_MODULE() RCT_EXPORT_METHOD(initNativeSdk @@ -152,8 +160,6 @@ - (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull) [mutableOptions removeObjectForKey:@"tracesSampler"]; [mutableOptions removeObjectForKey:@"enableTracing"]; - _timeToDisplay = [[RNSentryTimeToDisplay alloc] init]; - #if SENTRY_TARGET_REPLAY_SUPPORTED [RNSentryReplay updateOptions:mutableOptions]; #endif From 27d76dec32ef85ebde6e8724de7e5c23c6db53e5 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 14 Jan 2025 13:48:59 +0100 Subject: [PATCH 02/21] ref(ios): Extract Cocoa SDK init into standalone file --- packages/core/ios/RNSentry.h | 5 - packages/core/ios/RNSentry.mm | 152 +----------------------------- packages/core/ios/RNSentrySDK.h | 20 ++++ packages/core/ios/RNSentrySDK.m | 159 ++++++++++++++++++++++++++++++++ 4 files changed, 182 insertions(+), 154 deletions(-) create mode 100644 packages/core/ios/RNSentrySDK.h create mode 100644 packages/core/ios/RNSentrySDK.m diff --git a/packages/core/ios/RNSentry.h b/packages/core/ios/RNSentry.h index cfd0b74b28..66dc7219ac 100644 --- a/packages/core/ios/RNSentry.h +++ b/packages/core/ios/RNSentry.h @@ -20,11 +20,6 @@ SentrySDK (Private) @interface RNSentry : RCTEventEmitter -- (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull)options - error:(NSError *_Nullable *_Nonnull)errorPointer; - -- (void)setEventOriginTag:(SentryEvent *)event; - - (NSDictionary *_Nonnull)fetchNativeStackFramesBy:(NSArray *)instructionsAddr symbolicate:(SymbolicateCallbackType)symbolicate; diff --git a/packages/core/ios/RNSentry.mm b/packages/core/ios/RNSentry.mm index 79ff76d0ae..4fa8c4dde8 100644 --- a/packages/core/ios/RNSentry.mm +++ b/packages/core/ios/RNSentry.mm @@ -49,7 +49,7 @@ # import "RNSentryRNSScreen.h" #endif -#import "RNSentryVersion.h" +#import "RNSentrySDK.h" @interface SentrySDK (RNSentry) @@ -94,18 +94,13 @@ - (instancetype)init : (RCTPromiseRejectBlock)reject) { NSError *error = nil; - SentryOptions *sentryOptions = [self createOptionsWithDictionary:options error:&error]; + SentryOptions *sentryOptions = [RNSentrySDK createOptionsWithDictionary:options error:&error]; if (error != nil) { reject(@"SentryReactNative", error.localizedDescription, error); return; } - NSString *sdkVersion = [PrivateSentrySDKOnly getSdkVersionString]; - [PrivateSentrySDKOnly setSdkName:NATIVE_SDK_NAME andVersionString:sdkVersion]; - [PrivateSentrySDKOnly addSdkPackage:REACT_NATIVE_SDK_PACKAGE_NAME - version:REACT_NATIVE_SDK_PACKAGE_VERSION]; - - [SentrySDK startWithOptions:sentryOptions]; + [RNSentrySDK startWithOptions:sentryOptions]; #if TARGET_OS_IPHONE || TARGET_OS_MACCATALYST BOOL appIsActive = @@ -125,150 +120,9 @@ - (instancetype)init sentHybridSdkDidBecomeActive = true; } -#if SENTRY_TARGET_REPLAY_SUPPORTED - [RNSentryReplay postInit]; -#endif - resolve(@YES); } -- (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull)options - error:(NSError *_Nonnull *_Nonnull)errorPointer -{ - SentryBeforeSendEventCallback beforeSend = ^SentryEvent *(SentryEvent *event) - { - // We don't want to send an event after startup that came from a Unhandled JS Exception of - // react native Because we sent it already before the app crashed. - if (nil != event.exceptions.firstObject.type && - [event.exceptions.firstObject.type rangeOfString:@"Unhandled JS Exception"].location - != NSNotFound) { - return nil; - } - - [self setEventOriginTag:event]; - - return event; - }; - - NSMutableDictionary *mutableOptions = [options mutableCopy]; - [mutableOptions setValue:beforeSend forKey:@"beforeSend"]; - - // remove performance traces sample rate and traces sampler since we don't want to synchronize - // these configurations to the Native SDKs. The user could tho initialize the SDK manually and - // set themselves. - [mutableOptions removeObjectForKey:@"tracesSampleRate"]; - [mutableOptions removeObjectForKey:@"tracesSampler"]; - [mutableOptions removeObjectForKey:@"enableTracing"]; - -#if SENTRY_TARGET_REPLAY_SUPPORTED - [RNSentryReplay updateOptions:mutableOptions]; -#endif - - SentryOptions *sentryOptions = [[SentryOptions alloc] initWithDict:mutableOptions - didFailWithError:errorPointer]; - if (*errorPointer != nil) { - return nil; - } - - // Exclude Dev Server and Sentry Dsn request from Breadcrumbs - NSString *dsn = [self getURLFromDSN:[mutableOptions valueForKey:@"dsn"]]; - NSString *devServerUrl = [mutableOptions valueForKey:@"devServerUrl"]; - sentryOptions.beforeBreadcrumb - = ^SentryBreadcrumb *_Nullable(SentryBreadcrumb *_Nonnull breadcrumb) - { - NSString *url = breadcrumb.data[@"url"] ?: @""; - - if ([@"http" isEqualToString:breadcrumb.type] - && ((dsn != nil && [url hasPrefix:dsn]) - || (devServerUrl != nil && [url hasPrefix:devServerUrl]))) { - return nil; - } - return breadcrumb; - }; - - if ([mutableOptions valueForKey:@"enableNativeCrashHandling"] != nil) { - BOOL enableNativeCrashHandling = [mutableOptions[@"enableNativeCrashHandling"] boolValue]; - - if (!enableNativeCrashHandling) { - NSMutableArray *integrations = sentryOptions.integrations.mutableCopy; - [integrations removeObject:@"SentryCrashIntegration"]; - sentryOptions.integrations = integrations; - } - } - - // Set spotlight option - if ([mutableOptions valueForKey:@"spotlight"] != nil) { - id spotlightValue = [mutableOptions valueForKey:@"spotlight"]; - if ([spotlightValue isKindOfClass:[NSString class]]) { - NSLog(@"Using Spotlight on address: %@", spotlightValue); - sentryOptions.enableSpotlight = true; - sentryOptions.spotlightUrl = spotlightValue; - } else if ([spotlightValue isKindOfClass:[NSNumber class]]) { - sentryOptions.enableSpotlight = [spotlightValue boolValue]; - id defaultSpotlightUrl = [mutableOptions valueForKey:@"defaultSidecarUrl"]; - if (defaultSpotlightUrl != nil) { - sentryOptions.spotlightUrl = defaultSpotlightUrl; - } - } - } - - // Enable the App start and Frames tracking measurements - if ([mutableOptions valueForKey:@"enableAutoPerformanceTracing"] != nil) { - BOOL enableAutoPerformanceTracing = - [mutableOptions[@"enableAutoPerformanceTracing"] boolValue]; - PrivateSentrySDKOnly.appStartMeasurementHybridSDKMode = enableAutoPerformanceTracing; -#if TARGET_OS_IPHONE || TARGET_OS_MACCATALYST - PrivateSentrySDKOnly.framesTrackingMeasurementHybridSDKMode = enableAutoPerformanceTracing; -#endif - } - - // Failed requests can only be enabled in one SDK to avoid duplicates - sentryOptions.enableCaptureFailedRequests = NO; - - return sentryOptions; -} - -- (NSString *_Nullable)getURLFromDSN:(NSString *)dsn -{ - NSURL *url = [NSURL URLWithString:dsn]; - if (!url) { - return nil; - } - return [NSString stringWithFormat:@"%@://%@", url.scheme, url.host]; -} - -- (void)setEventOriginTag:(SentryEvent *)event -{ - if (event.sdk != nil) { - NSString *sdkName = event.sdk[@"name"]; - - // If the event is from react native, it gets set - // there and we do not handle it here. - if ([sdkName isEqual:NATIVE_SDK_NAME]) { - [self setEventEnvironmentTag:event origin:@"ios" environment:@"native"]; - } - } -} - -- (void)setEventEnvironmentTag:(SentryEvent *)event - origin:(NSString *)origin - environment:(NSString *)environment -{ - NSMutableDictionary *newTags = [NSMutableDictionary new]; - - if (nil != event.tags && [event.tags count] > 0) { - [newTags addEntriesFromDictionary:event.tags]; - } - if (nil != origin) { - [newTags setValue:origin forKey:@"event.origin"]; - } - if (nil != environment) { - [newTags setValue:environment forKey:@"event.environment"]; - } - - event.tags = newTags; -} - RCT_EXPORT_METHOD(initNativeReactNavigationNewFrameTracking : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) diff --git a/packages/core/ios/RNSentrySDK.h b/packages/core/ios/RNSentrySDK.h new file mode 100644 index 0000000000..af6444b34d --- /dev/null +++ b/packages/core/ios/RNSentrySDK.h @@ -0,0 +1,20 @@ +#import + +@interface RNSentrySDK : NSObject +SENTRY_NO_INIT + ++ (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull)options +error:(NSError *_Nonnull *_Nonnull)errorPointer; + +/** + * @experimental + * Inits and configures Sentry for React Native applications. Make sure to + * set a valid DSN. + * + * @discussion Call this method on the main thread. When calling it from a background thread, the + * SDK starts on the main thread async. + */ ++ (void)startWithOptions:(SentryOptions *)options NS_SWIFT_NAME(start(options:)); + +@end + diff --git a/packages/core/ios/RNSentrySDK.m b/packages/core/ios/RNSentrySDK.m new file mode 100644 index 0000000000..cd012fc607 --- /dev/null +++ b/packages/core/ios/RNSentrySDK.m @@ -0,0 +1,159 @@ +#import "RNSentrySDK.h" +#import "RNSentryReplay.h" +#import "RNSentryVersion.h" + +#import +#import + +@implementation RNSentrySDK + ++ (void)startWithOptions:(SentryOptions *)options NS_SWIFT_NAME(start(options:)) +{ + NSString *sdkVersion = [PrivateSentrySDKOnly getSdkVersionString]; + [PrivateSentrySDKOnly setSdkName:nativeSdkName andVersionString:sdkVersion]; + + [SentrySDK startWithOptions:options]; + +#if SENTRY_TARGET_REPLAY_SUPPORTED + [RNSentryReplay postInit]; +#endif +} + ++ (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull)options + error:(NSError *_Nonnull *_Nonnull)errorPointer +{ + SentryBeforeSendEventCallback beforeSend = ^SentryEvent *(SentryEvent *event) + { + // We don't want to send an event after startup that came from a Unhandled JS Exception of + // react native Because we sent it already before the app crashed. + if (nil != event.exceptions.firstObject.type && + [event.exceptions.firstObject.type rangeOfString:@"Unhandled JS Exception"].location + != NSNotFound) { + return nil; + } + + [self setEventOriginTag:event]; + + return event; + }; + + NSMutableDictionary *mutableOptions = [options mutableCopy]; + [mutableOptions setValue:beforeSend forKey:@"beforeSend"]; + + // remove performance traces sample rate and traces sampler since we don't want to synchronize + // these configurations to the Native SDKs. The user could tho initialize the SDK manually and + // set themselves. + [mutableOptions removeObjectForKey:@"tracesSampleRate"]; + [mutableOptions removeObjectForKey:@"tracesSampler"]; + [mutableOptions removeObjectForKey:@"enableTracing"]; + +#if SENTRY_TARGET_REPLAY_SUPPORTED + [RNSentryReplay updateOptions:mutableOptions]; +#endif + + SentryOptions *sentryOptions = [[SentryOptions alloc] initWithDict:mutableOptions + didFailWithError:errorPointer]; + if (*errorPointer != nil) { + return nil; + } + + // Exclude Dev Server and Sentry Dsn request from Breadcrumbs + NSString *dsn = [self getURLFromDSN:[mutableOptions valueForKey:@"dsn"]]; + NSString *devServerUrl = [mutableOptions valueForKey:@"devServerUrl"]; + sentryOptions.beforeBreadcrumb + = ^SentryBreadcrumb *_Nullable(SentryBreadcrumb *_Nonnull breadcrumb) + { + NSString *url = breadcrumb.data[@"url"] ?: @""; + + if ([@"http" isEqualToString:breadcrumb.type] + && ((dsn != nil && [url hasPrefix:dsn]) + || (devServerUrl != nil && [url hasPrefix:devServerUrl]))) { + return nil; + } + return breadcrumb; + }; + + if ([mutableOptions valueForKey:@"enableNativeCrashHandling"] != nil) { + BOOL enableNativeCrashHandling = [mutableOptions[@"enableNativeCrashHandling"] boolValue]; + + if (!enableNativeCrashHandling) { + NSMutableArray *integrations = sentryOptions.integrations.mutableCopy; + [integrations removeObject:@"SentryCrashIntegration"]; + sentryOptions.integrations = integrations; + } + } + + // Set spotlight option + if ([mutableOptions valueForKey:@"spotlight"] != nil) { + id spotlightValue = [mutableOptions valueForKey:@"spotlight"]; + if ([spotlightValue isKindOfClass:[NSString class]]) { + NSLog(@"Using Spotlight on address: %@", spotlightValue); + sentryOptions.enableSpotlight = true; + sentryOptions.spotlightUrl = spotlightValue; + } else if ([spotlightValue isKindOfClass:[NSNumber class]]) { + sentryOptions.enableSpotlight = [spotlightValue boolValue]; + id defaultSpotlightUrl = [mutableOptions valueForKey:@"defaultSidecarUrl"]; + if (defaultSpotlightUrl != nil) { + sentryOptions.spotlightUrl = defaultSpotlightUrl; + } + } + } + + // Enable the App start and Frames tracking measurements + if ([mutableOptions valueForKey:@"enableAutoPerformanceTracing"] != nil) { + BOOL enableAutoPerformanceTracing = + [mutableOptions[@"enableAutoPerformanceTracing"] boolValue]; + PrivateSentrySDKOnly.appStartMeasurementHybridSDKMode = enableAutoPerformanceTracing; +#if TARGET_OS_IPHONE || TARGET_OS_MACCATALYST + PrivateSentrySDKOnly.framesTrackingMeasurementHybridSDKMode = enableAutoPerformanceTracing; +#endif + } + + // Failed requests can only be enabled in one SDK to avoid duplicates + sentryOptions.enableCaptureFailedRequests = NO; + + return sentryOptions; +} + ++ (void)setEventOriginTag:(SentryEvent *)event +{ + if (event.sdk != nil) { + NSString *sdkName = event.sdk[@"name"]; + + // If the event is from react native, it gets set + // there and we do not handle it here. + if ([sdkName isEqual:nativeSdkName]) { + [self setEventEnvironmentTag:event origin:@"ios" environment:@"native"]; + } + } +} + ++ (void)setEventEnvironmentTag:(SentryEvent *)event + origin:(NSString *)origin + environment:(NSString *)environment +{ + NSMutableDictionary *newTags = [NSMutableDictionary new]; + + if (nil != event.tags && [event.tags count] > 0) { + [newTags addEntriesFromDictionary:event.tags]; + } + if (nil != origin) { + [newTags setValue:origin forKey:@"event.origin"]; + } + if (nil != environment) { + [newTags setValue:environment forKey:@"event.environment"]; + } + + event.tags = newTags; +} + ++ (NSString *_Nullable)getURLFromDSN:(NSString *)dsn +{ + NSURL *url = [NSURL URLWithString:dsn]; + if (!url) { + return nil; + } + return [NSString stringWithFormat:@"%@://%@", url.scheme, url.host]; +} + +@end From 5a28c0308fe815ea5c6984e8252287df9f1b967d Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 14 Jan 2025 13:49:00 +0100 Subject: [PATCH 03/21] fix lint --- packages/core/ios/RNSentry.mm | 1 + packages/core/ios/RNSentrySDK.h | 3 +-- packages/core/ios/RNSentrySDK.m | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/ios/RNSentry.mm b/packages/core/ios/RNSentry.mm index 4fa8c4dde8..88efb2d42f 100644 --- a/packages/core/ios/RNSentry.mm +++ b/packages/core/ios/RNSentry.mm @@ -50,6 +50,7 @@ #endif #import "RNSentrySDK.h" +#import "RNSentryVersion.h" @interface SentrySDK (RNSentry) diff --git a/packages/core/ios/RNSentrySDK.h b/packages/core/ios/RNSentrySDK.h index af6444b34d..c9b4d1c53c 100644 --- a/packages/core/ios/RNSentrySDK.h +++ b/packages/core/ios/RNSentrySDK.h @@ -4,7 +4,7 @@ SENTRY_NO_INIT + (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull)options -error:(NSError *_Nonnull *_Nonnull)errorPointer; + error:(NSError *_Nonnull *_Nonnull)errorPointer; /** * @experimental @@ -17,4 +17,3 @@ error:(NSError *_Nonnull *_Nonnull)errorPointer; + (void)startWithOptions:(SentryOptions *)options NS_SWIFT_NAME(start(options:)); @end - diff --git a/packages/core/ios/RNSentrySDK.m b/packages/core/ios/RNSentrySDK.m index cd012fc607..10227f8194 100644 --- a/packages/core/ios/RNSentrySDK.m +++ b/packages/core/ios/RNSentrySDK.m @@ -28,7 +28,7 @@ + (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull) // react native Because we sent it already before the app crashed. if (nil != event.exceptions.firstObject.type && [event.exceptions.firstObject.type rangeOfString:@"Unhandled JS Exception"].location - != NSNotFound) { + != NSNotFound) { return nil; } @@ -61,7 +61,7 @@ + (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull) NSString *dsn = [self getURLFromDSN:[mutableOptions valueForKey:@"dsn"]]; NSString *devServerUrl = [mutableOptions valueForKey:@"devServerUrl"]; sentryOptions.beforeBreadcrumb - = ^SentryBreadcrumb *_Nullable(SentryBreadcrumb *_Nonnull breadcrumb) + = ^SentryBreadcrumb *_Nullable(SentryBreadcrumb *_Nonnull breadcrumb) { NSString *url = breadcrumb.data[@"url"] ?: @""; @@ -102,7 +102,7 @@ + (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull) // Enable the App start and Frames tracking measurements if ([mutableOptions valueForKey:@"enableAutoPerformanceTracing"] != nil) { BOOL enableAutoPerformanceTracing = - [mutableOptions[@"enableAutoPerformanceTracing"] boolValue]; + [mutableOptions[@"enableAutoPerformanceTracing"] boolValue]; PrivateSentrySDKOnly.appStartMeasurementHybridSDKMode = enableAutoPerformanceTracing; #if TARGET_OS_IPHONE || TARGET_OS_MACCATALYST PrivateSentrySDKOnly.framesTrackingMeasurementHybridSDKMode = enableAutoPerformanceTracing; From 8b67bf1d672857a5e0b04597bc8a760c69a07efb Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 14 Jan 2025 13:49:00 +0100 Subject: [PATCH 04/21] rename rnsentrysdk to rnsentrystart --- packages/core/ios/RNSentry.mm | 2 +- packages/core/ios/{RNSentrySDK.h => RNSentryStart.h} | 0 packages/core/ios/{RNSentrySDK.m => RNSentryStart.m} | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename packages/core/ios/{RNSentrySDK.h => RNSentryStart.h} (100%) rename packages/core/ios/{RNSentrySDK.m => RNSentryStart.m} (99%) diff --git a/packages/core/ios/RNSentry.mm b/packages/core/ios/RNSentry.mm index 88efb2d42f..f85d5053d0 100644 --- a/packages/core/ios/RNSentry.mm +++ b/packages/core/ios/RNSentry.mm @@ -49,7 +49,7 @@ # import "RNSentryRNSScreen.h" #endif -#import "RNSentrySDK.h" +#import "RNSentryStart.h" #import "RNSentryVersion.h" @interface diff --git a/packages/core/ios/RNSentrySDK.h b/packages/core/ios/RNSentryStart.h similarity index 100% rename from packages/core/ios/RNSentrySDK.h rename to packages/core/ios/RNSentryStart.h diff --git a/packages/core/ios/RNSentrySDK.m b/packages/core/ios/RNSentryStart.m similarity index 99% rename from packages/core/ios/RNSentrySDK.m rename to packages/core/ios/RNSentryStart.m index 10227f8194..9d25ad6118 100644 --- a/packages/core/ios/RNSentrySDK.m +++ b/packages/core/ios/RNSentryStart.m @@ -1,4 +1,4 @@ -#import "RNSentrySDK.h" +#import "RNSentryStart.h" #import "RNSentryReplay.h" #import "RNSentryVersion.h" From 05cef298344f90150ff6b5d221e9e80fc62e7f95 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 14 Jan 2025 13:49:28 +0100 Subject: [PATCH 05/21] rename class name also to rnsentrystart, fix tests --- .../project.pbxproj | 4 + .../RNSentryStart+Test.h | 8 ++ .../RNSentryCocoaTesterTests/RNSentryTests.mm | 73 ++++++++++--------- packages/core/ios/RNSentry.mm | 4 +- packages/core/ios/RNSentryStart.h | 2 +- packages/core/ios/RNSentryStart.m | 4 +- 6 files changed, 56 insertions(+), 39 deletions(-) create mode 100644 packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStart+Test.h diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj b/packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj index f78b1be0e0..112c485d6f 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj @@ -25,6 +25,8 @@ 332D33482CDBDC7300547D76 /* RNSentry.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNSentry.h; path = ../ios/RNSentry.h; sourceTree = SOURCE_ROOT; }; 332D33492CDCC8E100547D76 /* RNSentryTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNSentryTests.h; sourceTree = ""; }; 332D334A2CDCC8EB00547D76 /* RNSentryCocoaTesterTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RNSentryCocoaTesterTests-Bridging-Header.h"; sourceTree = ""; }; + 333B58A82D35BA93000F8D04 /* RNSentryStart.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNSentryStart.h; path = ../ios/RNSentryStart.h; sourceTree = SOURCE_ROOT; }; + 333B58A92D35BB2D000F8D04 /* RNSentryStart+Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "RNSentryStart+Test.h"; path = "RNSentryCocoaTesterTests/RNSentryStart+Test.h"; sourceTree = SOURCE_ROOT; }; 336084382C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RNSentryReplayBreadcrumbConverterTests.swift; sourceTree = ""; }; 3360843A2C32E3A8008CC412 /* RNSentryReplayBreadcrumbConverter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSentryReplayBreadcrumbConverter.h; path = ../ios/RNSentryReplayBreadcrumbConverter.h; sourceTree = ""; }; 3360843C2C340C76008CC412 /* RNSentryBreadcrumbTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNSentryBreadcrumbTests.swift; sourceTree = ""; }; @@ -116,6 +118,8 @@ 33AFE0122B8F319000AAB120 /* RNSentry */ = { isa = PBXGroup; children = ( + 333B58A92D35BB2D000F8D04 /* RNSentryStart+Test.h */, + 333B58A82D35BA93000F8D04 /* RNSentryStart.h */, 3380C6C02CDEC56B0018B9B6 /* Replay */, 332D33482CDBDC7300547D76 /* RNSentry.h */, 3360843A2C32E3A8008CC412 /* RNSentryReplayBreadcrumbConverter.h */, diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStart+Test.h b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStart+Test.h new file mode 100644 index 0000000000..fcdfe7872b --- /dev/null +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStart+Test.h @@ -0,0 +1,8 @@ +#import "RNSentryStart.h" + +@interface +RNSentryStart (Test) + ++ (void)setEventOriginTag:(SentryEvent *)event; + +@end diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.mm b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.mm index 6e63793b85..1cbc57caf6 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.mm +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.mm @@ -1,4 +1,5 @@ #import "RNSentryTests.h" +#import "RNSentryStart+Test.h" #import #import #import @@ -25,8 +26,8 @@ - (void)testCreateOptionsWithDictionaryRemovesPerformanceProperties , @"enableTracing" : @YES, } ; -SentryOptions *actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary - error:&error]; +SentryOptions *actualOptions = + [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; XCTAssertNotNil(actualOptions, @"Did not create sentry options"); XCTAssertNil(error, @"Should not pass no error"); @@ -46,8 +47,8 @@ - (void)testCaptureFailedRequestsIsDisabled NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", }; - SentryOptions *actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary - error:&error]; + SentryOptions *actualOptions = + [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; XCTAssertNotNil(actualOptions, @"Did not create sentry options"); XCTAssertNil(error, @"Should not pass no error"); @@ -62,8 +63,8 @@ - (void)testCreateOptionsWithDictionaryNativeCrashHandlingDefault NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", }; - SentryOptions *actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary - error:&error]; + SentryOptions *actualOptions = + [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; XCTAssertNotNil(actualOptions, @"Did not create sentry options"); XCTAssertNil(error, @"Should not pass no error"); XCTAssertEqual([actualOptions.integrations containsObject:@"SentryCrashIntegration"], true, @@ -78,8 +79,8 @@ - (void)testCreateOptionsWithDictionaryAutoPerformanceTracingDefault NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", }; - SentryOptions *actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary - error:&error]; + SentryOptions *actualOptions = + [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; XCTAssertNotNil(actualOptions, @"Did not create sentry options"); XCTAssertNil(error, @"Should not pass no error"); XCTAssertEqual( @@ -95,8 +96,8 @@ - (void)testCreateOptionsWithDictionaryNativeCrashHandlingEnabled @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", @"enableNativeCrashHandling" : @YES, }; - SentryOptions *actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary - error:&error]; + SentryOptions *actualOptions = + [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; XCTAssertNotNil(actualOptions, @"Did not create sentry options"); XCTAssertNil(error, @"Should not pass no error"); XCTAssertEqual([actualOptions.integrations containsObject:@"SentryCrashIntegration"], true, @@ -112,8 +113,8 @@ - (void)testCreateOptionsWithDictionaryAutoPerformanceTracingEnabled @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", @"enableAutoPerformanceTracing" : @YES, }; - SentryOptions *actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary - error:&error]; + SentryOptions *actualOptions = + [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; XCTAssertNotNil(actualOptions, @"Did not create sentry options"); XCTAssertNil(error, @"Should not pass no error"); XCTAssertEqual( @@ -129,8 +130,8 @@ - (void)testCreateOptionsWithDictionaryNativeCrashHandlingDisabled @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", @"enableNativeCrashHandling" : @NO, }; - SentryOptions *actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary - error:&error]; + SentryOptions *actualOptions = + [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; XCTAssertNotNil(actualOptions, @"Did not create sentry options"); XCTAssertNil(error, @"Should not pass no error"); XCTAssertEqual([actualOptions.integrations containsObject:@"SentryCrashIntegration"], false, @@ -146,8 +147,8 @@ - (void)testCreateOptionsWithDictionaryAutoPerformanceTracingDisabled @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", @"enableAutoPerformanceTracing" : @NO, }; - SentryOptions *actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary - error:&error]; + SentryOptions *actualOptions = + [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; XCTAssertNotNil(actualOptions, @"Did not create sentry options"); XCTAssertNil(error, @"Should not pass no error"); XCTAssertEqual(actualOptions.enableAutoPerformanceTracing, false, @@ -164,8 +165,8 @@ - (void)testCreateOptionsWithDictionarySpotlightEnabled @"spotlight" : @YES, @"defaultSidecarUrl" : @"http://localhost:8969/teststream", }; - SentryOptions *actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary - error:&error]; + SentryOptions *actualOptions = + [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; XCTAssertNotNil(actualOptions, @"Did not create sentry options"); XCTAssertNil(error, @"Should not pass no error"); XCTAssertTrue(actualOptions.enableSpotlight, @"Did not enable spotlight"); @@ -182,8 +183,8 @@ - (void)testCreateOptionsWithDictionarySpotlightOne @"spotlight" : @1, @"defaultSidecarUrl" : @"http://localhost:8969/teststream", }; - SentryOptions *actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary - error:&error]; + SentryOptions *actualOptions = + [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; XCTAssertNotNil(actualOptions, @"Did not create sentry options"); XCTAssertNil(error, @"Should not pass no error"); XCTAssertTrue(actualOptions.enableSpotlight, @"Did not enable spotlight"); @@ -199,8 +200,8 @@ - (void)testCreateOptionsWithDictionarySpotlightUrl @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", @"spotlight" : @"http://localhost:8969/teststream", }; - SentryOptions *actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary - error:&error]; + SentryOptions *actualOptions = + [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; XCTAssertNotNil(actualOptions, @"Did not create sentry options"); XCTAssertNil(error, @"Should not pass no error"); XCTAssertTrue(actualOptions.enableSpotlight, @"Did not enable spotlight"); @@ -216,8 +217,8 @@ - (void)testCreateOptionsWithDictionarySpotlightDisabled @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", @"spotlight" : @NO, }; - SentryOptions *actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary - error:&error]; + SentryOptions *actualOptions = + [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; XCTAssertNotNil(actualOptions, @"Did not create sentry options"); XCTAssertNil(error, @"Should not pass no error"); XCTAssertFalse(actualOptions.enableSpotlight, @"Did not disable spotlight"); @@ -232,8 +233,8 @@ - (void)testCreateOptionsWithDictionarySpotlightZero @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", @"spotlight" : @0, }; - SentryOptions *actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary - error:&error]; + SentryOptions *actualOptions = + [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; XCTAssertNotNil(actualOptions, @"Did not create sentry options"); XCTAssertNil(error, @"Should not pass no error"); XCTAssertFalse(actualOptions.enableSpotlight, @"Did not disable spotlight"); @@ -247,8 +248,8 @@ - (void)testPassesErrorOnWrongDsn NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @"dsn" : @"not_a_valid_dsn", }; - SentryOptions *actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary - error:&error]; + SentryOptions *actualOptions = + [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; XCTAssertNil(actualOptions, @"Created invalid sentry options"); XCTAssertNotNil(error, @"Did not created error on invalid dsn"); @@ -263,7 +264,8 @@ - (void)testBeforeBreadcrumbsCallbackFiltersOutSentryDsnRequestBreadcrumbs @"dsn" : @"https://abc@def.ingest.sentry.io/1234567", @"devServerUrl" : @"http://localhost:8081" }; - SentryOptions *options = [rnSentry createOptionsWithDictionary:mockedDictionary error:&error]; + SentryOptions *options = [RNSentryStart createOptionsWithDictionary:mockedDictionary + error:&error]; SentryBreadcrumb *breadcrumb = [[SentryBreadcrumb alloc] init]; breadcrumb.type = @"http"; @@ -283,7 +285,8 @@ - (void)testBeforeBreadcrumbsCallbackFiltersOutDevServerRequestBreadcrumbs NSDictionary *_Nonnull mockedDictionary = @{ @"dsn" : @"https://abc@def.ingest.sentry.io/1234567", @"devServerUrl" : mockDevServer }; - SentryOptions *options = [rnSentry createOptionsWithDictionary:mockedDictionary error:&error]; + SentryOptions *options = [RNSentryStart createOptionsWithDictionary:mockedDictionary + error:&error]; SentryBreadcrumb *breadcrumb = [[SentryBreadcrumb alloc] init]; breadcrumb.type = @"http"; @@ -303,7 +306,8 @@ - (void)testBeforeBreadcrumbsCallbackDoesNotFiltersOutNonDevServerOrDsnRequestBr @"dsn" : @"https://abc@def.ingest.sentry.io/1234567", @"devServerUrl" : @"http://localhost:8081" }; - SentryOptions *options = [rnSentry createOptionsWithDictionary:mockedDictionary error:&error]; + SentryOptions *options = [RNSentryStart createOptionsWithDictionary:mockedDictionary + error:&error]; SentryBreadcrumb *breadcrumb = [[SentryBreadcrumb alloc] init]; breadcrumb.type = @"http"; @@ -322,7 +326,8 @@ - (void)testBeforeBreadcrumbsCallbackKeepsBreadcrumbWhenDevServerUrlIsNotPassedA NSDictionary *_Nonnull mockedDictionary = @{ // dsn is always validated in SentryOptions initialization @"dsn" : @"https://abc@def.ingest.sentry.io/1234567" }; - SentryOptions *options = [rnSentry createOptionsWithDictionary:mockedDictionary error:&error]; + SentryOptions *options = [RNSentryStart createOptionsWithDictionary:mockedDictionary + error:&error]; SentryBreadcrumb *breadcrumb = [[SentryBreadcrumb alloc] init]; breadcrumb.type = @"http"; @@ -341,7 +346,7 @@ - (void)testEventFromSentryCocoaReactNativeHasOriginAndEnvironmentTags @"name" : @"sentry.cocoa.react-native", }; - [rnSentry setEventOriginTag:testEvent]; + [RNSentryStart setEventOriginTag:testEvent]; XCTAssertEqual(testEvent.tags[@"event.origin"], @"ios"); XCTAssertEqual(testEvent.tags[@"event.environment"], @"native"); @@ -359,7 +364,7 @@ - (void)testEventFromSentryReactNativeOriginAndEnvironmentTagsAreOverwritten @"event.environment" : @"testEventEnvironmentTag", }; - [rnSentry setEventOriginTag:testEvent]; + [RNSentryStart setEventOriginTag:testEvent]; XCTAssertEqual(testEvent.tags[@"event.origin"], @"ios"); XCTAssertEqual(testEvent.tags[@"event.environment"], @"native"); diff --git a/packages/core/ios/RNSentry.mm b/packages/core/ios/RNSentry.mm index f85d5053d0..eb6588b361 100644 --- a/packages/core/ios/RNSentry.mm +++ b/packages/core/ios/RNSentry.mm @@ -95,13 +95,13 @@ - (instancetype)init : (RCTPromiseRejectBlock)reject) { NSError *error = nil; - SentryOptions *sentryOptions = [RNSentrySDK createOptionsWithDictionary:options error:&error]; + SentryOptions *sentryOptions = [RNSentryStart createOptionsWithDictionary:options error:&error]; if (error != nil) { reject(@"SentryReactNative", error.localizedDescription, error); return; } - [RNSentrySDK startWithOptions:sentryOptions]; + [RNSentryStart startWithOptions:sentryOptions]; #if TARGET_OS_IPHONE || TARGET_OS_MACCATALYST BOOL appIsActive = diff --git a/packages/core/ios/RNSentryStart.h b/packages/core/ios/RNSentryStart.h index c9b4d1c53c..5b52871251 100644 --- a/packages/core/ios/RNSentryStart.h +++ b/packages/core/ios/RNSentryStart.h @@ -1,6 +1,6 @@ #import -@interface RNSentrySDK : NSObject +@interface RNSentryStart : NSObject SENTRY_NO_INIT + (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull)options diff --git a/packages/core/ios/RNSentryStart.m b/packages/core/ios/RNSentryStart.m index 9d25ad6118..a5f6e8f1b3 100644 --- a/packages/core/ios/RNSentryStart.m +++ b/packages/core/ios/RNSentryStart.m @@ -5,14 +5,14 @@ #import #import -@implementation RNSentrySDK +@implementation RNSentryStart + (void)startWithOptions:(SentryOptions *)options NS_SWIFT_NAME(start(options:)) { NSString *sdkVersion = [PrivateSentrySDKOnly getSdkVersionString]; [PrivateSentrySDKOnly setSdkName:nativeSdkName andVersionString:sdkVersion]; - [SentrySDK startWithOptions:options]; + [self startWithOptions:options]; #if SENTRY_TARGET_REPLAY_SUPPORTED [RNSentryReplay postInit]; From 778478fe67425ff9c2a5f645ea2ea04e3d95005a Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 14 Jan 2025 13:49:28 +0100 Subject: [PATCH 06/21] explicitly import only used classes --- packages/core/ios/RNSentryStart.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/ios/RNSentryStart.h b/packages/core/ios/RNSentryStart.h index 5b52871251..bc5adf35af 100644 --- a/packages/core/ios/RNSentryStart.h +++ b/packages/core/ios/RNSentryStart.h @@ -1,4 +1,5 @@ -#import +#import +#import @interface RNSentryStart : NSObject SENTRY_NO_INIT From 3d3d0b4b7c7f3d02d56ccb0df10bb4ca4b5e1ebd Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 14 Jan 2025 13:49:28 +0100 Subject: [PATCH 07/21] fix call cocoa start not itself --- packages/core/ios/RNSentryStart.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/ios/RNSentryStart.m b/packages/core/ios/RNSentryStart.m index a5f6e8f1b3..11edc14f02 100644 --- a/packages/core/ios/RNSentryStart.m +++ b/packages/core/ios/RNSentryStart.m @@ -2,6 +2,7 @@ #import "RNSentryReplay.h" #import "RNSentryVersion.h" +#import #import #import @@ -12,7 +13,7 @@ + (void)startWithOptions:(SentryOptions *)options NS_SWIFT_NAME(start(options:)) NSString *sdkVersion = [PrivateSentrySDKOnly getSdkVersionString]; [PrivateSentrySDKOnly setSdkName:nativeSdkName andVersionString:sdkVersion]; - [self startWithOptions:options]; + [SentrySDK startWithOptions:options]; #if SENTRY_TARGET_REPLAY_SUPPORTED [RNSentryReplay postInit]; From e2e40fa0ef13ccc2404583d74d9db5c71c2371a8 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 14 Jan 2025 13:49:28 +0100 Subject: [PATCH 08/21] also extract didBecomeActive notification --- packages/core/ios/RNSentry.mm | 20 -------------------- packages/core/ios/RNSentryStart.m | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/packages/core/ios/RNSentry.mm b/packages/core/ios/RNSentry.mm index eb6588b361..6907513da5 100644 --- a/packages/core/ios/RNSentry.mm +++ b/packages/core/ios/RNSentry.mm @@ -64,7 +64,6 @@ + (void)storeEnvelope:(SentryEnvelope *)envelope; static bool hasFetchedAppStart; @implementation RNSentry { - bool sentHybridSdkDidBecomeActive; bool hasListeners; RNSentryTimeToDisplay *_timeToDisplay; } @@ -102,25 +101,6 @@ - (instancetype)init } [RNSentryStart startWithOptions:sentryOptions]; - -#if TARGET_OS_IPHONE || TARGET_OS_MACCATALYST - BOOL appIsActive = - [[UIApplication sharedApplication] applicationState] == UIApplicationStateActive; -#else - BOOL appIsActive = [[NSApplication sharedApplication] isActive]; -#endif - - // If the app is active/in foreground, and we have not sent the SentryHybridSdkDidBecomeActive - // notification, send it. - if (appIsActive && !sentHybridSdkDidBecomeActive - && (PrivateSentrySDKOnly.options.enableAutoSessionTracking - || PrivateSentrySDKOnly.options.enableWatchdogTerminationTracking)) { - [[NSNotificationCenter defaultCenter] postNotificationName:@"SentryHybridSdkDidBecomeActive" - object:nil]; - - sentHybridSdkDidBecomeActive = true; - } - resolve(@YES); } diff --git a/packages/core/ios/RNSentryStart.m b/packages/core/ios/RNSentryStart.m index 11edc14f02..708cdc3218 100644 --- a/packages/core/ios/RNSentryStart.m +++ b/packages/core/ios/RNSentryStart.m @@ -18,6 +18,8 @@ + (void)startWithOptions:(SentryOptions *)options NS_SWIFT_NAME(start(options:)) #if SENTRY_TARGET_REPLAY_SUPPORTED [RNSentryReplay postInit]; #endif + + [self postDidBecomeActiveNotification]; } + (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull)options @@ -157,4 +159,27 @@ + (NSString *_Nullable)getURLFromDSN:(NSString *)dsn return [NSString stringWithFormat:@"%@://%@", url.scheme, url.host]; } +static bool sentHybridSdkDidBecomeActive = NO; + ++ (void)postDidBecomeActiveNotification +{ +#if TARGET_OS_IPHONE || TARGET_OS_MACCATALYST + BOOL appIsActive = + [[UIApplication sharedApplication] applicationState] == UIApplicationStateActive; +#else + BOOL appIsActive = [[NSApplication sharedApplication] isActive]; +#endif + + // If the app is active/in foreground, and we have not sent the SentryHybridSdkDidBecomeActive + // notification, send it. + if (appIsActive && !sentHybridSdkDidBecomeActive + && (PrivateSentrySDKOnly.options.enableAutoSessionTracking + || PrivateSentrySDKOnly.options.enableWatchdogTerminationTracking)) { + [[NSNotificationCenter defaultCenter] postNotificationName:@"SentryHybridSdkDidBecomeActive" + object:nil]; + + sentHybridSdkDidBecomeActive = true; + } +} + @end From 83106a72a33236930917f64dbdf9767a012624ee Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 14 Jan 2025 13:53:44 +0100 Subject: [PATCH 09/21] add explanation comment to native crash handling --- packages/core/ios/RNSentryStart.m | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/ios/RNSentryStart.m b/packages/core/ios/RNSentryStart.m index 708cdc3218..59382c0d3b 100644 --- a/packages/core/ios/RNSentryStart.m +++ b/packages/core/ios/RNSentryStart.m @@ -76,6 +76,7 @@ + (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull) return breadcrumb; }; + // JS options.enableNativeCrashHandling equals to native options.enableCrashHandler if ([mutableOptions valueForKey:@"enableNativeCrashHandling"] != nil) { BOOL enableNativeCrashHandling = [mutableOptions[@"enableNativeCrashHandling"] boolValue]; From 742f28cf58d6668d6c06dadc81ada9c50e3897b2 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 14 Jan 2025 14:03:56 +0100 Subject: [PATCH 10/21] explain notification purpose --- packages/core/ios/RNSentryStart.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core/ios/RNSentryStart.m b/packages/core/ios/RNSentryStart.m index 59382c0d3b..c5f3b81a47 100644 --- a/packages/core/ios/RNSentryStart.m +++ b/packages/core/ios/RNSentryStart.m @@ -176,6 +176,8 @@ + (void)postDidBecomeActiveNotification if (appIsActive && !sentHybridSdkDidBecomeActive && (PrivateSentrySDKOnly.options.enableAutoSessionTracking || PrivateSentrySDKOnly.options.enableWatchdogTerminationTracking)) { + // Updates Native App State Manager https://github.com/getsentry/sentry-cocoa/blob/888a145b144b8077e03151a886520f332e47e297/Sources/Sentry/SentryAppStateManager.m#L136 + // Triggers Session Tracker https://github.com/getsentry/sentry-cocoa/blob/888a145b144b8077e03151a886520f332e47e297/Sources/Sentry/SentrySessionTracker.m#L144 [[NSNotificationCenter defaultCenter] postNotificationName:@"SentryHybridSdkDidBecomeActive" object:nil]; From 4897aa344e9466b36c44fa14bee464b1534e48d7 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 14 Jan 2025 14:04:38 +0100 Subject: [PATCH 11/21] fix sdk name --- packages/core/ios/RNSentryStart.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/core/ios/RNSentryStart.m b/packages/core/ios/RNSentryStart.m index c5f3b81a47..e2790bcc09 100644 --- a/packages/core/ios/RNSentryStart.m +++ b/packages/core/ios/RNSentryStart.m @@ -11,7 +11,9 @@ @implementation RNSentryStart + (void)startWithOptions:(SentryOptions *)options NS_SWIFT_NAME(start(options:)) { NSString *sdkVersion = [PrivateSentrySDKOnly getSdkVersionString]; - [PrivateSentrySDKOnly setSdkName:nativeSdkName andVersionString:sdkVersion]; + [PrivateSentrySDKOnly setSdkName:NATIVE_SDK_NAME andVersionString:sdkVersion]; + [PrivateSentrySDKOnly addSdkPackage:REACT_NATIVE_SDK_PACKAGE_NAME + version:REACT_NATIVE_SDK_PACKAGE_VERSION]; [SentrySDK startWithOptions:options]; @@ -126,7 +128,7 @@ + (void)setEventOriginTag:(SentryEvent *)event // If the event is from react native, it gets set // there and we do not handle it here. - if ([sdkName isEqual:nativeSdkName]) { + if ([sdkName isEqual:REACT_NATIVE_SDK_PACKAGE_NAME]) { [self setEventEnvironmentTag:event origin:@"ios" environment:@"native"]; } } From d37ebdd36bf6937ea601eeeb39760b7b1e9faca9 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 14 Jan 2025 15:23:56 +0100 Subject: [PATCH 12/21] fix native sdk not react native package --- packages/core/ios/RNSentryStart.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/ios/RNSentryStart.m b/packages/core/ios/RNSentryStart.m index e2790bcc09..0cca458672 100644 --- a/packages/core/ios/RNSentryStart.m +++ b/packages/core/ios/RNSentryStart.m @@ -128,7 +128,7 @@ + (void)setEventOriginTag:(SentryEvent *)event // If the event is from react native, it gets set // there and we do not handle it here. - if ([sdkName isEqual:REACT_NATIVE_SDK_PACKAGE_NAME]) { + if ([sdkName isEqual:NATIVE_SDK_NAME]) { [self setEventEnvironmentTag:event origin:@"ios" environment:@"native"]; } } From 13a0b89a14fec9e32ce8266a6153d9cac2a8bb40 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 14 Jan 2025 15:30:40 +0100 Subject: [PATCH 13/21] fix lint sentry start --- packages/core/ios/RNSentryStart.m | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/core/ios/RNSentryStart.m b/packages/core/ios/RNSentryStart.m index 0cca458672..e4ce65c314 100644 --- a/packages/core/ios/RNSentryStart.m +++ b/packages/core/ios/RNSentryStart.m @@ -168,7 +168,7 @@ + (void)postDidBecomeActiveNotification { #if TARGET_OS_IPHONE || TARGET_OS_MACCATALYST BOOL appIsActive = - [[UIApplication sharedApplication] applicationState] == UIApplicationStateActive; + [[UIApplication sharedApplication] applicationState] == UIApplicationStateActive; #else BOOL appIsActive = [[NSApplication sharedApplication] isActive]; #endif @@ -178,8 +178,10 @@ + (void)postDidBecomeActiveNotification if (appIsActive && !sentHybridSdkDidBecomeActive && (PrivateSentrySDKOnly.options.enableAutoSessionTracking || PrivateSentrySDKOnly.options.enableWatchdogTerminationTracking)) { - // Updates Native App State Manager https://github.com/getsentry/sentry-cocoa/blob/888a145b144b8077e03151a886520f332e47e297/Sources/Sentry/SentryAppStateManager.m#L136 - // Triggers Session Tracker https://github.com/getsentry/sentry-cocoa/blob/888a145b144b8077e03151a886520f332e47e297/Sources/Sentry/SentrySessionTracker.m#L144 + // Updates Native App State Manager + // https://github.com/getsentry/sentry-cocoa/blob/888a145b144b8077e03151a886520f332e47e297/Sources/Sentry/SentryAppStateManager.m#L136 + // Triggers Session Tracker + // https://github.com/getsentry/sentry-cocoa/blob/888a145b144b8077e03151a886520f332e47e297/Sources/Sentry/SentrySessionTracker.m#L144 [[NSNotificationCenter defaultCenter] postNotificationName:@"SentryHybridSdkDidBecomeActive" object:nil]; From d7270b1c656103c2d522a6af9360df1fe3ced74b Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 14 Jan 2025 15:33:24 +0100 Subject: [PATCH 14/21] add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f46face18c..f1f48bfc55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ ### Internal - Initialize `RNSentryTimeToDisplay` during native module `init` on iOS ([#4443](https://github.com/getsentry/sentry-react-native/pull/4443)) +- Extract iOS native initialization to standalone structures ([#4444](https://github.com/getsentry/sentry-react-native/pull/4444)) ### Dependencies From 580590ba8b7d9a91922a7e089b24237fd5254fcb Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 14 Jan 2025 16:25:22 +0100 Subject: [PATCH 15/21] feat(experimental): Add native `startWithConfigureOptions` for Apple platforms` --- CHANGELOG.md | 1 + packages/core/RNSentry.podspec | 2 +- .../project.pbxproj | 2 + .../RNSentryCocoaTesterTests/RNSentryTests.mm | 32 ++---- packages/core/ios/RNSentry.h | 3 + packages/core/ios/RNSentry.mm | 4 +- packages/core/ios/RNSentrySDK.h | 17 +++ packages/core/ios/RNSentrySDK.m | 15 +++ packages/core/ios/RNSentryStart.h | 6 ++ packages/core/ios/RNSentryStart.m | 101 ++++++++++++------ .../sentryreactnativesample/AppDelegate.mm | 6 ++ 11 files changed, 124 insertions(+), 65 deletions(-) create mode 100644 packages/core/ios/RNSentrySDK.h create mode 100644 packages/core/ios/RNSentrySDK.m diff --git a/CHANGELOG.md b/CHANGELOG.md index f1f48bfc55..512824779b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - Rename `navigation.processing` span to more expressive `Navigation dispatch to screen A mounted/navigation cancelled` ([#4423](https://github.com/getsentry/sentry-react-native/pull/4423)) - Add RN SDK package to `sdk.packages` for Cocoa ([#4381](https://github.com/getsentry/sentry-react-native/pull/4381)) +- Add experimental version of `startWithConfigureOptions` for Apple platforms ([#4444](https://github.com/getsentry/sentry-react-native/pull/4444)) ### Internal diff --git a/packages/core/RNSentry.podspec b/packages/core/RNSentry.podspec index 97c4fd315c..3c595c08eb 100644 --- a/packages/core/RNSentry.podspec +++ b/packages/core/RNSentry.podspec @@ -33,7 +33,7 @@ Pod::Spec.new do |s| s.preserve_paths = '*.js' s.source_files = 'ios/**/*.{h,m,mm}' - s.public_header_files = 'ios/RNSentry.h' + s.public_header_files = 'ios/RNSentry.h', 'ios/RNSentrySDK.h' s.compiler_flags = other_cflags diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj b/packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj index 112c485d6f..b19dfc43fc 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj @@ -27,6 +27,7 @@ 332D334A2CDCC8EB00547D76 /* RNSentryCocoaTesterTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RNSentryCocoaTesterTests-Bridging-Header.h"; sourceTree = ""; }; 333B58A82D35BA93000F8D04 /* RNSentryStart.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNSentryStart.h; path = ../ios/RNSentryStart.h; sourceTree = SOURCE_ROOT; }; 333B58A92D35BB2D000F8D04 /* RNSentryStart+Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "RNSentryStart+Test.h"; path = "RNSentryCocoaTesterTests/RNSentryStart+Test.h"; sourceTree = SOURCE_ROOT; }; + 333B58AF2D36A7FD000F8D04 /* RNSentrySDK.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNSentrySDK.h; path = ../ios/RNSentrySDK.h; sourceTree = SOURCE_ROOT; }; 336084382C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RNSentryReplayBreadcrumbConverterTests.swift; sourceTree = ""; }; 3360843A2C32E3A8008CC412 /* RNSentryReplayBreadcrumbConverter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSentryReplayBreadcrumbConverter.h; path = ../ios/RNSentryReplayBreadcrumbConverter.h; sourceTree = ""; }; 3360843C2C340C76008CC412 /* RNSentryBreadcrumbTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNSentryBreadcrumbTests.swift; sourceTree = ""; }; @@ -118,6 +119,7 @@ 33AFE0122B8F319000AAB120 /* RNSentry */ = { isa = PBXGroup; children = ( + 333B58AF2D36A7FD000F8D04 /* RNSentrySDK.h */, 333B58A92D35BB2D000F8D04 /* RNSentryStart+Test.h */, 333B58A82D35BA93000F8D04 /* RNSentryStart.h */, 3380C6C02CDEC56B0018B9B6 /* Replay */, diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.mm b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.mm index 1cbc57caf6..abe2ae70ce 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.mm +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.mm @@ -2,6 +2,7 @@ #import "RNSentryStart+Test.h" #import #import +#import #import #import #import @@ -12,9 +13,8 @@ @interface RNSentryInitNativeSdkTests : XCTestCase @implementation RNSentryInitNativeSdkTests -- (void)testCreateOptionsWithDictionaryRemovesPerformanceProperties +- (void)testStartWithDictionaryRemovesPerformanceProperties { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @@ -26,9 +26,8 @@ - (void)testCreateOptionsWithDictionaryRemovesPerformanceProperties , @"enableTracing" : @YES, } ; -SentryOptions *actualOptions = - [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; - +[RNSentryStart startWithOptions:mockedReactNativeDictionary error:&error]; +SentryOptions *actualOptions = PrivateSentrySDKOnly.options; XCTAssertNotNil(actualOptions, @"Did not create sentry options"); XCTAssertNil(error, @"Should not pass no error"); XCTAssertNotNil( @@ -41,14 +40,13 @@ - (void)testCreateOptionsWithDictionaryRemovesPerformanceProperties - (void)testCaptureFailedRequestsIsDisabled { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", }; - SentryOptions *actualOptions = - [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; + [RNSentryStart startWithOptions:mockedReactNativeDictionary error:&error]; + SentryOptions *actualOptions = PrivateSentrySDKOnly.options; XCTAssertNotNil(actualOptions, @"Did not create sentry options"); XCTAssertNil(error, @"Should not pass no error"); @@ -57,7 +55,6 @@ - (void)testCaptureFailedRequestsIsDisabled - (void)testCreateOptionsWithDictionaryNativeCrashHandlingDefault { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @@ -73,7 +70,6 @@ - (void)testCreateOptionsWithDictionaryNativeCrashHandlingDefault - (void)testCreateOptionsWithDictionaryAutoPerformanceTracingDefault { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @@ -89,7 +85,6 @@ - (void)testCreateOptionsWithDictionaryAutoPerformanceTracingDefault - (void)testCreateOptionsWithDictionaryNativeCrashHandlingEnabled { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @@ -106,7 +101,6 @@ - (void)testCreateOptionsWithDictionaryNativeCrashHandlingEnabled - (void)testCreateOptionsWithDictionaryAutoPerformanceTracingEnabled { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @@ -123,7 +117,6 @@ - (void)testCreateOptionsWithDictionaryAutoPerformanceTracingEnabled - (void)testCreateOptionsWithDictionaryNativeCrashHandlingDisabled { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @@ -140,7 +133,6 @@ - (void)testCreateOptionsWithDictionaryNativeCrashHandlingDisabled - (void)testCreateOptionsWithDictionaryAutoPerformanceTracingDisabled { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @@ -157,7 +149,6 @@ - (void)testCreateOptionsWithDictionaryAutoPerformanceTracingDisabled - (void)testCreateOptionsWithDictionarySpotlightEnabled { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @@ -175,7 +166,6 @@ - (void)testCreateOptionsWithDictionarySpotlightEnabled - (void)testCreateOptionsWithDictionarySpotlightOne { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @@ -193,7 +183,6 @@ - (void)testCreateOptionsWithDictionarySpotlightOne - (void)testCreateOptionsWithDictionarySpotlightUrl { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @@ -210,7 +199,6 @@ - (void)testCreateOptionsWithDictionarySpotlightUrl - (void)testCreateOptionsWithDictionarySpotlightDisabled { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @@ -226,7 +214,6 @@ - (void)testCreateOptionsWithDictionarySpotlightDisabled - (void)testCreateOptionsWithDictionarySpotlightZero { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @@ -242,7 +229,6 @@ - (void)testCreateOptionsWithDictionarySpotlightZero - (void)testPassesErrorOnWrongDsn { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @@ -257,7 +243,6 @@ - (void)testPassesErrorOnWrongDsn - (void)testBeforeBreadcrumbsCallbackFiltersOutSentryDsnRequestBreadcrumbs { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedDictionary = @{ @@ -278,7 +263,6 @@ - (void)testBeforeBreadcrumbsCallbackFiltersOutSentryDsnRequestBreadcrumbs - (void)testBeforeBreadcrumbsCallbackFiltersOutDevServerRequestBreadcrumbs { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSString *mockDevServer = @"http://localhost:8081"; @@ -299,7 +283,6 @@ - (void)testBeforeBreadcrumbsCallbackFiltersOutDevServerRequestBreadcrumbs - (void)testBeforeBreadcrumbsCallbackDoesNotFiltersOutNonDevServerOrDsnRequestBreadcrumbs { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedDictionary = @{ @@ -320,7 +303,6 @@ - (void)testBeforeBreadcrumbsCallbackDoesNotFiltersOutNonDevServerOrDsnRequestBr - (void)testBeforeBreadcrumbsCallbackKeepsBreadcrumbWhenDevServerUrlIsNotPassedAndDsnDoesNotMatch { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedDictionary = @{ // dsn is always validated in SentryOptions initialization @@ -340,7 +322,6 @@ - (void)testBeforeBreadcrumbsCallbackKeepsBreadcrumbWhenDevServerUrlIsNotPassedA - (void)testEventFromSentryCocoaReactNativeHasOriginAndEnvironmentTags { - RNSentry *rnSentry = [[RNSentry alloc] init]; SentryEvent *testEvent = [[SentryEvent alloc] init]; testEvent.sdk = @{ @"name" : @"sentry.cocoa.react-native", @@ -354,7 +335,6 @@ - (void)testEventFromSentryCocoaReactNativeHasOriginAndEnvironmentTags - (void)testEventFromSentryReactNativeOriginAndEnvironmentTagsAreOverwritten { - RNSentry *rnSentry = [[RNSentry alloc] init]; SentryEvent *testEvent = [[SentryEvent alloc] init]; testEvent.sdk = @{ @"name" : @"sentry.cocoa.react-native", diff --git a/packages/core/ios/RNSentry.h b/packages/core/ios/RNSentry.h index 66dc7219ac..c7fb93e0ea 100644 --- a/packages/core/ios/RNSentry.h +++ b/packages/core/ios/RNSentry.h @@ -11,6 +11,9 @@ #import #import +// This import exposes public RNSentrySDK start +#import "RNSentrySDK.h" + typedef int (*SymbolicateCallbackType)(const void *, Dl_info *); @interface diff --git a/packages/core/ios/RNSentry.mm b/packages/core/ios/RNSentry.mm index 6907513da5..69fd287403 100644 --- a/packages/core/ios/RNSentry.mm +++ b/packages/core/ios/RNSentry.mm @@ -94,13 +94,11 @@ - (instancetype)init : (RCTPromiseRejectBlock)reject) { NSError *error = nil; - SentryOptions *sentryOptions = [RNSentryStart createOptionsWithDictionary:options error:&error]; + [RNSentryStart startWithOptions:options error:&error]; if (error != nil) { reject(@"SentryReactNative", error.localizedDescription, error); return; } - - [RNSentryStart startWithOptions:sentryOptions]; resolve(@YES); } diff --git a/packages/core/ios/RNSentrySDK.h b/packages/core/ios/RNSentrySDK.h new file mode 100644 index 0000000000..28fca1d6d5 --- /dev/null +++ b/packages/core/ios/RNSentrySDK.h @@ -0,0 +1,17 @@ +#import + +@interface RNSentrySDK : NSObject +SENTRY_NO_INIT + +/** + * @experimental + * Inits and configures Sentry for React Native applications. Make sure to + * set a valid DSN. + * + * @discussion Call this method on the main thread. When calling it from a background thread, the + * SDK starts on the main thread async. + */ ++ (void)startWithConfigureOptions:(void (^)(SentryOptions *options))configureOptions + NS_SWIFT_NAME(start(configureOptions:)); + +@end diff --git a/packages/core/ios/RNSentrySDK.m b/packages/core/ios/RNSentrySDK.m new file mode 100644 index 0000000000..f4031dc06b --- /dev/null +++ b/packages/core/ios/RNSentrySDK.m @@ -0,0 +1,15 @@ +#import "RNSentrySDK.h" +#import "RNSentryStart.h" + +@implementation RNSentrySDK + ++ (void)startWithConfigureOptions:(void (^)(SentryOptions *options))configureOptions +{ + SentryOptions *options = [[SentryOptions alloc] init]; + [RNSentryStart updateWithReactDefaults:options]; + configureOptions(options); + [RNSentryStart updateWithReactFinals:options]; + [RNSentryStart startWithOptions:options]; +} + +@end diff --git a/packages/core/ios/RNSentryStart.h b/packages/core/ios/RNSentryStart.h index bc5adf35af..a45ddea679 100644 --- a/packages/core/ios/RNSentryStart.h +++ b/packages/core/ios/RNSentryStart.h @@ -4,9 +4,15 @@ @interface RNSentryStart : NSObject SENTRY_NO_INIT ++ (void)startWithOptions:(NSDictionary *_Nonnull)javascriptOptions + error:(NSError *_Nonnull *_Nonnull)errorPointer; + + (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull)options error:(NSError *_Nonnull *_Nonnull)errorPointer; ++ (void)updateWithReactDefaults:(SentryOptions *)options; ++ (void)updateWithReactFinals:(SentryOptions *)options; + /** * @experimental * Inits and configures Sentry for React Native applications. Make sure to diff --git a/packages/core/ios/RNSentryStart.m b/packages/core/ios/RNSentryStart.m index e4ce65c314..5cf0d430e4 100644 --- a/packages/core/ios/RNSentryStart.m +++ b/packages/core/ios/RNSentryStart.m @@ -2,12 +2,23 @@ #import "RNSentryReplay.h" #import "RNSentryVersion.h" -#import #import +#import +#import #import @implementation RNSentryStart ++ (void)startWithOptions:(NSDictionary *_Nonnull)javascriptOptions + error:(NSError *_Nonnull *_Nonnull)errorPointer +{ + SentryOptions *options = [self createOptionsWithDictionary:javascriptOptions + error:errorPointer]; + [self updateWithReactDefaults:options]; + [self updateWithReactFinals:options]; + [self startWithOptions:options]; +} + + (void)startWithOptions:(SentryOptions *)options NS_SWIFT_NAME(start(options:)) { NSString *sdkVersion = [PrivateSentrySDKOnly getSdkVersionString]; @@ -27,30 +38,7 @@ + (void)startWithOptions:(SentryOptions *)options NS_SWIFT_NAME(start(options:)) + (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull)options error:(NSError *_Nonnull *_Nonnull)errorPointer { - SentryBeforeSendEventCallback beforeSend = ^SentryEvent *(SentryEvent *event) - { - // We don't want to send an event after startup that came from a Unhandled JS Exception of - // react native Because we sent it already before the app crashed. - if (nil != event.exceptions.firstObject.type && - [event.exceptions.firstObject.type rangeOfString:@"Unhandled JS Exception"].location - != NSNotFound) { - return nil; - } - - [self setEventOriginTag:event]; - - return event; - }; - NSMutableDictionary *mutableOptions = [options mutableCopy]; - [mutableOptions setValue:beforeSend forKey:@"beforeSend"]; - - // remove performance traces sample rate and traces sampler since we don't want to synchronize - // these configurations to the Native SDKs. The user could tho initialize the SDK manually and - // set themselves. - [mutableOptions removeObjectForKey:@"tracesSampleRate"]; - [mutableOptions removeObjectForKey:@"tracesSampler"]; - [mutableOptions removeObjectForKey:@"enableTracing"]; #if SENTRY_TARGET_REPLAY_SUPPORTED [RNSentryReplay updateOptions:mutableOptions]; @@ -63,6 +51,7 @@ + (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull) } // Exclude Dev Server and Sentry Dsn request from Breadcrumbs + // TODO: Migrate for manual init NSString *dsn = [self getURLFromDSN:[mutableOptions valueForKey:@"dsn"]]; NSString *devServerUrl = [mutableOptions valueForKey:@"devServerUrl"]; sentryOptions.beforeBreadcrumb @@ -105,20 +94,62 @@ + (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull) } } - // Enable the App start and Frames tracking measurements - if ([mutableOptions valueForKey:@"enableAutoPerformanceTracing"] != nil) { - BOOL enableAutoPerformanceTracing = - [mutableOptions[@"enableAutoPerformanceTracing"] boolValue]; - PrivateSentrySDKOnly.appStartMeasurementHybridSDKMode = enableAutoPerformanceTracing; + return sentryOptions; +} + +/** + * This function updates the options with RNSentry defaults. These default can be + * overwritten by users during manual native initialization. + */ ++ (void)updateWithReactDefaults:(SentryOptions *)options +{ + // Failed requests are captured only in JS to avoid duplicates + options.enableCaptureFailedRequests = NO; + + // Tracing is only enabled in JS to avoid duplicate navigation spans + options.tracesSampleRate = nil; + options.tracesSampler = nil; + options.enableTracing = NO; +} + +/** + * This function updates options with changes RNSentry users should not change + * and so this is applied after the configureOptions callback during manual native initialization. + */ ++ (void)updateWithReactFinals:(SentryOptions *)options +{ + SentryBeforeSendEventCallback userBeforeSend = options.beforeSend; + options.beforeSend = ^SentryEvent *(SentryEvent *event) + { + // Unhandled JS Exception are processed by the SDK on JS layer + // To avoid duplicates we drop them in the native SDKs + if (nil != event.exceptions.firstObject.type && + [event.exceptions.firstObject.type rangeOfString:@"Unhandled JS Exception"].location + != NSNotFound) { + return nil; + } + + [self setEventOriginTag:event]; + if (userBeforeSend == nil) { + return event; + } else { + return userBeforeSend(event); + } + }; + + if (options.enableAutoPerformanceTracing != nil) { + // App Start Hybrid mode doesn't wait for didFinishLaunchNotification and the + // didBecomeVisibleNotification as they will be missed when auto initializing from JS + // App Start measurements are created right after the tracking starts + PrivateSentrySDKOnly.appStartMeasurementHybridSDKMode + = options.enableAutoPerformanceTracing; #if TARGET_OS_IPHONE || TARGET_OS_MACCATALYST - PrivateSentrySDKOnly.framesTrackingMeasurementHybridSDKMode = enableAutoPerformanceTracing; + // Frames Tracking Hybrid Mode ensures tracking + // is enabled without tracing enabled in the native SDK + PrivateSentrySDKOnly.framesTrackingMeasurementHybridSDKMode + = options.enableAutoPerformanceTracing; #endif } - - // Failed requests can only be enabled in one SDK to avoid duplicates - sentryOptions.enableCaptureFailedRequests = NO; - - return sentryOptions; } + (void)setEventOriginTag:(SentryEvent *)event diff --git a/samples/react-native/ios/sentryreactnativesample/AppDelegate.mm b/samples/react-native/ios/sentryreactnativesample/AppDelegate.mm index 71a62884ac..2a6a0a0956 100644 --- a/samples/react-native/ios/sentryreactnativesample/AppDelegate.mm +++ b/samples/react-native/ios/sentryreactnativesample/AppDelegate.mm @@ -9,6 +9,7 @@ # import #endif +#import #import #import @@ -57,6 +58,11 @@ - (BOOL)application:(UIApplication *)application // When the native init is enabled the `autoInitializeNativeSdk` // in JS has to be set to `false` // [self initializeSentry]; + // [RNSentrySDK startWithConfigureOptions:^(SentryOptions *options) { + // options.dsn = + // @"https://1df17bd4e543fdb31351dee1768bb679@o447951.ingest.sentry.io/5428561"; + // options.debug = YES; + // }]; self.moduleName = @"sentry-react-native-sample"; // You can add your custom initial props in the dictionary below. From cfc6293f0d16e5368f8f9dca97de937fc2c1a799 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Mon, 20 Jan 2025 15:48:12 +0100 Subject: [PATCH 16/21] fix lint --- packages/core/ios/RNSentryStart.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/ios/RNSentryStart.m b/packages/core/ios/RNSentryStart.m index e4ce65c314..c364b6c630 100644 --- a/packages/core/ios/RNSentryStart.m +++ b/packages/core/ios/RNSentryStart.m @@ -2,8 +2,8 @@ #import "RNSentryReplay.h" #import "RNSentryVersion.h" -#import #import +#import #import @implementation RNSentryStart From f2a041e8e0867451e09a11a4c2398a867a855e14 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Mon, 20 Jan 2025 16:44:33 +0100 Subject: [PATCH 17/21] remove unused rnSentry --- .../RNSentryCocoaTesterTests/RNSentryTests.mm | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.mm b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.mm index 1cbc57caf6..9cefc4747a 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.mm +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.mm @@ -14,7 +14,6 @@ @implementation RNSentryInitNativeSdkTests - (void)testCreateOptionsWithDictionaryRemovesPerformanceProperties { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @@ -41,7 +40,6 @@ - (void)testCreateOptionsWithDictionaryRemovesPerformanceProperties - (void)testCaptureFailedRequestsIsDisabled { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @@ -57,7 +55,6 @@ - (void)testCaptureFailedRequestsIsDisabled - (void)testCreateOptionsWithDictionaryNativeCrashHandlingDefault { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @@ -73,7 +70,6 @@ - (void)testCreateOptionsWithDictionaryNativeCrashHandlingDefault - (void)testCreateOptionsWithDictionaryAutoPerformanceTracingDefault { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @@ -89,7 +85,6 @@ - (void)testCreateOptionsWithDictionaryAutoPerformanceTracingDefault - (void)testCreateOptionsWithDictionaryNativeCrashHandlingEnabled { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @@ -106,7 +101,6 @@ - (void)testCreateOptionsWithDictionaryNativeCrashHandlingEnabled - (void)testCreateOptionsWithDictionaryAutoPerformanceTracingEnabled { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @@ -123,7 +117,6 @@ - (void)testCreateOptionsWithDictionaryAutoPerformanceTracingEnabled - (void)testCreateOptionsWithDictionaryNativeCrashHandlingDisabled { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @@ -140,7 +133,6 @@ - (void)testCreateOptionsWithDictionaryNativeCrashHandlingDisabled - (void)testCreateOptionsWithDictionaryAutoPerformanceTracingDisabled { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @@ -157,7 +149,6 @@ - (void)testCreateOptionsWithDictionaryAutoPerformanceTracingDisabled - (void)testCreateOptionsWithDictionarySpotlightEnabled { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @@ -175,7 +166,6 @@ - (void)testCreateOptionsWithDictionarySpotlightEnabled - (void)testCreateOptionsWithDictionarySpotlightOne { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @@ -193,7 +183,6 @@ - (void)testCreateOptionsWithDictionarySpotlightOne - (void)testCreateOptionsWithDictionarySpotlightUrl { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @@ -210,7 +199,6 @@ - (void)testCreateOptionsWithDictionarySpotlightUrl - (void)testCreateOptionsWithDictionarySpotlightDisabled { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @@ -226,7 +214,6 @@ - (void)testCreateOptionsWithDictionarySpotlightDisabled - (void)testCreateOptionsWithDictionarySpotlightZero { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @@ -242,7 +229,6 @@ - (void)testCreateOptionsWithDictionarySpotlightZero - (void)testPassesErrorOnWrongDsn { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @@ -257,7 +243,6 @@ - (void)testPassesErrorOnWrongDsn - (void)testBeforeBreadcrumbsCallbackFiltersOutSentryDsnRequestBreadcrumbs { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedDictionary = @{ @@ -278,7 +263,6 @@ - (void)testBeforeBreadcrumbsCallbackFiltersOutSentryDsnRequestBreadcrumbs - (void)testBeforeBreadcrumbsCallbackFiltersOutDevServerRequestBreadcrumbs { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSString *mockDevServer = @"http://localhost:8081"; @@ -299,7 +283,6 @@ - (void)testBeforeBreadcrumbsCallbackFiltersOutDevServerRequestBreadcrumbs - (void)testBeforeBreadcrumbsCallbackDoesNotFiltersOutNonDevServerOrDsnRequestBreadcrumbs { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedDictionary = @{ @@ -320,7 +303,6 @@ - (void)testBeforeBreadcrumbsCallbackDoesNotFiltersOutNonDevServerOrDsnRequestBr - (void)testBeforeBreadcrumbsCallbackKeepsBreadcrumbWhenDevServerUrlIsNotPassedAndDsnDoesNotMatch { - RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; NSDictionary *_Nonnull mockedDictionary = @{ // dsn is always validated in SentryOptions initialization @@ -340,7 +322,6 @@ - (void)testBeforeBreadcrumbsCallbackKeepsBreadcrumbWhenDevServerUrlIsNotPassedA - (void)testEventFromSentryCocoaReactNativeHasOriginAndEnvironmentTags { - RNSentry *rnSentry = [[RNSentry alloc] init]; SentryEvent *testEvent = [[SentryEvent alloc] init]; testEvent.sdk = @{ @"name" : @"sentry.cocoa.react-native", @@ -354,7 +335,6 @@ - (void)testEventFromSentryCocoaReactNativeHasOriginAndEnvironmentTags - (void)testEventFromSentryReactNativeOriginAndEnvironmentTagsAreOverwritten { - RNSentry *rnSentry = [[RNSentry alloc] init]; SentryEvent *testEvent = [[SentryEvent alloc] init]; testEvent.sdk = @{ @"name" : @"sentry.cocoa.react-native", From fc4ff7efb558e2a9eb59b1db5aedcdc47bcc5a8b Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Mon, 20 Jan 2025 16:44:40 +0100 Subject: [PATCH 18/21] fix changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b3ed24744..dc596b2e6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ ### Internal - Initialize `RNSentryTimeToDisplay` during native module `init` on iOS ([#4443](https://github.com/getsentry/sentry-react-native/pull/4443)) -- Extract iOS native initialization to standalone structures ([#4444](https://github.com/getsentry/sentry-react-native/pull/4444)) +- Extract iOS native initialization to standalone structures ([#4442](https://github.com/getsentry/sentry-react-native/pull/4442)) ### Dependencies From dec9869775c7167f1b192c5069280b0b9e791164 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Mon, 20 Jan 2025 18:57:40 +0100 Subject: [PATCH 19/21] wip! add manual init tests --- .../project.pbxproj | 4 + ...RNSentryCocoaTesterTests-Bridging-Header.h | 2 + .../RNSentryStartTests.swift | 112 ++++++++++++++++++ packages/core/ios/RNSentrySDK.h | 2 +- packages/core/ios/RNSentryStart.h | 2 +- packages/core/ios/RNSentryStart.m | 2 +- 6 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj b/packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj index b19dfc43fc..74701dfe9e 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 336084392C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 336084382C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift */; }; 3380C6C42CE25ECA0018B9B6 /* RNSentryReplayPostInitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3380C6C32CE25ECA0018B9B6 /* RNSentryReplayPostInitTests.swift */; }; 33958C692BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 33958C682BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m */; }; + 339C6C3C2D3EB25100CA72ED /* RNSentryStartTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 339C6C3B2D3EB23B00CA72ED /* RNSentryStartTests.swift */; }; 33AFDFED2B8D14B300AAB120 /* RNSentryFramesTrackerListenerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 33AFDFEC2B8D14B300AAB120 /* RNSentryFramesTrackerListenerTests.m */; }; 33AFDFF12B8D15E500AAB120 /* RNSentryDependencyContainerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 33AFDFF02B8D15E500AAB120 /* RNSentryDependencyContainerTests.m */; }; 33F58AD02977037D008F60EA /* RNSentryTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33F58ACF2977037D008F60EA /* RNSentryTests.mm */; }; @@ -38,6 +39,7 @@ 338739072A7D7D2800950DDD /* RNSentryReplay.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNSentryReplay.h; path = ../ios/RNSentryReplay.h; sourceTree = ""; }; 33958C672BFCEF5A00AD1FB6 /* RNSentryOnDrawReporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSentryOnDrawReporter.h; path = ../ios/RNSentryOnDrawReporter.h; sourceTree = ""; }; 33958C682BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNSentryOnDrawReporterTests.m; sourceTree = ""; }; + 339C6C3B2D3EB23B00CA72ED /* RNSentryStartTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNSentryStartTests.swift; sourceTree = ""; }; 33AFDFEC2B8D14B300AAB120 /* RNSentryFramesTrackerListenerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNSentryFramesTrackerListenerTests.m; sourceTree = ""; }; 33AFDFEE2B8D14C200AAB120 /* RNSentryFramesTrackerListenerTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNSentryFramesTrackerListenerTests.h; sourceTree = ""; }; 33AFDFF02B8D15E500AAB120 /* RNSentryDependencyContainerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNSentryDependencyContainerTests.m; sourceTree = ""; }; @@ -91,6 +93,7 @@ 3360899029524164007C7730 /* RNSentryCocoaTesterTests */ = { isa = PBXGroup; children = ( + 339C6C3B2D3EB23B00CA72ED /* RNSentryStartTests.swift */, 332D334A2CDCC8EB00547D76 /* RNSentryCocoaTesterTests-Bridging-Header.h */, 332D33492CDCC8E100547D76 /* RNSentryTests.h */, 336084382C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift */, @@ -245,6 +248,7 @@ files = ( AEFB00422CC90C4B00EC8A9A /* RNSentryBreadcrumbTests.swift in Sources */, 332D33472CDBDBB600547D76 /* RNSentryReplayOptionsTests.swift in Sources */, + 339C6C3C2D3EB25100CA72ED /* RNSentryStartTests.swift in Sources */, 33AFDFF12B8D15E500AAB120 /* RNSentryDependencyContainerTests.m in Sources */, 336084392C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift in Sources */, 33F58AD02977037D008F60EA /* RNSentryTests.mm in Sources */, diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryCocoaTesterTests-Bridging-Header.h b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryCocoaTesterTests-Bridging-Header.h index bc2bdd0304..f7aa88e193 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryCocoaTesterTests-Bridging-Header.h +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryCocoaTesterTests-Bridging-Header.h @@ -7,3 +7,5 @@ #import "RNSentryReplayBreadcrumbConverter.h" #import "RNSentryReplayMask.h" #import "RNSentryReplayUnmask.h" +#import "RNSentrySDK.h" +#import "RNSentryStart.h" diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift new file mode 100644 index 0000000000..15d56bc26a --- /dev/null +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift @@ -0,0 +1,112 @@ +import XCTest + +final class RNSentryStartTests: XCTestCase { + + func testStartDoesNotThrowWithoutConfigure() { + RNSentrySDK.start(configureOptions: nil) + } + + func assertReactDefaults(_ actualOptions: Options?) { + XCTAssertFalse(actualOptions!.enableCaptureFailedRequests) + XCTAssertNil(actualOptions!.tracesSampleRate) + XCTAssertNil(actualOptions!.tracesSampler) + XCTAssertFalse(actualOptions!.enableTracing) + } + + func testStartSetsReactDeafults() { + var actualOptions: Options? = nil + + RNSentrySDK.start { options in + actualOptions = options + } + + XCTAssertNotNil(actualOptions, "start have not provided default options or have not executed configure callback") + assertReactDefaults(actualOptions) + } + + func testAutoStartSetsReactDefaults() throws { + try startFromRN(options: [ + "dsn" : "https://abcd@efgh.ingest.sentry.io/123456" + ]) + + let actualOptions = PrivateSentrySDKOnly.options; + assertReactDefaults(actualOptions) + } + + func testAutoStartWithEmptyOptionsThrows() { + XCTAssertThrowsError(try startFromRN(options: [:])) + } + + func testStartEnablesHybridTracing() throws { + let testCases: [() throws -> Void] = [ + { + RNSentrySDK.start { options in + options.dsn = "https://abcd@efgh.ingest.sentry.io/123456" + } + }, + { + try self.startFromRN(options: [ + "dsn" : "https://abcd@efgh.ingest.sentry.io/123456", + ]) + }, + { + RNSentrySDK.start { options in + options.dsn = "https://abcd@efgh.ingest.sentry.io/123456" + options.enableAutoPerformanceTracing = true + } + }, + { + try self.startFromRN(options: [ + "dsn" : "https://abcd@efgh.ingest.sentry.io/123456", + "enableAutoPerformanceTracing": true, + ]) + } + ] + + // Test each implementation + for startMethod in testCases { + try startMethod() + } + + let actualOptions = PrivateSentrySDKOnly.options + + XCTAssertTrue(PrivateSentrySDKOnly.appStartMeasurementHybridSDKMode) + XCTAssertTrue(PrivateSentrySDKOnly.framesTrackingMeasurementHybridSDKMode) + } + + func testStartDisablesHybridTracing() throws { + let testCases: [() throws -> Void] = [ + { + RNSentrySDK.start { options in + options.dsn = "https://abcd@efgh.ingest.sentry.io/123456" + options.enableAutoPerformanceTracing = false + } + }, + { + try self.startFromRN(options: [ + "dsn" : "https://abcd@efgh.ingest.sentry.io/123456", + "enableAutoPerformanceTracing": false, + ]) + } + ] + + // Test each implementation + for startMethod in testCases { + try startMethod() + } + + let actualOptions = PrivateSentrySDKOnly.options + + XCTAssertFalse(PrivateSentrySDKOnly.appStartMeasurementHybridSDKMode) + XCTAssertFalse(PrivateSentrySDKOnly.framesTrackingMeasurementHybridSDKMode) + } + + func startFromRN(options: [AnyHashable: Any]) throws { + var error: NSError? + RNSentryStart.start(options: options, error: &error) + + if let error = error { + throw error + } + } +} diff --git a/packages/core/ios/RNSentrySDK.h b/packages/core/ios/RNSentrySDK.h index 28fca1d6d5..71b5be3498 100644 --- a/packages/core/ios/RNSentrySDK.h +++ b/packages/core/ios/RNSentrySDK.h @@ -11,7 +11,7 @@ SENTRY_NO_INIT * @discussion Call this method on the main thread. When calling it from a background thread, the * SDK starts on the main thread async. */ -+ (void)startWithConfigureOptions:(void (^)(SentryOptions *options))configureOptions ++ (void)startWithConfigureOptions:(void (^_Nullable)(SentryOptions *_Nonnull options))configureOptions NS_SWIFT_NAME(start(configureOptions:)); @end diff --git a/packages/core/ios/RNSentryStart.h b/packages/core/ios/RNSentryStart.h index a45ddea679..01a0617148 100644 --- a/packages/core/ios/RNSentryStart.h +++ b/packages/core/ios/RNSentryStart.h @@ -5,7 +5,7 @@ SENTRY_NO_INIT + (void)startWithOptions:(NSDictionary *_Nonnull)javascriptOptions - error:(NSError *_Nonnull *_Nonnull)errorPointer; + error:(NSError *_Nullable *_Nullable)errorPointer; + (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull)options error:(NSError *_Nonnull *_Nonnull)errorPointer; diff --git a/packages/core/ios/RNSentryStart.m b/packages/core/ios/RNSentryStart.m index 5cf0d430e4..cfbc6f8d64 100644 --- a/packages/core/ios/RNSentryStart.m +++ b/packages/core/ios/RNSentryStart.m @@ -10,7 +10,7 @@ @implementation RNSentryStart + (void)startWithOptions:(NSDictionary *_Nonnull)javascriptOptions - error:(NSError *_Nonnull *_Nonnull)errorPointer + error:(NSError *_Nullable*_Nullable)errorPointer { SentryOptions *options = [self createOptionsWithDictionary:javascriptOptions error:errorPointer]; From 22e88c7d8d2e44b7ebceb0da2dc5b094af121893 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 21 Jan 2025 14:01:14 +0100 Subject: [PATCH 20/21] add tests --- .../project.pbxproj | 2 + ...RNSentryCocoaTesterTests-Bridging-Header.h | 1 + .../RNSentryStartTests.swift | 178 +++++++++++++++--- packages/core/ios/RNSentrySDK.h | 3 +- packages/core/ios/RNSentrySDK.m | 4 +- packages/core/ios/RNSentryStart.m | 21 +-- 6 files changed, 174 insertions(+), 35 deletions(-) diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj b/packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj index 74701dfe9e..1621383063 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj @@ -40,6 +40,7 @@ 33958C672BFCEF5A00AD1FB6 /* RNSentryOnDrawReporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSentryOnDrawReporter.h; path = ../ios/RNSentryOnDrawReporter.h; sourceTree = ""; }; 33958C682BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNSentryOnDrawReporterTests.m; sourceTree = ""; }; 339C6C3B2D3EB23B00CA72ED /* RNSentryStartTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNSentryStartTests.swift; sourceTree = ""; }; + 339C6C3D2D3FA04D00CA72ED /* RNSentryVersion.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNSentryVersion.h; path = ../ios/RNSentryVersion.h; sourceTree = SOURCE_ROOT; }; 33AFDFEC2B8D14B300AAB120 /* RNSentryFramesTrackerListenerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNSentryFramesTrackerListenerTests.m; sourceTree = ""; }; 33AFDFEE2B8D14C200AAB120 /* RNSentryFramesTrackerListenerTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNSentryFramesTrackerListenerTests.h; sourceTree = ""; }; 33AFDFF02B8D15E500AAB120 /* RNSentryDependencyContainerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNSentryDependencyContainerTests.m; sourceTree = ""; }; @@ -122,6 +123,7 @@ 33AFE0122B8F319000AAB120 /* RNSentry */ = { isa = PBXGroup; children = ( + 339C6C3D2D3FA04D00CA72ED /* RNSentryVersion.h */, 333B58AF2D36A7FD000F8D04 /* RNSentrySDK.h */, 333B58A92D35BB2D000F8D04 /* RNSentryStart+Test.h */, 333B58A82D35BA93000F8D04 /* RNSentryStart.h */, diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryCocoaTesterTests-Bridging-Header.h b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryCocoaTesterTests-Bridging-Header.h index f7aa88e193..ba8d8f703d 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryCocoaTesterTests-Bridging-Header.h +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryCocoaTesterTests-Bridging-Header.h @@ -9,3 +9,4 @@ #import "RNSentryReplayUnmask.h" #import "RNSentrySDK.h" #import "RNSentryStart.h" +#import "RNSentryVersion.h" diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift index 15d56bc26a..b9d12200cf 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift @@ -14,7 +14,7 @@ final class RNSentryStartTests: XCTestCase { } func testStartSetsReactDeafults() { - var actualOptions: Options? = nil + var actualOptions: Options? RNSentrySDK.start { options in actualOptions = options @@ -26,17 +26,13 @@ final class RNSentryStartTests: XCTestCase { func testAutoStartSetsReactDefaults() throws { try startFromRN(options: [ - "dsn" : "https://abcd@efgh.ingest.sentry.io/123456" + "dsn": "https://abcd@efgh.ingest.sentry.io/123456" ]) - let actualOptions = PrivateSentrySDKOnly.options; + let actualOptions = PrivateSentrySDKOnly.options assertReactDefaults(actualOptions) } - func testAutoStartWithEmptyOptionsThrows() { - XCTAssertThrowsError(try startFromRN(options: [:])) - } - func testStartEnablesHybridTracing() throws { let testCases: [() throws -> Void] = [ { @@ -46,7 +42,7 @@ final class RNSentryStartTests: XCTestCase { }, { try self.startFromRN(options: [ - "dsn" : "https://abcd@efgh.ingest.sentry.io/123456", + "dsn": "https://abcd@efgh.ingest.sentry.io/123456" ]) }, { @@ -57,8 +53,8 @@ final class RNSentryStartTests: XCTestCase { }, { try self.startFromRN(options: [ - "dsn" : "https://abcd@efgh.ingest.sentry.io/123456", - "enableAutoPerformanceTracing": true, + "dsn": "https://abcd@efgh.ingest.sentry.io/123456", + "enableAutoPerformanceTracing": true ]) } ] @@ -66,12 +62,12 @@ final class RNSentryStartTests: XCTestCase { // Test each implementation for startMethod in testCases { try startMethod() - } - let actualOptions = PrivateSentrySDKOnly.options + let actualOptions = PrivateSentrySDKOnly.options - XCTAssertTrue(PrivateSentrySDKOnly.appStartMeasurementHybridSDKMode) - XCTAssertTrue(PrivateSentrySDKOnly.framesTrackingMeasurementHybridSDKMode) + XCTAssertTrue(PrivateSentrySDKOnly.appStartMeasurementHybridSDKMode) + XCTAssertTrue(PrivateSentrySDKOnly.framesTrackingMeasurementHybridSDKMode) + } } func testStartDisablesHybridTracing() throws { @@ -84,24 +80,124 @@ final class RNSentryStartTests: XCTestCase { }, { try self.startFromRN(options: [ - "dsn" : "https://abcd@efgh.ingest.sentry.io/123456", - "enableAutoPerformanceTracing": false, + "dsn": "https://abcd@efgh.ingest.sentry.io/123456", + "enableAutoPerformanceTracing": false ]) } ] - // Test each implementation for startMethod in testCases { try startMethod() + + let actualOptions = PrivateSentrySDKOnly.options + + XCTAssertFalse(PrivateSentrySDKOnly.appStartMeasurementHybridSDKMode) + XCTAssertFalse(PrivateSentrySDKOnly.framesTrackingMeasurementHybridSDKMode) } + } - let actualOptions = PrivateSentrySDKOnly.options + func testStartIgnoresUnhandledJsExceptions() throws { + let testCases: [() throws -> Void] = [ + { + RNSentrySDK.start { options in + options.dsn = "https://abcd@efgh.ingest.sentry.io/123456" + } + }, + { + try self.startFromRN(options: [ + "dsn": "https://abcd@efgh.ingest.sentry.io/123456" + ]) + } + ] + + for startMethod in testCases { + try startMethod() + + let actualOptions = PrivateSentrySDKOnly.options - XCTAssertFalse(PrivateSentrySDKOnly.appStartMeasurementHybridSDKMode) - XCTAssertFalse(PrivateSentrySDKOnly.framesTrackingMeasurementHybridSDKMode) + let actualEvent = actualOptions.beforeSend!(createUnhandledJsExceptionEvent()) + + XCTAssertNil(actualEvent) + } } - func startFromRN(options: [AnyHashable: Any]) throws { + func testStartSetsNativeEventOrigin() throws { + let testCases: [() throws -> Void] = [ + { + RNSentrySDK.start { options in + options.dsn = "https://abcd@efgh.ingest.sentry.io/123456" + } + }, + { + try self.startFromRN(options: [ + "dsn": "https://abcd@efgh.ingest.sentry.io/123456" + ]) + } + ] + + for startMethod in testCases { + try startMethod() + + let actualOptions = PrivateSentrySDKOnly.options + + let actualEvent = actualOptions.beforeSend!(createNativeEvent()) + + XCTAssertNotNil(actualEvent) + XCTAssertNotNil(actualEvent!.tags) + XCTAssertEqual(actualEvent!.tags!["event.origin"], "ios") + XCTAssertEqual(actualEvent!.tags!["event.environment"], "native") + } + } + + func testStartDoesNotOverwriteUserBeforeSend() { + var executed = false + + RNSentrySDK.start { options in + options.dsn = "https://abcd@efgh.ingest.sentry.io/123456" + options.beforeSend = { event in + executed = true + return event + } + } + + PrivateSentrySDKOnly.options.beforeSend!(genericEvent()) + + XCTAssertTrue(executed) + } + + func testStartSetsHybridSdkName() throws { + let testCases: [() throws -> Void] = [ + { + RNSentrySDK.start { options in + options.dsn = "https://abcd@efgh.ingest.sentry.io/123456" + } + }, + { + try self.startFromRN(options: [ + "dsn": "https://abcd@efgh.ingest.sentry.io/123456" + ]) + } + ] + + for startMethod in testCases { + try startMethod() + + let actualEvent = captuteTestEvent() + + XCTAssertNotNil(actualEvent) + XCTAssertNotNil(actualEvent!.sdk) + XCTAssertEqual(actualEvent!.sdk!["name"] as! String, NATIVE_SDK_NAME) + + let packages = actualEvent!.sdk!["packages"] as! [[String: String]] + let reactPackage = packages.first { $0["name"] == REACT_NATIVE_SDK_PACKAGE_NAME } + + XCTAssertNotNil(reactPackage) + XCTAssertEqual(reactPackage!["name"], REACT_NATIVE_SDK_PACKAGE_NAME) + XCTAssertEqual(reactPackage!["version"], REACT_NATIVE_SDK_PACKAGE_VERSION) + } + } + + func startFromRN(options: [String: Any]) throws { var error: NSError? RNSentryStart.start(options: options, error: &error) @@ -109,4 +205,44 @@ final class RNSentryStartTests: XCTestCase { throw error } } + + func createUnhandledJsExceptionEvent() -> Event { + let event = Event() + event.exceptions = [] + event.exceptions!.append(Exception(value: "Test", type: "Unhandled JS Exception: undefined is not a function")) + return event + } + + func createNativeEvent() -> Event { + let event = Event() + event.sdk = [ + "name": NATIVE_SDK_NAME, + "version": "1.2.3" + ] + return event + } + + func genericEvent() -> Event { + return Event() + } + + func captuteTestEvent() -> Event? { + var actualEvent: Event? + + // This is the closest to the sent event we can get using the actual Sentry start method + let originalBeforeSend = PrivateSentrySDKOnly.options.beforeSend + PrivateSentrySDKOnly.options.beforeSend = { event in + if let originalBeforeSend = originalBeforeSend { + let processedEvent = originalBeforeSend(event) + actualEvent = processedEvent + return processedEvent + } + actualEvent = event + return event + } + + SentrySDK.capture(message: "Test") + + return actualEvent + } } diff --git a/packages/core/ios/RNSentrySDK.h b/packages/core/ios/RNSentrySDK.h index 71b5be3498..7d3512bb5d 100644 --- a/packages/core/ios/RNSentrySDK.h +++ b/packages/core/ios/RNSentrySDK.h @@ -11,7 +11,8 @@ SENTRY_NO_INIT * @discussion Call this method on the main thread. When calling it from a background thread, the * SDK starts on the main thread async. */ -+ (void)startWithConfigureOptions:(void (^_Nullable)(SentryOptions *_Nonnull options))configureOptions ++ (void)startWithConfigureOptions: + (void (^_Nullable)(SentryOptions *_Nonnull options))configureOptions NS_SWIFT_NAME(start(configureOptions:)); @end diff --git a/packages/core/ios/RNSentrySDK.m b/packages/core/ios/RNSentrySDK.m index f4031dc06b..b7ed6f4a7b 100644 --- a/packages/core/ios/RNSentrySDK.m +++ b/packages/core/ios/RNSentrySDK.m @@ -7,7 +7,9 @@ + (void)startWithConfigureOptions:(void (^)(SentryOptions *options))configureOpt { SentryOptions *options = [[SentryOptions alloc] init]; [RNSentryStart updateWithReactDefaults:options]; - configureOptions(options); + if (configureOptions != nil) { + configureOptions(options); + } [RNSentryStart updateWithReactFinals:options]; [RNSentryStart startWithOptions:options]; } diff --git a/packages/core/ios/RNSentryStart.m b/packages/core/ios/RNSentryStart.m index cfbc6f8d64..5b8398e06c 100644 --- a/packages/core/ios/RNSentryStart.m +++ b/packages/core/ios/RNSentryStart.m @@ -10,7 +10,7 @@ @implementation RNSentryStart + (void)startWithOptions:(NSDictionary *_Nonnull)javascriptOptions - error:(NSError *_Nullable*_Nullable)errorPointer + error:(NSError *_Nullable *_Nullable)errorPointer { SentryOptions *options = [self createOptionsWithDictionary:javascriptOptions error:errorPointer]; @@ -137,19 +137,16 @@ + (void)updateWithReactFinals:(SentryOptions *)options } }; - if (options.enableAutoPerformanceTracing != nil) { - // App Start Hybrid mode doesn't wait for didFinishLaunchNotification and the - // didBecomeVisibleNotification as they will be missed when auto initializing from JS - // App Start measurements are created right after the tracking starts - PrivateSentrySDKOnly.appStartMeasurementHybridSDKMode - = options.enableAutoPerformanceTracing; + // App Start Hybrid mode doesn't wait for didFinishLaunchNotification and the + // didBecomeVisibleNotification as they will be missed when auto initializing from JS + // App Start measurements are created right after the tracking starts + PrivateSentrySDKOnly.appStartMeasurementHybridSDKMode = options.enableAutoPerformanceTracing; #if TARGET_OS_IPHONE || TARGET_OS_MACCATALYST - // Frames Tracking Hybrid Mode ensures tracking - // is enabled without tracing enabled in the native SDK - PrivateSentrySDKOnly.framesTrackingMeasurementHybridSDKMode - = options.enableAutoPerformanceTracing; + // Frames Tracking Hybrid Mode ensures tracking + // is enabled without tracing enabled in the native SDK + PrivateSentrySDKOnly.framesTrackingMeasurementHybridSDKMode + = options.enableAutoPerformanceTracing; #endif - } } + (void)setEventOriginTag:(SentryEvent *)event From f92edd920b95a2680ab4fc707ef2fdd62153384c Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 21 Jan 2025 17:09:00 +0100 Subject: [PATCH 21/21] remove unavailable and unused global events processors import --- packages/core/ios/RNSentryStart.m | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/ios/RNSentryStart.m b/packages/core/ios/RNSentryStart.m index 5b8398e06c..b3d4d5d77e 100644 --- a/packages/core/ios/RNSentryStart.m +++ b/packages/core/ios/RNSentryStart.m @@ -4,7 +4,6 @@ #import #import -#import #import @implementation RNSentryStart