diff --git a/CHANGELOG.md b/CHANGELOG.md index f40d4e8ebe..9e4ec245e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- Redact `react-native-svg` SVGs when `maskAllVectors` ([#3930](https://github.com/getsentry/sentry-react-native/pull/3930)) - Set `currentScreen` on native scope ([#3927](https://github.com/getsentry/sentry-react-native/pull/3927)) - Add `annotateReactComponents` option to `@sentry/react-native/metro` ([#3916](https://github.com/getsentry/sentry-react-native/pull/3916)) diff --git a/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java b/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java index ac661a075a..7954bc1493 100644 --- a/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java +++ b/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java @@ -41,9 +41,11 @@ import java.io.InputStream; import java.nio.charset.Charset; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.Set; import java.util.concurrent.CountDownLatch; import io.sentry.Breadcrumb; @@ -326,6 +328,11 @@ private SentryReplayOptions getReplayOptions(@NotNull ReadableMap rnOptions) { androidReplayOptions.setRedactAllText(!rnMobileReplayOptions.hasKey("maskAllText") || rnMobileReplayOptions.getBoolean("maskAllText")); androidReplayOptions.setRedactAllImages(!rnMobileReplayOptions.hasKey("maskAllImages") || rnMobileReplayOptions.getBoolean("maskAllImages")); + final boolean redactVectors = !rnMobileReplayOptions.hasKey("maskAllVectors") || rnMobileReplayOptions.getBoolean("maskAllVectors"); + if (redactVectors) { + androidReplayOptions.addClassToRedact("com.horcrux.svg.SvgView"); // react-native-svg + } + return androidReplayOptions; } diff --git a/ios/RNSentryReplay.m b/ios/RNSentryReplay.m index ecd7a4d16b..3693888191 100644 --- a/ios/RNSentryReplay.m +++ b/ios/RNSentryReplay.m @@ -40,11 +40,23 @@ + (void)updateOptions:(NSMutableDictionary *)options { + (void)addReplayRNRedactClasses:(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]; + } + } if ([replayOptions[@"maskAllImages"] boolValue] == YES) { - [classesToRedact addObject:NSClassFromString(@"RCTImageView")]; + Class _Nullable maybeRCTImageClass = NSClassFromString(@"RCTImageView"); + if (maybeRCTImageClass != nil) { + [classesToRedact addObject:maybeRCTImageClass]; + } } if ([replayOptions[@"maskAllText"] boolValue] == YES) { - [classesToRedact addObject:NSClassFromString(@"RCTTextView")]; + Class _Nullable maybeRCTTextClass = NSClassFromString(@"RCTTextView"); + if (maybeRCTTextClass != nil) { + [classesToRedact addObject:maybeRCTTextClass]; + } } [PrivateSentrySDKOnly addReplayRedactClasses:classesToRedact]; } diff --git a/samples/react-native/package.json b/samples/react-native/package.json index 8482a18392..6e7d61291c 100644 --- a/samples/react-native/package.json +++ b/samples/react-native/package.json @@ -32,6 +32,7 @@ "react-native-reanimated": "3.8.1", "react-native-safe-area-context": "4.8.0", "react-native-screens": "3.29.0", + "react-native-svg": "^15.3.0", "react-native-vector-icons": "^10.0.3", "react-redux": "^8.1.3", "redux": "^4.2.1" diff --git a/samples/react-native/src/App.tsx b/samples/react-native/src/App.tsx index 539d387a0d..338a3cef92 100644 --- a/samples/react-native/src/App.tsx +++ b/samples/react-native/src/App.tsx @@ -93,7 +93,8 @@ Sentry.init({ }), Sentry.metrics.metricsAggregatorIntegration(), Sentry.mobileReplayIntegration({ - maskAllImages: false, + maskAllImages: true, + maskAllVectors: true, // maskAllText: false, }), ); diff --git a/samples/react-native/src/Screens/PlaygroundScreen.tsx b/samples/react-native/src/Screens/PlaygroundScreen.tsx index 690b14bbe1..7a5d212892 100644 --- a/samples/react-native/src/Screens/PlaygroundScreen.tsx +++ b/samples/react-native/src/Screens/PlaygroundScreen.tsx @@ -13,6 +13,7 @@ import { SafeAreaView, Pressable, } from 'react-native'; +import SvgGraphic from '../components/SvgGraphic'; const multilineText = `This is @@ -65,6 +66,8 @@ const PlaygroundScreen = () => { }}> Press me + react-native-svg + diff --git a/samples/react-native/src/components/SvgGraphic.tsx b/samples/react-native/src/components/SvgGraphic.tsx new file mode 100644 index 0000000000..525c7cccc4 --- /dev/null +++ b/samples/react-native/src/components/SvgGraphic.tsx @@ -0,0 +1,288 @@ +import * as React from 'react'; +import Svg, { SvgProps, Defs, G, Path, Ellipse } from 'react-native-svg'; + +const SvgComponent = (props: SvgProps) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); +export default SvgComponent; diff --git a/samples/react-native/yarn.lock b/samples/react-native/yarn.lock index 84f702e16d..0c5340d4c8 100644 --- a/samples/react-native/yarn.lock +++ b/samples/react-native/yarn.lock @@ -4284,6 +4284,11 @@ bl@^4.1.0: inherits "^2.0.4" readable-stream "^3.4.0" +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -4702,6 +4707,30 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +css-select@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" + integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== + dependencies: + boolbase "^1.0.0" + css-what "^6.1.0" + domhandler "^5.0.2" + domutils "^3.0.1" + nth-check "^2.0.1" + +css-tree@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== + dependencies: + mdn-data "2.0.14" + source-map "^0.6.1" + +css-what@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" + integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== + csstype@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9" @@ -4847,6 +4876,36 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + +domelementtype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + +domutils@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" + integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -4877,6 +4936,11 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== +entities@^4.2.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + envinfo@^7.10.0: version "7.11.0" resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.11.0.tgz#c3793f44284a55ff8c82faf1ffd91bc6478ea01f" @@ -6834,6 +6898,11 @@ marky@^1.2.2: resolved "https://registry.yarnpkg.com/marky/-/marky-1.2.5.tgz#55796b688cbd72390d2d399eaaf1832c9413e3c0" integrity sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q== +mdn-data@2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" + integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== + memoize-one@^5.0.0: version "5.2.1" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" @@ -7381,6 +7450,13 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" +nth-check@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" + nullthrows@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1" @@ -7910,6 +7986,14 @@ react-native-screens@3.29.0: react-freeze "^1.0.0" warn-once "^0.1.0" +react-native-svg@^15.3.0: + version "15.3.0" + resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-15.3.0.tgz#e24b833fe330714c99f1dd894bb0da52ad859a4c" + integrity sha512-mBHu/fdlzUbpGX8SZFxgbKvK/sgqLfDLP8uh8G7Us+zJgdjO8OSEeqHQs+kPRdQmdLJQiqPJX2WXgCl7ToTWqw== + dependencies: + css-select "^5.1.0" + css-tree "^1.1.3" + react-native-vector-icons@^10.0.3: version "10.0.3" resolved "https://registry.yarnpkg.com/react-native-vector-icons/-/react-native-vector-icons-10.0.3.tgz#369824a3b17994b2cd65edbaa32dbf9540d49678" diff --git a/src/js/replay/mobilereplay.ts b/src/js/replay/mobilereplay.ts index 6d376ad4fb..aa99c256c8 100644 --- a/src/js/replay/mobilereplay.ts +++ b/src/js/replay/mobilereplay.ts @@ -12,18 +12,31 @@ export const MOBILE_REPLAY_INTEGRATION_NAME = 'MobileReplay'; export interface MobileReplayOptions { /** * Mask all text in recordings + * + * @default true */ maskAllText?: boolean; /** * Mask all text in recordings + * + * @default true */ maskAllImages?: boolean; + + /** + * Mask all vector graphics in recordings + * Supports `react-native-svg` + * + * @default true + */ + maskAllVectors?: boolean; } const defaultOptions: Required = { maskAllText: true, maskAllImages: true, + maskAllVectors: true, }; type MobileReplayIntegration = IntegrationFnResult & {