Skip to content

Commit

Permalink
feat: Allow for masking of attributes via maskAttributesFn
Browse files Browse the repository at this point in the history
This currently adds a new API, `maskAttributesFn: (key: string, value: string, element: HTMLElement) => string`, that is used as a callback in `transformAttribute`. I prefer this API as it gives more flexibility for users (though it may need to pass the el node for most flexibility), but it is inconsistent with `maskTextFn` and `maskInputFn`.

other options:

* Rename this to something else (open to ideas)
* Change this to pass value, and dom element (similar to MaskInputFn) to customize masking instead of decision maker of when to mask and introduce a simpler declarative API for what attributes to mask
* ???
  • Loading branch information
billyvg committed Jul 18, 2023
1 parent c6600e7 commit 7481949
Show file tree
Hide file tree
Showing 14 changed files with 920 additions and 50 deletions.
6 changes: 6 additions & 0 deletions .changeset/twenty-tables-call.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'rrweb-snapshot': patch
'rrweb': patch
---

Add `maskAttributesFn` to be called when transforming an attribute. This is typically used to determine if an attribute should be masked or not.
1 change: 1 addition & 0 deletions guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ The parameter of `rrweb.record` accepts the following options.
| maskTextSelector | null | Use a string to configure which selector should be masked, refer to the [privacy](#privacy) chapter |
| maskAllInputs | false | mask all input content as \* |
| maskInputOptions | { password: true } | mask some kinds of input \*<br />refer to the [list](https://github.com/rrweb-io/rrweb/blob/588164aa12f1d94576f89ae0210b98f6e971c895/packages/rrweb-snapshot/src/types.ts#L77-L95) |
| maskAttributeFn | - | callback before transforming attribute. can be used to mask specific attributes |
| maskInputFn | - | customize mask input content recording logic |
| maskTextFn | - | customize mask text content recording logic |
| slimDOMOptions | {} | remove unnecessary parts of the DOM <br />refer to the [list](https://github.com/rrweb-io/rrweb/blob/588164aa12f1d94576f89ae0210b98f6e971c895/packages/rrweb-snapshot/src/types.ts#L97-L108) |
Expand Down
24 changes: 24 additions & 0 deletions packages/rrweb-snapshot/src/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
KeepIframeSrcFn,
ICanvas,
serializedElementNodeWithId,
MaskAttributeFn,
} from './types';
import {
Mirror,
Expand Down Expand Up @@ -229,6 +230,8 @@ export function transformAttribute(
tagName: Lowercase<string>,
name: Lowercase<string>,
value: string | null,
element: HTMLElement,
maskAttributeFn: MaskAttributeFn | undefined,
): string | null {
if (!value) {
return value;
Expand Down Expand Up @@ -257,6 +260,11 @@ export function transformAttribute(
return absoluteToDoc(doc, value);
}

// Custom attribute masking
if (typeof maskAttributeFn === 'function') {
return maskAttributeFn(name, value, element);
}

return value;
}

Expand Down Expand Up @@ -437,6 +445,7 @@ function serializeNode(
mirror: Mirror;
blockClass: string | RegExp;
blockSelector: string | null;
maskAttributeFn: MaskAttributeFn | undefined;
maskTextClass: string | RegExp;
maskTextSelector: string | null;
inlineStylesheet: boolean;
Expand All @@ -458,6 +467,7 @@ function serializeNode(
mirror,
blockClass,
blockSelector,
maskAttributeFn,
maskTextClass,
maskTextSelector,
inlineStylesheet,
Expand Down Expand Up @@ -500,6 +510,7 @@ function serializeNode(
blockClass,
blockSelector,
inlineStylesheet,
maskAttributeFn,
maskInputOptions,
maskInputFn,
dataURLOptions,
Expand Down Expand Up @@ -605,6 +616,7 @@ function serializeElementNode(
blockClass: string | RegExp;
blockSelector: string | null;
inlineStylesheet: boolean;
maskAttributeFn: MaskAttributeFn | undefined;
maskInputOptions: MaskInputOptions;
maskInputFn: MaskInputFn | undefined;
dataURLOptions?: DataURLOptions;
Expand All @@ -624,6 +636,7 @@ function serializeElementNode(
blockSelector,
inlineStylesheet,
maskInputOptions = {},
maskAttributeFn,
maskInputFn,
dataURLOptions = {},
inlineImages,
Expand All @@ -644,6 +657,8 @@ function serializeElementNode(
tagName,
toLowerCase(attr.name),
attr.value,
n,
maskAttributeFn,
);
}
}
Expand Down Expand Up @@ -938,6 +953,7 @@ export function serializeNodeWithId(
inlineStylesheet: boolean;
newlyAddedElement?: boolean;
maskInputOptions?: MaskInputOptions;
maskAttributeFn: MaskAttributeFn | undefined;
maskTextFn: MaskTextFn | undefined;
maskInputFn: MaskInputFn | undefined;
slimDOMOptions: SlimDOMOptions;
Expand Down Expand Up @@ -969,6 +985,7 @@ export function serializeNodeWithId(
skipChild = false,
inlineStylesheet = true,
maskInputOptions = {},
maskAttributeFn,
maskTextFn,
maskInputFn,
slimDOMOptions,
Expand All @@ -993,6 +1010,7 @@ export function serializeNodeWithId(
maskTextSelector,
inlineStylesheet,
maskInputOptions,
maskAttributeFn,
maskTextFn,
maskInputFn,
dataURLOptions,
Expand Down Expand Up @@ -1066,6 +1084,7 @@ export function serializeNodeWithId(
skipChild,
inlineStylesheet,
maskInputOptions,
maskAttributeFn,
maskTextFn,
maskInputFn,
slimDOMOptions,
Expand Down Expand Up @@ -1126,6 +1145,7 @@ export function serializeNodeWithId(
skipChild: false,
inlineStylesheet,
maskInputOptions,
maskAttributeFn,
maskTextFn,
maskInputFn,
slimDOMOptions,
Expand Down Expand Up @@ -1173,6 +1193,7 @@ export function serializeNodeWithId(
skipChild: false,
inlineStylesheet,
maskInputOptions,
maskAttributeFn,
maskTextFn,
maskInputFn,
slimDOMOptions,
Expand Down Expand Up @@ -1213,6 +1234,7 @@ function snapshot(
maskTextSelector?: string | null;
inlineStylesheet?: boolean;
maskAllInputs?: boolean | MaskInputOptions;
maskAttributeFn?: MaskAttributeFn;
maskTextFn?: MaskTextFn;
maskInputFn?: MaskTextFn;
slimDOM?: 'all' | boolean | SlimDOMOptions;
Expand Down Expand Up @@ -1244,6 +1266,7 @@ function snapshot(
inlineImages = false,
recordCanvas = false,
maskAllInputs = false,
maskAttributeFn,
maskTextFn,
maskInputFn,
slimDOM = false,
Expand Down Expand Up @@ -1309,6 +1332,7 @@ function snapshot(
skipChild: false,
inlineStylesheet,
maskInputOptions,
maskAttributeFn,
maskTextFn,
maskInputFn,
slimDOMOptions,
Expand Down
5 changes: 5 additions & 0 deletions packages/rrweb-snapshot/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ export type DataURLOptions = Partial<{

export type MaskTextFn = (text: string) => string;
export type MaskInputFn = (text: string, element: HTMLElement) => string;
export type MaskAttributeFn = (
attributeName: string,
attributeValue: string,
element: HTMLElement,
) => string;

export type KeepIframeSrcFn = (src: string) => boolean;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,12 @@ exports[`integration tests [html file]: picture-in-frame.html 1`] = `
</body></html>"
`;
exports[`integration tests [html file]: picture-with-inline-onload.html 1`] = `
"<html xmlns=\\"http://www.w3.org/1999/xhtml\\"><head></head><body>
<img src=\\"http://localhost:3030/images/robot.png\\" alt=\\"This is a robot\\" style=\\"opacity: 1;\\" _onload=\\"this.style.opacity=1\\" />
</body></html>"
`;

exports[`integration tests [html file]: preload.html 1`] = `
"<!DOCTYPE html><html lang=\\"en\\"><head>
<meta charset=\\"UTF-8\\" />
Expand Down
47 changes: 47 additions & 0 deletions packages/rrweb-snapshot/test/snapshot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { JSDOM } from 'jsdom';
import {
absoluteToStylesheet,
serializeNodeWithId,
transformAttribute,
_isBlockedElement,
} from '../src/snapshot';
import { serializedNodeWithId } from '../src/types';
Expand Down Expand Up @@ -110,6 +111,50 @@ describe('absolute url to stylesheet', () => {
});
});

describe('transformAttribute()', () => {
it('handles empty attribute value', () => {
expect(
transformAttribute(
document,
'a',
'data-loading',
null,
document.createElement('span'),
undefined,
),
).toBe(null);
expect(
transformAttribute(
document,
'a',
'data-loading',
'',
document.createElement('span'),
undefined,
),
).toBe('');
});

it('handles custom masking function', () => {
const maskAttributeFn = jest
.fn()
.mockImplementation((_key, value): string => {
return value.split('').reverse().join('');
}) as any;
expect(
transformAttribute(
document,
'a',
'data-loading',
'foo',
document.createElement('span'),
maskAttributeFn,
),
).toBe('oof');
expect(maskAttributeFn).toHaveBeenCalledTimes(1);
});
});

describe('isBlockedElement()', () => {
const subject = (html: string, opt: any = {}) =>
_isBlockedElement(render(html), 'rr-block', opt.blockSelector);
Expand Down Expand Up @@ -147,6 +192,7 @@ describe('style elements', () => {
maskTextSelector: null,
skipChild: false,
inlineStylesheet: true,
maskAttributeFn: undefined,
maskTextFn: undefined,
maskInputFn: undefined,
slimDOMOptions: {},
Expand Down Expand Up @@ -192,6 +238,7 @@ describe('scrollTop/scrollLeft', () => {
maskTextSelector: null,
skipChild: false,
inlineStylesheet: true,
maskAttributeFn: undefined,
maskTextFn: undefined,
maskInputFn: undefined,
slimDOMOptions: {},
Expand Down
4 changes: 4 additions & 0 deletions packages/rrweb/src/record/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ function record<T = eventWithTime>(
maskAllInputs,
maskInputOptions: _maskInputOptions,
slimDOMOptions: _slimDOMOptions,
maskAttributeFn,
maskInputFn,
maskTextFn,
hooks,
Expand Down Expand Up @@ -328,6 +329,7 @@ function record<T = eventWithTime>(
inlineStylesheet,
maskInputOptions,
dataURLOptions,
maskAttributeFn,
maskTextFn,
maskInputFn,
recordCanvas,
Expand Down Expand Up @@ -370,6 +372,7 @@ function record<T = eventWithTime>(
maskTextSelector,
inlineStylesheet,
maskAllInputs: maskInputOptions,
maskAttributeFn,
maskTextFn,
slimDOM: slimDOMOptions,
dataURLOptions,
Expand Down Expand Up @@ -532,6 +535,7 @@ function record<T = eventWithTime>(
userTriggeredOnInput,
collectFonts,
doc,
maskAttributeFn,
maskInputFn,
maskTextFn,
keepIframeSrcFn,
Expand Down
5 changes: 5 additions & 0 deletions packages/rrweb/src/record/mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ export default class MutationBuffer {
private maskTextSelector: observerParam['maskTextSelector'];
private inlineStylesheet: observerParam['inlineStylesheet'];
private maskInputOptions: observerParam['maskInputOptions'];
private maskAttributeFn: observerParam['maskAttributeFn'];
private maskTextFn: observerParam['maskTextFn'];
private maskInputFn: observerParam['maskInputFn'];
private keepIframeSrcFn: observerParam['keepIframeSrcFn'];
Expand All @@ -200,6 +201,7 @@ export default class MutationBuffer {
'maskTextSelector',
'inlineStylesheet',
'maskInputOptions',
'maskAttributeFn',
'maskTextFn',
'maskInputFn',
'keepIframeSrcFn',
Expand Down Expand Up @@ -303,6 +305,7 @@ export default class MutationBuffer {
newlyAddedElement: true,
inlineStylesheet: this.inlineStylesheet,
maskInputOptions: this.maskInputOptions,
maskAttributeFn: this.maskAttributeFn,
maskTextFn: this.maskTextFn,
maskInputFn: this.maskInputFn,
slimDOMOptions: this.slimDOMOptions,
Expand Down Expand Up @@ -601,6 +604,8 @@ export default class MutationBuffer {
toLowerCase(target.tagName),
toLowerCase(attributeName),
value,
target,
this.maskAttributeFn,
);
}
break;
Expand Down
4 changes: 4 additions & 0 deletions packages/rrweb/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
MaskInputFn,
MaskTextFn,
DataURLOptions,
MaskAttributeFn,
} from 'rrweb-snapshot';
import type { PackFn, UnpackFn } from './packer/base';
import type { IframeManager } from './record/iframe-manager';
Expand Down Expand Up @@ -50,6 +51,7 @@ export type recordOptions<T> = {
maskTextSelector?: string;
maskAllInputs?: boolean;
maskInputOptions?: MaskInputOptions;
maskAttributeFn?: MaskAttributeFn;
maskInputFn?: MaskInputFn;
maskTextFn?: MaskTextFn;
slimDOMOptions?: SlimDOMOptions | 'all' | true;
Expand Down Expand Up @@ -87,6 +89,7 @@ export type observerParam = {
maskTextClass: maskTextClass;
maskTextSelector: string | null;
maskInputOptions: MaskInputOptions;
maskAttributeFn?: MaskAttributeFn;
maskInputFn?: MaskInputFn;
maskTextFn?: MaskTextFn;
keepIframeSrcFn: KeepIframeSrcFn;
Expand Down Expand Up @@ -130,6 +133,7 @@ export type MutationBufferParam = Pick<
| 'maskTextSelector'
| 'inlineStylesheet'
| 'maskInputOptions'
| 'maskAttributeFn'
| 'maskTextFn'
| 'maskInputFn'
| 'keepIframeSrcFn'
Expand Down
Loading

0 comments on commit 7481949

Please sign in to comment.