From b4e79be5c14ed6be2b36a46a2886447e8ad4d950 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Thu, 7 Nov 2024 11:03:40 +0100 Subject: [PATCH] fix(replay): RN Classes to redact were not used by cocoa --- .../project.pbxproj | 10 +- ...RNSentryCocoaTesterTests-Bridging-Header.h | 1 + .../RNSentryReplayOptionsTests.swift | 155 ++++++++++++++++++ packages/core/ios/RNSentryReplay.m | 30 +--- 4 files changed, 173 insertions(+), 23 deletions(-) create mode 100644 packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryReplayOptionsTests.swift diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj b/packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj index b1cc2aa6f4..8d96fe625e 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* 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 */; }; 33958C692BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 33958C682BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m */; }; 33AFDFED2B8D14B300AAB120 /* RNSentryFramesTrackerListenerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 33AFDFEC2B8D14B300AAB120 /* RNSentryFramesTrackerListenerTests.m */; }; @@ -18,11 +19,13 @@ /* Begin PBXFileReference section */ 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 = ""; }; 330F308D2C0F385A002A0D4E /* RNSentryBreadcrumb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSentryBreadcrumb.h; path = ../ios/RNSentryBreadcrumb.h; sourceTree = ""; }; + 332D33462CDBDBB600547D76 /* RNSentryReplayOptionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNSentryReplayOptionsTests.swift; sourceTree = ""; }; + 332D33482CDBDC7300547D76 /* RNSentry.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNSentry.h; path = ../ios/RNSentry.h; sourceTree = SOURCE_ROOT; }; 336084382C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RNSentryReplayBreadcrumbConverterTests.swift; sourceTree = ""; }; 3360843A2C32E3A8008CC412 /* RNSentryReplayBreadcrumbConverter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSentryReplayBreadcrumbConverter.h; path = ../ios/RNSentryReplayBreadcrumbConverter.h; sourceTree = ""; }; 3360843C2C340C76008CC412 /* RNSentryBreadcrumbTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNSentryBreadcrumbTests.swift; sourceTree = ""; }; 3360898D29524164007C7730 /* RNSentryCocoaTesterTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RNSentryCocoaTesterTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 338739072A7D7D2800950DDD /* RNSentryTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNSentryTests.h; sourceTree = ""; }; + 338739072A7D7D2800950DDD /* RNSentryReplay.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNSentryReplay.h; path = ../ios/RNSentryReplay.h; sourceTree = ""; }; 33958C672BFCEF5A00AD1FB6 /* RNSentryOnDrawReporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSentryOnDrawReporter.h; path = ../ios/RNSentryOnDrawReporter.h; sourceTree = ""; }; 33958C682BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNSentryOnDrawReporterTests.m; sourceTree = ""; }; 33AFDFEC2B8D14B300AAB120 /* RNSentryFramesTrackerListenerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNSentryFramesTrackerListenerTests.m; sourceTree = ""; }; @@ -80,13 +83,13 @@ children = ( 336084382C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift */, 33F58ACF2977037D008F60EA /* RNSentryTests.mm */, - 338739072A7D7D2800950DDD /* RNSentryTests.h */, 33AFDFEC2B8D14B300AAB120 /* RNSentryFramesTrackerListenerTests.m */, 33AFDFEE2B8D14C200AAB120 /* RNSentryFramesTrackerListenerTests.h */, 33AFDFF02B8D15E500AAB120 /* RNSentryDependencyContainerTests.m */, 33AFDFF22B8D15F600AAB120 /* RNSentryDependencyContainerTests.h */, 33958C682BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m */, 3360843C2C340C76008CC412 /* RNSentryBreadcrumbTests.swift */, + 332D33462CDBDBB600547D76 /* RNSentryReplayOptionsTests.swift */, ); path = RNSentryCocoaTesterTests; sourceTree = ""; @@ -94,10 +97,12 @@ 33AFE0122B8F319000AAB120 /* RNSentry */ = { isa = PBXGroup; children = ( + 332D33482CDBDC7300547D76 /* RNSentry.h */, 3360843A2C32E3A8008CC412 /* RNSentryReplayBreadcrumbConverter.h */, 330F308D2C0F385A002A0D4E /* RNSentryBreadcrumb.h */, 33958C672BFCEF5A00AD1FB6 /* RNSentryOnDrawReporter.h */, 33AFE0132B8F31AF00AAB120 /* RNSentryDependencyContainer.h */, + 338739072A7D7D2800950DDD /* RNSentryReplay.h */, ); name = RNSentry; sourceTree = ""; @@ -212,6 +217,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 332D33472CDBDBB600547D76 /* RNSentryReplayOptionsTests.swift in Sources */, 33AFDFF12B8D15E500AAB120 /* RNSentryDependencyContainerTests.m in Sources */, 336084392C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift in Sources */, 33F58AD02977037D008F60EA /* RNSentryTests.mm in Sources */, diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryCocoaTesterTests-Bridging-Header.h b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryCocoaTesterTests-Bridging-Header.h index 6b949b5967..b6c8d70abf 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryCocoaTesterTests-Bridging-Header.h +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryCocoaTesterTests-Bridging-Header.h @@ -4,3 +4,4 @@ #import "RNSentryBreadcrumb.h" #import "RNSentryReplayBreadcrumbConverter.h" +#import "RNSentryReplay.h" diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryReplayOptionsTests.swift b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryReplayOptionsTests.swift new file mode 100644 index 0000000000..18a329ebec --- /dev/null +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryReplayOptionsTests.swift @@ -0,0 +1,155 @@ +import XCTest +import Sentry + +final class RNSentryReplayOptions: XCTestCase { + + func testOptionsWithoutExperimentalAreIgnored() { + let optionsDict = NSMutableDictionary() + RNSentryReplay.updateOptions(optionsDict) + + XCTAssertEqual(optionsDict.count, 0) + } + + func testExperimentalOptionsWithoutReplaySampleRatesAreRemoved() { + let optionsDict = (["_experiments": [:]] as NSDictionary).mutableCopy() as! NSMutableDictionary + RNSentryReplay.updateOptions(optionsDict) + + XCTAssertEqual(optionsDict.count, 0) + } + + func testReplayOptionsDictContainsAllOptionsKeysWhenSessionSampleRateUsed() { + let optionsDict = ([ + "dsn": "https://abc@def.ingest.sentry.io/1234567", + "_experiments": [ + "replaysSessionSampleRate": 0.75 + ] + ] as NSDictionary).mutableCopy() as! NSMutableDictionary + RNSentryReplay.updateOptions(optionsDict) + + let experimental = optionsDict["experimental"] as! [String:Any] + let sessionReplay = experimental["sessionReplay"] as! [String:Any] + + assertAllDefaultReplayOptionsAreNotNil(replayOptions: sessionReplay) + } + + func testReplayOptionsDictContainsAllOptionsKeysWhenErrorSampleRateUsed() { + let optionsDict = ([ + "dsn": "https://abc@def.ingest.sentry.io/1234567", + "_experiments": [ + "replaysOnErrorSampleRate": 0.75 + ] + ] as NSDictionary).mutableCopy() as! NSMutableDictionary + RNSentryReplay.updateOptions(optionsDict) + + let experimental = optionsDict["experimental"] as! [String:Any] + let sessionReplay = experimental["sessionReplay"] as! [String:Any] + + assertAllDefaultReplayOptionsAreNotNil(replayOptions: sessionReplay) + } + + func testReplayOptionsDictContainsAllOptionsKeysWhenErrorAndSessionSampleRatesUsed() { + let optionsDict = ([ + "dsn": "https://abc@def.ingest.sentry.io/1234567", + "_experiments": [ + "replaysOnErrorSampleRate": 0.75, + "replaysSessionSampleRate": 0.75 + ] + ] as NSDictionary).mutableCopy() as! NSMutableDictionary + RNSentryReplay.updateOptions(optionsDict) + + let experimental = optionsDict["experimental"] as! [String:Any] + let sessionReplay = experimental["sessionReplay"] as! [String:Any] + + assertAllDefaultReplayOptionsAreNotNil(replayOptions: sessionReplay) + } + + func assertAllDefaultReplayOptionsAreNotNil(replayOptions: [String: Any]) { + XCTAssertEqual(replayOptions.count, 5) + XCTAssertNotNil(replayOptions["sessionSampleRate"]) + XCTAssertNotNil(replayOptions["errorSampleRate"]) + XCTAssertNotNil(replayOptions["redactAllImages"]) + XCTAssertNotNil(replayOptions["redactAllText"]) + XCTAssertNotNil(replayOptions["maskedViewClasses"]) + } + + func testSessionSampleRate() { + let optionsDict = ([ + "dsn": "https://abc@def.ingest.sentry.io/1234567", + "_experiments": [ "replaysSessionSampleRate": 0.75 ] + ] as NSDictionary).mutableCopy() as! NSMutableDictionary + RNSentryReplay.updateOptions(optionsDict) + + let actualOptions = try! Options(dict: optionsDict as! [String: Any]) + XCTAssertEqual(actualOptions.experimental.sessionReplay.sessionSampleRate, 0.75) + } + + func testOnErrorSampleRate() { + let optionsDict = ([ + "dsn": "https://abc@def.ingest.sentry.io/1234567", + "_experiments": [ "replaysOnErrorSampleRate": 0.75 ] + ] as NSDictionary).mutableCopy() as! NSMutableDictionary + RNSentryReplay.updateOptions(optionsDict) + + let actualOptions = try! Options(dict: optionsDict as! [String: Any]) + XCTAssertEqual(actualOptions.experimental.sessionReplay.onErrorSampleRate, 0.75) + } + + func testMaskAllVectors() { + let optionsDict = ([ + "dsn": "https://abc@def.ingest.sentry.io/1234567", + "_experiments": [ "replaysOnErrorSampleRate": 0.75 ], + "mobileReplayOptions": [ "maskAllVectors": true ] + ] as NSDictionary).mutableCopy() as! NSMutableDictionary + + RNSentryReplay.updateOptions(optionsDict) + + XCTAssertEqual(optionsDict.count, 3) + + let experimental = optionsDict["experimental"] as! [String:Any] + 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) + } + + func testMaskAllImages() { + let optionsDict = ([ + "dsn": "https://abc@def.ingest.sentry.io/1234567", + "_experiments": [ "replaysOnErrorSampleRate": 0.75 ], + "mobileReplayOptions": [ "maskAllImages": true ] + ] as NSDictionary).mutableCopy() as! NSMutableDictionary + + RNSentryReplay.updateOptions(optionsDict) + + 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")!)) + } + + func testMaskAllText() { + let optionsDict = ([ + "dsn": "https://abc@def.ingest.sentry.io/1234567", + "_experiments": [ "replaysOnErrorSampleRate": 0.75 ], + "mobileReplayOptions": [ "maskAllText": true ] + ] as NSDictionary).mutableCopy() as! NSMutableDictionary + + RNSentryReplay.updateOptions(optionsDict) + + 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")!)) + } + +} diff --git a/packages/core/ios/RNSentryReplay.m b/packages/core/ios/RNSentryReplay.m index 9ef2c4efc0..ca63fb9dde 100644 --- a/packages/core/ios/RNSentryReplay.m +++ b/packages/core/ios/RNSentryReplay.m @@ -30,40 +30,28 @@ + (void)updateOptions:(NSMutableDictionary *)options @"errorSampleRate" : experiments[@"replaysOnErrorSampleRate"] ?: [NSNull null], @"redactAllImages" : replayOptions[@"maskAllImages"] ?: [NSNull null], @"redactAllText" : replayOptions[@"maskAllText"] ?: [NSNull null], + @"maskedViewClasses" : [RNSentryReplay getReplayRNRedactClasses:replayOptions], } } forKey:@"experimental"]; - - [RNSentryReplay addReplayRNRedactClasses:replayOptions]; } -+ (void)addReplayRNRedactClasses:(NSDictionary *_Nullable)replayOptions ++ (NSArray *_Nonnull)getReplayRNRedactClasses:(NSDictionary *_Nullable)replayOptions { NSMutableArray *_Nonnull classesToRedact = [[NSMutableArray alloc] init]; + if ([replayOptions[@"maskAllVectors"] boolValue] == YES) { - Class _Nullable maybeRNSVGViewClass = NSClassFromString(@"RNSVGSvgView"); - if (maybeRNSVGViewClass != nil) { - [classesToRedact addObject:maybeRNSVGViewClass]; - } + [classesToRedact addObject:@"RNSVGSvgView"]; } if ([replayOptions[@"maskAllImages"] boolValue] == YES) { - Class _Nullable maybeRCTImageClass = NSClassFromString(@"RCTImageView"); - if (maybeRCTImageClass != nil) { - [classesToRedact addObject:maybeRCTImageClass]; - } + [classesToRedact addObject:@"RCTImageView"]; } if ([replayOptions[@"maskAllText"] boolValue] == YES) { - Class _Nullable maybeRCTTextClass = NSClassFromString(@"RCTTextView"); - if (maybeRCTTextClass != nil) { - [classesToRedact addObject:maybeRCTTextClass]; - } - Class _Nullable maybeRCTParagraphComponentViewClass - = NSClassFromString(@"RCTParagraphComponentView"); - if (maybeRCTParagraphComponentViewClass != nil) { - [classesToRedact addObject:maybeRCTParagraphComponentViewClass]; - } + [classesToRedact addObject:@"RCTTextView"]; + [classesToRedact addObject:@"RCTParagraphComponentView"]; } - [PrivateSentrySDKOnly addReplayRedactClasses:classesToRedact]; + + return classesToRedact; } + (void)postInit