diff --git a/CHANGELOG.md b/CHANGELOG.md index b9f3c844e4..2e19b0d0b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ - Fix compatibility with `react-native-legal` ([#5253](https://github.com/getsentry/sentry-react-native/pull/5253)) - The licenses json file is correctly generated and placed into the `res/` folder now - Handle missing shouldAddToIgnoreList callback in Metro ([#5260](https://github.com/getsentry/sentry-react-native/pull/5260)) +- Overrides the default Cocoa SDK behavior that disables Session Replay on iOS 26.0 ([#5268](https://github.com/getsentry/sentry-react-native/pull/5268)) + - If you are using Apple's Liquid Glass we recommend that you disable Session Replay on iOS to prevent potential PII leaks (see [sentry-cocoa 8.57.0 release note warning](https://github.com/getsentry/sentry-cocoa/releases/tag/8.57.0)) ### Dependencies @@ -30,6 +32,9 @@ - Bump Bundler Plugins from v4.3.0 to v4.4.0 ([#5256](https://github.com/getsentry/sentry-react-native/pull/5256)) - [changelog](https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/main/CHANGELOG.md#440) - [diff](https://github.com/getsentry/sentry-javascript-bundler-plugins/compare/4.3.0...4.4.0) +- Bump Cocoa SDK from v8.56.2 to v8.57.0 ([#5263](https://github.com/getsentry/sentry-react-native/pull/5263)) + - [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8570) + - [diff](https://github.com/getsentry/sentry-cocoa/compare/8.56.2...8.57.0) ## 7.3.0 diff --git a/packages/core/RNSentry.podspec b/packages/core/RNSentry.podspec index 3548e17a6f..bab1567295 100644 --- a/packages/core/RNSentry.podspec +++ b/packages/core/RNSentry.podspec @@ -46,7 +46,7 @@ Pod::Spec.new do |s| s.compiler_flags = other_cflags - s.dependency 'Sentry/HybridSDK', '8.56.2' + s.dependency 'Sentry/HybridSDK', '8.57.0' if defined? install_modules_dependencies # Default React Native dependencies for 0.71 and above (new and legacy architecture) diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.m b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.m index 4c9a9d1e04..1929c12b7b 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.m +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.m @@ -736,4 +736,129 @@ - (void)testIgnoreErrorsRegexAndStringBothWork XCTAssertNotNil(result3, @"Event with non-matching error should not be dropped"); } +- (void)testCreateOptionsWithDictionaryEnableSessionReplayInUnreliableEnvironmentDefault +{ + RNSentry *rnSentry = [[RNSentry alloc] init]; + NSError *error = nil; + + NSDictionary *_Nonnull mockedReactNativeDictionary = @{ + @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", + }; + SentryOptions *actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary + error:&error]; + + XCTAssertNotNil(actualOptions, @"Did not create sentry options"); + XCTAssertNil(error, @"Should not pass no error"); + + id experimentalOptions = [actualOptions valueForKey:@"experimental"]; + XCTAssertNotNil(experimentalOptions, @"Experimental options should not be nil"); + + BOOL enableUnhandledCPPExceptions = + [[experimentalOptions valueForKey:@"enableSessionReplayInUnreliableEnvironment"] boolValue]; + XCTAssertFalse(enableUnhandledCPPExceptions, + @"enableSessionReplayInUnreliableEnvironment should be disabled"); +} + +- (void)testCreateOptionsWithDictionaryEnableSessionReplayInUnreliableEnvironmentWithErrorSampleRate +{ + RNSentry *rnSentry = [[RNSentry alloc] init]; + NSError *error = nil; + + NSDictionary *_Nonnull mockedReactNativeDictionary = @{ + @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", + @"replaysOnErrorSampleRate" : @1.0, + @"replaysSessionSampleRate" : @0 + }; + SentryOptions *actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary + error:&error]; + + XCTAssertNotNil(actualOptions, @"Did not create sentry options"); + XCTAssertNil(error, @"Should not pass no error"); + + id experimentalOptions = [actualOptions valueForKey:@"experimental"]; + XCTAssertNotNil(experimentalOptions, @"Experimental options should not be nil"); + + BOOL enableUnhandledCPPExceptions = + [[experimentalOptions valueForKey:@"enableSessionReplayInUnreliableEnvironment"] boolValue]; + XCTAssertTrue(enableUnhandledCPPExceptions, + @"enableSessionReplayInUnreliableEnvironment should be enabled"); +} + +- (void) + testCreateOptionsWithDictionaryEnableSessionReplayInUnreliableEnvironmentWithSessionSampleRate +{ + RNSentry *rnSentry = [[RNSentry alloc] init]; + NSError *error = nil; + + NSDictionary *_Nonnull mockedReactNativeDictionary = @{ + @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", + @"replaysOnErrorSampleRate" : @0.0, + @"replaysSessionSampleRate" : @0.1 + }; + SentryOptions *actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary + error:&error]; + + XCTAssertNotNil(actualOptions, @"Did not create sentry options"); + XCTAssertNil(error, @"Should not pass no error"); + + id experimentalOptions = [actualOptions valueForKey:@"experimental"]; + XCTAssertNotNil(experimentalOptions, @"Experimental options should not be nil"); + + BOOL enableUnhandledCPPExceptions = + [[experimentalOptions valueForKey:@"enableSessionReplayInUnreliableEnvironment"] boolValue]; + XCTAssertTrue(enableUnhandledCPPExceptions, + @"enableSessionReplayInUnreliableEnvironment should be enabled"); +} + +- (void) + testCreateOptionsWithDictionaryEnableSessionReplayInUnreliableEnvironmentWithSessionSampleRates +{ + RNSentry *rnSentry = [[RNSentry alloc] init]; + NSError *error = nil; + + NSDictionary *_Nonnull mockedReactNativeDictionary = @{ + @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", + @"replaysOnErrorSampleRate" : @1.0, + @"replaysSessionSampleRate" : @0.1 + }; + SentryOptions *actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary + error:&error]; + + XCTAssertNotNil(actualOptions, @"Did not create sentry options"); + XCTAssertNil(error, @"Should not pass no error"); + + id experimentalOptions = [actualOptions valueForKey:@"experimental"]; + XCTAssertNotNil(experimentalOptions, @"Experimental options should not be nil"); + + BOOL enableUnhandledCPPExceptions = + [[experimentalOptions valueForKey:@"enableSessionReplayInUnreliableEnvironment"] boolValue]; + XCTAssertTrue(enableUnhandledCPPExceptions, + @"enableSessionReplayInUnreliableEnvironment should be enabled"); +} + +- (void)testCreateOptionsWithDictionaryEnableSessionReplayInUnreliableEnvironmentDisabled +{ + RNSentry *rnSentry = [[RNSentry alloc] init]; + NSError *error = nil; + + NSDictionary *_Nonnull mockedReactNativeDictionary = @{ + @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", + @"replaysOnErrorSampleRate" : @0, + @"replaysSessionSampleRate" : @0 + }; + SentryOptions *actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary + error:&error]; + + XCTAssertNotNil(actualOptions, @"Did not create sentry options"); + XCTAssertNil(error, @"Should not pass no error"); + + id experimentalOptions = [actualOptions valueForKey:@"experimental"]; + XCTAssertNotNil(experimentalOptions, @"Experimental options should not be nil"); + + BOOL enableUnhandledCPPExceptions = + [[experimentalOptions valueForKey:@"enableSessionReplayInUnreliableEnvironment"] boolValue]; + XCTAssertFalse(enableUnhandledCPPExceptions, + @"enableSessionReplayInUnreliableEnvironment should be disabled"); +} + @end diff --git a/packages/core/ios/RNSentry.mm b/packages/core/ios/RNSentry.mm index 2f0f3cb529..c47cf779c0 100644 --- a/packages/core/ios/RNSentry.mm +++ b/packages/core/ios/RNSentry.mm @@ -234,7 +234,10 @@ - (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull) [mutableOptions removeObjectForKey:@"enableTracing"]; #if SENTRY_TARGET_REPLAY_SUPPORTED - [RNSentryReplay updateOptions:mutableOptions]; + BOOL isSessionReplayEnabled = [RNSentryReplay updateOptions:mutableOptions]; +#else + // Defaulting to false for unsupported targets + BOOL isSessionReplayEnabled = NO; #endif SentryOptions *sentryOptions = [SentryOptionsInternal initWithDict:mutableOptions @@ -315,6 +318,11 @@ - (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull) sentryOptions:sentryOptions]; } + if (isSessionReplayEnabled) { + [RNSentryExperimentalOptions setEnableSessionReplayInUnreliableEnvironment:YES + sentryOptions:sentryOptions]; + } + return sentryOptions; } diff --git a/packages/core/ios/RNSentryExperimentalOptions.h b/packages/core/ios/RNSentryExperimentalOptions.h index 05fab2ddef..ec0501cb05 100644 --- a/packages/core/ios/RNSentryExperimentalOptions.h +++ b/packages/core/ios/RNSentryExperimentalOptions.h @@ -28,6 +28,15 @@ NS_ASSUME_NONNULL_BEGIN */ + (void)setEnableLogs:(BOOL)enabled sentryOptions:(SentryOptions *)sentryOptions; +/** + * Sets the enableSessionReplayInUnreliableEnvironment experimental option on SentryOptions + * @param sentryOptions The SentryOptions instance to configure + * @param enabled Whether enableSessionReplayInUnreliableEnvironment from sentry Cocoa should be + * enabled + */ ++ (void)setEnableSessionReplayInUnreliableEnvironment:(BOOL)enabled + sentryOptions:(SentryOptions *)sentryOptions; + @end NS_ASSUME_NONNULL_END diff --git a/packages/core/ios/RNSentryExperimentalOptions.m b/packages/core/ios/RNSentryExperimentalOptions.m index 2e7db99a71..7e0974e527 100644 --- a/packages/core/ios/RNSentryExperimentalOptions.m +++ b/packages/core/ios/RNSentryExperimentalOptions.m @@ -27,4 +27,13 @@ + (void)setEnableLogs:(BOOL)enabled sentryOptions:(SentryOptions *)sentryOptions sentryOptions.experimental.enableLogs = enabled; } ++ (void)setEnableSessionReplayInUnreliableEnvironment:(BOOL)enabled + sentryOptions:(SentryOptions *)sentryOptions +{ + if (sentryOptions == nil) { + return; + } + sentryOptions.experimental.enableSessionReplayInUnreliableEnvironment = enabled; +} + @end diff --git a/packages/core/ios/RNSentryReplay.h b/packages/core/ios/RNSentryReplay.h index 452914af15..cda7035550 100644 --- a/packages/core/ios/RNSentryReplay.h +++ b/packages/core/ios/RNSentryReplay.h @@ -1,7 +1,11 @@ @interface RNSentryReplay : NSObject -+ (void)updateOptions:(NSMutableDictionary *)options; +/** + * Updates the session replay options + * @return true when session replay is enabled + */ ++ (BOOL)updateOptions:(NSMutableDictionary *)options; + (void)postInit; diff --git a/packages/core/ios/RNSentryReplay.mm b/packages/core/ios/RNSentryReplay.mm index 263ce6f6cb..94fa30b4e4 100644 --- a/packages/core/ios/RNSentryReplay.mm +++ b/packages/core/ios/RNSentryReplay.mm @@ -12,12 +12,14 @@ @implementation RNSentryReplay { } -+ (void)updateOptions:(NSMutableDictionary *)options ++ (BOOL)updateOptions:(NSMutableDictionary *)options { - if (options[@"replaysSessionSampleRate"] == nil - && options[@"replaysOnErrorSampleRate"] == nil) { + NSNumber *sessionSampleRate = options[@"replaysSessionSampleRate"]; + NSNumber *errorSampleRate = options[@"replaysOnErrorSampleRate"]; + + if (sessionSampleRate == nil && errorSampleRate == nil) { NSLog(@"Session replay disabled via configuration"); - return; + return NO; } NSLog(@"Setting up session replay"); @@ -26,8 +28,8 @@ + (void)updateOptions:(NSMutableDictionary *)options NSString *qualityString = options[@"replaysSessionQuality"]; [options setValue:@{ - @"sessionSampleRate" : options[@"replaysSessionSampleRate"] ?: [NSNull null], - @"errorSampleRate" : options[@"replaysOnErrorSampleRate"] ?: [NSNull null], + @"sessionSampleRate" : sessionSampleRate ?: [NSNull null], + @"errorSampleRate" : errorSampleRate ?: [NSNull null], @"quality" : @([RNSentryReplayQuality parseReplayQuality:qualityString]), @"maskAllImages" : replayOptions[@"maskAllImages"] ?: [NSNull null], @"maskAllText" : replayOptions[@"maskAllText"] ?: [NSNull null], @@ -38,6 +40,8 @@ + (void)updateOptions:(NSMutableDictionary *)options @ { @"name" : REACT_NATIVE_SDK_NAME, @"version" : REACT_NATIVE_SDK_PACKAGE_VERSION } } forKey:@"sessionReplay"]; + return (errorSampleRate != nil && [errorSampleRate doubleValue] > 0) + || (sessionSampleRate != nil && [sessionSampleRate doubleValue] > 0); } + (NSArray *_Nonnull)getReplayRNRedactClasses:(NSDictionary *_Nullable)replayOptions