Skip to content

Commit 31e8a6f

Browse files
committed
feat can unmask attributes, masks input type=button/submit (only with maskAllText enabled)
1 parent 586a61f commit 31e8a6f

File tree

5 files changed

+72
-33
lines changed

5 files changed

+72
-33
lines changed

packages/browser-integration-tests/suites/replay/privacyDefault/test.ts-snapshots/privacy.json

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,21 @@
131131
"textContent": "\n ",
132132
"id": 20
133133
},
134+
{
135+
"type": 2,
136+
"tagName": "input",
137+
"attributes": {
138+
"data-sentry-unmask": "",
139+
"placeholder": "Placeholder can be unmasked"
140+
},
141+
"childNodes": [],
142+
"id": 21
143+
},
144+
{
145+
"type": 3,
146+
"textContent": "\n ",
147+
"id": 22
148+
},
134149
{
135150
"type": 2,
136151
"tagName": "div",
@@ -141,15 +156,15 @@
141156
{
142157
"type": 3,
143158
"textContent": "***** ****** ** ******",
144-
"id": 22
159+
"id": 24
145160
}
146161
],
147-
"id": 21
162+
"id": 23
148163
},
149164
{
150165
"type": 3,
151166
"textContent": "\n ",
152-
"id": 23
167+
"id": 25
153168
},
154169
{
155170
"type": 2,
@@ -160,12 +175,12 @@
160175
},
161176
"childNodes": [],
162177
"isSVG": true,
163-
"id": 24
178+
"id": 26
164179
},
165180
{
166181
"type": 3,
167182
"textContent": "\n ",
168-
"id": 25
183+
"id": 27
169184
},
170185
{
171186
"type": 2,
@@ -184,32 +199,32 @@
184199
},
185200
"childNodes": [],
186201
"isSVG": true,
187-
"id": 27
202+
"id": 29
188203
},
189204
{
190205
"type": 2,
191206
"tagName": "area",
192207
"attributes": {},
193208
"childNodes": [],
194209
"isSVG": true,
195-
"id": 28
210+
"id": 30
196211
},
197212
{
198213
"type": 2,
199214
"tagName": "rect",
200215
"attributes": {},
201216
"childNodes": [],
202217
"isSVG": true,
203-
"id": 29
218+
"id": 31
204219
}
205220
],
206221
"isSVG": true,
207-
"id": 26
222+
"id": 28
208223
},
209224
{
210225
"type": 3,
211226
"textContent": "\n ",
212-
"id": 30
227+
"id": 32
213228
},
214229
{
215230
"type": 2,
@@ -219,12 +234,12 @@
219234
"rr_height": "[100-150]px"
220235
},
221236
"childNodes": [],
222-
"id": 31
237+
"id": 33
223238
},
224239
{
225240
"type": 3,
226241
"textContent": "\n ",
227-
"id": 32
242+
"id": 34
228243
},
229244
{
230245
"type": 2,
@@ -235,12 +250,12 @@
235250
"src": "file:///none.png"
236251
},
237252
"childNodes": [],
238-
"id": 33
253+
"id": 35
239254
},
240255
{
241256
"type": 3,
242257
"textContent": "\n ",
243-
"id": 34
258+
"id": 36
244259
},
245260
{
246261
"type": 2,
@@ -250,17 +265,17 @@
250265
"rr_height": "[0-50]px"
251266
},
252267
"childNodes": [],
253-
"id": 35
268+
"id": 37
254269
},
255270
{
256271
"type": 3,
257272
"textContent": "\n ",
258-
"id": 36
273+
"id": 38
259274
},
260275
{
261276
"type": 3,
262277
"textContent": "\n\n",
263-
"id": 37
278+
"id": 39
264279
}
265280
],
266281
"id": 8

packages/browser-integration-tests/suites/replay/privacyInput/test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,18 @@ import { IncrementalSource } from '@sentry-internal/rrweb';
44

55
import { sentryTest } from '../../../utils/fixtures';
66
import type { IncrementalRecordingSnapshot } from '../../../utils/replayHelpers';
7+
<<<<<<< HEAD
78
import {
89
getFullRecordingSnapshots,
10+
=======
11+
import { getFullRecordingSnapshots ,
12+
>>>>>>> d495cdedf (feat can unmask attributes, masks input type=button/submit (only with maskAllText enabled))
913
getIncrementalRecordingSnapshots,
1014
shouldSkipReplayTest,
1115
waitForReplayRequest,
1216
} from '../../../utils/replayHelpers';
1317

18+
1419
function isInputMutation(
1520
snap: IncrementalRecordingSnapshot,
1621
): snap is IncrementalRecordingSnapshot & { data: inputData } {

packages/browser-integration-tests/suites/replay/privacyInputMaskAll/test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { inputData } from '@sentry-internal/rrweb';
33
import { IncrementalSource } from '@sentry-internal/rrweb';
44

55
import { sentryTest } from '../../../utils/fixtures';
6-
import type { IncrementalRecordingSnapshot } from '../../../utils/replayHelpers';
6+
import { getFullRecordingSnapshots, IncrementalRecordingSnapshot } from '../../../utils/replayHelpers';
77
import {
88
getFullRecordingSnapshots,
99
getIncrementalRecordingSnapshots,

packages/replay/src/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,6 @@ export const MIN_REPLAY_DURATION_LIMIT = 15_000;
5050

5151
/** The max. length of a replay. */
5252
export const MAX_REPLAY_DURATION = 3_600_000; // 60 minutes in ms;
53+
54+
/** Default attributes to be ignored when `maskAllText` is enabled */
55+
export const DEFAULT_IGNORED_ATTRIBUTES = ['title', 'placeholder'];

packages/replay/src/integration.ts

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -105,33 +105,49 @@ export class Replay implements Integration {
105105
}: ReplayConfiguration = {}) {
106106
this.name = Replay.id;
107107

108+
const privacyOptions = getPrivacyOptions({
109+
mask,
110+
unmask,
111+
block,
112+
unblock,
113+
ignore,
114+
blockClass,
115+
blockSelector,
116+
maskTextClass,
117+
maskTextSelector,
118+
ignoreClass,
119+
});
120+
108121
this._recordingOptions = {
109122
maskAllInputs,
110123
maskAllText,
111124
maskInputOptions: { ...(maskInputOptions || {}), password: true },
112125
maskTextFn: maskFn,
113126
maskInputFn: maskFn,
114-
maskAttributeFn: (key: string, value: string): string => {
115-
// For now, always mask these attributes
116-
if (maskAttributes.includes(key)) {
127+
maskAttributeFn: (key: string, value: string, el: HTMLElement): string => {
128+
// We only mask attributes if `maskAllText` is true
129+
if (!maskAllText) {
130+
return value;
131+
}
132+
133+
// unmaskTextSelector takes precendence
134+
if (privacyOptions.unmaskTextSelector && el.matches(privacyOptions.unmaskTextSelector)) {
135+
return value;
136+
}
137+
138+
if (
139+
maskAttributes.includes(key) ||
140+
// Need to mask `value` attribute for `<input>` if it's a button-like
141+
// type
142+
(key === 'value' && el.tagName === 'INPUT' && ['submit', 'button'].includes(el.getAttribute('type') || ''))
143+
) {
117144
return value.replace(/[\S]/g, '*');
118145
}
119146

120147
return value;
121148
},
122149

123-
...getPrivacyOptions({
124-
mask,
125-
unmask,
126-
block,
127-
unblock,
128-
ignore,
129-
blockClass,
130-
blockSelector,
131-
maskTextClass,
132-
maskTextSelector,
133-
ignoreClass,
134-
}),
150+
...privacyOptions,
135151

136152
// Our defaults
137153
slimDOMOptions: 'all',

0 commit comments

Comments
 (0)