Skip to content

Commit 5dff5ee

Browse files
krystofwoldrichbruno-garciaantonisromtsngithub-actions[bot]
authored
feat: Session Replay is GA (#4384)
* feat: Session Replay is GA * update cocoa tests * fix changelog pr num * add js tests * Update packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryReplayOptionsTests.swift Co-authored-by: Antonis Lilis <[email protected]> * chore(deps): update Android SDK to v7.20.0 (#4411) Co-authored-by: GitHub <[email protected]> Co-authored-by: Roman Zavarnitsyn <[email protected]> * chore(deps): update Cocoa SDK to v8.43.0 (#4410) Co-authored-by: GitHub <[email protected]> Co-authored-by: Roman Zavarnitsyn <[email protected]> * Set SdkVersion to react native for replay events on Android * Use new options in samples * Fixes testMaskAllVectors failing test --------- Co-authored-by: Bruno Garcia <[email protected]> Co-authored-by: Antonis Lilis <[email protected]> Co-authored-by: Roman Zavarnitsyn <[email protected]> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: GitHub <[email protected]>
1 parent 8fe7c9d commit 5dff5ee

File tree

13 files changed

+211
-125
lines changed

13 files changed

+211
-125
lines changed

CHANGELOG.md

+27-6
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,26 @@
1010

1111
### Features
1212

13+
- Mobile Session Replay is now generally available and ready for production use ([#4384](https://github.com/getsentry/sentry-react-native/pull/4384))
14+
15+
To learn about privacy, custom masking or performance overhead visit [the documentation](https://docs.sentry.io/platforms/react-native/session-replay/).
16+
17+
```js
18+
import * as Sentry from '@sentry/react-native';
19+
20+
Sentry.init({
21+
replaysSessionSampleRate: 1.0,
22+
replaysOnErrorSampleRate: 1.0,
23+
integrations: [
24+
Sentry.mobileReplayIntegration({
25+
maskAllImages: true,
26+
maskAllVectors: true,
27+
maskAllText: true,
28+
}),
29+
],
30+
});
31+
```
32+
1333
- Adds new `captureFeedback` and deprecates the `captureUserFeedback` API ([#4320](https://github.com/getsentry/sentry-react-native/pull/4320))
1434

1535
```jsx
@@ -40,21 +60,22 @@
4060
### Changes
4161

4262
- Falsy values of `options.environment` (empty string, undefined...) default to `production`
63+
- Deprecated `_experiments.replaysSessionSampleRate` and `_experiments.replaysOnErrorSampleRate` use `replaysSessionSampleRate` and `replaysOnErrorSampleRate` ([#4384](https://github.com/getsentry/sentry-react-native/pull/4384))
4364

4465
### Dependencies
4566

4667
- Bump CLI from v2.38.2 to v2.39.1 ([#4305](https://github.com/getsentry/sentry-react-native/pull/4305), [#4316](https://github.com/getsentry/sentry-react-native/pull/4316))
4768
- [changelog](https://github.com/getsentry/sentry-cli/blob/master/CHANGELOG.md#2391)
4869
- [diff](https://github.com/getsentry/sentry-cli/compare/2.38.2...2.39.1)
49-
- Bump Android SDK from v7.18.0 to v7.19.1 ([#4329](https://github.com/getsentry/sentry-react-native/pull/4329), [#4365](https://github.com/getsentry/sentry-react-native/pull/4365), [#4405](https://github.com/getsentry/sentry-react-native/pull/4405))
50-
- [changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md#7191)
51-
- [diff](https://github.com/getsentry/sentry-java/compare/7.18.0...7.19.1)
70+
- Bump Android SDK from v7.18.0 to v7.20.0 ([#4329](https://github.com/getsentry/sentry-react-native/pull/4329), [#4365](https://github.com/getsentry/sentry-react-native/pull/4365), [#4405](https://github.com/getsentry/sentry-react-native/pull/4405), [#4411](https://github.com/getsentry/sentry-react-native/pull/4411))
71+
- [changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md#7200)
72+
- [diff](https://github.com/getsentry/sentry-java/compare/7.18.0...7.20.0)
5273
- Bump JavaScript SDK from v8.40.0 to v8.47.0 ([#4351](https://github.com/getsentry/sentry-react-native/pull/4351), [#4325](https://github.com/getsentry/sentry-react-native/pull/4325), [#4371](https://github.com/getsentry/sentry-react-native/pull/4371), [#4382](https://github.com/getsentry/sentry-react-native/pull/4382), [#4388](https://github.com/getsentry/sentry-react-native/pull/4388), [#4393](https://github.com/getsentry/sentry-react-native/pull/4393))
5374
- [changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md#8470)
5475
- [diff](https://github.com/getsentry/sentry-javascript/compare/8.40.0...8.47.0)
55-
- Bump Cocoa SDK from v8.41.0 to v8.42.1 ([#4387](https://github.com/getsentry/sentry-react-native/pull/4387), [#4399](https://github.com/getsentry/sentry-react-native/pull/4399))
56-
- [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8421)
57-
- [diff](https://github.com/getsentry/sentry-cocoa/compare/8.41.0...8.42.1)
76+
- Bump Cocoa SDK from v8.41.0 to v8.43.0 ([#4387](https://github.com/getsentry/sentry-react-native/pull/4387), [#4399](https://github.com/getsentry/sentry-react-native/pull/4399), [#4410](https://github.com/getsentry/sentry-react-native/pull/4410))
77+
- [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8430)
78+
- [diff](https://github.com/getsentry/sentry-cocoa/compare/8.41.0...8.43.0)
5879

5980
## 6.4.0
6081

packages/core/RNSentry.podspec

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Pod::Spec.new do |s|
3737

3838
s.compiler_flags = other_cflags
3939

40-
s.dependency 'Sentry/HybridSDK', '8.42.1'
40+
s.dependency 'Sentry/HybridSDK', '8.43.0'
4141

4242
if defined? install_modules_dependencies
4343
# Default React Native dependencies for 0.71 and above (new and legacy architecture)

packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryReplayOptionsTests.swift

+27-44
Original file line numberDiff line numberDiff line change
@@ -10,55 +10,39 @@ final class RNSentryReplayOptions: XCTestCase {
1010
XCTAssertEqual(optionsDict.count, 0)
1111
}
1212

13-
func testExperimentalOptionsWithoutReplaySampleRatesAreRemoved() {
14-
let optionsDict = (["_experiments": [:]] as NSDictionary).mutableCopy() as! NSMutableDictionary
15-
RNSentryReplay.updateOptions(optionsDict)
16-
17-
XCTAssertEqual(optionsDict.count, 0)
18-
}
19-
2013
func testReplayOptionsDictContainsAllOptionsKeysWhenSessionSampleRateUsed() {
2114
let optionsDict = ([
2215
"dsn": "https://[email protected]/1234567",
23-
"_experiments": [
24-
"replaysSessionSampleRate": 0.75
25-
]
16+
"replaysSessionSampleRate": 0.75
2617
] as NSDictionary).mutableCopy() as! NSMutableDictionary
2718
RNSentryReplay.updateOptions(optionsDict)
2819

29-
let experimental = optionsDict["experimental"] as! [String: Any]
30-
let sessionReplay = experimental["sessionReplay"] as! [String: Any]
20+
let sessionReplay = optionsDict["sessionReplay"] as! [String: Any]
3121

3222
assertAllDefaultReplayOptionsAreNotNil(replayOptions: sessionReplay)
3323
}
3424

3525
func testReplayOptionsDictContainsAllOptionsKeysWhenErrorSampleRateUsed() {
3626
let optionsDict = ([
3727
"dsn": "https://[email protected]/1234567",
38-
"_experiments": [
39-
"replaysOnErrorSampleRate": 0.75
40-
]
28+
"replaysOnErrorSampleRate": 0.75
4129
] as NSDictionary).mutableCopy() as! NSMutableDictionary
4230
RNSentryReplay.updateOptions(optionsDict)
4331

44-
let experimental = optionsDict["experimental"] as! [String: Any]
45-
let sessionReplay = experimental["sessionReplay"] as! [String: Any]
32+
let sessionReplay = optionsDict["sessionReplay"] as! [String: Any]
4633

4734
assertAllDefaultReplayOptionsAreNotNil(replayOptions: sessionReplay)
4835
}
4936

5037
func testReplayOptionsDictContainsAllOptionsKeysWhenErrorAndSessionSampleRatesUsed() {
5138
let optionsDict = ([
5239
"dsn": "https://[email protected]/1234567",
53-
"_experiments": [
54-
"replaysOnErrorSampleRate": 0.75,
55-
"replaysSessionSampleRate": 0.75
56-
]
40+
"replaysOnErrorSampleRate": 0.75,
41+
"replaysSessionSampleRate": 0.75
5742
] as NSDictionary).mutableCopy() as! NSMutableDictionary
5843
RNSentryReplay.updateOptions(optionsDict)
5944

60-
let experimental = optionsDict["experimental"] as! [String: Any]
61-
let sessionReplay = experimental["sessionReplay"] as! [String: Any]
45+
let sessionReplay = optionsDict["sessionReplay"] as! [String: Any]
6246

6347
assertAllDefaultReplayOptionsAreNotNil(replayOptions: sessionReplay)
6448
}
@@ -75,38 +59,37 @@ final class RNSentryReplayOptions: XCTestCase {
7559
func testSessionSampleRate() {
7660
let optionsDict = ([
7761
"dsn": "https://[email protected]/1234567",
78-
"_experiments": [ "replaysSessionSampleRate": 0.75 ]
62+
"replaysSessionSampleRate": 0.75
7963
] as NSDictionary).mutableCopy() as! NSMutableDictionary
8064
RNSentryReplay.updateOptions(optionsDict)
8165

8266
let actualOptions = try! Options(dict: optionsDict as! [String: Any])
83-
XCTAssertEqual(actualOptions.experimental.sessionReplay.sessionSampleRate, 0.75)
67+
XCTAssertEqual(actualOptions.sessionReplay.sessionSampleRate, 0.75)
8468
}
8569

8670
func testOnErrorSampleRate() {
8771
let optionsDict = ([
8872
"dsn": "https://[email protected]/1234567",
89-
"_experiments": [ "replaysOnErrorSampleRate": 0.75 ]
73+
"replaysOnErrorSampleRate": 0.75
9074
] as NSDictionary).mutableCopy() as! NSMutableDictionary
9175
RNSentryReplay.updateOptions(optionsDict)
9276

9377
let actualOptions = try! Options(dict: optionsDict as! [String: Any])
94-
XCTAssertEqual(actualOptions.experimental.sessionReplay.onErrorSampleRate, 0.75)
78+
XCTAssertEqual(actualOptions.sessionReplay.onErrorSampleRate, 0.75)
9579
}
9680

9781
func testMaskAllVectors() {
9882
let optionsDict = ([
9983
"dsn": "https://[email protected]/1234567",
100-
"_experiments": [ "replaysOnErrorSampleRate": 0.75 ],
84+
"replaysOnErrorSampleRate": 0.75,
10185
"mobileReplayOptions": [ "maskAllVectors": true ]
10286
] as NSDictionary).mutableCopy() as! NSMutableDictionary
10387

10488
RNSentryReplay.updateOptions(optionsDict)
10589

106-
XCTAssertEqual(optionsDict.count, 3)
90+
XCTAssertEqual(optionsDict.count, 4)
10791

108-
let experimental = optionsDict["experimental"] as! [String: Any]
109-
let sessionReplay = experimental["sessionReplay"] as! [String: Any]
92+
let sessionReplay = optionsDict["sessionReplay"] as! [String: Any]
11093

11194
let maskedViewClasses = sessionReplay["maskedViewClasses"] as! [String]
11295
XCTAssertTrue(maskedViewClasses.contains("RNSVGSvgView"))
@@ -115,47 +98,47 @@ final class RNSentryReplayOptions: XCTestCase {
11598
func testMaskAllImages() {
11699
let optionsDict = ([
117100
"dsn": "https://[email protected]/1234567",
118-
"_experiments": [ "replaysOnErrorSampleRate": 0.75 ],
101+
"replaysOnErrorSampleRate": 0.75,
119102
"mobileReplayOptions": [ "maskAllImages": true ]
120103
] as NSDictionary).mutableCopy() as! NSMutableDictionary
121104

122105
RNSentryReplay.updateOptions(optionsDict)
123106

124107
let actualOptions = try! Options(dict: optionsDict as! [String: Any])
125108

126-
XCTAssertEqual(actualOptions.experimental.sessionReplay.maskAllImages, true)
127-
assertContainsClass(classArray: actualOptions.experimental.sessionReplay.maskedViewClasses, stringClass: "RCTImageView")
109+
XCTAssertEqual(actualOptions.sessionReplay.maskAllImages, true)
110+
assertContainsClass(classArray: actualOptions.sessionReplay.maskedViewClasses, stringClass: "RCTImageView")
128111
}
129112

130113
func testMaskAllImagesFalse() {
131114
let optionsDict = ([
132115
"dsn": "https://[email protected]/1234567",
133-
"_experiments": [ "replaysOnErrorSampleRate": 0.75 ],
116+
"replaysOnErrorSampleRate": 0.75,
134117
"mobileReplayOptions": [ "maskAllImages": false ]
135118
] as NSDictionary).mutableCopy() as! NSMutableDictionary
136119

137120
RNSentryReplay.updateOptions(optionsDict)
138121

139122
let actualOptions = try! Options(dict: optionsDict as! [String: Any])
140123

141-
XCTAssertEqual(actualOptions.experimental.sessionReplay.maskAllImages, false)
142-
XCTAssertEqual(actualOptions.experimental.sessionReplay.maskedViewClasses.count, 0)
124+
XCTAssertEqual(actualOptions.sessionReplay.maskAllImages, false)
125+
XCTAssertEqual(actualOptions.sessionReplay.maskedViewClasses.count, 0)
143126
}
144127

145128
func testMaskAllText() {
146129
let optionsDict = ([
147130
"dsn": "https://[email protected]/1234567",
148-
"_experiments": [ "replaysOnErrorSampleRate": 0.75 ],
131+
"replaysOnErrorSampleRate": 0.75,
149132
"mobileReplayOptions": [ "maskAllText": true ]
150133
] as NSDictionary).mutableCopy() as! NSMutableDictionary
151134

152135
RNSentryReplay.updateOptions(optionsDict)
153136

154137
let actualOptions = try! Options(dict: optionsDict as! [String: Any])
155138

156-
XCTAssertEqual(actualOptions.experimental.sessionReplay.maskAllText, true)
157-
assertContainsClass(classArray: actualOptions.experimental.sessionReplay.maskedViewClasses, stringClass: "RCTTextView")
158-
assertContainsClass(classArray: actualOptions.experimental.sessionReplay.maskedViewClasses, stringClass: "RCTParagraphComponentView")
139+
XCTAssertEqual(actualOptions.sessionReplay.maskAllText, true)
140+
assertContainsClass(classArray: actualOptions.sessionReplay.maskedViewClasses, stringClass: "RCTTextView")
141+
assertContainsClass(classArray: actualOptions.sessionReplay.maskedViewClasses, stringClass: "RCTParagraphComponentView")
159142
}
160143

161144
func assertContainsClass(classArray: [AnyClass], stringClass: String) {
@@ -169,16 +152,16 @@ final class RNSentryReplayOptions: XCTestCase {
169152
func testMaskAllTextFalse() {
170153
let optionsDict = ([
171154
"dsn": "https://[email protected]/1234567",
172-
"_experiments": [ "replaysOnErrorSampleRate": 0.75 ],
155+
"replaysOnErrorSampleRate": 0.75,
173156
"mobileReplayOptions": [ "maskAllText": false ]
174157
] as NSDictionary).mutableCopy() as! NSMutableDictionary
175158

176159
RNSentryReplay.updateOptions(optionsDict)
177160

178161
let actualOptions = try! Options(dict: optionsDict as! [String: Any])
179162

180-
XCTAssertEqual(actualOptions.experimental.sessionReplay.maskAllText, false)
181-
XCTAssertEqual(actualOptions.experimental.sessionReplay.maskedViewClasses.count, 0)
163+
XCTAssertEqual(actualOptions.sessionReplay.maskAllText, false)
164+
XCTAssertEqual(actualOptions.sessionReplay.maskedViewClasses.count, 0)
182165
}
183166

184167
}

packages/core/android/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,5 @@ android {
5454

5555
dependencies {
5656
implementation 'com.facebook.react:react-native:+'
57-
api 'io.sentry:sentry-android:7.19.1'
57+
api 'io.sentry:sentry-android:7.20.0'
5858
}

packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java

+23-15
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,10 @@ protected void getSentryAndroidOptions(
277277
options.setSpotlightConnectionUrl(rnOptions.getString("spotlight"));
278278
}
279279
}
280-
if (rnOptions.hasKey("_experiments")) {
281-
options.getExperimental().setSessionReplay(getReplayOptions(rnOptions));
280+
281+
SentryReplayOptions replayOptions = getReplayOptions(rnOptions);
282+
options.setSessionReplay(replayOptions);
283+
if (isReplayEnabled(replayOptions)) {
282284
options.getReplayController().setBreadcrumbConverter(new RNSentryReplayBreadcrumbConverter());
283285
}
284286

@@ -330,26 +332,32 @@ protected void getSentryAndroidOptions(
330332
}
331333
}
332334

333-
private SentryReplayOptions getReplayOptions(@NotNull ReadableMap rnOptions) {
334-
@NotNull final SentryReplayOptions androidReplayOptions = new SentryReplayOptions(false);
335-
336-
@Nullable final ReadableMap rnExperimentsOptions = rnOptions.getMap("_experiments");
337-
if (rnExperimentsOptions == null) {
338-
return androidReplayOptions;
339-
}
335+
private boolean isReplayEnabled(SentryReplayOptions replayOptions) {
336+
return replayOptions.getSessionSampleRate() != null
337+
|| replayOptions.getOnErrorSampleRate() != null;
338+
}
340339

341-
if (!(rnExperimentsOptions.hasKey("replaysSessionSampleRate")
342-
|| rnExperimentsOptions.hasKey("replaysOnErrorSampleRate"))) {
340+
private SentryReplayOptions getReplayOptions(@NotNull ReadableMap rnOptions) {
341+
final SdkVersion replaySdkVersion =
342+
new SdkVersion(
343+
RNSentryVersion.REACT_NATIVE_SDK_PACKAGE_NAME,
344+
RNSentryVersion.REACT_NATIVE_SDK_PACKAGE_VERSION);
345+
@NotNull
346+
final SentryReplayOptions androidReplayOptions =
347+
new SentryReplayOptions(false, replaySdkVersion);
348+
349+
if (!(rnOptions.hasKey("replaysSessionSampleRate")
350+
|| rnOptions.hasKey("replaysOnErrorSampleRate"))) {
343351
return androidReplayOptions;
344352
}
345353

346354
androidReplayOptions.setSessionSampleRate(
347-
rnExperimentsOptions.hasKey("replaysSessionSampleRate")
348-
? rnExperimentsOptions.getDouble("replaysSessionSampleRate")
355+
rnOptions.hasKey("replaysSessionSampleRate")
356+
? rnOptions.getDouble("replaysSessionSampleRate")
349357
: null);
350358
androidReplayOptions.setOnErrorSampleRate(
351-
rnExperimentsOptions.hasKey("replaysOnErrorSampleRate")
352-
? rnExperimentsOptions.getDouble("replaysOnErrorSampleRate")
359+
rnOptions.hasKey("replaysOnErrorSampleRate")
360+
? rnOptions.getDouble("replaysOnErrorSampleRate")
353361
: null);
354362

355363
if (!rnOptions.hasKey("mobileReplayOptions")) {

packages/core/ios/RNSentryReplay.mm

+8-17
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,8 @@ @implementation RNSentryReplay {
1212

1313
+ (void)updateOptions:(NSMutableDictionary *)options
1414
{
15-
NSDictionary *experiments = options[@"_experiments"];
16-
[options removeObjectForKey:@"_experiments"];
17-
if (experiments == nil) {
18-
NSLog(@"Session replay disabled via configuration");
19-
return;
20-
}
21-
22-
if (experiments[@"replaysSessionSampleRate"] == nil
23-
&& experiments[@"replaysOnErrorSampleRate"] == nil) {
15+
if (options[@"replaysSessionSampleRate"] == nil
16+
&& options[@"replaysOnErrorSampleRate"] == nil) {
2417
NSLog(@"Session replay disabled via configuration");
2518
return;
2619
}
@@ -29,15 +22,13 @@ + (void)updateOptions:(NSMutableDictionary *)options
2922
NSDictionary *replayOptions = options[@"mobileReplayOptions"] ?: @{};
3023

3124
[options setValue:@{
32-
@"sessionReplay" : @ {
33-
@"sessionSampleRate" : experiments[@"replaysSessionSampleRate"] ?: [NSNull null],
34-
@"errorSampleRate" : experiments[@"replaysOnErrorSampleRate"] ?: [NSNull null],
35-
@"maskAllImages" : replayOptions[@"maskAllImages"] ?: [NSNull null],
36-
@"maskAllText" : replayOptions[@"maskAllText"] ?: [NSNull null],
37-
@"maskedViewClasses" : [RNSentryReplay getReplayRNRedactClasses:replayOptions],
38-
}
25+
@"sessionSampleRate" : options[@"replaysSessionSampleRate"] ?: [NSNull null],
26+
@"errorSampleRate" : options[@"replaysOnErrorSampleRate"] ?: [NSNull null],
27+
@"maskAllImages" : replayOptions[@"maskAllImages"] ?: [NSNull null],
28+
@"maskAllText" : replayOptions[@"maskAllText"] ?: [NSNull null],
29+
@"maskedViewClasses" : [RNSentryReplay getReplayRNRedactClasses:replayOptions],
3930
}
40-
forKey:@"experimental"];
31+
forKey:@"sessionReplay"];
4132
}
4233

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

0 commit comments

Comments
 (0)