Skip to content

Commit

Permalink
support for mask all and unmask by selector and class rrweb-io#1096
Browse files Browse the repository at this point in the history
  • Loading branch information
mdellanoce committed Apr 6, 2023
1 parent d7beb11 commit f355dd3
Show file tree
Hide file tree
Showing 10 changed files with 1,923 additions and 272 deletions.
4 changes: 4 additions & 0 deletions guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,11 @@ The parameter of `rrweb.record` accepts the following options.
| blockSelector | null | Use a string to configure which selector 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 |
| ignoreCSSAttributes | null | array of CSS attributes that should be ignored |
| maskAllText | false | mask all text content as \* |
| maskTextClass | 'rr-mask' | Use a string or RegExp to configure which elements should be masked, refer to the [privacy](#privacy) chapter |
| unmaskTextClass | 'rr-unmask' | Use a string or RegExp to configure which elements should be unmasked, refer to the [privacy](#privacy) chapter |
| maskTextSelector | null | Use a string to configure which selector should be masked, refer to the [privacy](#privacy) chapter |
| unmaskTextSelector | null | Use a string to configure which selector should be unmasked, 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) |
| maskInputFn | - | customize mask input content recording logic |
Expand All @@ -172,6 +175,7 @@ You may find some contents on the webpage which are not willing to be recorded,
- An element with the class name `.rr-block` will not be recorded. Instead, it will replay as a placeholder with the same dimension.
- An element with the class name `.rr-ignore` will not record its input events.
- All text of elements with the class name `.rr-mask` and their children will be masked.
- All text of elements with the class name `.rr-unmask` and their children will be unmasked, unless any child is marked with `.rr-mask`.
- `input[type="password"]` will be masked by default.
- Mask options to mask the content in input elements.

Expand Down
157 changes: 138 additions & 19 deletions packages/rrweb-snapshot/src/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,45 +292,110 @@ export function classMatchesRegex(
regex: RegExp,
checkAncestors: boolean,
): boolean {
if (!node) return false;
return distanceToClassRegexMatch(node, regex, checkAncestors) >= 0;
}

function distanceToClassRegexMatch(
node: Node | null,
regex: RegExp,
checkAncestors: boolean,
distance = 0,
): number {
if (!node) return -1;
if (node.nodeType !== node.ELEMENT_NODE) {
if (!checkAncestors) return false;
return classMatchesRegex(node.parentNode, regex, checkAncestors);
if (!checkAncestors) return -1;
return distanceToClassRegexMatch(node.parentNode, regex, checkAncestors);
}

for (let eIndex = (node as HTMLElement).classList.length; eIndex--; ) {
const className = (node as HTMLElement).classList[eIndex];
if (regex.test(className)) {
return true;
return distance;
}
}
if (!checkAncestors) return false;
return classMatchesRegex(node.parentNode, regex, checkAncestors);
if (!checkAncestors) return -1;
return distanceToClassRegexMatch(
node.parentNode,
regex,
checkAncestors,
distance + 1,
);
}

function distanceToSelectorMatch(el: HTMLElement, selector: string): number {
if (!el) return -1;
if (el.matches(selector)) return 0;
const closestParent = el.closest(selector);
if (closestParent) {
let current = el;
let distance = 0;
while (current && current !== closestParent) {
current = current.parentNode as HTMLElement;
if (!current) {
return -1;
}
distance++;
}
return distance;
}
return -1;
}

function distanceToMatch(
el: HTMLElement,
className: string | RegExp,
selector: string | null,
): number {
let classDistance = -1;
let selectorDistance = -1;

if (typeof className === 'string') {
classDistance = distanceToSelectorMatch(el, `.${className}`);
} else {
classDistance = distanceToClassRegexMatch(el, className, true);
}

if (selector) {
selectorDistance = distanceToSelectorMatch(el, selector);
}

return selectorDistance >= 0
? classDistance >= 0
? Math.min(classDistance, selectorDistance)
: selectorDistance
: classDistance >= 0
? classDistance
: -1;
}

export function needMaskingText(
node: Node,
maskTextClass: string | RegExp,
maskTextSelector: string | null,
unmaskTextClass: string | RegExp,
unmaskTextSelector: string | null,
maskAllText: boolean,
): boolean {
const el: HTMLElement | null =
node.nodeType === node.ELEMENT_NODE
? (node as HTMLElement)
: node.parentElement;
if (el === null) return false;

if (typeof maskTextClass === 'string') {
if (el.classList.contains(maskTextClass)) return true;
if (el.closest(`.${maskTextClass}`)) return true;
} else {
if (classMatchesRegex(el, maskTextClass, true)) return true;
}
const maskDistance = distanceToMatch(el, maskTextClass, maskTextSelector);
const unmaskDistance = distanceToMatch(
el,
unmaskTextClass,
unmaskTextSelector,
);

if (maskTextSelector) {
if (el.matches(maskTextSelector)) return true;
if (el.closest(maskTextSelector)) return true;
}
return false;
return maskDistance >= 0
? unmaskDistance >= 0
? maskDistance <= unmaskDistance
: true
: unmaskDistance >= 0
? false
: !!maskAllText;
}

// https://stackoverflow.com/a/36155560
Expand Down Expand Up @@ -424,8 +489,11 @@ function serializeNode(
mirror: Mirror;
blockClass: string | RegExp;
blockSelector: string | null;
maskAllText: boolean;
maskTextClass: string | RegExp;
unmaskTextClass: string | RegExp;
maskTextSelector: string | null;
unmaskTextSelector: string | null;
inlineStylesheet: boolean;
maskInputOptions: MaskInputOptions;
maskTextFn: MaskTextFn | undefined;
Expand All @@ -445,8 +513,11 @@ function serializeNode(
mirror,
blockClass,
blockSelector,
maskAllText,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
inlineStylesheet,
maskInputOptions = {},
maskTextFn,
Expand Down Expand Up @@ -498,8 +569,11 @@ function serializeNode(
});
case n.TEXT_NODE:
return serializeTextNode(n as Text, {
maskAllText,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
maskTextFn,
rootId,
});
Expand Down Expand Up @@ -529,13 +603,24 @@ function getRootId(doc: Document, mirror: Mirror): number | undefined {
function serializeTextNode(
n: Text,
options: {
maskAllText: boolean;
maskTextClass: string | RegExp;
unmaskTextClass: string | RegExp;
maskTextSelector: string | null;
unmaskTextSelector: string | null;
maskTextFn: MaskTextFn | undefined;
rootId: number | undefined;
},
): serializedNode {
const { maskTextClass, maskTextSelector, maskTextFn, rootId } = options;
const {
maskAllText,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
maskTextFn,
rootId,
} = options;
// The parent node may not be a html element which has a tagName attribute.
// So just let it be undefined which is ok in this use case.
const parentTagName = n.parentNode && (n.parentNode as HTMLElement).tagName;
Expand Down Expand Up @@ -570,7 +655,14 @@ function serializeTextNode(
!isStyle &&
!isScript &&
textContent &&
needMaskingText(n, maskTextClass, maskTextSelector)
needMaskingText(
n,
maskTextClass,
maskTextSelector,
unmaskTextClass,
unmaskTextSelector,
maskAllText,
)
) {
textContent = maskTextFn
? maskTextFn(textContent)
Expand Down Expand Up @@ -923,11 +1015,14 @@ export function serializeNodeWithId(
blockClass: string | RegExp;
blockSelector: string | null;
maskTextClass: string | RegExp;
unmaskTextClass: string | RegExp;
maskTextSelector: string | null;
unmaskTextSelector: string | null;
skipChild: boolean;
inlineStylesheet: boolean;
newlyAddedElement?: boolean;
maskInputOptions?: MaskInputOptions;
maskAllText: boolean;
maskTextFn: MaskTextFn | undefined;
maskInputFn: MaskInputFn | undefined;
slimDOMOptions: SlimDOMOptions;
Expand All @@ -954,8 +1049,11 @@ export function serializeNodeWithId(
mirror,
blockClass,
blockSelector,
maskAllText,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
skipChild = false,
inlineStylesheet = true,
maskInputOptions = {},
Expand All @@ -979,8 +1077,11 @@ export function serializeNodeWithId(
mirror,
blockClass,
blockSelector,
maskAllText,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
inlineStylesheet,
maskInputOptions,
maskTextFn,
Expand Down Expand Up @@ -1051,8 +1152,11 @@ export function serializeNodeWithId(
mirror,
blockClass,
blockSelector,
maskAllText,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
skipChild,
inlineStylesheet,
maskInputOptions,
Expand Down Expand Up @@ -1111,8 +1215,11 @@ export function serializeNodeWithId(
mirror,
blockClass,
blockSelector,
maskAllText,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
skipChild: false,
inlineStylesheet,
maskInputOptions,
Expand Down Expand Up @@ -1158,8 +1265,11 @@ export function serializeNodeWithId(
mirror,
blockClass,
blockSelector,
maskAllText,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
skipChild: false,
inlineStylesheet,
maskInputOptions,
Expand Down Expand Up @@ -1199,8 +1309,11 @@ function snapshot(
mirror?: Mirror;
blockClass?: string | RegExp;
blockSelector?: string | null;
maskAllText?: boolean;
maskTextClass?: string | RegExp;
unmaskTextClass?: string | RegExp;
maskTextSelector?: string | null;
unmaskTextSelector?: string | null;
inlineStylesheet?: boolean;
maskAllInputs?: boolean | MaskInputOptions;
maskTextFn?: MaskTextFn;
Expand Down Expand Up @@ -1228,8 +1341,11 @@ function snapshot(
mirror = new Mirror(),
blockClass = 'rr-block',
blockSelector = null,
maskAllText = false,
maskTextClass = 'rr-mask',
unmaskTextClass = 'rr-unmask',
maskTextSelector = null,
unmaskTextSelector = null,
inlineStylesheet = true,
inlineImages = false,
recordCanvas = false,
Expand Down Expand Up @@ -1294,8 +1410,11 @@ function snapshot(
mirror,
blockClass,
blockSelector,
maskAllText,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
skipChild: false,
inlineStylesheet,
maskInputOptions,
Expand Down
Loading

0 comments on commit f355dd3

Please sign in to comment.