diff --git a/guide.md b/guide.md
index 87fc4700..4cd3403c 100644
--- a/guide.md
+++ b/guide.md
@@ -142,10 +142,13 @@ The parameter of `rrweb.record` accepts the following options.
| checkoutEveryNms | - | take a full snapshot after every N ms
refer to the [checkout](#checkout) chapter |
| blockClass | 'rr-block' | Use a string or RegExp to configure which elements should be blocked, refer to the [privacy](#privacy) chapter |
| ignoreClass | 'rr-ignore' | Use a string or RegExp to configure which elements should be ignored, refer to the [privacy](#privacy) chapter |
-| blockSelector | null | Use a string or RegExp to configure which selector should be blocked, refer to the [privacy](#privacy) chapter |
+| maskTextClass | 'rr-mask' | Use a string or RegExp to configure which elements should be masked, refer to the [privacy](#privacy) chapter |
+| blockSelector | null | Use a string to configure which selector should be blocked, refer to the [privacy](#privacy) chapter |
+| 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 | {} | mask some kinds of input \*
refer to the [list](https://github.com/rrweb-io/rrweb-snapshot/blob/6728d12b3cddd96951c86d948578f99ada5749ff/src/types.ts#L72) |
-| maskInputFn | - | customize mask input content recording logic |
+| maskInputOptions | {} | mask some kinds of input \*
refer to the [list](https://github.com/rrweb-io/rrweb-snapshot/blob/6728d12b3cddd96951c86d948578f99ada5749ff/src/types.ts#L72) |
+| maskInputFn | - | customize mask input content recording logic |
+| maskTextFn | - | customize mask text content recording logic |
| slimDOMOptions | {} | remove unnecessary parts of the DOM
refer to the [list](https://github.com/rrweb-io/rrweb-snapshot/blob/6728d12b3cddd96951c86d948578f99ada5749ff/src/types.ts#L91) |
| inlineStylesheet | true | whether to inline the stylesheet in the events |
| hooks | {} | hooks for events
refer to the [list](https://github.com/rrweb-io/rrweb/blob/9488deb6d54a5f04350c063d942da5e96ab74075/src/types.ts#L207) |
@@ -163,6 +166,7 @@ You may find some contents on the webpage which are not willing to be recorded,
- An element with the class name `.rr-ignore` will not record its input events.
- `input[type="password"]` will be ignored as default.
- Mask options to mask the content in input elements.
+- A text of elements with the class name `.rr-mask` and its children will be masked.
#### Checkout
diff --git a/package.json b/package.json
index 5af64ac8..4c3d86fe 100644
--- a/package.json
+++ b/package.json
@@ -66,6 +66,6 @@
"@xstate/fsm": "^1.4.0",
"fflate": "^0.4.4",
"mitt": "^1.1.3",
- "rrweb-snapshot": "^1.1.1"
+ "rrweb-snapshot": "^1.1.2"
}
}
diff --git a/src/record/index.ts b/src/record/index.ts
index eb8638f6..3a7668dc 100644
--- a/src/record/index.ts
+++ b/src/record/index.ts
@@ -43,11 +43,14 @@ function record(
blockClass = 'rr-block',
blockSelector = null,
ignoreClass = 'rr-ignore',
+ maskTextClass = 'rr-mask',
+ maskTextSelector = null,
inlineStylesheet = true,
maskAllInputs,
maskInputOptions: _maskInputOptions,
slimDOMOptions: _slimDOMOptions,
maskInputFn,
+ maskTextFn,
hooks,
packFn,
sampling = {},
@@ -203,8 +206,11 @@ function record(
bypassOptions: {
blockClass,
blockSelector,
+ maskTextClass,
+ maskTextSelector,
inlineStylesheet,
maskInputOptions,
+ maskTextFn,
recordCanvas,
slimDOMOptions,
iframeManager,
@@ -228,8 +234,11 @@ function record(
const [node, idNodeMap] = snapshot(document, {
blockClass,
blockSelector,
+ maskTextClass,
+ maskTextSelector,
inlineStylesheet,
maskAllInputs: maskInputOptions,
+ maskTextFn,
slimDOM: slimDOMOptions,
recordCanvas,
onSerialize: (n) => {
@@ -396,6 +405,8 @@ function record(
),
blockClass,
ignoreClass,
+ maskTextClass,
+ maskTextSelector,
maskInputOptions,
inlineStylesheet,
sampling,
@@ -403,6 +414,7 @@ function record(
collectFonts,
doc,
maskInputFn,
+ maskTextFn,
logOptions,
blockSelector,
slimDOMOptions,
diff --git a/src/record/mutation.ts b/src/record/mutation.ts
index 3717afc8..4b65e796 100644
--- a/src/record/mutation.ts
+++ b/src/record/mutation.ts
@@ -6,15 +6,18 @@ import {
SlimDOMOptions,
IGNORED_NODE,
isShadowRoot,
+ needMaskingText,
} from 'rrweb-snapshot';
import {
mutationRecord,
blockClass,
+ maskTextClass,
mutationCallBack,
textCursor,
attributeCursor,
removedNodeMutation,
addedNodeMutation,
+ MaskTextFn,
} from '../types';
import {
mirror,
@@ -159,8 +162,11 @@ export default class MutationBuffer {
private emissionCallback: mutationCallBack;
private blockClass: blockClass;
private blockSelector: string | null;
+ private maskTextClass: maskTextClass;
+ private maskTextSelector: string | null;
private inlineStylesheet: boolean;
private maskInputOptions: MaskInputOptions;
+ private maskTextFn: MaskTextFn | undefined;
private recordCanvas: boolean;
private slimDOMOptions: SlimDOMOptions;
private doc: Document;
@@ -172,8 +178,11 @@ export default class MutationBuffer {
cb: mutationCallBack,
blockClass: blockClass,
blockSelector: string | null,
+ maskTextClass: maskTextClass,
+ maskTextSelector: string | null,
inlineStylesheet: boolean,
maskInputOptions: MaskInputOptions,
+ maskTextFn: MaskTextFn | undefined,
recordCanvas: boolean,
slimDOMOptions: SlimDOMOptions,
doc: Document,
@@ -182,8 +191,11 @@ export default class MutationBuffer {
) {
this.blockClass = blockClass;
this.blockSelector = blockSelector;
+ this.maskTextClass = maskTextClass;
+ this.maskTextSelector = maskTextSelector;
this.inlineStylesheet = inlineStylesheet;
this.maskInputOptions = maskInputOptions;
+ this.maskTextFn = maskTextFn;
this.recordCanvas = recordCanvas;
this.slimDOMOptions = slimDOMOptions;
this.emissionCallback = cb;
@@ -266,9 +278,12 @@ export default class MutationBuffer {
map: mirror.map,
blockClass: this.blockClass,
blockSelector: this.blockSelector,
+ maskTextClass: this.maskTextClass,
+ maskTextSelector: this.maskTextSelector,
skipChild: true,
inlineStylesheet: this.inlineStylesheet,
maskInputOptions: this.maskInputOptions,
+ maskTextFn: this.maskTextFn,
slimDOMOptions: this.slimDOMOptions,
recordCanvas: this.recordCanvas,
onSerialize: (currentN) => {
@@ -409,7 +424,16 @@ export default class MutationBuffer {
const value = m.target.textContent;
if (!isBlocked(m.target, this.blockClass) && value !== m.oldValue) {
this.texts.push({
- value,
+ value:
+ needMaskingText(
+ m.target,
+ this.maskTextClass,
+ this.maskTextSelector,
+ ) && value
+ ? this.maskTextFn
+ ? this.maskTextFn(value)
+ : value.replace(/[\S]/g, '*')
+ : value,
node: m.target,
});
}
diff --git a/src/record/observer.ts b/src/record/observer.ts
index c4fa81d1..1bc6babd 100644
--- a/src/record/observer.ts
+++ b/src/record/observer.ts
@@ -26,6 +26,7 @@ import {
inputCallback,
hookResetter,
blockClass,
+ maskTextClass,
IncrementalSource,
hooksParam,
Arguments,
@@ -36,6 +37,7 @@ import {
fontCallback,
fontParam,
MaskInputFn,
+ MaskTextFn,
logCallback,
LogRecordOptions,
Logger,
@@ -62,8 +64,11 @@ export function initMutationObserver(
doc: Document,
blockClass: blockClass,
blockSelector: string | null,
+ maskTextClass: maskTextClass,
+ maskTextSelector: string | null,
inlineStylesheet: boolean,
maskInputOptions: MaskInputOptions,
+ maskTextFn: MaskTextFn | undefined,
recordCanvas: boolean,
slimDOMOptions: SlimDOMOptions,
iframeManager: IframeManager,
@@ -77,8 +82,11 @@ export function initMutationObserver(
cb,
blockClass,
blockSelector,
+ maskTextClass,
+ maskTextSelector,
inlineStylesheet,
maskInputOptions,
+ maskTextFn,
recordCanvas,
slimDOMOptions,
doc,
@@ -777,8 +785,11 @@ export function initObservers(
o.doc,
o.blockClass,
o.blockSelector,
+ o.maskTextClass,
+ o.maskTextSelector,
o.inlineStylesheet,
o.maskInputOptions,
+ o.maskTextFn,
o.recordCanvas,
o.slimDOMOptions,
o.iframeManager,
diff --git a/src/record/shadow-dom-manager.ts b/src/record/shadow-dom-manager.ts
index ea57a111..6681e11b 100644
--- a/src/record/shadow-dom-manager.ts
+++ b/src/record/shadow-dom-manager.ts
@@ -1,4 +1,4 @@
-import { mutationCallBack, blockClass } from '../types';
+import { mutationCallBack, blockClass, maskTextClass, MaskTextFn } from '../types';
import { MaskInputOptions, SlimDOMOptions } from 'rrweb-snapshot';
import { IframeManager } from './iframe-manager';
import { initMutationObserver } from './observer';
@@ -6,8 +6,11 @@ import { initMutationObserver } from './observer';
type BypassOptions = {
blockClass: blockClass;
blockSelector: string | null;
+ maskTextClass: maskTextClass;
+ maskTextSelector: string | null;
inlineStylesheet: boolean;
maskInputOptions: MaskInputOptions;
+ maskTextFn: MaskTextFn | undefined;
recordCanvas: boolean;
slimDOMOptions: SlimDOMOptions;
iframeManager: IframeManager;
@@ -31,8 +34,11 @@ export class ShadowDomManager {
doc,
this.bypassOptions.blockClass,
this.bypassOptions.blockSelector,
+ this.bypassOptions.maskTextClass,
+ this.bypassOptions.maskTextSelector,
this.bypassOptions.inlineStylesheet,
this.bypassOptions.maskInputOptions,
+ this.bypassOptions.maskTextFn,
this.bypassOptions.recordCanvas,
this.bypassOptions.slimDOMOptions,
this.bypassOptions.iframeManager,
diff --git a/src/types.ts b/src/types.ts
index ed283b39..3be785f0 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -163,6 +163,8 @@ export type eventWithTime = event & {
export type blockClass = string | RegExp;
+export type maskTextClass = string | RegExp;
+
export type SamplingStrategy = Partial<{
/**
* false means not to record mouse/touch move events
@@ -196,9 +198,12 @@ export type recordOptions = {
blockClass?: blockClass;
blockSelector?: string;
ignoreClass?: string;
+ maskTextClass?: maskTextClass;
+ maskTextSelector?: string;
maskAllInputs?: boolean;
maskInputOptions?: MaskInputOptions;
maskInputFn?: MaskInputFn;
+ maskTextFn?: MaskTextFn;
slimDOMOptions?: SlimDOMOptions | 'all' | true;
inlineStylesheet?: boolean;
hooks?: hooksParam;
@@ -222,8 +227,11 @@ export type observerParam = {
blockClass: blockClass;
blockSelector: string | null;
ignoreClass: string;
+ maskTextClass: maskTextClass;
+ maskTextSelector: string | null;
maskInputOptions: MaskInputOptions;
maskInputFn?: MaskInputFn;
+ maskTextFn?: MaskTextFn;
inlineStylesheet: boolean;
styleSheetRuleCb: styleSheetRuleCallback;
canvasMutationCb: canvasMutationCallback;
@@ -583,6 +591,8 @@ export enum ReplayerEvents {
export type MaskInputFn = (text: string) => string;
+export type MaskTextFn = (text: string) => string;
+
// store the state that would be changed during the process(unmount from dom and mount again)
export type ElementState = {
// [scrollLeft,scrollTop]
diff --git a/test/__snapshots__/integration.test.ts.snap b/test/__snapshots__/integration.test.ts.snap
index 35bd2406..ac6f2cd3 100644
--- a/test/__snapshots__/integration.test.ts.snap
+++ b/test/__snapshots__/integration.test.ts.snap
@@ -4111,6 +4111,746 @@ exports[`mask 1`] = `
]"
`;
+exports[`mask-character-data 1`] = `
+"[
+ {
+ \\"type\\": 0,
+ \\"data\\": {}
+ },
+ {
+ \\"type\\": 1,
+ \\"data\\": {}
+ },
+ {
+ \\"type\\": 4,
+ \\"data\\": {
+ \\"href\\": \\"about:blank\\",
+ \\"width\\": 1920,
+ \\"height\\": 1080
+ }
+ },
+ {
+ \\"type\\": 2,
+ \\"data\\": {
+ \\"node\\": {
+ \\"type\\": 0,
+ \\"childNodes\\": [
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"html\\",
+ \\"attributes\\": {},
+ \\"childNodes\\": [
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"head\\",
+ \\"attributes\\": {},
+ \\"childNodes\\": [],
+ \\"id\\": 3
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"body\\",
+ \\"attributes\\": {},
+ \\"childNodes\\": [
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 5
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"p\\",
+ \\"attributes\\": {},
+ \\"childNodes\\": [
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"mutation observer\\",
+ \\"id\\": 7
+ }
+ ],
+ \\"id\\": 6
+ },
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 8
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"ul\\",
+ \\"attributes\\": {},
+ \\"childNodes\\": [
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 10
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"li\\",
+ \\"attributes\\": {},
+ \\"childNodes\\": [],
+ \\"id\\": 11
+ },
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 12
+ }
+ ],
+ \\"id\\": 9
+ },
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n\\\\n \\",
+ \\"id\\": 13
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"script\\",
+ \\"attributes\\": {},
+ \\"childNodes\\": [
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\",
+ \\"id\\": 15
+ }
+ ],
+ \\"id\\": 14
+ },
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\\\n \\",
+ \\"id\\": 16
+ }
+ ],
+ \\"id\\": 4
+ }
+ ],
+ \\"id\\": 2
+ }
+ ],
+ \\"id\\": 1
+ },
+ \\"initialOffset\\": {
+ \\"left\\": 0,
+ \\"top\\": 0
+ }
+ }
+ },
+ {
+ \\"type\\": 3,
+ \\"data\\": {
+ \\"source\\": 0,
+ \\"texts\\": [],
+ \\"attributes\\": [
+ {
+ \\"id\\": 6,
+ \\"attributes\\": {
+ \\"class\\": \\"rr-mask\\"
+ }
+ }
+ ],
+ \\"removes\\": [
+ {
+ \\"parentId\\": 6,
+ \\"id\\": 7
+ }
+ ],
+ \\"adds\\": [
+ {
+ \\"parentId\\": 9,
+ \\"nextId\\": null,
+ \\"node\\": {
+ \\"type\\": 2,
+ \\"tagName\\": \\"li\\",
+ \\"attributes\\": {
+ \\"class\\": \\"rr-mask\\"
+ },
+ \\"childNodes\\": [],
+ \\"id\\": 17
+ }
+ },
+ {
+ \\"parentId\\": 17,
+ \\"nextId\\": null,
+ \\"node\\": {
+ \\"type\\": 3,
+ \\"textContent\\": \\"*** **** ****\\",
+ \\"id\\": 18
+ }
+ },
+ {
+ \\"parentId\\": 6,
+ \\"nextId\\": null,
+ \\"node\\": {
+ \\"type\\": 3,
+ \\"textContent\\": \\"*******\\",
+ \\"id\\": 19
+ }
+ }
+ ]
+ }
+ }
+]"
+`;
+
+exports[`mask-text 1`] = `
+"[
+ {
+ \\"type\\": 0,
+ \\"data\\": {}
+ },
+ {
+ \\"type\\": 1,
+ \\"data\\": {}
+ },
+ {
+ \\"type\\": 4,
+ \\"data\\": {
+ \\"href\\": \\"about:blank\\",
+ \\"width\\": 1920,
+ \\"height\\": 1080
+ }
+ },
+ {
+ \\"type\\": 2,
+ \\"data\\": {
+ \\"node\\": {
+ \\"type\\": 0,
+ \\"childNodes\\": [
+ {
+ \\"type\\": 1,
+ \\"name\\": \\"html\\",
+ \\"publicId\\": \\"\\",
+ \\"systemId\\": \\"\\",
+ \\"id\\": 2
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"html\\",
+ \\"attributes\\": {
+ \\"lang\\": \\"en\\"
+ },
+ \\"childNodes\\": [
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"head\\",
+ \\"attributes\\": {},
+ \\"childNodes\\": [
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 5
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"meta\\",
+ \\"attributes\\": {
+ \\"charset\\": \\"UTF-8\\"
+ },
+ \\"childNodes\\": [],
+ \\"id\\": 6
+ },
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 7
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"meta\\",
+ \\"attributes\\": {
+ \\"name\\": \\"viewport\\",
+ \\"content\\": \\"width=device-width, initial-scale=1.0\\"
+ },
+ \\"childNodes\\": [],
+ \\"id\\": 8
+ },
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 9
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"meta\\",
+ \\"attributes\\": {
+ \\"http-equiv\\": \\"X-UA-Compatible\\",
+ \\"content\\": \\"ie=edge\\"
+ },
+ \\"childNodes\\": [],
+ \\"id\\": 10
+ },
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 11
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"title\\",
+ \\"attributes\\": {},
+ \\"childNodes\\": [
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"Mask text\\",
+ \\"id\\": 13
+ }
+ ],
+ \\"id\\": 12
+ },
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 14
+ }
+ ],
+ \\"id\\": 4
+ },
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 15
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"body\\",
+ \\"attributes\\": {},
+ \\"childNodes\\": [
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 17
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"p\\",
+ \\"attributes\\": {
+ \\"class\\": \\"rr-mask\\"
+ },
+ \\"childNodes\\": [
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"*****\\",
+ \\"id\\": 19
+ }
+ ],
+ \\"id\\": 18
+ },
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 20
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"div\\",
+ \\"attributes\\": {
+ \\"class\\": \\"rr-mask\\"
+ },
+ \\"childNodes\\": [
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 22
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"span\\",
+ \\"attributes\\": {},
+ \\"childNodes\\": [
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"*****\\",
+ \\"id\\": 24
+ }
+ ],
+ \\"id\\": 23
+ },
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 25
+ }
+ ],
+ \\"id\\": 21
+ },
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 26
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"div\\",
+ \\"attributes\\": {
+ \\"data-masking\\": \\"true\\"
+ },
+ \\"childNodes\\": [
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 28
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"div\\",
+ \\"attributes\\": {},
+ \\"childNodes\\": [
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 30
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"div\\",
+ \\"attributes\\": {},
+ \\"childNodes\\": [
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"*****\\",
+ \\"id\\": 32
+ }
+ ],
+ \\"id\\": 31
+ },
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 33
+ }
+ ],
+ \\"id\\": 29
+ },
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 34
+ }
+ ],
+ \\"id\\": 27
+ },
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\\\n \\",
+ \\"id\\": 35
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"script\\",
+ \\"attributes\\": {},
+ \\"childNodes\\": [
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\",
+ \\"id\\": 37
+ }
+ ],
+ \\"id\\": 36
+ },
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\",
+ \\"id\\": 38
+ }
+ ],
+ \\"id\\": 16
+ }
+ ],
+ \\"id\\": 3
+ }
+ ],
+ \\"id\\": 1
+ },
+ \\"initialOffset\\": {
+ \\"left\\": 0,
+ \\"top\\": 0
+ }
+ }
+ }
+]"
+`;
+
+exports[`mask-text-fn 1`] = `
+"[
+ {
+ \\"type\\": 0,
+ \\"data\\": {}
+ },
+ {
+ \\"type\\": 1,
+ \\"data\\": {}
+ },
+ {
+ \\"type\\": 4,
+ \\"data\\": {
+ \\"href\\": \\"about:blank\\",
+ \\"width\\": 1920,
+ \\"height\\": 1080
+ }
+ },
+ {
+ \\"type\\": 2,
+ \\"data\\": {
+ \\"node\\": {
+ \\"type\\": 0,
+ \\"childNodes\\": [
+ {
+ \\"type\\": 1,
+ \\"name\\": \\"html\\",
+ \\"publicId\\": \\"\\",
+ \\"systemId\\": \\"\\",
+ \\"id\\": 2
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"html\\",
+ \\"attributes\\": {
+ \\"lang\\": \\"en\\"
+ },
+ \\"childNodes\\": [
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"head\\",
+ \\"attributes\\": {},
+ \\"childNodes\\": [
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 5
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"meta\\",
+ \\"attributes\\": {
+ \\"charset\\": \\"UTF-8\\"
+ },
+ \\"childNodes\\": [],
+ \\"id\\": 6
+ },
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 7
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"meta\\",
+ \\"attributes\\": {
+ \\"name\\": \\"viewport\\",
+ \\"content\\": \\"width=device-width, initial-scale=1.0\\"
+ },
+ \\"childNodes\\": [],
+ \\"id\\": 8
+ },
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 9
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"meta\\",
+ \\"attributes\\": {
+ \\"http-equiv\\": \\"X-UA-Compatible\\",
+ \\"content\\": \\"ie=edge\\"
+ },
+ \\"childNodes\\": [],
+ \\"id\\": 10
+ },
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 11
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"title\\",
+ \\"attributes\\": {},
+ \\"childNodes\\": [
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"Mask text\\",
+ \\"id\\": 13
+ }
+ ],
+ \\"id\\": 12
+ },
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 14
+ }
+ ],
+ \\"id\\": 4
+ },
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 15
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"body\\",
+ \\"attributes\\": {},
+ \\"childNodes\\": [
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 17
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"p\\",
+ \\"attributes\\": {
+ \\"class\\": \\"rr-mask\\"
+ },
+ \\"childNodes\\": [
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"****1\\",
+ \\"id\\": 19
+ }
+ ],
+ \\"id\\": 18
+ },
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 20
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"div\\",
+ \\"attributes\\": {
+ \\"class\\": \\"rr-mask\\"
+ },
+ \\"childNodes\\": [
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 22
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"span\\",
+ \\"attributes\\": {},
+ \\"childNodes\\": [
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"****2\\",
+ \\"id\\": 24
+ }
+ ],
+ \\"id\\": 23
+ },
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 25
+ }
+ ],
+ \\"id\\": 21
+ },
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 26
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"div\\",
+ \\"attributes\\": {
+ \\"data-masking\\": \\"true\\"
+ },
+ \\"childNodes\\": [
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 28
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"div\\",
+ \\"attributes\\": {},
+ \\"childNodes\\": [
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 30
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"div\\",
+ \\"attributes\\": {},
+ \\"childNodes\\": [
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"****3\\",
+ \\"id\\": 32
+ }
+ ],
+ \\"id\\": 31
+ },
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 33
+ }
+ ],
+ \\"id\\": 29
+ },
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\",
+ \\"id\\": 34
+ }
+ ],
+ \\"id\\": 27
+ },
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\\\n \\",
+ \\"id\\": 35
+ },
+ {
+ \\"type\\": 2,
+ \\"tagName\\": \\"script\\",
+ \\"attributes\\": {},
+ \\"childNodes\\": [
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\",
+ \\"id\\": 37
+ }
+ ],
+ \\"id\\": 36
+ },
+ {
+ \\"type\\": 3,
+ \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\",
+ \\"id\\": 38
+ }
+ ],
+ \\"id\\": 16
+ }
+ ],
+ \\"id\\": 3
+ }
+ ],
+ \\"id\\": 1
+ },
+ \\"initialOffset\\": {
+ \\"left\\": 0,
+ \\"top\\": 0
+ }
+ }
+ }
+]"
+`;
+
exports[`maskInputOptions 1`] = `
"[
{
diff --git a/test/html/mask-text.html b/test/html/mask-text.html
new file mode 100644
index 00000000..2abaaaa5
--- /dev/null
+++ b/test/html/mask-text.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ Mask text
+
+
+ mask1
+
+ mask2
+
+
+
+
diff --git a/test/integration.test.ts b/test/integration.test.ts
index c9d1f0e1..207b8782 100644
--- a/test/integration.test.ts
+++ b/test/integration.test.ts
@@ -72,8 +72,10 @@ describe('record integration tests', function (this: ISuite) {
emit: event => {
window.snapshots.push(event);
},
+ maskTextSelector: ${JSON.stringify(options.maskTextSelector)},
maskAllInputs: ${options.maskAllInputs},
maskInputOptions: ${JSON.stringify(options.maskAllInputs)},
+ maskTextFn: ${options.maskTextFn},
recordCanvas: ${options.recordCanvas},
recordLog: ${options.recordLog},
});
@@ -456,4 +458,52 @@ describe('record integration tests', function (this: ISuite) {
const snapshots = await page.evaluate('window.snapshots');
assertSnapshot(snapshots, __filename, 'shadow-dom');
});
+
+ it('should mask texts', async () => {
+ const page: puppeteer.Page = await this.browser.newPage();
+ await page.goto('about:blank');
+ await page.setContent(
+ getHtml.call(this, 'mask-text.html', {
+ maskTextSelector: '[data-masking="true"]',
+ }),
+ );
+
+ const snapshots = await page.evaluate('window.snapshots');
+ assertSnapshot(snapshots, __filename, 'mask-text');
+ });
+
+ it('should mask texts using maskTextFn', async () => {
+ const page: puppeteer.Page = await this.browser.newPage();
+ await page.goto('about:blank');
+ await page.setContent(
+ getHtml.call(this, 'mask-text.html', {
+ maskTextSelector: '[data-masking="true"]',
+ maskTextFn: (t: string) => t.replace(/[a-z]/g, '*'),
+ }),
+ );
+
+ const snapshots = await page.evaluate('window.snapshots');
+ assertSnapshot(snapshots, __filename, 'mask-text-fn');
+ });
+
+ it('can mask character data mutations', async () => {
+ const page: puppeteer.Page = await this.browser.newPage();
+ await page.goto('about:blank');
+ await page.setContent(getHtml.call(this, 'mutation-observer.html'));
+
+ await page.evaluate(() => {
+ const li = document.createElement('li');
+ const ul = document.querySelector('ul') as HTMLUListElement;
+ const p = document.querySelector('p') as HTMLParagraphElement;
+ [li, p].forEach((element) => {
+ element.className = 'rr-mask';
+ });
+ ul.appendChild(li);
+ li.innerText = 'new list item';
+ p.innerText = 'mutated';
+ });
+
+ const snapshots = await page.evaluate('window.snapshots');
+ assertSnapshot(snapshots, __filename, 'mask-character-data');
+ });
});
diff --git a/typings/types.d.ts b/typings/types.d.ts
index 442fd5b6..1d56b52a 100644
--- a/typings/types.d.ts
+++ b/typings/types.d.ts
@@ -3,6 +3,7 @@ import { serializedNodeWithId, idNodeMap, INode, MaskInputOptions, SlimDOMOption
import { PackFn, UnpackFn } from './packer/base';
import { FontFaceDescriptors } from 'css-font-loading-module';
import { IframeManager } from './record/iframe-manager';
+import { MaskTextFn } from '../src/types';
export declare enum EventType {
DomContentLoaded = 0,
Load = 1,
@@ -109,6 +110,7 @@ export declare type eventWithTime = event & {
delay?: number;
};
export declare type blockClass = string | RegExp;
+export declare type maskTextClass = string | RegExp;
export declare type SamplingStrategy = Partial<{
mousemove: boolean | number;
mousemoveCallback: number;
@@ -123,9 +125,12 @@ export declare type recordOptions = {
blockClass?: blockClass;
blockSelector?: string;
ignoreClass?: string;
+ maskTextClass?: maskTextClass;
+ maskTextSelector?: string | null;
maskAllInputs?: boolean;
maskInputOptions?: MaskInputOptions;
maskInputFn?: MaskInputFn;
+ maskTextFn?: MaskTextFn;
slimDOMOptions?: SlimDOMOptions | 'all' | true;
inlineStylesheet?: boolean;
hooks?: hooksParam;
@@ -146,9 +151,12 @@ export declare type observerParam = {
mediaInteractionCb: mediaInteractionCallback;
blockClass: blockClass;
blockSelector: string | null;
+ maskTextClass: maskTextClass;
+ maskTextSelector: string | null;
ignoreClass: string;
maskInputOptions: MaskInputOptions;
maskInputFn?: MaskInputFn;
+ maskTextFn?: MaskTextFn;
inlineStylesheet: boolean;
styleSheetRuleCb: styleSheetRuleCallback;
canvasMutationCb: canvasMutationCallback;
diff --git a/yarn.lock b/yarn.lock
index ff79e7ec..fb4e92ef 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2767,10 +2767,10 @@ rollup@^2.3.3:
optionalDependencies:
fsevents "~2.1.2"
-rrweb-snapshot@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/rrweb-snapshot/-/rrweb-snapshot-1.1.1.tgz#71da8792f43b8bd7017851edcd02e3d7c7cfef9f"
- integrity sha512-xRX7s2/MA/Ifnul4ImAquD1w/Nkz6WOACm3xdKDdQrCD/xKdgcu1yWoJ8eSIXyfVSuIt4VfrhxJdeHyhC1gmGQ==
+rrweb-snapshot@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/rrweb-snapshot/-/rrweb-snapshot-1.1.2.tgz#e9a5ce11f2dba8ff58e22f7f0b96954ef9133be2"
+ integrity sha512-J/BCClbk1fs9ilU9Bn8J/vUdumUllvNsmC1rVSz7rxqVzo+MthpI83gll1S3rBQVcxmQcLGmJWpkyO7M2rGyTw==
run-async@^2.2.0:
version "2.4.1"