diff --git a/packages/browser-integration-tests/suites/replay/privacyDefault/test.ts-snapshots/privacy.json b/packages/browser-integration-tests/suites/replay/privacyDefault/test.ts-snapshots/privacy.json index 964209872b06..69f74ba00da8 100644 --- a/packages/browser-integration-tests/suites/replay/privacyDefault/test.ts-snapshots/privacy.json +++ b/packages/browser-integration-tests/suites/replay/privacyDefault/test.ts-snapshots/privacy.json @@ -131,6 +131,21 @@ "textContent": "\n ", "id": 20 }, + { + "type": 2, + "tagName": "input", + "attributes": { + "data-sentry-unmask": "", + "placeholder": "Placeholder can be unmasked" + }, + "childNodes": [], + "id": 21 + }, + { + "type": 3, + "textContent": "\n ", + "id": 22 + }, { "type": 2, "tagName": "div", @@ -141,15 +156,15 @@ { "type": 3, "textContent": "***** ****** ** ******", - "id": 22 + "id": 24 } ], - "id": 21 + "id": 23 }, { "type": 3, "textContent": "\n ", - "id": 23 + "id": 25 }, { "type": 2, @@ -160,12 +175,12 @@ }, "childNodes": [], "isSVG": true, - "id": 24 + "id": 26 }, { "type": 3, "textContent": "\n ", - "id": 25 + "id": 27 }, { "type": 2, @@ -184,7 +199,7 @@ }, "childNodes": [], "isSVG": true, - "id": 27 + "id": 29 }, { "type": 2, @@ -192,7 +207,7 @@ "attributes": {}, "childNodes": [], "isSVG": true, - "id": 28 + "id": 30 }, { "type": 2, @@ -200,16 +215,16 @@ "attributes": {}, "childNodes": [], "isSVG": true, - "id": 29 + "id": 31 } ], "isSVG": true, - "id": 26 + "id": 28 }, { "type": 3, "textContent": "\n ", - "id": 30 + "id": 32 }, { "type": 2, @@ -219,12 +234,12 @@ "rr_height": "[100-150]px" }, "childNodes": [], - "id": 31 + "id": 33 }, { "type": 3, "textContent": "\n ", - "id": 32 + "id": 34 }, { "type": 2, @@ -235,12 +250,12 @@ "src": "file:///none.png" }, "childNodes": [], - "id": 33 + "id": 35 }, { "type": 3, "textContent": "\n ", - "id": 34 + "id": 36 }, { "type": 2, @@ -250,17 +265,17 @@ "rr_height": "[0-50]px" }, "childNodes": [], - "id": 35 + "id": 37 }, { "type": 3, "textContent": "\n ", - "id": 36 + "id": 38 }, { "type": 3, "textContent": "\n\n", - "id": 37 + "id": 39 } ], "id": 8 diff --git a/packages/browser-integration-tests/suites/replay/privacyInput/test.ts b/packages/browser-integration-tests/suites/replay/privacyInput/test.ts index 3b76e5622225..8a7208688262 100644 --- a/packages/browser-integration-tests/suites/replay/privacyInput/test.ts +++ b/packages/browser-integration-tests/suites/replay/privacyInput/test.ts @@ -4,13 +4,18 @@ import { IncrementalSource } from '@sentry-internal/rrweb'; import { sentryTest } from '../../../utils/fixtures'; import type { IncrementalRecordingSnapshot } from '../../../utils/replayHelpers'; +<<<<<<< HEAD import { getFullRecordingSnapshots, +======= +import { getFullRecordingSnapshots , +>>>>>>> d495cdedf (feat can unmask attributes, masks input type=button/submit (only with maskAllText enabled)) getIncrementalRecordingSnapshots, shouldSkipReplayTest, waitForReplayRequest, } from '../../../utils/replayHelpers'; + function isInputMutation( snap: IncrementalRecordingSnapshot, ): snap is IncrementalRecordingSnapshot & { data: inputData } { diff --git a/packages/browser-integration-tests/suites/replay/privacyInputMaskAll/test.ts b/packages/browser-integration-tests/suites/replay/privacyInputMaskAll/test.ts index e98839f7fa37..e305211cfd6a 100644 --- a/packages/browser-integration-tests/suites/replay/privacyInputMaskAll/test.ts +++ b/packages/browser-integration-tests/suites/replay/privacyInputMaskAll/test.ts @@ -3,7 +3,7 @@ import type { inputData } from '@sentry-internal/rrweb'; import { IncrementalSource } from '@sentry-internal/rrweb'; import { sentryTest } from '../../../utils/fixtures'; -import type { IncrementalRecordingSnapshot } from '../../../utils/replayHelpers'; +import { getFullRecordingSnapshots, IncrementalRecordingSnapshot } from '../../../utils/replayHelpers'; import { getFullRecordingSnapshots, getIncrementalRecordingSnapshots, diff --git a/packages/replay/src/constants.ts b/packages/replay/src/constants.ts index d8d5e792a619..982c4c165ae8 100644 --- a/packages/replay/src/constants.ts +++ b/packages/replay/src/constants.ts @@ -50,3 +50,6 @@ export const REPLAY_MAX_EVENT_BUFFER_SIZE = 20_000_000; // ~20MB export const MIN_REPLAY_DURATION = 4_999; /* The max. allowed value that the minReplayDuration can be set to. */ export const MIN_REPLAY_DURATION_LIMIT = 15_000; + +/** Default attributes to be ignored when `maskAllText` is enabled */ +export const DEFAULT_IGNORED_ATTRIBUTES = ['title', 'placeholder']; diff --git a/packages/replay/src/integration.ts b/packages/replay/src/integration.ts index ed70c3fb597b..5e2b6aaf559b 100644 --- a/packages/replay/src/integration.ts +++ b/packages/replay/src/integration.ts @@ -101,33 +101,49 @@ export class Replay implements Integration { // eslint-disable-next-line deprecation/deprecation ignoreClass, }: ReplayConfiguration = {}) { + const privacyOptions = getPrivacyOptions({ + mask, + unmask, + block, + unblock, + ignore, + blockClass, + blockSelector, + maskTextClass, + maskTextSelector, + ignoreClass, + }); + this._recordingOptions = { maskAllInputs, maskAllText, maskInputOptions: { ...(maskInputOptions || {}), password: true }, maskTextFn: maskFn, maskInputFn: maskFn, - maskAttributeFn: (key: string, value: string): string => { - // For now, always mask these attributes - if (maskAttributes.includes(key)) { + maskAttributeFn: (key: string, value: string, el: HTMLElement): string => { + // We only mask attributes if `maskAllText` is true + if (!maskAllText) { + return value; + } + + // unmaskTextSelector takes precendence + if (privacyOptions.unmaskTextSelector && el.matches(privacyOptions.unmaskTextSelector)) { + return value; + } + + if ( + maskAttributes.includes(key) || + // Need to mask `value` attribute for `` if it's a button-like + // type + (key === 'value' && el.tagName === 'INPUT' && ['submit', 'button'].includes(el.getAttribute('type') || '')) + ) { return value.replace(/[\S]/g, '*'); } return value; }, - ...getPrivacyOptions({ - mask, - unmask, - block, - unblock, - ignore, - blockClass, - blockSelector, - maskTextClass, - maskTextSelector, - ignoreClass, - }), + ...privacyOptions, // Our defaults slimDOMOptions: 'all',