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 Oct 27, 2023
1 parent 8444cb2 commit 3bfa800
Show file tree
Hide file tree
Showing 12 changed files with 1,680 additions and 266 deletions.
4 changes: 4 additions & 0 deletions guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,11 @@ The parameter of `rrweb.record` accepts the following options.
| ignoreClass | 'rr-ignore' | Use a string or RegExp to configure which elements should be ignored, refer to the [privacy](#privacy) chapter |
| ignoreSelector | null | Use a string to configure which selector 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 @@ -173,6 +176,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
159 changes: 140 additions & 19 deletions packages/rrweb-snapshot/src/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,26 +290,89 @@ 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 {
try {
const el: HTMLElement | null =
Expand All @@ -318,21 +381,25 @@ export function needMaskingText(
: 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 maskDistance >= 0
? unmaskDistance >= 0
? maskDistance <= unmaskDistance
: true
: unmaskDistance >= 0
? false
: !!maskAllText;
} catch (e) {
//
}
return false;

return !!maskAllText;
}

// https://stackoverflow.com/a/36155560
Expand Down Expand Up @@ -426,8 +493,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 @@ -447,8 +517,11 @@ function serializeNode(
mirror,
blockClass,
blockSelector,
maskAllText,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
inlineStylesheet,
maskInputOptions = {},
maskTextFn,
Expand Down Expand Up @@ -500,8 +573,11 @@ function serializeNode(
});
case n.TEXT_NODE:
return serializeTextNode(n as Text, {
maskAllText,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
maskTextFn,
rootId,
});
Expand Down Expand Up @@ -531,13 +607,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 @@ -572,7 +659,14 @@ function serializeTextNode(
!isStyle &&
!isScript &&
textContent &&
needMaskingText(n, maskTextClass, maskTextSelector)
needMaskingText(
n,
maskTextClass,
maskTextSelector,
unmaskTextClass,
unmaskTextSelector,
maskAllText,
)
) {
textContent = maskTextFn
? maskTextFn(textContent, n.parentElement)
Expand Down Expand Up @@ -922,11 +1016,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 @@ -953,8 +1050,11 @@ export function serializeNodeWithId(
mirror,
blockClass,
blockSelector,
maskAllText,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
skipChild = false,
inlineStylesheet = true,
maskInputOptions = {},
Expand All @@ -978,8 +1078,11 @@ export function serializeNodeWithId(
mirror,
blockClass,
blockSelector,
maskAllText,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
inlineStylesheet,
maskInputOptions,
maskTextFn,
Expand Down Expand Up @@ -1050,8 +1153,11 @@ export function serializeNodeWithId(
mirror,
blockClass,
blockSelector,
maskAllText,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
skipChild,
inlineStylesheet,
maskInputOptions,
Expand Down Expand Up @@ -1110,8 +1216,11 @@ export function serializeNodeWithId(
mirror,
blockClass,
blockSelector,
maskAllText,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
skipChild: false,
inlineStylesheet,
maskInputOptions,
Expand Down Expand Up @@ -1157,8 +1266,11 @@ export function serializeNodeWithId(
mirror,
blockClass,
blockSelector,
maskAllText,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
skipChild: false,
inlineStylesheet,
maskInputOptions,
Expand Down Expand Up @@ -1198,8 +1310,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 @@ -1227,8 +1342,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 @@ -1293,8 +1411,11 @@ function snapshot(
mirror,
blockClass,
blockSelector,
maskAllText,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
skipChild: false,
inlineStylesheet,
maskInputOptions,
Expand Down
Loading

0 comments on commit 3bfa800

Please sign in to comment.