|
21 | 21 | import io.sentry.android.core.NdkIntegration; |
22 | 22 | import io.sentry.android.core.SentryAndroid; |
23 | 23 | import io.sentry.android.core.SentryAndroidOptions; |
| 24 | +import io.sentry.android.core.SentryScreenshotOptions; |
| 25 | +import io.sentry.android.core.internal.util.ScreenshotMasking; |
24 | 26 | import io.sentry.protocol.SdkVersion; |
25 | 27 | import io.sentry.react.replay.RNSentryReplayMask; |
26 | 28 | import io.sentry.react.replay.RNSentryReplayUnmask; |
@@ -141,35 +143,10 @@ static void getSentryAndroidOptions( |
141 | 143 | if (rnOptions.hasKey("attachScreenshot")) { |
142 | 144 | options.setAttachScreenshot(rnOptions.getBoolean("attachScreenshot")); |
143 | 145 | } |
144 | | - if (rnOptions.hasKey("screenshot")) { |
145 | | - @Nullable final ReadableMap screenshotOptions = rnOptions.getMap("screenshot"); |
146 | | - if (screenshotOptions != null) { |
147 | | - if (screenshotOptions.hasKey("maskAllText")) { |
148 | | - options.getScreenshot().setMaskAllText(screenshotOptions.getBoolean("maskAllText")); |
149 | | - } |
150 | | - if (screenshotOptions.hasKey("maskAllImages")) { |
151 | | - options.getScreenshot().setMaskAllImages(screenshotOptions.getBoolean("maskAllImages")); |
152 | | - } |
153 | | - if (screenshotOptions.hasKey("maskedViewClasses")) { |
154 | | - @Nullable |
155 | | - final ReadableArray maskedClasses = screenshotOptions.getArray("maskedViewClasses"); |
156 | | - if (maskedClasses != null) { |
157 | | - for (int i = 0; i < maskedClasses.size(); i++) { |
158 | | - options.getScreenshot().addMaskViewClass(maskedClasses.getString(i)); |
159 | | - } |
160 | | - } |
161 | | - } |
162 | | - if (screenshotOptions.hasKey("unmaskedViewClasses")) { |
163 | | - @Nullable |
164 | | - final ReadableArray unmaskedClasses = screenshotOptions.getArray("unmaskedViewClasses"); |
165 | | - if (unmaskedClasses != null) { |
166 | | - for (int i = 0; i < unmaskedClasses.size(); i++) { |
167 | | - options.getScreenshot().addUnmaskViewClass(unmaskedClasses.getString(i)); |
168 | | - } |
169 | | - } |
170 | | - } |
171 | | - } |
172 | | - } |
| 146 | + configureScreenshotMasking( |
| 147 | + options.getScreenshot(), |
| 148 | + rnOptions.hasKey("screenshot") ? rnOptions.getMap("screenshot") : null, |
| 149 | + ScreenshotMasking.isAvailable(logger)); |
173 | 150 | if (rnOptions.hasKey("attachViewHierarchy")) { |
174 | 151 | options.setAttachViewHierarchy(rnOptions.getBoolean("attachViewHierarchy")); |
175 | 152 | } |
@@ -409,6 +386,56 @@ private static boolean isReplayEnabled(SentryReplayOptions replayOptions) { |
409 | 386 | || replayOptions.getOnErrorSampleRate() != null; |
410 | 387 | } |
411 | 388 |
|
| 389 | + /** |
| 390 | + * Masking is enabled by default, as documented in the JS options, while the native Android SDK |
| 391 | + * leaves it off until its next major. Package-private for unit testing. |
| 392 | + */ |
| 393 | + static void configureScreenshotMasking( |
| 394 | + final @NotNull SentryScreenshotOptions screenshot, |
| 395 | + final @Nullable ReadableMap rnScreenshotOptions, |
| 396 | + final boolean isReplayAvailable) { |
| 397 | + final boolean maskAllText = |
| 398 | + rnScreenshotOptions == null |
| 399 | + || !rnScreenshotOptions.hasKey("maskAllText") |
| 400 | + || rnScreenshotOptions.getBoolean("maskAllText"); |
| 401 | + final boolean maskAllImages = |
| 402 | + rnScreenshotOptions == null |
| 403 | + || !rnScreenshotOptions.hasKey("maskAllImages") |
| 404 | + || rnScreenshotOptions.getBoolean("maskAllImages"); |
| 405 | + screenshot.setMaskAllText(maskAllText); |
| 406 | + screenshot.setMaskAllImages(maskAllImages); |
| 407 | + if (maskAllImages) { |
| 408 | + screenshot.addMaskViewClass("com.horcrux.svg.SvgView"); // react-native-svg |
| 409 | + } |
| 410 | + |
| 411 | + if (rnScreenshotOptions != null && rnScreenshotOptions.hasKey("maskedViewClasses")) { |
| 412 | + @Nullable |
| 413 | + final ReadableArray maskedClasses = rnScreenshotOptions.getArray("maskedViewClasses"); |
| 414 | + if (maskedClasses != null) { |
| 415 | + for (int i = 0; i < maskedClasses.size(); i++) { |
| 416 | + screenshot.addMaskViewClass(maskedClasses.getString(i)); |
| 417 | + } |
| 418 | + } |
| 419 | + } |
| 420 | + if (rnScreenshotOptions != null && rnScreenshotOptions.hasKey("unmaskedViewClasses")) { |
| 421 | + @Nullable |
| 422 | + final ReadableArray unmaskedClasses = rnScreenshotOptions.getArray("unmaskedViewClasses"); |
| 423 | + if (unmaskedClasses != null) { |
| 424 | + for (int i = 0; i < unmaskedClasses.size(); i++) { |
| 425 | + screenshot.addUnmaskViewClass(unmaskedClasses.getString(i)); |
| 426 | + } |
| 427 | + } |
| 428 | + } |
| 429 | + |
| 430 | + // Honor <Sentry.Mask> and <Sentry.Unmask> in error screenshots, the same as in Session Replay. |
| 431 | + // Setting the mask container also turns masking on, which needs the replay module. Without it |
| 432 | + // no screenshot is taken at all, so skip it to keep the masking opt-out working. |
| 433 | + if (isReplayAvailable) { |
| 434 | + screenshot.setMaskViewContainerClass(RNSentryReplayMask.class.getName()); |
| 435 | + screenshot.setUnmaskViewContainerClass(RNSentryReplayUnmask.class.getName()); |
| 436 | + } |
| 437 | + } |
| 438 | + |
412 | 439 | // Package-private for unit testing (see RNSentryReplayOptionsTest), matching the other |
413 | 440 | // testable static helpers in this class. |
414 | 441 | static SentryReplayOptions getReplayOptions(@NotNull ReadableMap rnOptions) { |
|
0 commit comments