Skip to content

Commit b4c06ca

Browse files
fix(replay): Backport iOS masking fixes from RN SDK v6 (#4309)
1 parent 5f56c92 commit b4c06ca

File tree

6 files changed

+211
-27
lines changed

6 files changed

+211
-27
lines changed

CHANGELOG.md

+9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@
55
### Fixes
66

77
- `browserReplayIntegration` is no longer included by default on React Native Web ([#4270](https://github.com/getsentry/sentry-react-native/pull/4270), [#4308](https://github.com/getsentry/sentry-react-native/pull/4308))
8+
- Replay `maskAll*` set to `false` on iOS kept all masked ([#4257](https://github.com/getsentry/sentry-react-native/pull/4257), [#4309](https://github.com/getsentry/sentry-react-native/pull/4309))
9+
- `browserReplayIntegration` is no longer included by default on React Native Web ([#4270](https://github.com/getsentry/sentry-react-native/pull/4270), [#4308](https://github.com/getsentry/sentry-react-native/pull/4308))
10+
- Fix Replay redacting of RN Classes on iOS ([#4243](https://github.com/getsentry/sentry-react-native/pull/4243), [#4309](https://github.com/getsentry/sentry-react-native/pull/4309))
11+
12+
### Dependencies
13+
14+
- Bump Cocoa SDK from v8.37.0 to v8.41.0 ([#4309](https://github.com/getsentry/sentry-react-native/pull/4309))
15+
- [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8410)
16+
- [diff](https://github.com/getsentry/sentry-cocoa/compare/8.37.0...8.41.0)
817

918
## 5.35.0
1019

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.37.0'
40+
s.dependency 'Sentry/HybridSDK', '8.41.0'
4141

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

RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj

+6-4
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,18 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10-
330F308C2C0F3840002A0D4E /* RNSentryBreadcrumbTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 330F308B2C0F3840002A0D4E /* RNSentryBreadcrumbTests.m */; };
1110
336084392C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 336084382C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift */; };
1211
33958C692BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 33958C682BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m */; };
1312
33AFDFED2B8D14B300AAB120 /* RNSentryFramesTrackerListenerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 33AFDFEC2B8D14B300AAB120 /* RNSentryFramesTrackerListenerTests.m */; };
1413
33AFDFF12B8D15E500AAB120 /* RNSentryDependencyContainerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 33AFDFF02B8D15E500AAB120 /* RNSentryDependencyContainerTests.m */; };
14+
33BB05DF2CF4C72400EE1DD1 /* RNSentryReplayOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33BB05DE2CF4C72400EE1DD1 /* RNSentryReplayOptionsTests.swift */; };
1515
33F58AD02977037D008F60EA /* RNSentryTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33F58ACF2977037D008F60EA /* RNSentryTests.mm */; };
1616
B5859A50A3E865EF5E61465A /* libPods-RNSentryCocoaTesterTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 650CB718ACFBD05609BF2126 /* libPods-RNSentryCocoaTesterTests.a */; };
1717
/* End PBXBuildFile section */
1818

1919
/* Begin PBXFileReference section */
2020
1482D5685A340AB93348A43D /* Pods-RNSentryCocoaTesterTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RNSentryCocoaTesterTests.release.xcconfig"; path = "Target Support Files/Pods-RNSentryCocoaTesterTests/Pods-RNSentryCocoaTesterTests.release.xcconfig"; sourceTree = "<group>"; };
2121
330F308D2C0F385A002A0D4E /* RNSentryBreadcrumb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSentryBreadcrumb.h; path = ../ios/RNSentryBreadcrumb.h; sourceTree = "<group>"; };
22-
336084372C32E382008CC412 /* RNSentryCocoaTesterTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RNSentryCocoaTesterTests-Bridging-Header.h"; sourceTree = "<group>"; };
2322
336084382C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RNSentryReplayBreadcrumbConverterTests.swift; sourceTree = "<group>"; };
2423
3360843A2C32E3A8008CC412 /* RNSentryReplayBreadcrumbConverter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSentryReplayBreadcrumbConverter.h; path = ../ios/RNSentryReplayBreadcrumbConverter.h; sourceTree = "<group>"; };
2524
3360843C2C340C76008CC412 /* RNSentryBreadcrumbTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNSentryBreadcrumbTests.swift; sourceTree = "<group>"; };
@@ -32,6 +31,8 @@
3231
33AFDFF02B8D15E500AAB120 /* RNSentryDependencyContainerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNSentryDependencyContainerTests.m; sourceTree = "<group>"; };
3332
33AFDFF22B8D15F600AAB120 /* RNSentryDependencyContainerTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNSentryDependencyContainerTests.h; sourceTree = "<group>"; };
3433
33AFE0132B8F31AF00AAB120 /* RNSentryDependencyContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSentryDependencyContainer.h; path = ../ios/RNSentryDependencyContainer.h; sourceTree = "<group>"; };
34+
33BB05DE2CF4C72400EE1DD1 /* RNSentryReplayOptionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNSentryReplayOptionsTests.swift; sourceTree = "<group>"; };
35+
33BB05E02CF4C77C00EE1DD1 /* RNSentryReplay.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNSentryReplay.h; path = ../ios/RNSentryReplay.h; sourceTree = SOURCE_ROOT; };
3536
33F58ACF2977037D008F60EA /* RNSentryTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RNSentryTests.mm; sourceTree = "<group>"; };
3637
650CB718ACFBD05609BF2126 /* libPods-RNSentryCocoaTesterTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RNSentryCocoaTesterTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
3738
E2321E7CFA55AB617247098E /* Pods-RNSentryCocoaTesterTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RNSentryCocoaTesterTests.debug.xcconfig"; path = "Target Support Files/Pods-RNSentryCocoaTesterTests/Pods-RNSentryCocoaTesterTests.debug.xcconfig"; sourceTree = "<group>"; };
@@ -80,6 +81,7 @@
8081
3360899029524164007C7730 /* RNSentryCocoaTesterTests */ = {
8182
isa = PBXGroup;
8283
children = (
84+
33BB05DE2CF4C72400EE1DD1 /* RNSentryReplayOptionsTests.swift */,
8385
336084382C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift */,
8486
33F58ACF2977037D008F60EA /* RNSentryTests.mm */,
8587
338739072A7D7D2800950DDD /* RNSentryTests.h */,
@@ -89,14 +91,14 @@
8991
33AFDFF22B8D15F600AAB120 /* RNSentryDependencyContainerTests.h */,
9092
33958C682BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m */,
9193
3360843C2C340C76008CC412 /* RNSentryBreadcrumbTests.swift */,
92-
3360843B2C340C75008CC412 /* RNSentryCocoaTesterTests-Bridging-Header.h */,
9394
);
9495
path = RNSentryCocoaTesterTests;
9596
sourceTree = "<group>";
9697
};
9798
33AFE0122B8F319000AAB120 /* RNSentry */ = {
9899
isa = PBXGroup;
99100
children = (
101+
33BB05E02CF4C77C00EE1DD1 /* RNSentryReplay.h */,
100102
3360843A2C32E3A8008CC412 /* RNSentryReplayBreadcrumbConverter.h */,
101103
330F308D2C0F385A002A0D4E /* RNSentryBreadcrumb.h */,
102104
33958C672BFCEF5A00AD1FB6 /* RNSentryOnDrawReporter.h */,
@@ -217,9 +219,9 @@
217219
files = (
218220
33AFDFF12B8D15E500AAB120 /* RNSentryDependencyContainerTests.m in Sources */,
219221
336084392C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift in Sources */,
222+
33BB05DF2CF4C72400EE1DD1 /* RNSentryReplayOptionsTests.swift in Sources */,
220223
33F58AD02977037D008F60EA /* RNSentryTests.mm in Sources */,
221224
33958C692BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m in Sources */,
222-
3360843D2C340C76008CC412 /* RNSentryBreadcrumbTests.swift in Sources */,
223225
33AFDFED2B8D14B300AAB120 /* RNSentryFramesTrackerListenerTests.m in Sources */,
224226
);
225227
runOnlyForDeploymentPostprocessing = 0;

RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryCocoaTesterTests-Bridging-Header.h

+1
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44

55
#import "RNSentryReplayBreadcrumbConverter.h"
66
#import "RNSentryBreadcrumb.h"
7+
#import "RNSentryReplay.h"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import XCTest
2+
import Sentry
3+
4+
final class RNSentryReplayOptions: XCTestCase {
5+
6+
func testOptionsWithoutExperimentalAreIgnored() {
7+
let optionsDict = NSMutableDictionary()
8+
RNSentryReplay.updateOptions(optionsDict)
9+
10+
XCTAssertEqual(optionsDict.count, 0)
11+
}
12+
13+
func testExperimentalOptionsWithoutReplaySampleRatesAreRemoved() {
14+
let optionsDict = (["_experiments": [:]] as NSDictionary).mutableCopy() as! NSMutableDictionary
15+
RNSentryReplay.updateOptions(optionsDict)
16+
17+
XCTAssertEqual(optionsDict.count, 0)
18+
}
19+
20+
func testReplayOptionsDictContainsAllOptionsKeysWhenSessionSampleRateUsed() {
21+
let optionsDict = ([
22+
"dsn": "https://[email protected]/1234567",
23+
"_experiments": [
24+
"replaysSessionSampleRate": 0.75
25+
]
26+
] as NSDictionary).mutableCopy() as! NSMutableDictionary
27+
RNSentryReplay.updateOptions(optionsDict)
28+
29+
let experimental = optionsDict["experimental"] as! [String:Any]
30+
let sessionReplay = experimental["sessionReplay"] as! [String:Any]
31+
32+
assertAllDefaultReplayOptionsAreNotNil(replayOptions: sessionReplay)
33+
}
34+
35+
func testReplayOptionsDictContainsAllOptionsKeysWhenErrorSampleRateUsed() {
36+
let optionsDict = ([
37+
"dsn": "https://[email protected]/1234567",
38+
"_experiments": [
39+
"replaysOnErrorSampleRate": 0.75
40+
]
41+
] as NSDictionary).mutableCopy() as! NSMutableDictionary
42+
RNSentryReplay.updateOptions(optionsDict)
43+
44+
let experimental = optionsDict["experimental"] as! [String:Any]
45+
let sessionReplay = experimental["sessionReplay"] as! [String:Any]
46+
47+
assertAllDefaultReplayOptionsAreNotNil(replayOptions: sessionReplay)
48+
}
49+
50+
func testReplayOptionsDictContainsAllOptionsKeysWhenErrorAndSessionSampleRatesUsed() {
51+
let optionsDict = ([
52+
"dsn": "https://[email protected]/1234567",
53+
"_experiments": [
54+
"replaysOnErrorSampleRate": 0.75,
55+
"replaysSessionSampleRate": 0.75
56+
]
57+
] as NSDictionary).mutableCopy() as! NSMutableDictionary
58+
RNSentryReplay.updateOptions(optionsDict)
59+
60+
let experimental = optionsDict["experimental"] as! [String:Any]
61+
let sessionReplay = experimental["sessionReplay"] as! [String:Any]
62+
63+
assertAllDefaultReplayOptionsAreNotNil(replayOptions: sessionReplay)
64+
}
65+
66+
func assertAllDefaultReplayOptionsAreNotNil(replayOptions: [String: Any]) {
67+
XCTAssertEqual(replayOptions.count, 5)
68+
XCTAssertNotNil(replayOptions["sessionSampleRate"])
69+
XCTAssertNotNil(replayOptions["errorSampleRate"])
70+
XCTAssertNotNil(replayOptions["maskAllImages"])
71+
XCTAssertNotNil(replayOptions["maskAllText"])
72+
XCTAssertNotNil(replayOptions["maskedViewClasses"])
73+
}
74+
75+
func testSessionSampleRate() {
76+
let optionsDict = ([
77+
"dsn": "https://[email protected]/1234567",
78+
"_experiments": [ "replaysSessionSampleRate": 0.75 ]
79+
] as NSDictionary).mutableCopy() as! NSMutableDictionary
80+
RNSentryReplay.updateOptions(optionsDict)
81+
82+
let actualOptions = try! Options(dict: optionsDict as! [String: Any])
83+
XCTAssertEqual(actualOptions.experimental.sessionReplay.sessionSampleRate, 0.75)
84+
}
85+
86+
func testOnErrorSampleRate() {
87+
let optionsDict = ([
88+
"dsn": "https://[email protected]/1234567",
89+
"_experiments": [ "replaysOnErrorSampleRate": 0.75 ]
90+
] as NSDictionary).mutableCopy() as! NSMutableDictionary
91+
RNSentryReplay.updateOptions(optionsDict)
92+
93+
let actualOptions = try! Options(dict: optionsDict as! [String: Any])
94+
XCTAssertEqual(actualOptions.experimental.sessionReplay.onErrorSampleRate, 0.75)
95+
}
96+
97+
func testMaskAllVectors() {
98+
let optionsDict = ([
99+
"dsn": "https://[email protected]/1234567",
100+
"_experiments": [ "replaysOnErrorSampleRate": 0.75 ],
101+
"mobileReplayOptions": [ "maskAllVectors": true ]
102+
] as NSDictionary).mutableCopy() as! NSMutableDictionary
103+
104+
RNSentryReplay.updateOptions(optionsDict)
105+
106+
XCTAssertEqual(optionsDict.count, 3)
107+
108+
let experimental = optionsDict["experimental"] as! [String:Any]
109+
let sessionReplay = experimental["sessionReplay"] as! [String:Any]
110+
111+
let maskedViewClasses = sessionReplay["maskedViewClasses"] as! [String]
112+
XCTAssertEqual(maskedViewClasses.count, 1)
113+
XCTAssertEqual(maskedViewClasses[0], "RNSVGSvgView")
114+
115+
let actualOptions = try! Options(dict: optionsDict as! [String: Any])
116+
XCTAssertEqual(actualOptions.experimental.sessionReplay.maskedViewClasses.count, 0)
117+
}
118+
119+
func testMaskAllImages() {
120+
let optionsDict = ([
121+
"dsn": "https://[email protected]/1234567",
122+
"_experiments": [ "replaysOnErrorSampleRate": 0.75 ],
123+
"mobileReplayOptions": [ "maskAllImages": true ]
124+
] as NSDictionary).mutableCopy() as! NSMutableDictionary
125+
126+
RNSentryReplay.updateOptions(optionsDict)
127+
128+
let actualOptions = try! Options(dict: optionsDict as! [String: Any])
129+
130+
XCTAssertEqual(actualOptions.experimental.sessionReplay.maskAllImages, true)
131+
XCTAssertEqual(actualOptions.experimental.sessionReplay.maskedViewClasses.count, 1)
132+
XCTAssertNotNil(actualOptions.experimental.sessionReplay.maskedViewClasses[0])
133+
XCTAssertEqual(ObjectIdentifier(actualOptions.experimental.sessionReplay.maskedViewClasses[0]), ObjectIdentifier(NSClassFromString("RCTImageView")!))
134+
}
135+
136+
func testMaskAllImagesFalse() {
137+
let optionsDict = ([
138+
"dsn": "https://[email protected]/1234567",
139+
"_experiments": [ "replaysOnErrorSampleRate": 0.75 ],
140+
"mobileReplayOptions": [ "maskAllImages": false ]
141+
] as NSDictionary).mutableCopy() as! NSMutableDictionary
142+
143+
RNSentryReplay.updateOptions(optionsDict)
144+
145+
let actualOptions = try! Options(dict: optionsDict as! [String: Any])
146+
147+
XCTAssertEqual(actualOptions.experimental.sessionReplay.maskAllImages, false)
148+
XCTAssertEqual(actualOptions.experimental.sessionReplay.maskedViewClasses.count, 0)
149+
}
150+
151+
func testMaskAllText() {
152+
let optionsDict = ([
153+
"dsn": "https://[email protected]/1234567",
154+
"_experiments": [ "replaysOnErrorSampleRate": 0.75 ],
155+
"mobileReplayOptions": [ "maskAllText": true ]
156+
] as NSDictionary).mutableCopy() as! NSMutableDictionary
157+
158+
RNSentryReplay.updateOptions(optionsDict)
159+
160+
let actualOptions = try! Options(dict: optionsDict as! [String: Any])
161+
162+
XCTAssertEqual(actualOptions.experimental.sessionReplay.maskAllText, true)
163+
XCTAssertEqual(actualOptions.experimental.sessionReplay.maskedViewClasses.count, 2)
164+
XCTAssertNotNil(actualOptions.experimental.sessionReplay.maskedViewClasses[0])
165+
XCTAssertNotNil(actualOptions.experimental.sessionReplay.maskedViewClasses[1])
166+
XCTAssertEqual(ObjectIdentifier(actualOptions.experimental.sessionReplay.maskedViewClasses[0]), ObjectIdentifier(NSClassFromString("RCTTextView")!))
167+
XCTAssertEqual(ObjectIdentifier(actualOptions.experimental.sessionReplay.maskedViewClasses[1]), ObjectIdentifier(NSClassFromString("RCTParagraphComponentView")!))
168+
}
169+
170+
func testMaskAllTextFalse() {
171+
let optionsDict = ([
172+
"dsn": "https://[email protected]/1234567",
173+
"_experiments": [ "replaysOnErrorSampleRate": 0.75 ],
174+
"mobileReplayOptions": [ "maskAllText": false ]
175+
] as NSDictionary).mutableCopy() as! NSMutableDictionary
176+
177+
RNSentryReplay.updateOptions(optionsDict)
178+
179+
let actualOptions = try! Options(dict: optionsDict as! [String: Any])
180+
181+
XCTAssertEqual(actualOptions.experimental.sessionReplay.maskAllText, false)
182+
XCTAssertEqual(actualOptions.experimental.sessionReplay.maskedViewClasses.count, 0)
183+
}
184+
185+
}

ios/RNSentryReplay.m

+9-22
Original file line numberDiff line numberDiff line change
@@ -29,40 +29,27 @@ + (void)updateOptions:(NSMutableDictionary *)options {
2929
?: [NSNull null],
3030
@"errorSampleRate" : experiments[@"replaysOnErrorSampleRate"]
3131
?: [NSNull null],
32-
@"redactAllImages" : replayOptions[@"maskAllImages"] ?: [NSNull null],
33-
@"redactAllText" : replayOptions[@"maskAllText"] ?: [NSNull null],
32+
@"maskAllImages" : replayOptions[@"maskAllImages"] ?: [NSNull null],
33+
@"maskAllText" : replayOptions[@"maskAllText"] ?: [NSNull null],
34+
@"maskedViewClasses" : [RNSentryReplay getReplayRNRedactClasses:replayOptions],
3435
}
3536
}
3637
forKey:@"experimental"];
37-
38-
[RNSentryReplay addReplayRNRedactClasses:replayOptions];
3938
}
4039

41-
+ (void)addReplayRNRedactClasses:(NSDictionary *_Nullable)replayOptions {
40+
+ (NSArray *_Nonnull)getReplayRNRedactClasses:(NSDictionary *_Nullable)replayOptions {
4241
NSMutableArray *_Nonnull classesToRedact = [[NSMutableArray alloc] init];
4342
if ([replayOptions[@"maskAllVectors"] boolValue] == YES) {
44-
Class _Nullable maybeRNSVGViewClass = NSClassFromString(@"RNSVGSvgView");
45-
if (maybeRNSVGViewClass != nil) {
46-
[classesToRedact addObject:maybeRNSVGViewClass];
47-
}
43+
[classesToRedact addObject:@"RNSVGSvgView"];
4844
}
4945
if ([replayOptions[@"maskAllImages"] boolValue] == YES) {
50-
Class _Nullable maybeRCTImageClass = NSClassFromString(@"RCTImageView");
51-
if (maybeRCTImageClass != nil) {
52-
[classesToRedact addObject:maybeRCTImageClass];
53-
}
46+
[classesToRedact addObject:@"RCTImageView"];
5447
}
5548
if ([replayOptions[@"maskAllText"] boolValue] == YES) {
56-
Class _Nullable maybeRCTTextClass = NSClassFromString(@"RCTTextView");
57-
if (maybeRCTTextClass != nil) {
58-
[classesToRedact addObject:maybeRCTTextClass];
59-
}
60-
Class _Nullable maybeRCTParagraphComponentViewClass = NSClassFromString(@"RCTParagraphComponentView");
61-
if (maybeRCTParagraphComponentViewClass != nil) {
62-
[classesToRedact addObject:maybeRCTParagraphComponentViewClass];
63-
}
49+
[classesToRedact addObject:@"RCTTextView"];
50+
[classesToRedact addObject:@"RCTParagraphComponentView"];
6451
}
65-
[PrivateSentrySDKOnly addReplayRedactClasses:classesToRedact];
52+
return classesToRedact;
6653
}
6754

6855
+ (void)postInit {

0 commit comments

Comments
 (0)