Skip to content

Commit

Permalink
feat(replay): Add SentryMask and SentryUnmask iOS components (#4224)
Browse files Browse the repository at this point in the history
  • Loading branch information
krystofwoldrich authored Nov 25, 2024
1 parent 077ecb1 commit 07257af
Show file tree
Hide file tree
Showing 25 changed files with 477 additions and 18 deletions.
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,29 @@
> make sure you follow our [migration guide](https://docs.sentry.io/platforms/react-native/migration/) first.
<!-- prettier-ignore-end -->

## Unreleased

### Features

- Add Replay Custom Masking for iOS ([#4224](https://github.com/getsentry/sentry-react-native/pull/4224))

```jsx
import * as Sentry from '@sentry/react-native';

const Example = () => {
return (
<View>
<Sentry.Mask>
<Text>${"All children of Sentry.Mask will be masked."}</Text>
</Sentry.Mask>
<Sentry.Unmask>
<Text>${"Only direct children of Sentry.Unmask will be unmasked."}</Text>
</Sentry.Unmask>
</View>
);
};
```

## 6.3.0

### Features
Expand Down
6 changes: 6 additions & 0 deletions packages/core/.npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@
!react-native.config.js
!/ios/**/*
!/android/**/*

# New Architecture Codegen
!src/js/NativeRNSentry.ts
!src/js/RNSentryReplayMaskNativeComponent.ts
!src/js/RNSentryReplayUnmaskNativeComponent.ts

# Scripts
!scripts/collect-modules.sh
!scripts/copy-debugid.js
!scripts/has-sourcemap-debugid.js
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
332D33472CDBDBB600547D76 /* RNSentryReplayOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 332D33462CDBDBB600547D76 /* RNSentryReplayOptionsTests.swift */; };
336084392C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 336084382C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift */; };
3380C6C42CE25ECA0018B9B6 /* RNSentryReplayPostInitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3380C6C32CE25ECA0018B9B6 /* RNSentryReplayPostInitTests.swift */; };
33958C692BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 33958C682BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m */; };
33AFDFED2B8D14B300AAB120 /* RNSentryFramesTrackerListenerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 33AFDFEC2B8D14B300AAB120 /* RNSentryFramesTrackerListenerTests.m */; };
33AFDFF12B8D15E500AAB120 /* RNSentryDependencyContainerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 33AFDFF02B8D15E500AAB120 /* RNSentryDependencyContainerTests.m */; };
Expand All @@ -28,6 +29,9 @@
3360843A2C32E3A8008CC412 /* RNSentryReplayBreadcrumbConverter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSentryReplayBreadcrumbConverter.h; path = ../ios/RNSentryReplayBreadcrumbConverter.h; sourceTree = "<group>"; };
3360843C2C340C76008CC412 /* RNSentryBreadcrumbTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNSentryBreadcrumbTests.swift; sourceTree = "<group>"; };
3360898D29524164007C7730 /* RNSentryCocoaTesterTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RNSentryCocoaTesterTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3380C6C12CDEC5850018B9B6 /* RNSentryReplayUnmask.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNSentryReplayUnmask.h; path = ../ios/Replay/RNSentryReplayUnmask.h; sourceTree = SOURCE_ROOT; };
3380C6C22CDEC6630018B9B6 /* RNSentryReplayMask.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNSentryReplayMask.h; path = ../ios/Replay/RNSentryReplayMask.h; sourceTree = SOURCE_ROOT; };
3380C6C32CE25ECA0018B9B6 /* RNSentryReplayPostInitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNSentryReplayPostInitTests.swift; sourceTree = "<group>"; };
338739072A7D7D2800950DDD /* RNSentryReplay.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNSentryReplay.h; path = ../ios/RNSentryReplay.h; sourceTree = "<group>"; };
33958C672BFCEF5A00AD1FB6 /* RNSentryOnDrawReporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSentryOnDrawReporter.h; path = ../ios/RNSentryOnDrawReporter.h; sourceTree = "<group>"; };
33958C682BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNSentryOnDrawReporterTests.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -95,13 +99,24 @@
33958C682BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m */,
3360843C2C340C76008CC412 /* RNSentryBreadcrumbTests.swift */,
332D33462CDBDBB600547D76 /* RNSentryReplayOptionsTests.swift */,
3380C6C32CE25ECA0018B9B6 /* RNSentryReplayPostInitTests.swift */,
);
path = RNSentryCocoaTesterTests;
sourceTree = "<group>";
};
3380C6C02CDEC56B0018B9B6 /* Replay */ = {
isa = PBXGroup;
children = (
3380C6C22CDEC6630018B9B6 /* RNSentryReplayMask.h */,
3380C6C12CDEC5850018B9B6 /* RNSentryReplayUnmask.h */,
);
path = Replay;
sourceTree = "<group>";
};
33AFE0122B8F319000AAB120 /* RNSentry */ = {
isa = PBXGroup;
children = (
3380C6C02CDEC56B0018B9B6 /* Replay */,
332D33482CDBDC7300547D76 /* RNSentry.h */,
3360843A2C32E3A8008CC412 /* RNSentryReplayBreadcrumbConverter.h */,
330F308D2C0F385A002A0D4E /* RNSentryBreadcrumb.h */,
Expand Down Expand Up @@ -228,6 +243,7 @@
336084392C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift in Sources */,
33F58AD02977037D008F60EA /* RNSentryTests.mm in Sources */,
33958C692BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m in Sources */,
3380C6C42CE25ECA0018B9B6 /* RNSentryReplayPostInitTests.swift in Sources */,
33AFDFED2B8D14B300AAB120 /* RNSentryFramesTrackerListenerTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@
#import "RNSentryBreadcrumb.h"
#import "RNSentryReplay.h"
#import "RNSentryReplayBreadcrumbConverter.h"
#import "RNSentryReplayMask.h"
#import "RNSentryReplayUnmask.h"
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,7 @@ final class RNSentryReplayOptions: XCTestCase {
let sessionReplay = experimental["sessionReplay"] as! [String: Any]

let maskedViewClasses = sessionReplay["maskedViewClasses"] as! [String]
XCTAssertEqual(maskedViewClasses.count, 1)
XCTAssertEqual(maskedViewClasses[0], "RNSVGSvgView")

let actualOptions = try! Options(dict: optionsDict as! [String: Any])
XCTAssertEqual(actualOptions.experimental.sessionReplay.maskedViewClasses.count, 0)
XCTAssertTrue(maskedViewClasses.contains("RNSVGSvgView"))
}

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

XCTAssertEqual(actualOptions.experimental.sessionReplay.maskAllImages, true)
XCTAssertEqual(actualOptions.experimental.sessionReplay.maskedViewClasses.count, 1)
XCTAssertNotNil(actualOptions.experimental.sessionReplay.maskedViewClasses[0])
XCTAssertEqual(ObjectIdentifier(actualOptions.experimental.sessionReplay.maskedViewClasses[0]), ObjectIdentifier(NSClassFromString("RCTImageView")!))
assertContainsClass(classArray: actualOptions.experimental.sessionReplay.maskedViewClasses, stringClass: "RCTImageView")
}

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

XCTAssertEqual(actualOptions.experimental.sessionReplay.maskAllText, true)
XCTAssertEqual(actualOptions.experimental.sessionReplay.maskedViewClasses.count, 2)
XCTAssertNotNil(actualOptions.experimental.sessionReplay.maskedViewClasses[0])
XCTAssertNotNil(actualOptions.experimental.sessionReplay.maskedViewClasses[1])
XCTAssertEqual(ObjectIdentifier(actualOptions.experimental.sessionReplay.maskedViewClasses[0]), ObjectIdentifier(NSClassFromString("RCTTextView")!))
XCTAssertEqual(ObjectIdentifier(actualOptions.experimental.sessionReplay.maskedViewClasses[1]), ObjectIdentifier(NSClassFromString("RCTParagraphComponentView")!))
assertContainsClass(classArray: actualOptions.experimental.sessionReplay.maskedViewClasses, stringClass: "RCTTextView")
assertContainsClass(classArray: actualOptions.experimental.sessionReplay.maskedViewClasses, stringClass: "RCTParagraphComponentView")
}

func assertContainsClass(classArray: [AnyClass], stringClass: String) {
XCTAssertTrue(mapToObjectIdentifiers(classArray: classArray).contains(ObjectIdentifier(NSClassFromString(stringClass)!)))
}

func mapToObjectIdentifiers(classArray: [AnyClass]) -> [ObjectIdentifier] {
return classArray.map { ObjectIdentifier($0) }
}

func testMaskAllTextFalse() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Sentry
import XCTest

final class RNSentryReplayPostInitTests: XCTestCase {

func testMask() {
XCTAssertEqual(ObjectIdentifier(RNSentryReplay.getMaskClass()), ObjectIdentifier(RNSentryReplayMask.self))
}

func testUnmask() {
XCTAssertEqual(ObjectIdentifier(RNSentryReplay.getUnmaskClass()), ObjectIdentifier(RNSentryReplayUnmask.self))
}
}
4 changes: 4 additions & 0 deletions packages/core/ios/RNSentryReplay.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@

+ (void)postInit;

+ (Class)getMaskClass;

+ (Class)getUnmaskClass;

@end
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
#import "RNSentryReplay.h"
#import "RNSentryReplayBreadcrumbConverter.h"
#import "RNSentryReplayBreadcrumbConverterHelper.h"
#import "React/RCTTextView.h"
#import "Replay/RNSentryReplayMask.h"
#import "Replay/RNSentryReplayUnmask.h"
#import <Sentry/PrivateSentrySDKOnly.h>

#if SENTRY_TARGET_REPLAY_SUPPORTED

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

+ (void)postInit
{
RNSentryReplayBreadcrumbConverter *breadcrumbConverter =
[[RNSentryReplayBreadcrumbConverter alloc] init];
[PrivateSentrySDKOnly configureSessionReplayWith:breadcrumbConverter screenshotProvider:nil];
// We can't import RNSentryReplayMask.h here because it's Objective-C++
// To avoid typos, we test the class existence in the tests
[PrivateSentrySDKOnly setRedactContainerClass:[RNSentryReplay getMaskClass]];
[PrivateSentrySDKOnly setIgnoreContainerClass:[RNSentryReplay getUnmaskClass]];
[RNSentryReplayBreadcrumbConverterHelper configureSessionReplayWithConverter];
}

+ (Class)getMaskClass
{
return RNSentryReplayMask.class;
}

+ (Class)getUnmaskClass
{
return RNSentryReplayUnmask.class;
}

@end
Expand Down
7 changes: 7 additions & 0 deletions packages/core/ios/RNSentryReplayBreadcrumbConverterHelper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#import <Sentry/SentryDefines.h>

@interface RNSentryReplayBreadcrumbConverterHelper : NSObject

+ (void)configureSessionReplayWithConverter;

@end
17 changes: 17 additions & 0 deletions packages/core/ios/RNSentryReplayBreadcrumbConverterHelper.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#import "RNSentryReplayBreadcrumbConverterHelper.h"

#if SENTRY_TARGET_REPLAY_SUPPORTED
# import "RNSentryReplayBreadcrumbConverter.h"

@implementation RNSentryReplayBreadcrumbConverterHelper

+ (void)configureSessionReplayWithConverter
{
RNSentryReplayBreadcrumbConverter *breadcrumbConverter =
[[RNSentryReplayBreadcrumbConverter alloc] init];
[PrivateSentrySDKOnly configureSessionReplayWith:breadcrumbConverter screenshotProvider:nil];
}

@end

#endif
24 changes: 24 additions & 0 deletions packages/core/ios/Replay/RNSentryReplayMask.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#import <Sentry/SentryDefines.h>

#if SENTRY_HAS_UIKIT

# import <React/RCTViewManager.h>

# ifdef RCT_NEW_ARCH_ENABLED
# import <React/RCTViewComponentView.h>
# else
# import <React/RCTView.h>
# endif

@interface RNSentryReplayMaskManager : RCTViewManager
@end

@interface RNSentryReplayMask :
# ifdef RCT_NEW_ARCH_ENABLED
RCTViewComponentView
# else
RCTView
# endif
@end

#endif
51 changes: 51 additions & 0 deletions packages/core/ios/Replay/RNSentryReplayMask.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#import <Sentry/SentryDefines.h>

#if SENTRY_HAS_UIKIT

# import "RNSentryReplayMask.h"

# ifdef RCT_NEW_ARCH_ENABLED
# import <react/renderer/components/RNSentrySpec/ComponentDescriptors.h>
# import <react/renderer/components/RNSentrySpec/RCTComponentViewHelpers.h>
// RCTFabricComponentsPlugins needed for RNSentryReplayMaskCls
# import <React/RCTFabricComponentsPlugins.h>
# endif

@implementation RNSentryReplayMaskManager

RCT_EXPORT_MODULE(RNSentryReplayMask)

- (UIView *)view
{
return [RNSentryReplayMask new];
}

@end

# ifdef RCT_NEW_ARCH_ENABLED
@interface
RNSentryReplayMask () <RCTRNSentryReplayMaskViewProtocol>
@end
# endif

@implementation RNSentryReplayMask

# ifdef RCT_NEW_ARCH_ENABLED
+ (facebook::react::ComponentDescriptorProvider)componentDescriptorProvider
{
return facebook::react::concreteComponentDescriptorProvider<
facebook::react::RNSentryReplayMaskComponentDescriptor>();
}
# endif

@end

# ifdef RCT_NEW_ARCH_ENABLED
Class<RCTComponentViewProtocol>
RNSentryReplayMaskCls(void)
{
return RNSentryReplayMask.class;
}
# endif

#endif
24 changes: 24 additions & 0 deletions packages/core/ios/Replay/RNSentryReplayUnmask.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#import <Sentry/SentryDefines.h>

#if SENTRY_HAS_UIKIT

# import <React/RCTViewManager.h>

# ifdef RCT_NEW_ARCH_ENABLED
# import <React/RCTViewComponentView.h>
# else
# import <React/RCTView.h>
# endif

@interface RNSentryReplayUnmaskManager : RCTViewManager
@end

@interface RNSentryReplayUnmask :
# ifdef RCT_NEW_ARCH_ENABLED
RCTViewComponentView
# else
RCTView
# endif
@end

#endif
51 changes: 51 additions & 0 deletions packages/core/ios/Replay/RNSentryReplayUnmask.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#import <Sentry/SentryDefines.h>

#if SENTRY_HAS_UIKIT

# import "RNSentryReplayUnmask.h"

# ifdef RCT_NEW_ARCH_ENABLED
# import <react/renderer/components/RNSentrySpec/ComponentDescriptors.h>
# import <react/renderer/components/RNSentrySpec/RCTComponentViewHelpers.h>
// RCTFabricComponentsPlugins needed for RNSentryReplayUnmaskCls
# import <React/RCTFabricComponentsPlugins.h>
# endif

@implementation RNSentryReplayUnmaskManager

RCT_EXPORT_MODULE(RNSentryReplayUnmask)

- (UIView *)view
{
return [RNSentryReplayUnmask new];
}

@end

# ifdef RCT_NEW_ARCH_ENABLED
@interface
RNSentryReplayUnmask () <RCTRNSentryReplayUnmaskViewProtocol>
@end
# endif

@implementation RNSentryReplayUnmask

# ifdef RCT_NEW_ARCH_ENABLED
+ (facebook::react::ComponentDescriptorProvider)componentDescriptorProvider
{
return facebook::react::concreteComponentDescriptorProvider<
facebook::react::RNSentryReplayUnmaskComponentDescriptor>();
}
# endif

@end

# ifdef RCT_NEW_ARCH_ENABLED
Class<RCTComponentViewProtocol>
RNSentryReplayUnmaskCls(void)
{
return RNSentryReplayUnmask.class;
}
# endif

#endif
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@
},
"codegenConfig": {
"name": "RNSentrySpec",
"type": "modules",
"type": "all",
"jsSrcsDir": "src",
"android": {
"javaPackageName": "io.sentry.react"
Expand Down
Loading

0 comments on commit 07257af

Please sign in to comment.