Skip to content

Commit 0c33a51

Browse files
committed
review fixes (maskAttribue -> fn and try/catch
1 parent 2392447 commit 0c33a51

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
@@ -13,6 +13,7 @@ import { ReplayContainer } from './replay';
1313
import type { RecordingOptions, ReplayConfiguration, ReplayPluginOptions, SendBufferedReplayOptions } from './types';
1414
import { getPrivacyOptions } from './util/getPrivacyOptions';
1515
import { isBrowser } from './util/isBrowser';
16+
import { maskAttribute } from './util/maskAttribute';
1617

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

150138
...privacyOptions,
151139

@@ -158,8 +146,13 @@ export class Replay implements Integration {
158146
// origin for playback
159147
collectFonts: true,
160148
errorHandler: (err: Error) => {
161-
// @ts-ignore Set this so that replay SDK can ignore errors originating from rrweb
162-
err.__rrweb__ = true;
149+
try {
150+
// @ts-ignore Set this so that replay SDK can ignore errors originating from rrweb
151+
err.__rrweb__ = true;
152+
} catch {
153+
// avoid any potential hazards here
154+
}
155+
// return true to suppress throwing the error inside of rrweb
163156
return true;
164157
},
165158
};
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)