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 & {