diff --git a/CHANGELOG.md b/CHANGELOG.md index 63cdd0b2e8..9eb3ad7f4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,22 @@ ### Features - Adds GraphQL integration ([#5299](https://github.com/getsentry/sentry-react-native/pull/5299)) +- Add new _experimental_ Canvas Capture Strategy for Session Replay ([#5301](https://github.com/getsentry/sentry-react-native/pull/5301)) + - A new screenshot capture strategy that uses Android's Canvas API for more accurate and reliable text and image masking + - Any `.drawText()` or `.drawBitmap()` calls are replaced by rectangles, ensuring no text or images are present in the resulting output + - Note: If this strategy is used, all text and images will be masked, regardless of any masking configuration + - To enable this feature, set the `screenshotStrategy` to `canvas`: + ```js + import * as Sentry from '@sentry/react-native'; + + Sentry.init({ + integrations: [ + Sentry.mobileReplayIntegration({ + screenshotStrategy: 'canvas', + }), + ], + }); + ``` ### Dependencies diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java index 3b9bbf30f1..f375fa0a17 100644 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java @@ -37,6 +37,7 @@ import io.sentry.ISerializer; import io.sentry.Integration; import io.sentry.ScopesAdapter; +import io.sentry.ScreenshotStrategyType; import io.sentry.Sentry; import io.sentry.SentryDate; import io.sentry.SentryDateProvider; @@ -415,12 +416,36 @@ private SentryReplayOptions getReplayOptions(@NotNull ReadableMap rnOptions) { androidReplayOptions.addMaskViewClass("com.horcrux.svg.SvgView"); // react-native-svg } + if (rnMobileReplayOptions.hasKey("screenshotStrategy")) { + final String screenshotStrategyString = rnMobileReplayOptions.getString("screenshotStrategy"); + final ScreenshotStrategyType screenshotStrategy = + parseScreenshotStrategy(screenshotStrategyString); + androidReplayOptions.setScreenshotStrategy(screenshotStrategy); + } + androidReplayOptions.setMaskViewContainerClass(RNSentryReplayMask.class.getName()); androidReplayOptions.setUnmaskViewContainerClass(RNSentryReplayUnmask.class.getName()); return androidReplayOptions; } + private ScreenshotStrategyType parseScreenshotStrategy(@Nullable String strategyString) { + if (strategyString == null) { + return ScreenshotStrategyType.PIXEL_COPY; + } + + try { + switch (strategyString.toLowerCase(Locale.ROOT)) { + case "canvas": + return ScreenshotStrategyType.CANVAS; + default: + return ScreenshotStrategyType.PIXEL_COPY; + } + } catch (Exception e) { + return ScreenshotStrategyType.PIXEL_COPY; + } + } + private SentryReplayQuality parseReplayQuality(@Nullable String qualityString) { if (qualityString == null) { return SentryReplayQuality.MEDIUM; diff --git a/packages/core/src/js/replay/mobilereplay.ts b/packages/core/src/js/replay/mobilereplay.ts index 34b32a811e..af84d0a01a 100644 --- a/packages/core/src/js/replay/mobilereplay.ts +++ b/packages/core/src/js/replay/mobilereplay.ts @@ -8,6 +8,14 @@ import { enrichXhrBreadcrumbsForMobileReplay } from './xhrUtils'; export const MOBILE_REPLAY_INTEGRATION_NAME = 'MobileReplay'; +/** + * Screenshot strategy type for Android Session Replay. + * + * - `'canvas'`: Canvas-based screenshot strategy. This strategy does **not** support any masking options, it always masks text and images. Use this if your application has strict PII requirements. + * - `'pixelCopy'`: Pixel copy screenshot strategy (default). Supports all masking options. + */ +export type ScreenshotStrategy = 'canvas' | 'pixelCopy'; + export interface MobileReplayOptions { /** * Mask all text in recordings @@ -69,6 +77,19 @@ export interface MobileReplayOptions { * @default false */ enableFastViewRendering?: boolean; + + /** + * Sets the screenshot strategy used by the Session Replay integration on Android. + * + * If your application has strict PII requirements we recommend using `'canvas'`. + * This strategy does **not** support any masking options, it always masks text and images. + * + * - Experiment: In case you are noticing issues with the canvas screenshot strategy, please report the issue on [GitHub](https://github.com/getsentry/sentry-java). + * + * @default 'pixelCopy' + * @platform android + */ + screenshotStrategy?: ScreenshotStrategy; } const defaultOptions: Required = { @@ -78,6 +99,7 @@ const defaultOptions: Required = { enableExperimentalViewRenderer: false, enableViewRendererV2: true, enableFastViewRendering: false, + screenshotStrategy: 'pixelCopy', }; function mergeOptions(initOptions: Partial): Required { diff --git a/samples/react-native/src/App.tsx b/samples/react-native/src/App.tsx index fca0fe3f64..c9a24aea6f 100644 --- a/samples/react-native/src/App.tsx +++ b/samples/react-native/src/App.tsx @@ -94,6 +94,7 @@ Sentry.init({ maskAllVectors: true, maskAllText: true, enableViewRendererV2: true, + screenshotStrategy: 'canvas', // if you have strict PII requirements }), Sentry.appStartIntegration({ standalone: false,