Skip to content

Commit f508831

Browse files
committed
feat can unmask attributes, masks input type=button/submit (only with maskAllText enabled)
1 parent 18705f7 commit f508831

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 REPLAY_MAX_EVENT_BUFFER_SIZE = 20_000_000; // ~20MB
5050
export const MIN_REPLAY_DURATION = 4_999;
5151
/* The max. allowed value that the minReplayDuration can be set to. */
5252
export const MIN_REPLAY_DURATION_LIMIT = 15_000;
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
@@ -103,33 +103,49 @@ export class Replay implements Integration {
103103
}: ReplayConfiguration = {}) {
104104
this.name = Replay.id;
105105

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

118145
return value;
119146
},
120147

121-
...getPrivacyOptions({
122-
mask,
123-
unmask,
124-
block,
125-
unblock,
126-
ignore,
127-
blockClass,
128-
blockSelector,
129-
maskTextClass,
130-
maskTextSelector,
131-
ignoreClass,
132-
}),
148+
...privacyOptions,
133149

134150
// Our defaults
135151
slimDOMOptions: 'all',

0 commit comments

Comments
 (0)