Skip to content

Commit 07257af

Browse files
feat(replay): Add SentryMask and SentryUnmask iOS components (#4224)
1 parent 077ecb1 commit 07257af

25 files changed

+477
-18
lines changed

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,29 @@
66
> make sure you follow our [migration guide](https://docs.sentry.io/platforms/react-native/migration/) first.
77
<!-- prettier-ignore-end -->
88
9+
## Unreleased
10+
11+
### Features
12+
13+
- Add Replay Custom Masking for iOS ([#4224](https://github.com/getsentry/sentry-react-native/pull/4224))
14+
15+
```jsx
16+
import * as Sentry from '@sentry/react-native';
17+
18+
const Example = () => {
19+
return (
20+
<View>
21+
<Sentry.Mask>
22+
<Text>${"All children of Sentry.Mask will be masked."}</Text>
23+
</Sentry.Mask>
24+
<Sentry.Unmask>
25+
<Text>${"Only direct children of Sentry.Unmask will be unmasked."}</Text>
26+
</Sentry.Unmask>
27+
</View>
28+
);
29+
};
30+
```
31+
932
## 6.3.0
1033

1134
### Features

packages/core/.npmignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@
1111
!react-native.config.js
1212
!/ios/**/*
1313
!/android/**/*
14+
15+
# New Architecture Codegen
1416
!src/js/NativeRNSentry.ts
17+
!src/js/RNSentryReplayMaskNativeComponent.ts
18+
!src/js/RNSentryReplayUnmaskNativeComponent.ts
19+
20+
# Scripts
1521
!scripts/collect-modules.sh
1622
!scripts/copy-debugid.js
1723
!scripts/has-sourcemap-debugid.js

packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
/* Begin PBXBuildFile section */
1010
332D33472CDBDBB600547D76 /* RNSentryReplayOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 332D33462CDBDBB600547D76 /* RNSentryReplayOptionsTests.swift */; };
1111
336084392C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 336084382C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift */; };
12+
3380C6C42CE25ECA0018B9B6 /* RNSentryReplayPostInitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3380C6C32CE25ECA0018B9B6 /* RNSentryReplayPostInitTests.swift */; };
1213
33958C692BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 33958C682BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m */; };
1314
33AFDFED2B8D14B300AAB120 /* RNSentryFramesTrackerListenerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 33AFDFEC2B8D14B300AAB120 /* RNSentryFramesTrackerListenerTests.m */; };
1415
33AFDFF12B8D15E500AAB120 /* RNSentryDependencyContainerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 33AFDFF02B8D15E500AAB120 /* RNSentryDependencyContainerTests.m */; };
@@ -28,6 +29,9 @@
2829
3360843A2C32E3A8008CC412 /* RNSentryReplayBreadcrumbConverter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSentryReplayBreadcrumbConverter.h; path = ../ios/RNSentryReplayBreadcrumbConverter.h; sourceTree = "<group>"; };
2930
3360843C2C340C76008CC412 /* RNSentryBreadcrumbTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNSentryBreadcrumbTests.swift; sourceTree = "<group>"; };
3031
3360898D29524164007C7730 /* RNSentryCocoaTesterTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RNSentryCocoaTesterTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
32+
3380C6C12CDEC5850018B9B6 /* RNSentryReplayUnmask.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNSentryReplayUnmask.h; path = ../ios/Replay/RNSentryReplayUnmask.h; sourceTree = SOURCE_ROOT; };
33+
3380C6C22CDEC6630018B9B6 /* RNSentryReplayMask.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNSentryReplayMask.h; path = ../ios/Replay/RNSentryReplayMask.h; sourceTree = SOURCE_ROOT; };
34+
3380C6C32CE25ECA0018B9B6 /* RNSentryReplayPostInitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNSentryReplayPostInitTests.swift; sourceTree = "<group>"; };
3135
338739072A7D7D2800950DDD /* RNSentryReplay.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNSentryReplay.h; path = ../ios/RNSentryReplay.h; sourceTree = "<group>"; };
3236
33958C672BFCEF5A00AD1FB6 /* RNSentryOnDrawReporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSentryOnDrawReporter.h; path = ../ios/RNSentryOnDrawReporter.h; sourceTree = "<group>"; };
3337
33958C682BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNSentryOnDrawReporterTests.m; sourceTree = "<group>"; };
@@ -95,13 +99,24 @@
9599
33958C682BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m */,
96100
3360843C2C340C76008CC412 /* RNSentryBreadcrumbTests.swift */,
97101
332D33462CDBDBB600547D76 /* RNSentryReplayOptionsTests.swift */,
102+
3380C6C32CE25ECA0018B9B6 /* RNSentryReplayPostInitTests.swift */,
98103
);
99104
path = RNSentryCocoaTesterTests;
100105
sourceTree = "<group>";
101106
};
107+
3380C6C02CDEC56B0018B9B6 /* Replay */ = {
108+
isa = PBXGroup;
109+
children = (
110+
3380C6C22CDEC6630018B9B6 /* RNSentryReplayMask.h */,
111+
3380C6C12CDEC5850018B9B6 /* RNSentryReplayUnmask.h */,
112+
);
113+
path = Replay;
114+
sourceTree = "<group>";
115+
};
102116
33AFE0122B8F319000AAB120 /* RNSentry */ = {
103117
isa = PBXGroup;
104118
children = (
119+
3380C6C02CDEC56B0018B9B6 /* Replay */,
105120
332D33482CDBDC7300547D76 /* RNSentry.h */,
106121
3360843A2C32E3A8008CC412 /* RNSentryReplayBreadcrumbConverter.h */,
107122
330F308D2C0F385A002A0D4E /* RNSentryBreadcrumb.h */,
@@ -228,6 +243,7 @@
228243
336084392C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift in Sources */,
229244
33F58AD02977037D008F60EA /* RNSentryTests.mm in Sources */,
230245
33958C692BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m in Sources */,
246+
3380C6C42CE25ECA0018B9B6 /* RNSentryReplayPostInitTests.swift in Sources */,
231247
33AFDFED2B8D14B300AAB120 /* RNSentryFramesTrackerListenerTests.m in Sources */,
232248
);
233249
runOnlyForDeploymentPostprocessing = 0;

packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryCocoaTesterTests-Bridging-Header.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@
55
#import "RNSentryBreadcrumb.h"
66
#import "RNSentryReplay.h"
77
#import "RNSentryReplayBreadcrumbConverter.h"
8+
#import "RNSentryReplayMask.h"
9+
#import "RNSentryReplayUnmask.h"

packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryReplayOptionsTests.swift

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,7 @@ final class RNSentryReplayOptions: XCTestCase {
109109
let sessionReplay = experimental["sessionReplay"] as! [String: Any]
110110

111111
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)
112+
XCTAssertTrue(maskedViewClasses.contains("RNSVGSvgView"))
117113
}
118114

119115
func testMaskAllImages() {
@@ -128,9 +124,7 @@ final class RNSentryReplayOptions: XCTestCase {
128124
let actualOptions = try! Options(dict: optionsDict as! [String: Any])
129125

130126
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")!))
127+
assertContainsClass(classArray: actualOptions.experimental.sessionReplay.maskedViewClasses, stringClass: "RCTImageView")
134128
}
135129

136130
func testMaskAllImagesFalse() {
@@ -160,11 +154,16 @@ final class RNSentryReplayOptions: XCTestCase {
160154
let actualOptions = try! Options(dict: optionsDict as! [String: Any])
161155

162156
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")!))
157+
assertContainsClass(classArray: actualOptions.experimental.sessionReplay.maskedViewClasses, stringClass: "RCTTextView")
158+
assertContainsClass(classArray: actualOptions.experimental.sessionReplay.maskedViewClasses, stringClass: "RCTParagraphComponentView")
159+
}
160+
161+
func assertContainsClass(classArray: [AnyClass], stringClass: String) {
162+
XCTAssertTrue(mapToObjectIdentifiers(classArray: classArray).contains(ObjectIdentifier(NSClassFromString(stringClass)!)))
163+
}
164+
165+
func mapToObjectIdentifiers(classArray: [AnyClass]) -> [ObjectIdentifier] {
166+
return classArray.map { ObjectIdentifier($0) }
168167
}
169168

170169
func testMaskAllTextFalse() {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import Sentry
2+
import XCTest
3+
4+
final class RNSentryReplayPostInitTests: XCTestCase {
5+
6+
func testMask() {
7+
XCTAssertEqual(ObjectIdentifier(RNSentryReplay.getMaskClass()), ObjectIdentifier(RNSentryReplayMask.self))
8+
}
9+
10+
func testUnmask() {
11+
XCTAssertEqual(ObjectIdentifier(RNSentryReplay.getUnmaskClass()), ObjectIdentifier(RNSentryReplayUnmask.self))
12+
}
13+
}

packages/core/ios/RNSentryReplay.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,8 @@
55

66
+ (void)postInit;
77

8+
+ (Class)getMaskClass;
9+
10+
+ (Class)getUnmaskClass;
11+
812
@end

packages/core/ios/RNSentryReplay.m renamed to packages/core/ios/RNSentryReplay.mm

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
#import "RNSentryReplay.h"
2-
#import "RNSentryReplayBreadcrumbConverter.h"
2+
#import "RNSentryReplayBreadcrumbConverterHelper.h"
3+
#import "React/RCTTextView.h"
4+
#import "Replay/RNSentryReplayMask.h"
5+
#import "Replay/RNSentryReplayUnmask.h"
6+
#import <Sentry/PrivateSentrySDKOnly.h>
37

48
#if SENTRY_TARGET_REPLAY_SUPPORTED
59

@@ -56,9 +60,21 @@ + (NSArray *_Nonnull)getReplayRNRedactClasses:(NSDictionary *_Nullable)replayOpt
5660

5761
+ (void)postInit
5862
{
59-
RNSentryReplayBreadcrumbConverter *breadcrumbConverter =
60-
[[RNSentryReplayBreadcrumbConverter alloc] init];
61-
[PrivateSentrySDKOnly configureSessionReplayWith:breadcrumbConverter screenshotProvider:nil];
63+
// We can't import RNSentryReplayMask.h here because it's Objective-C++
64+
// To avoid typos, we test the class existence in the tests
65+
[PrivateSentrySDKOnly setRedactContainerClass:[RNSentryReplay getMaskClass]];
66+
[PrivateSentrySDKOnly setIgnoreContainerClass:[RNSentryReplay getUnmaskClass]];
67+
[RNSentryReplayBreadcrumbConverterHelper configureSessionReplayWithConverter];
68+
}
69+
70+
+ (Class)getMaskClass
71+
{
72+
return RNSentryReplayMask.class;
73+
}
74+
75+
+ (Class)getUnmaskClass
76+
{
77+
return RNSentryReplayUnmask.class;
6278
}
6379

6480
@end
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#import <Sentry/SentryDefines.h>
2+
3+
@interface RNSentryReplayBreadcrumbConverterHelper : NSObject
4+
5+
+ (void)configureSessionReplayWithConverter;
6+
7+
@end
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#import "RNSentryReplayBreadcrumbConverterHelper.h"
2+
3+
#if SENTRY_TARGET_REPLAY_SUPPORTED
4+
# import "RNSentryReplayBreadcrumbConverter.h"
5+
6+
@implementation RNSentryReplayBreadcrumbConverterHelper
7+
8+
+ (void)configureSessionReplayWithConverter
9+
{
10+
RNSentryReplayBreadcrumbConverter *breadcrumbConverter =
11+
[[RNSentryReplayBreadcrumbConverter alloc] init];
12+
[PrivateSentrySDKOnly configureSessionReplayWith:breadcrumbConverter screenshotProvider:nil];
13+
}
14+
15+
@end
16+
17+
#endif
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#import <Sentry/SentryDefines.h>
2+
3+
#if SENTRY_HAS_UIKIT
4+
5+
# import <React/RCTViewManager.h>
6+
7+
# ifdef RCT_NEW_ARCH_ENABLED
8+
# import <React/RCTViewComponentView.h>
9+
# else
10+
# import <React/RCTView.h>
11+
# endif
12+
13+
@interface RNSentryReplayMaskManager : RCTViewManager
14+
@end
15+
16+
@interface RNSentryReplayMask :
17+
# ifdef RCT_NEW_ARCH_ENABLED
18+
RCTViewComponentView
19+
# else
20+
RCTView
21+
# endif
22+
@end
23+
24+
#endif
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#import <Sentry/SentryDefines.h>
2+
3+
#if SENTRY_HAS_UIKIT
4+
5+
# import "RNSentryReplayMask.h"
6+
7+
# ifdef RCT_NEW_ARCH_ENABLED
8+
# import <react/renderer/components/RNSentrySpec/ComponentDescriptors.h>
9+
# import <react/renderer/components/RNSentrySpec/RCTComponentViewHelpers.h>
10+
// RCTFabricComponentsPlugins needed for RNSentryReplayMaskCls
11+
# import <React/RCTFabricComponentsPlugins.h>
12+
# endif
13+
14+
@implementation RNSentryReplayMaskManager
15+
16+
RCT_EXPORT_MODULE(RNSentryReplayMask)
17+
18+
- (UIView *)view
19+
{
20+
return [RNSentryReplayMask new];
21+
}
22+
23+
@end
24+
25+
# ifdef RCT_NEW_ARCH_ENABLED
26+
@interface
27+
RNSentryReplayMask () <RCTRNSentryReplayMaskViewProtocol>
28+
@end
29+
# endif
30+
31+
@implementation RNSentryReplayMask
32+
33+
# ifdef RCT_NEW_ARCH_ENABLED
34+
+ (facebook::react::ComponentDescriptorProvider)componentDescriptorProvider
35+
{
36+
return facebook::react::concreteComponentDescriptorProvider<
37+
facebook::react::RNSentryReplayMaskComponentDescriptor>();
38+
}
39+
# endif
40+
41+
@end
42+
43+
# ifdef RCT_NEW_ARCH_ENABLED
44+
Class<RCTComponentViewProtocol>
45+
RNSentryReplayMaskCls(void)
46+
{
47+
return RNSentryReplayMask.class;
48+
}
49+
# endif
50+
51+
#endif
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#import <Sentry/SentryDefines.h>
2+
3+
#if SENTRY_HAS_UIKIT
4+
5+
# import <React/RCTViewManager.h>
6+
7+
# ifdef RCT_NEW_ARCH_ENABLED
8+
# import <React/RCTViewComponentView.h>
9+
# else
10+
# import <React/RCTView.h>
11+
# endif
12+
13+
@interface RNSentryReplayUnmaskManager : RCTViewManager
14+
@end
15+
16+
@interface RNSentryReplayUnmask :
17+
# ifdef RCT_NEW_ARCH_ENABLED
18+
RCTViewComponentView
19+
# else
20+
RCTView
21+
# endif
22+
@end
23+
24+
#endif
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#import <Sentry/SentryDefines.h>
2+
3+
#if SENTRY_HAS_UIKIT
4+
5+
# import "RNSentryReplayUnmask.h"
6+
7+
# ifdef RCT_NEW_ARCH_ENABLED
8+
# import <react/renderer/components/RNSentrySpec/ComponentDescriptors.h>
9+
# import <react/renderer/components/RNSentrySpec/RCTComponentViewHelpers.h>
10+
// RCTFabricComponentsPlugins needed for RNSentryReplayUnmaskCls
11+
# import <React/RCTFabricComponentsPlugins.h>
12+
# endif
13+
14+
@implementation RNSentryReplayUnmaskManager
15+
16+
RCT_EXPORT_MODULE(RNSentryReplayUnmask)
17+
18+
- (UIView *)view
19+
{
20+
return [RNSentryReplayUnmask new];
21+
}
22+
23+
@end
24+
25+
# ifdef RCT_NEW_ARCH_ENABLED
26+
@interface
27+
RNSentryReplayUnmask () <RCTRNSentryReplayUnmaskViewProtocol>
28+
@end
29+
# endif
30+
31+
@implementation RNSentryReplayUnmask
32+
33+
# ifdef RCT_NEW_ARCH_ENABLED
34+
+ (facebook::react::ComponentDescriptorProvider)componentDescriptorProvider
35+
{
36+
return facebook::react::concreteComponentDescriptorProvider<
37+
facebook::react::RNSentryReplayUnmaskComponentDescriptor>();
38+
}
39+
# endif
40+
41+
@end
42+
43+
# ifdef RCT_NEW_ARCH_ENABLED
44+
Class<RCTComponentViewProtocol>
45+
RNSentryReplayUnmaskCls(void)
46+
{
47+
return RNSentryReplayUnmask.class;
48+
}
49+
# endif
50+
51+
#endif

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@
118118
},
119119
"codegenConfig": {
120120
"name": "RNSentrySpec",
121-
"type": "modules",
121+
"type": "all",
122122
"jsSrcsDir": "src",
123123
"android": {
124124
"javaPackageName": "io.sentry.react"

0 commit comments

Comments
 (0)