Skip to content

Commit 8efa80b

Browse files
committed
review fixes (maskAttribue -> fn and try/catch
1 parent 1d0e51c commit 8efa80b

File tree

3 files changed

+114
-24
lines changed

3 files changed

+114
-24
lines changed

packages/replay/src/integration.ts

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { ReplayContainer } from './replay';
1212
import type { RecordingOptions, ReplayConfiguration, ReplayPluginOptions, SendBufferedReplayOptions } from './types';
1313
import { getPrivacyOptions } from './util/getPrivacyOptions';
1414
import { isBrowser } from './util/isBrowser';
15+
import { maskAttribute } from './util/maskAttribute';
1516

1617
const MEDIA_SELECTORS =
1718
'img,image,svg,video,object,picture,embed,map,audio,link[rel="icon"],link[rel="apple-touch-icon"]';
@@ -122,28 +123,15 @@ export class Replay implements Integration {
122123
maskInputOptions: { ...(maskInputOptions || {}), password: true },
123124
maskTextFn: maskFn,
124125
maskInputFn: maskFn,
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-
) {
142-
return value.replace(/[\S]/g, '*');
143-
}
144-
145-
return value;
146-
},
126+
maskAttributeFn: (key: string, value: string, el: HTMLElement): string =>
127+
maskAttribute({
128+
maskAttributes,
129+
maskAllText,
130+
privacyOptions,
131+
key,
132+
value,
133+
el,
134+
}),
147135

148136
...privacyOptions,
149137

@@ -156,8 +144,13 @@ export class Replay implements Integration {
156144
// origin for playback
157145
collectFonts: true,
158146
errorHandler: (err: Error) => {
159-
// @ts-ignore Set this so that replay SDK can ignore errors originating from rrweb
160-
err.__rrweb__ = true;
147+
try {
148+
// @ts-ignore Set this so that replay SDK can ignore errors originating from rrweb
149+
err.__rrweb__ = true;
150+
} catch {
151+
// avoid any potential hazards here
152+
}
153+
// return true to suppress throwing the error inside of rrweb
161154
return true;
162155
},
163156
};
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import type { getPrivacyOptions } from './getPrivacyOptions';
2+
3+
interface MaskAttributeParams {
4+
maskAttributes: string[];
5+
maskAllText: boolean;
6+
privacyOptions: ReturnType<typeof getPrivacyOptions>;
7+
key: string;
8+
value: string;
9+
el: HTMLElement;
10+
}
11+
12+
/**
13+
* Masks an attribute if necessary, otherwise return attribute value as-is.
14+
*/
15+
export function maskAttribute({
16+
el,
17+
key,
18+
maskAttributes,
19+
maskAllText,
20+
privacyOptions,
21+
value,
22+
}: MaskAttributeParams): string {
23+
// We only mask attributes if `maskAllText` is true
24+
if (!maskAllText) {
25+
return value;
26+
}
27+
28+
// unmaskTextSelector takes precendence
29+
if (privacyOptions.unmaskTextSelector && el.matches(privacyOptions.unmaskTextSelector)) {
30+
return value;
31+
}
32+
33+
if (
34+
maskAttributes.includes(key) ||
35+
// Need to mask `value` attribute for `<input>` if it's a button-like
36+
// type
37+
(key === 'value' && el.tagName === 'INPUT' && ['submit', 'button'].includes(el.getAttribute('type') || ''))
38+
) {
39+
return value.replace(/[\S]/g, '*');
40+
}
41+
42+
return value;
43+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { maskAttribute } from '../../../src/util/maskAttribute';
2+
3+
describe('maskAttribute', () => {
4+
const defaultEl = document.createElement('div');
5+
defaultEl.className = 'classy';
6+
const privacyOptions = {
7+
maskTextSelector: '',
8+
unmaskTextSelector: '.unmask',
9+
blockSelector: '',
10+
unblockSelector: '',
11+
ignoreSelector: '',
12+
};
13+
const defaultArgs = {
14+
el: defaultEl,
15+
key: 'title',
16+
maskAttributes: ['title'],
17+
maskAllText: true,
18+
privacyOptions,
19+
value: 'foo',
20+
};
21+
22+
const inputSubmit = document.createElement('input');
23+
const inputButton = document.createElement('input');
24+
[inputSubmit, inputButton].forEach(el => {
25+
el.type = 'submit';
26+
});
27+
28+
test.each([
29+
['masks if `maskAllText` is true', defaultArgs, '***'],
30+
[
31+
'does not mask if `maskAllText` is false, despite `maskTextSelector` ',
32+
{ ...defaultArgs, maskAllText: false, maskTextSelector: 'classy' },
33+
'foo',
34+
],
35+
['does not mask if `maskAllText` is false', { ...defaultArgs, maskAllText: false }, 'foo'],
36+
[
37+
'does not mask if `unmaskTextSelector` matches',
38+
{ ...defaultArgs, privacyOptions: { ...privacyOptions, unmaskTextSelector: '.classy' } },
39+
'foo',
40+
],
41+
[
42+
'masks `value` attribute on `<input>` with type "submit"',
43+
{ ...defaultArgs, el: inputSubmit, value: 'input value' },
44+
'***** *****',
45+
],
46+
[
47+
'masks `value` attribute on `<input>` with type "button"',
48+
{ ...defaultArgs, el: inputButton, value: 'input value' },
49+
'***** *****',
50+
],
51+
])('%s', (_: string, input, output) => {
52+
expect(maskAttribute(input)).toEqual(output);
53+
});
54+
});

0 commit comments

Comments
 (0)