From 1c201218a8487824c7a783849c2ccc7284ca18b3 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Fri, 30 Oct 2026 07:59:30 +0100 Subject: [PATCH 1/3] fix(ios): Honor Sentry.Mask in error screenshots Register RNSentryReplayMask as a masked view class for screenshots so is masked in JS error screenshots, the same as in Session Replay. The native SDK only lets hybrid SDKs set the mask container class on the replay photographer, not the screenshot one. Fixes #6970 Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + .../RNSentryStartTests.swift | 38 +++++++++++++++++++ packages/core/ios/RNSentryStart.m | 10 +++++ 3 files changed, 49 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9169d6117f..11843d5ec2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ### Fixes - Enable native logs on Android when `enableLogs` is set ([#6967](https://github.com/getsentry/sentry-react-native/pull/6967)) +- Honor `` in iOS error screenshots ([#6970](https://github.com/getsentry/sentry-react-native/issues/6970)) ## 8.33.0 diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift index 1e875e74fc..a48974b567 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift @@ -225,6 +225,44 @@ final class RNSentryStartTests: XCTestCase { XCTAssertTrue(actualOptions.screenshot.maskAllImages) } + func testScreenshotMasksSentryMaskComponent() throws { + try startFromRN(options: [ + "dsn": "https://abcd@efgh.ingest.sentry.io/123456", + "attachScreenshot": true, + "screenshot": [ + "maskAllText": false, + "maskAllImages": false + ] + ]) + + let maskedViewClasses = SentrySDK.internal.options.screenshot.maskedViewClasses + XCTAssertTrue(maskedViewClasses.contains { $0 == RNSentryReplay.getMaskClass() }) + } + + func testScreenshotMasksSentryMaskComponentWithoutScreenshotOptions() throws { + try startFromRN(options: [ + "dsn": "https://abcd@efgh.ingest.sentry.io/123456", + "attachScreenshot": true + ]) + + let maskedViewClasses = SentrySDK.internal.options.screenshot.maskedViewClasses + XCTAssertTrue(maskedViewClasses.contains { $0 == RNSentryReplay.getMaskClass() }) + } + + func testScreenshotKeepsUserMaskedViewClassesWithSentryMaskComponent() throws { + try startFromRN(options: [ + "dsn": "https://abcd@efgh.ingest.sentry.io/123456", + "attachScreenshot": true, + "screenshot": [ + "maskedViewClasses": ["UILabel"] + ] + ]) + + let maskedViewClasses = SentrySDK.internal.options.screenshot.maskedViewClasses + XCTAssertTrue(maskedViewClasses.contains { $0 == UILabel.self }) + XCTAssertTrue(maskedViewClasses.contains { $0 == RNSentryReplay.getMaskClass() }) + } + func testMemoryIntrospectionOption() throws { try startFromRN(options: [ "dsn": "https://abcd@efgh.ingest.sentry.io/123456", diff --git a/packages/core/ios/RNSentryStart.m b/packages/core/ios/RNSentryStart.m index 3ff2ae837c..0c0c848615 100644 --- a/packages/core/ios/RNSentryStart.m +++ b/packages/core/ios/RNSentryStart.m @@ -121,6 +121,16 @@ + (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull) sentryOptions.screenshot.unmaskedViewClasses = classes; } } + + // Honor in error screenshots, the same as in Session Replay. The native SDK only + // lets hybrid SDKs set the mask container class on the replay photographer, so register it as + // a masked view class instead: the wrapper's whole frame is masked, covering its children. + // needs a container class (children are unmasked), which isn't available yet. + Class maskClass = [RNSentryReplay getMaskClass]; + if (![sentryOptions.screenshot.maskedViewClasses containsObject:maskClass]) { + sentryOptions.screenshot.maskedViewClasses = + [sentryOptions.screenshot.maskedViewClasses arrayByAddingObject:maskClass]; + } #endif // Exclude Dev Server and Sentry Dsn request from Breadcrumbs From 7218536ab7903c2a55f0cfe2777cb203a0c75c14 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Fri, 30 Oct 2026 07:59:55 +0100 Subject: [PATCH 2/3] docs(changelog): Link PR #6974 Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11843d5ec2..9f299bdd56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ ### Fixes - Enable native logs on Android when `enableLogs` is set ([#6967](https://github.com/getsentry/sentry-react-native/pull/6967)) -- Honor `` in iOS error screenshots ([#6970](https://github.com/getsentry/sentry-react-native/issues/6970)) +- Honor `` in iOS error screenshots ([#6974](https://github.com/getsentry/sentry-react-native/pull/6974)) ## 8.33.0 From f9860208b86f0364930f9725f8210f299bf403fd Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Fri, 30 Oct 2026 08:07:45 +0100 Subject: [PATCH 3/3] fix(ios): Gate screenshot mask class on replay support Co-Authored-By: Claude Opus 5.5 --- packages/core/ios/RNSentryStart.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core/ios/RNSentryStart.m b/packages/core/ios/RNSentryStart.m index 0c0c848615..7f5113db9a 100644 --- a/packages/core/ios/RNSentryStart.m +++ b/packages/core/ios/RNSentryStart.m @@ -126,11 +126,13 @@ + (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull) // lets hybrid SDKs set the mask container class on the replay photographer, so register it as // a masked view class instead: the wrapper's whole frame is masked, covering its children. // needs a container class (children are unmasked), which isn't available yet. +# if SENTRY_TARGET_REPLAY_SUPPORTED Class maskClass = [RNSentryReplay getMaskClass]; if (![sentryOptions.screenshot.maskedViewClasses containsObject:maskClass]) { sentryOptions.screenshot.maskedViewClasses = [sentryOptions.screenshot.maskedViewClasses arrayByAddingObject:maskClass]; } +# endif #endif // Exclude Dev Server and Sentry Dsn request from Breadcrumbs