Skip to content

Commit 1d8a64b

Browse files
authored
feat(session-replay): Set enableSessionReplayInUnreliableEnvironment on iOS (#5268)
* feat(session-replay): Set enableSessionReplayInUnreliableEnvironment on iOS * Adds tests * Add changelog * fix disabled condition * Add comment for targets that do not support session replay * Add reference to cocoa release note
1 parent 954c620 commit 1d8a64b

File tree

7 files changed

+169
-8
lines changed

7 files changed

+169
-8
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
- Fix compatibility with `react-native-legal` ([#5253](https://github.com/getsentry/sentry-react-native/pull/5253))
1919
- The licenses json file is correctly generated and placed into the `res/` folder now
2020
- Handle missing shouldAddToIgnoreList callback in Metro ([#5260](https://github.com/getsentry/sentry-react-native/pull/5260))
21+
- Overrides the default Cocoa SDK behavior that disables Session Replay on iOS 26.0 ([#5268](https://github.com/getsentry/sentry-react-native/pull/5268))
22+
- 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))
2123

2224
### Dependencies
2325

packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.m

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,4 +736,129 @@ - (void)testIgnoreErrorsRegexAndStringBothWork
736736
XCTAssertNotNil(result3, @"Event with non-matching error should not be dropped");
737737
}
738738

739+
- (void)testCreateOptionsWithDictionaryEnableSessionReplayInUnreliableEnvironmentDefault
740+
{
741+
RNSentry *rnSentry = [[RNSentry alloc] init];
742+
NSError *error = nil;
743+
744+
NSDictionary *_Nonnull mockedReactNativeDictionary = @{
745+
@"dsn" : @"https://[email protected]/123456",
746+
};
747+
SentryOptions *actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary
748+
error:&error];
749+
750+
XCTAssertNotNil(actualOptions, @"Did not create sentry options");
751+
XCTAssertNil(error, @"Should not pass no error");
752+
753+
id experimentalOptions = [actualOptions valueForKey:@"experimental"];
754+
XCTAssertNotNil(experimentalOptions, @"Experimental options should not be nil");
755+
756+
BOOL enableUnhandledCPPExceptions =
757+
[[experimentalOptions valueForKey:@"enableSessionReplayInUnreliableEnvironment"] boolValue];
758+
XCTAssertFalse(enableUnhandledCPPExceptions,
759+
@"enableSessionReplayInUnreliableEnvironment should be disabled");
760+
}
761+
762+
- (void)testCreateOptionsWithDictionaryEnableSessionReplayInUnreliableEnvironmentWithErrorSampleRate
763+
{
764+
RNSentry *rnSentry = [[RNSentry alloc] init];
765+
NSError *error = nil;
766+
767+
NSDictionary *_Nonnull mockedReactNativeDictionary = @{
768+
@"dsn" : @"https://[email protected]/123456",
769+
@"replaysOnErrorSampleRate" : @1.0,
770+
@"replaysSessionSampleRate" : @0
771+
};
772+
SentryOptions *actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary
773+
error:&error];
774+
775+
XCTAssertNotNil(actualOptions, @"Did not create sentry options");
776+
XCTAssertNil(error, @"Should not pass no error");
777+
778+
id experimentalOptions = [actualOptions valueForKey:@"experimental"];
779+
XCTAssertNotNil(experimentalOptions, @"Experimental options should not be nil");
780+
781+
BOOL enableUnhandledCPPExceptions =
782+
[[experimentalOptions valueForKey:@"enableSessionReplayInUnreliableEnvironment"] boolValue];
783+
XCTAssertTrue(enableUnhandledCPPExceptions,
784+
@"enableSessionReplayInUnreliableEnvironment should be enabled");
785+
}
786+
787+
- (void)
788+
testCreateOptionsWithDictionaryEnableSessionReplayInUnreliableEnvironmentWithSessionSampleRate
789+
{
790+
RNSentry *rnSentry = [[RNSentry alloc] init];
791+
NSError *error = nil;
792+
793+
NSDictionary *_Nonnull mockedReactNativeDictionary = @{
794+
@"dsn" : @"https://[email protected]/123456",
795+
@"replaysOnErrorSampleRate" : @0.0,
796+
@"replaysSessionSampleRate" : @0.1
797+
};
798+
SentryOptions *actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary
799+
error:&error];
800+
801+
XCTAssertNotNil(actualOptions, @"Did not create sentry options");
802+
XCTAssertNil(error, @"Should not pass no error");
803+
804+
id experimentalOptions = [actualOptions valueForKey:@"experimental"];
805+
XCTAssertNotNil(experimentalOptions, @"Experimental options should not be nil");
806+
807+
BOOL enableUnhandledCPPExceptions =
808+
[[experimentalOptions valueForKey:@"enableSessionReplayInUnreliableEnvironment"] boolValue];
809+
XCTAssertTrue(enableUnhandledCPPExceptions,
810+
@"enableSessionReplayInUnreliableEnvironment should be enabled");
811+
}
812+
813+
- (void)
814+
testCreateOptionsWithDictionaryEnableSessionReplayInUnreliableEnvironmentWithSessionSampleRates
815+
{
816+
RNSentry *rnSentry = [[RNSentry alloc] init];
817+
NSError *error = nil;
818+
819+
NSDictionary *_Nonnull mockedReactNativeDictionary = @{
820+
@"dsn" : @"https://[email protected]/123456",
821+
@"replaysOnErrorSampleRate" : @1.0,
822+
@"replaysSessionSampleRate" : @0.1
823+
};
824+
SentryOptions *actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary
825+
error:&error];
826+
827+
XCTAssertNotNil(actualOptions, @"Did not create sentry options");
828+
XCTAssertNil(error, @"Should not pass no error");
829+
830+
id experimentalOptions = [actualOptions valueForKey:@"experimental"];
831+
XCTAssertNotNil(experimentalOptions, @"Experimental options should not be nil");
832+
833+
BOOL enableUnhandledCPPExceptions =
834+
[[experimentalOptions valueForKey:@"enableSessionReplayInUnreliableEnvironment"] boolValue];
835+
XCTAssertTrue(enableUnhandledCPPExceptions,
836+
@"enableSessionReplayInUnreliableEnvironment should be enabled");
837+
}
838+
839+
- (void)testCreateOptionsWithDictionaryEnableSessionReplayInUnreliableEnvironmentDisabled
840+
{
841+
RNSentry *rnSentry = [[RNSentry alloc] init];
842+
NSError *error = nil;
843+
844+
NSDictionary *_Nonnull mockedReactNativeDictionary = @{
845+
@"dsn" : @"https://[email protected]/123456",
846+
@"replaysOnErrorSampleRate" : @0,
847+
@"replaysSessionSampleRate" : @0
848+
};
849+
SentryOptions *actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary
850+
error:&error];
851+
852+
XCTAssertNotNil(actualOptions, @"Did not create sentry options");
853+
XCTAssertNil(error, @"Should not pass no error");
854+
855+
id experimentalOptions = [actualOptions valueForKey:@"experimental"];
856+
XCTAssertNotNil(experimentalOptions, @"Experimental options should not be nil");
857+
858+
BOOL enableUnhandledCPPExceptions =
859+
[[experimentalOptions valueForKey:@"enableSessionReplayInUnreliableEnvironment"] boolValue];
860+
XCTAssertFalse(enableUnhandledCPPExceptions,
861+
@"enableSessionReplayInUnreliableEnvironment should be disabled");
862+
}
863+
739864
@end

packages/core/ios/RNSentry.mm

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,10 @@ - (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull)
234234
[mutableOptions removeObjectForKey:@"enableTracing"];
235235

236236
#if SENTRY_TARGET_REPLAY_SUPPORTED
237-
[RNSentryReplay updateOptions:mutableOptions];
237+
BOOL isSessionReplayEnabled = [RNSentryReplay updateOptions:mutableOptions];
238+
#else
239+
// Defaulting to false for unsupported targets
240+
BOOL isSessionReplayEnabled = NO;
238241
#endif
239242

240243
SentryOptions *sentryOptions = [SentryOptionsInternal initWithDict:mutableOptions
@@ -315,6 +318,11 @@ - (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull)
315318
sentryOptions:sentryOptions];
316319
}
317320

321+
if (isSessionReplayEnabled) {
322+
[RNSentryExperimentalOptions setEnableSessionReplayInUnreliableEnvironment:YES
323+
sentryOptions:sentryOptions];
324+
}
325+
318326
return sentryOptions;
319327
}
320328

packages/core/ios/RNSentryExperimentalOptions.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ NS_ASSUME_NONNULL_BEGIN
2828
*/
2929
+ (void)setEnableLogs:(BOOL)enabled sentryOptions:(SentryOptions *)sentryOptions;
3030

31+
/**
32+
* Sets the enableSessionReplayInUnreliableEnvironment experimental option on SentryOptions
33+
* @param sentryOptions The SentryOptions instance to configure
34+
* @param enabled Whether enableSessionReplayInUnreliableEnvironment from sentry Cocoa should be
35+
* enabled
36+
*/
37+
+ (void)setEnableSessionReplayInUnreliableEnvironment:(BOOL)enabled
38+
sentryOptions:(SentryOptions *)sentryOptions;
39+
3140
@end
3241

3342
NS_ASSUME_NONNULL_END

packages/core/ios/RNSentryExperimentalOptions.m

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,13 @@ + (void)setEnableLogs:(BOOL)enabled sentryOptions:(SentryOptions *)sentryOptions
2727
sentryOptions.experimental.enableLogs = enabled;
2828
}
2929

30+
+ (void)setEnableSessionReplayInUnreliableEnvironment:(BOOL)enabled
31+
sentryOptions:(SentryOptions *)sentryOptions
32+
{
33+
if (sentryOptions == nil) {
34+
return;
35+
}
36+
sentryOptions.experimental.enableSessionReplayInUnreliableEnvironment = enabled;
37+
}
38+
3039
@end

packages/core/ios/RNSentryReplay.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11

22
@interface RNSentryReplay : NSObject
33

4-
+ (void)updateOptions:(NSMutableDictionary *)options;
4+
/**
5+
* Updates the session replay options
6+
* @return true when session replay is enabled
7+
*/
8+
+ (BOOL)updateOptions:(NSMutableDictionary *)options;
59

610
+ (void)postInit;
711

packages/core/ios/RNSentryReplay.mm

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@
1212
@implementation RNSentryReplay {
1313
}
1414

15-
+ (void)updateOptions:(NSMutableDictionary *)options
15+
+ (BOOL)updateOptions:(NSMutableDictionary *)options
1616
{
17-
if (options[@"replaysSessionSampleRate"] == nil
18-
&& options[@"replaysOnErrorSampleRate"] == nil) {
17+
NSNumber *sessionSampleRate = options[@"replaysSessionSampleRate"];
18+
NSNumber *errorSampleRate = options[@"replaysOnErrorSampleRate"];
19+
20+
if (sessionSampleRate == nil && errorSampleRate == nil) {
1921
NSLog(@"Session replay disabled via configuration");
20-
return;
22+
return NO;
2123
}
2224

2325
NSLog(@"Setting up session replay");
@@ -26,8 +28,8 @@ + (void)updateOptions:(NSMutableDictionary *)options
2628
NSString *qualityString = options[@"replaysSessionQuality"];
2729

2830
[options setValue:@{
29-
@"sessionSampleRate" : options[@"replaysSessionSampleRate"] ?: [NSNull null],
30-
@"errorSampleRate" : options[@"replaysOnErrorSampleRate"] ?: [NSNull null],
31+
@"sessionSampleRate" : sessionSampleRate ?: [NSNull null],
32+
@"errorSampleRate" : errorSampleRate ?: [NSNull null],
3133
@"quality" : @([RNSentryReplayQuality parseReplayQuality:qualityString]),
3234
@"maskAllImages" : replayOptions[@"maskAllImages"] ?: [NSNull null],
3335
@"maskAllText" : replayOptions[@"maskAllText"] ?: [NSNull null],
@@ -38,6 +40,8 @@ + (void)updateOptions:(NSMutableDictionary *)options
3840
@ { @"name" : REACT_NATIVE_SDK_NAME, @"version" : REACT_NATIVE_SDK_PACKAGE_VERSION }
3941
}
4042
forKey:@"sessionReplay"];
43+
return (errorSampleRate != nil && [errorSampleRate doubleValue] > 0)
44+
|| (sessionSampleRate != nil && [sessionSampleRate doubleValue] > 0);
4145
}
4246

4347
+ (NSArray *_Nonnull)getReplayRNRedactClasses:(NSDictionary *_Nullable)replayOptions

0 commit comments

Comments
 (0)