diff --git a/CHANGELOG.md b/CHANGELOG.md index bbc8da1e88..99da2ce40a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ ### Features -- Add Replay Custom Masking for iOS and Android ([#4224](https://github.com/getsentry/sentry-react-native/pull/4224), [#4265](https://github.com/getsentry/sentry-react-native/pull/4265)) +- Add Replay Custom Masking for iOS, Android and Web ([#4224](https://github.com/getsentry/sentry-react-native/pull/4224), [#4265](https://github.com/getsentry/sentry-react-native/pull/4265), [#4272](https://github.com/getsentry/sentry-react-native/pull/4272)) ```jsx import * as Sentry from '@sentry/react-native'; diff --git a/packages/core/src/js/integrations/exports.ts b/packages/core/src/js/integrations/exports.ts index 07cc3289a9..dfc9e2c3e1 100644 --- a/packages/core/src/js/integrations/exports.ts +++ b/packages/core/src/js/integrations/exports.ts @@ -13,6 +13,7 @@ export { viewHierarchyIntegration } from './viewhierarchy'; export { expoContextIntegration } from './expocontext'; export { spotlightIntegration } from './spotlight'; export { mobileReplayIntegration } from '../replay/mobilereplay'; +export { browserReplayIntegration } from '../replay/browserReplay'; export { appStartIntegration } from '../tracing/integrations/appStart'; export { nativeFramesIntegration, createNativeFramesIntegrations } from '../tracing/integrations/nativeFrames'; export { stallTrackingIntegration } from '../tracing/integrations/stalltracking'; @@ -30,5 +31,4 @@ export { inboundFiltersIntegration, linkedErrorsIntegration as browserLinkedErrorsIntegration, rewriteFramesIntegration, - replayIntegration as browserReplayIntegration, } from '@sentry/react'; diff --git a/packages/core/src/js/replay/CustomMask.web.tsx b/packages/core/src/js/replay/CustomMask.web.tsx index 87275b9986..1d89dacc59 100644 --- a/packages/core/src/js/replay/CustomMask.web.tsx +++ b/packages/core/src/js/replay/CustomMask.web.tsx @@ -2,17 +2,35 @@ import * as React from 'react'; import type { ViewProps } from 'react-native'; import { View } from 'react-native'; +// Wrapping children in a View and div can cause styling issues +// but with the current implementation of react-native-web +// we can't avoid it. +// +// the prop is dropped by react-native-web +// https://github.com/necolas/react-native-web/blob/a5ba27c6226aa182979a9cff8cc23c0f5caa4d88/packages/react-native-web/src/exports/View/index.js#L47 +// +// So we need to wrap the children in a react-dom div. +// We are using className instead of data-attribute to +// allow for easier CSS styling adjustments. + const Mask = (props: ViewProps): React.ReactElement => { - // We have to ensure that the warning is visible even if the app is running without debug - // eslint-disable-next-line no-console - console.warn('[SentrySessionReplay] Mask component is not supported on web.'); - return ; + return ( + +
+ {props.children} +
+
+ ); }; + const Unmask = (props: ViewProps): React.ReactElement => { - // We have to ensure that the warning is visible even if the app is running without debug - // eslint-disable-next-line no-console - console.warn('[SentrySessionReplay] Unmask component is not supported on web.'); - return ; + return ( + +
+ {props.children} +
+
+ ); }; export { Mask, Unmask }; diff --git a/packages/core/src/js/replay/browserReplay.ts b/packages/core/src/js/replay/browserReplay.ts new file mode 100644 index 0000000000..b72c0be69f --- /dev/null +++ b/packages/core/src/js/replay/browserReplay.ts @@ -0,0 +1,13 @@ +import { replayIntegration } from '@sentry/react'; + +const browserReplayIntegration = ( + options: Parameters[0] = {}, +): ReturnType => { + return replayIntegration({ + ...options, + mask: ['.sentry-react-native-mask', ...(options.mask || [])], + unmask: ['.sentry-react-native-unmask:not(.sentry-react-native-mask *) > *', ...(options.unmask || [])], + }); +}; + +export { browserReplayIntegration }; diff --git a/packages/core/tsconfig.build.json b/packages/core/tsconfig.build.json index 812f7eb4b7..aa8afa3015 100644 --- a/packages/core/tsconfig.build.json +++ b/packages/core/tsconfig.build.json @@ -2,7 +2,9 @@ "extends": "./node_modules/@sentry-internal/typescript/tsconfig.json", "include": [ "./src/js/*.ts", + "./src/js/*.tsx", "./src/js/**/*.web.ts", + "./src/js/**/*.web.tsx", "./typings/react-native.d.ts" ], "exclude": ["node_modules"], diff --git a/samples/expo/app/(tabs)/two.tsx b/samples/expo/app/(tabs)/two.tsx index f2ea47eebd..4482a15da3 100644 --- a/samples/expo/app/(tabs)/two.tsx +++ b/samples/expo/app/(tabs)/two.tsx @@ -1,4 +1,5 @@ import { StyleSheet } from 'react-native'; +import * as Sentry from '@sentry/react-native'; import EditScreenInfo from '@/components/EditScreenInfo'; import { Text, View } from '@/components/Themed'; @@ -8,6 +9,16 @@ export default function TabTwoScreen() { Tab Two + + This is unmasked because it's direct child of Sentry.Unmask (can be masked if Sentry.Masked is used higher in the hierarchy) + + This is masked always because it's a child of a Sentry.Mask + + {/* Sentry.Unmask does not override the Sentry.Mask from above in the hierarchy */} + This is masked always because it's a child of Sentry.Mask + + + ); diff --git a/samples/expo/app/_layout.tsx b/samples/expo/app/_layout.tsx index 0145f84feb..9430d8f22f 100644 --- a/samples/expo/app/_layout.tsx +++ b/samples/expo/app/_layout.tsx @@ -56,6 +56,7 @@ process.env.EXPO_SKIP_DURING_EXPORT !== 'true' && Sentry.init({ }), navigationIntegration, Sentry.reactNativeTracingIntegration(), + Sentry.browserReplayIntegration(), ); return integrations.filter(i => i.name !== 'Dedupe'); },