From 5c3c29987227cbde8fa6f458d31248c737fc5edc Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Thu, 16 Apr 2026 13:20:56 +0200 Subject: [PATCH 1/9] feat(android): Expose screenshot masking options for error screenshots Add `screenshot.maskAllText` and `screenshot.maskAllImages` options to control masking of sensitive content in error screenshots on Android. These options bridge to the Android SDK's `SentryScreenshotOptions` API (available since sentry-android 8.34.0). Closes #5763 Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 1 + .../java/io/sentry/react/RNSentryStart.java | 11 +++++++++ packages/core/src/js/options.ts | 23 +++++++++++++++++++ packages/core/test/wrapper.test.ts | 22 ++++++++++++++++++ samples/react-native/src/App.tsx | 5 ++++ 5 files changed, 62 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6aaee019b..5a4332ce6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Features +- Expose Android screenshot masking options (`screenshot.maskAllText`, `screenshot.maskAllImages`) for error screenshots ([#5763](https://github.com/getsentry/sentry-react-native/issues/5763)) - Enable "Open Sentry" button in Playground for Expo apps ([#5947](https://github.com/getsentry/sentry-react-native/pull/5947)) - Add `attachAllThreads` option to attach full stack traces for all threads to captured events on iOS ([#5960](https://github.com/getsentry/sentry-react-native/issues/5960)) - Add `strictTraceContinuation` and `orgId` options for trace continuation validation ([#5829](https://github.com/getsentry/sentry-react-native/pull/5829)) diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryStart.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryStart.java index ebf305ad9d..d4e8c9baa7 100644 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentryStart.java +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryStart.java @@ -136,6 +136,17 @@ static void getSentryAndroidOptions( if (rnOptions.hasKey("attachScreenshot")) { options.setAttachScreenshot(rnOptions.getBoolean("attachScreenshot")); } + if (rnOptions.hasKey("screenshot")) { + @Nullable final ReadableMap screenshotOptions = rnOptions.getMap("screenshot"); + if (screenshotOptions != null) { + if (screenshotOptions.hasKey("maskAllText")) { + options.getScreenshot().setMaskAllText(screenshotOptions.getBoolean("maskAllText")); + } + if (screenshotOptions.hasKey("maskAllImages")) { + options.getScreenshot().setMaskAllImages(screenshotOptions.getBoolean("maskAllImages")); + } + } + } if (rnOptions.hasKey("attachViewHierarchy")) { options.setAttachViewHierarchy(rnOptions.getBoolean("attachViewHierarchy")); } diff --git a/packages/core/src/js/options.ts b/packages/core/src/js/options.ts index abe769069b..5799e8b873 100644 --- a/packages/core/src/js/options.ts +++ b/packages/core/src/js/options.ts @@ -194,6 +194,29 @@ export interface BaseReactNativeOptions { */ attachScreenshot?: boolean; + /** + * Options for configuring screenshot masking on error screenshots (Android only). + * When `attachScreenshot` is enabled, these options control what gets masked in the screenshot. + * + * Requires `sentry-android-replay` module at runtime for masking to work. + */ + screenshot?: { + /** + * Mask all text content in error screenshots. + * + * @platform android + * @default true + */ + maskAllText?: boolean; + /** + * Mask all images in error screenshots. + * + * @platform android + * @default true + */ + maskAllImages?: boolean; + }; + /** * When enabled Sentry includes the current view hierarchy in the error attachments. * diff --git a/packages/core/test/wrapper.test.ts b/packages/core/test/wrapper.test.ts index 29b6c99781..c39c725a77 100644 --- a/packages/core/test/wrapper.test.ts +++ b/packages/core/test/wrapper.test.ts @@ -414,6 +414,28 @@ describe('Tests Native Wrapper', () => { expect(initParameter.strictTraceContinuation).toBe(true); }); + test('passes screenshot options to native SDK', async () => { + await NATIVE.initNativeSdk({ + dsn: 'test', + enableNative: true, + autoInitializeNativeSdk: true, + screenshot: { + maskAllText: false, + maskAllImages: true, + }, + devServerUrl: undefined, + defaultSidecarUrl: undefined, + mobileReplayOptions: undefined, + }); + + expect(RNSentry.initNativeSdk).toHaveBeenCalled(); + const initParameter = (RNSentry.initNativeSdk as jest.MockedFunction).mock.calls[0][0]; + expect(initParameter.screenshot).toEqual({ + maskAllText: false, + maskAllImages: true, + }); + }); + test('passes orgId option to native SDK', async () => { await NATIVE.initNativeSdk({ dsn: 'test', diff --git a/samples/react-native/src/App.tsx b/samples/react-native/src/App.tsx index eb81ad6282..20546c8233 100644 --- a/samples/react-native/src/App.tsx +++ b/samples/react-native/src/App.tsx @@ -152,6 +152,11 @@ Sentry.init({ attachStacktrace: true, // Attach screenshots to events. attachScreenshot: true, + // Screenshot masking options (Android only). + screenshot: { + maskAllText: true, + maskAllImages: true, + }, // Attach view hierarchy to events. attachViewHierarchy: true, // Enables capture failed requests in JS and native. From ec388d8bb4aa4555b44e6945b82dfabbc189f43d Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Thu, 16 Apr 2026 13:27:08 +0200 Subject: [PATCH 2/9] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a4332ce6a..9ec3a888e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ ### Features -- Expose Android screenshot masking options (`screenshot.maskAllText`, `screenshot.maskAllImages`) for error screenshots ([#5763](https://github.com/getsentry/sentry-react-native/issues/5763)) +- Expose Android screenshot masking options (`screenshot.maskAllText`, `screenshot.maskAllImages`) for error screenshots ([#6007](https://github.com/getsentry/sentry-react-native/pull/6007)) - Enable "Open Sentry" button in Playground for Expo apps ([#5947](https://github.com/getsentry/sentry-react-native/pull/5947)) - Add `attachAllThreads` option to attach full stack traces for all threads to captured events on iOS ([#5960](https://github.com/getsentry/sentry-react-native/issues/5960)) - Add `strictTraceContinuation` and `orgId` options for trace continuation validation ([#5829](https://github.com/getsentry/sentry-react-native/pull/5829)) From 8940dcdecb4e90bc781f2e4d70f5cf08bb9e9a15 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Thu, 16 Apr 2026 16:02:18 +0200 Subject: [PATCH 3/9] Update changelog --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32f812f5da..11f5bfb2a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,16 @@ > make sure you follow our [migration guide](https://docs.sentry.io/platforms/react-native/migration/) first. -## 8.8.0 +## Unreleased ### Features - Expose Android screenshot masking options (`screenshot.maskAllText`, `screenshot.maskAllImages`) for error screenshots ([#6007](https://github.com/getsentry/sentry-react-native/pull/6007)) + +## 8.8.0 + +### Features + - Enable "Open Sentry" button in Playground for Expo apps ([#5947](https://github.com/getsentry/sentry-react-native/pull/5947)) - Add `attachAllThreads` option to attach full stack traces for all threads to captured events on iOS ([#5960](https://github.com/getsentry/sentry-react-native/issues/5960)) - Add `strictTraceContinuation` and `orgId` options for trace continuation validation ([#5829](https://github.com/getsentry/sentry-react-native/pull/5829)) From 566760fd6d732a4ef841e6cec188acc3f3e4f759 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Thu, 16 Apr 2026 16:14:44 +0200 Subject: [PATCH 4/9] feat(ios): Add iOS screenshot masking support Bridge screenshot masking options to iOS via the Cocoa SDK's SentryViewScreenshotOptions init(dictionary:) API. Both platforms (Android and iOS) now support screenshot.maskAllText and screenshot.maskAllImages. Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 2 +- .../RNSentryStartTests.swift | 28 +++++++++++++++++++ packages/core/ios/RNSentryStart.m | 8 ++++++ packages/core/src/js/options.ts | 6 +--- samples/react-native/src/App.tsx | 2 +- 5 files changed, 39 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11f5bfb2a4..2435e5c38e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ ### Features -- Expose Android screenshot masking options (`screenshot.maskAllText`, `screenshot.maskAllImages`) for error screenshots ([#6007](https://github.com/getsentry/sentry-react-native/pull/6007)) +- Expose screenshot masking options (`screenshot.maskAllText`, `screenshot.maskAllImages`) for error screenshots ([#6007](https://github.com/getsentry/sentry-react-native/pull/6007)) ## 8.8.0 diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift index b9d12200cf..f36e999c5d 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift @@ -197,6 +197,34 @@ final class RNSentryStartTests: XCTestCase { } } + func testScreenshotMaskingOptions() throws { + try startFromRN(options: [ + "dsn": "https://abcd@efgh.ingest.sentry.io/123456", + "attachScreenshot": true, + "screenshot": [ + "maskAllText": false, + "maskAllImages": true, + ] + ]) + + let actualOptions = PrivateSentrySDKOnly.options + XCTAssertTrue(actualOptions.attachScreenshot) + XCTAssertFalse(actualOptions.screenshot.maskAllText) + XCTAssertTrue(actualOptions.screenshot.maskAllImages) + } + + func testScreenshotMaskingOptionsDefaults() throws { + try startFromRN(options: [ + "dsn": "https://abcd@efgh.ingest.sentry.io/123456", + "attachScreenshot": true, + ]) + + let actualOptions = PrivateSentrySDKOnly.options + XCTAssertTrue(actualOptions.attachScreenshot) + XCTAssertTrue(actualOptions.screenshot.maskAllText) + XCTAssertTrue(actualOptions.screenshot.maskAllImages) + } + func startFromRN(options: [String: Any]) throws { var error: NSError? RNSentryStart.start(options: options, error: &error) diff --git a/packages/core/ios/RNSentryStart.m b/packages/core/ios/RNSentryStart.m index 03ca9e2ccd..c844070a21 100644 --- a/packages/core/ios/RNSentryStart.m +++ b/packages/core/ios/RNSentryStart.m @@ -55,6 +55,14 @@ + (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull) return nil; } +#if SENTRY_TARGET_REPLAY_SUPPORTED + NSDictionary *screenshotDict = mutableOptions[@"screenshot"]; + if ([screenshotDict isKindOfClass:[NSDictionary class]]) { + sentryOptions.screenshot = + [[SentryViewScreenshotOptions alloc] initWithDictionary:screenshotDict]; + } +#endif + // Exclude Dev Server and Sentry Dsn request from Breadcrumbs NSString *dsn = [self getURLFromDSN:[mutableOptions valueForKey:@"dsn"]]; // TODO: For Auto Init from JS dev server is resolved automatically, for init from options file diff --git a/packages/core/src/js/options.ts b/packages/core/src/js/options.ts index 5799e8b873..b36bad533e 100644 --- a/packages/core/src/js/options.ts +++ b/packages/core/src/js/options.ts @@ -195,23 +195,19 @@ export interface BaseReactNativeOptions { attachScreenshot?: boolean; /** - * Options for configuring screenshot masking on error screenshots (Android only). + * Options for configuring screenshot masking on error screenshots. * When `attachScreenshot` is enabled, these options control what gets masked in the screenshot. - * - * Requires `sentry-android-replay` module at runtime for masking to work. */ screenshot?: { /** * Mask all text content in error screenshots. * - * @platform android * @default true */ maskAllText?: boolean; /** * Mask all images in error screenshots. * - * @platform android * @default true */ maskAllImages?: boolean; diff --git a/samples/react-native/src/App.tsx b/samples/react-native/src/App.tsx index 20546c8233..5501974645 100644 --- a/samples/react-native/src/App.tsx +++ b/samples/react-native/src/App.tsx @@ -152,7 +152,7 @@ Sentry.init({ attachStacktrace: true, // Attach screenshots to events. attachScreenshot: true, - // Screenshot masking options (Android only). + // Screenshot masking options. screenshot: { maskAllText: true, maskAllImages: true, From 0a336189b107e2c623b26f0146015c32b7ee9e2d Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Thu, 16 Apr 2026 16:36:53 +0200 Subject: [PATCH 5/9] feat: Add maskedViewClasses and unmaskedViewClasses to screenshot options Support masking/unmasking specific native view classes in error screenshots. Useful for third-party native views like map SDKs, payment forms, or video players. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../java/io/sentry/react/RNSentryStart.java | 19 +++++++++++++++++++ packages/core/src/js/options.ts | 12 ++++++++++++ packages/core/test/wrapper.test.ts | 4 ++++ 3 files changed, 35 insertions(+) diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryStart.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryStart.java index d4e8c9baa7..1ab8394b66 100644 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentryStart.java +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryStart.java @@ -2,6 +2,7 @@ import android.app.Activity; import android.content.Context; +import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableType; import com.facebook.react.common.JavascriptException; @@ -145,6 +146,24 @@ static void getSentryAndroidOptions( if (screenshotOptions.hasKey("maskAllImages")) { options.getScreenshot().setMaskAllImages(screenshotOptions.getBoolean("maskAllImages")); } + if (screenshotOptions.hasKey("maskedViewClasses")) { + @Nullable + final ReadableArray maskedClasses = screenshotOptions.getArray("maskedViewClasses"); + if (maskedClasses != null) { + for (int i = 0; i < maskedClasses.size(); i++) { + options.getScreenshot().addMaskViewClass(maskedClasses.getString(i)); + } + } + } + if (screenshotOptions.hasKey("unmaskedViewClasses")) { + @Nullable + final ReadableArray unmaskedClasses = screenshotOptions.getArray("unmaskedViewClasses"); + if (unmaskedClasses != null) { + for (int i = 0; i < unmaskedClasses.size(); i++) { + options.getScreenshot().addUnmaskViewClass(unmaskedClasses.getString(i)); + } + } + } } } if (rnOptions.hasKey("attachViewHierarchy")) { diff --git a/packages/core/src/js/options.ts b/packages/core/src/js/options.ts index b36bad533e..4dc92f7bdd 100644 --- a/packages/core/src/js/options.ts +++ b/packages/core/src/js/options.ts @@ -211,6 +211,18 @@ export interface BaseReactNativeOptions { * @default true */ maskAllImages?: boolean; + /** + * A list of native view class names to mask in error screenshots. + * Useful for masking views from third-party native libraries (e.g., map views, payment forms). + * + * @example ['com.example.MyCustomView'] // Android + * @example ['MKMapView'] // iOS + */ + maskedViewClasses?: string[]; + /** + * A list of native view class names to exclude from masking in error screenshots. + */ + unmaskedViewClasses?: string[]; }; /** diff --git a/packages/core/test/wrapper.test.ts b/packages/core/test/wrapper.test.ts index c39c725a77..eb7ac283e5 100644 --- a/packages/core/test/wrapper.test.ts +++ b/packages/core/test/wrapper.test.ts @@ -422,6 +422,8 @@ describe('Tests Native Wrapper', () => { screenshot: { maskAllText: false, maskAllImages: true, + maskedViewClasses: ['com.example.MyView'], + unmaskedViewClasses: ['com.example.SafeView'], }, devServerUrl: undefined, defaultSidecarUrl: undefined, @@ -433,6 +435,8 @@ describe('Tests Native Wrapper', () => { expect(initParameter.screenshot).toEqual({ maskAllText: false, maskAllImages: true, + maskedViewClasses: ['com.example.MyView'], + unmaskedViewClasses: ['com.example.SafeView'], }); }); From a504753398385629df2bd9e0db74890167fc0381 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Thu, 16 Apr 2026 16:39:23 +0200 Subject: [PATCH 6/9] fix(ios): Use SENTRY_HAS_UIKIT guard for screenshot options Screenshot masking depends on UIKit, not session replay. Use SENTRY_HAS_UIKIT to match the guard used by the Cocoa SDK for attachScreenshot. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/core/ios/RNSentryStart.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/ios/RNSentryStart.m b/packages/core/ios/RNSentryStart.m index c844070a21..67fa48871d 100644 --- a/packages/core/ios/RNSentryStart.m +++ b/packages/core/ios/RNSentryStart.m @@ -55,7 +55,7 @@ + (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull) return nil; } -#if SENTRY_TARGET_REPLAY_SUPPORTED +#if SENTRY_HAS_UIKIT NSDictionary *screenshotDict = mutableOptions[@"screenshot"]; if ([screenshotDict isKindOfClass:[NSDictionary class]]) { sentryOptions.screenshot = From af14ae913a66ca25bca5415952550a52d1af1fe5 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Thu, 16 Apr 2026 16:45:48 +0200 Subject: [PATCH 7/9] docs: Update changelog with maskedViewClasses and unmaskedViewClasses Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2435e5c38e..5601ffc99c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ ### Features -- Expose screenshot masking options (`screenshot.maskAllText`, `screenshot.maskAllImages`) for error screenshots ([#6007](https://github.com/getsentry/sentry-react-native/pull/6007)) +- Expose screenshot masking options (`screenshot.maskAllText`, `screenshot.maskAllImages`, `screenshot.maskedViewClasses`, `screenshot.unmaskedViewClasses`) for error screenshots ([#6007](https://github.com/getsentry/sentry-react-native/pull/6007)) ## 8.8.0 From 5cc659b717a6315f65ea9da21fcb000b5150c200 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Fri, 17 Apr 2026 14:52:09 +0200 Subject: [PATCH 8/9] fix: Remove trailing commas in Swift test literals Co-Authored-By: Claude Opus 4.6 (1M context) --- .../RNSentryCocoaTesterTests/RNSentryStartTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift index f36e999c5d..c437b83049 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift @@ -203,7 +203,7 @@ final class RNSentryStartTests: XCTestCase { "attachScreenshot": true, "screenshot": [ "maskAllText": false, - "maskAllImages": true, + "maskAllImages": true ] ]) @@ -216,7 +216,7 @@ final class RNSentryStartTests: XCTestCase { func testScreenshotMaskingOptionsDefaults() throws { try startFromRN(options: [ "dsn": "https://abcd@efgh.ingest.sentry.io/123456", - "attachScreenshot": true, + "attachScreenshot": true ]) let actualOptions = PrivateSentrySDKOnly.options From df9b91281eac12b045847e32ed7e26c69dc6f667 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Fri, 17 Apr 2026 15:04:05 +0200 Subject: [PATCH 9/9] fix(ios): Set screenshot options via properties instead of init(dictionary:) The SentryViewScreenshotOptions.init(dictionary:) convenience initializer is internal (not @objc exposed) and not accessible from Objective-C. Set properties directly on the default instance instead. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/core/ios/RNSentryStart.m | 36 +++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/packages/core/ios/RNSentryStart.m b/packages/core/ios/RNSentryStart.m index 67fa48871d..fcc60857e9 100644 --- a/packages/core/ios/RNSentryStart.m +++ b/packages/core/ios/RNSentryStart.m @@ -58,8 +58,40 @@ + (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull) #if SENTRY_HAS_UIKIT NSDictionary *screenshotDict = mutableOptions[@"screenshot"]; if ([screenshotDict isKindOfClass:[NSDictionary class]]) { - sentryOptions.screenshot = - [[SentryViewScreenshotOptions alloc] initWithDictionary:screenshotDict]; + id maskAllText = screenshotDict[@"maskAllText"]; + if ([maskAllText isKindOfClass:[NSNumber class]]) { + sentryOptions.screenshot.maskAllText = [maskAllText boolValue]; + } + id maskAllImages = screenshotDict[@"maskAllImages"]; + if ([maskAllImages isKindOfClass:[NSNumber class]]) { + sentryOptions.screenshot.maskAllImages = [maskAllImages boolValue]; + } + id maskedViewClasses = screenshotDict[@"maskedViewClasses"]; + if ([maskedViewClasses isKindOfClass:[NSArray class]]) { + NSMutableArray *classes = [NSMutableArray array]; + for (id className in maskedViewClasses) { + if ([className isKindOfClass:[NSString class]]) { + Class cls = NSClassFromString(className); + if (cls != nil) { + [classes addObject:cls]; + } + } + } + sentryOptions.screenshot.maskedViewClasses = classes; + } + id unmaskedViewClasses = screenshotDict[@"unmaskedViewClasses"]; + if ([unmaskedViewClasses isKindOfClass:[NSArray class]]) { + NSMutableArray *classes = [NSMutableArray array]; + for (id className in unmaskedViewClasses) { + if ([className isKindOfClass:[NSString class]]) { + Class cls = NSClassFromString(className); + if (cls != nil) { + [classes addObject:cls]; + } + } + } + sentryOptions.screenshot.unmaskedViewClasses = classes; + } } #endif