Enable to mask texts (#73)
* chore: reorder options * feat: enable to mask texts * feat: add the default mask function * refactor: rename options to identify the difference between mask text and mask input * test: add tests about masking
This commit is contained in:
@@ -3,6 +3,7 @@ import snapshot, {
|
|||||||
transformAttribute,
|
transformAttribute,
|
||||||
visitSnapshot,
|
visitSnapshot,
|
||||||
cleanupSnapshot,
|
cleanupSnapshot,
|
||||||
|
needMaskingText,
|
||||||
IGNORED_NODE,
|
IGNORED_NODE,
|
||||||
} from './snapshot';
|
} from './snapshot';
|
||||||
import rebuild, { buildNodeWithSN, addHoverClass } from './rebuild';
|
import rebuild, { buildNodeWithSN, addHoverClass } from './rebuild';
|
||||||
@@ -18,5 +19,6 @@ export {
|
|||||||
transformAttribute,
|
transformAttribute,
|
||||||
visitSnapshot,
|
visitSnapshot,
|
||||||
cleanupSnapshot,
|
cleanupSnapshot,
|
||||||
|
needMaskingText,
|
||||||
IGNORED_NODE,
|
IGNORED_NODE,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
idNodeMap,
|
idNodeMap,
|
||||||
MaskInputOptions,
|
MaskInputOptions,
|
||||||
SlimDOMOptions,
|
SlimDOMOptions,
|
||||||
|
MaskTextFn,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { isElement, isShadowRoot } from './utils';
|
import { isElement, isShadowRoot } from './utils';
|
||||||
|
|
||||||
@@ -206,6 +207,40 @@ export function _isBlockedElement(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function needMaskingText(
|
||||||
|
node: Node | null,
|
||||||
|
maskTextClass: string | RegExp,
|
||||||
|
maskTextSelector: string | null,
|
||||||
|
): boolean {
|
||||||
|
if (!node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (node.nodeType === node.ELEMENT_NODE) {
|
||||||
|
if (typeof maskTextClass === 'string') {
|
||||||
|
if ((node as HTMLElement).classList.contains(maskTextClass)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(node as HTMLElement).classList.forEach((className) => {
|
||||||
|
if (maskTextClass.test(className)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (maskTextSelector) {
|
||||||
|
if ((node as HTMLElement).matches(maskTextSelector)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return needMaskingText(node.parentNode, maskTextClass, maskTextSelector);
|
||||||
|
}
|
||||||
|
if (node.nodeType === node.TEXT_NODE) {
|
||||||
|
// check parent node since text node do not have class name
|
||||||
|
return needMaskingText(node.parentNode, maskTextClass, maskTextSelector);
|
||||||
|
}
|
||||||
|
return needMaskingText(node.parentNode, maskTextClass, maskTextSelector);
|
||||||
|
}
|
||||||
|
|
||||||
// https://stackoverflow.com/a/36155560
|
// https://stackoverflow.com/a/36155560
|
||||||
function onceIframeLoaded(
|
function onceIframeLoaded(
|
||||||
iframeEl: HTMLIFrameElement,
|
iframeEl: HTMLIFrameElement,
|
||||||
@@ -259,8 +294,11 @@ function serializeNode(
|
|||||||
doc: Document;
|
doc: Document;
|
||||||
blockClass: string | RegExp;
|
blockClass: string | RegExp;
|
||||||
blockSelector: string | null;
|
blockSelector: string | null;
|
||||||
|
maskTextClass: string | RegExp;
|
||||||
|
maskTextSelector: string | null;
|
||||||
inlineStylesheet: boolean;
|
inlineStylesheet: boolean;
|
||||||
maskInputOptions: MaskInputOptions;
|
maskInputOptions: MaskInputOptions;
|
||||||
|
maskTextFn: MaskTextFn | undefined;
|
||||||
recordCanvas: boolean;
|
recordCanvas: boolean;
|
||||||
},
|
},
|
||||||
): serializedNode | false {
|
): serializedNode | false {
|
||||||
@@ -268,8 +306,11 @@ function serializeNode(
|
|||||||
doc,
|
doc,
|
||||||
blockClass,
|
blockClass,
|
||||||
blockSelector,
|
blockSelector,
|
||||||
|
maskTextClass,
|
||||||
|
maskTextSelector,
|
||||||
inlineStylesheet,
|
inlineStylesheet,
|
||||||
maskInputOptions = {},
|
maskInputOptions = {},
|
||||||
|
maskTextFn,
|
||||||
recordCanvas,
|
recordCanvas,
|
||||||
} = options;
|
} = options;
|
||||||
// Only record root id when document object is not the base document
|
// Only record root id when document object is not the base document
|
||||||
@@ -412,12 +453,23 @@ function serializeNode(
|
|||||||
n.parentNode && (n.parentNode as HTMLElement).tagName;
|
n.parentNode && (n.parentNode as HTMLElement).tagName;
|
||||||
let textContent = (n as Text).textContent;
|
let textContent = (n as Text).textContent;
|
||||||
const isStyle = parentTagName === 'STYLE' ? true : undefined;
|
const isStyle = parentTagName === 'STYLE' ? true : undefined;
|
||||||
|
const isScript = parentTagName === 'SCRIPT' ? true : undefined;
|
||||||
if (isStyle && textContent) {
|
if (isStyle && textContent) {
|
||||||
textContent = absoluteToStylesheet(textContent, getHref());
|
textContent = absoluteToStylesheet(textContent, getHref());
|
||||||
}
|
}
|
||||||
if (parentTagName === 'SCRIPT') {
|
if (isScript) {
|
||||||
textContent = 'SCRIPT_PLACEHOLDER';
|
textContent = 'SCRIPT_PLACEHOLDER';
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
!isStyle &&
|
||||||
|
!isScript &&
|
||||||
|
needMaskingText(n, maskTextClass, maskTextSelector) &&
|
||||||
|
textContent
|
||||||
|
) {
|
||||||
|
textContent = maskTextFn
|
||||||
|
? maskTextFn(textContent)
|
||||||
|
: textContent.replace(/[\S]/g, '*');
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
type: NodeType.Text,
|
type: NodeType.Text,
|
||||||
textContent: textContent || '',
|
textContent: textContent || '',
|
||||||
@@ -540,9 +592,12 @@ export function serializeNodeWithId(
|
|||||||
map: idNodeMap;
|
map: idNodeMap;
|
||||||
blockClass: string | RegExp;
|
blockClass: string | RegExp;
|
||||||
blockSelector: string | null;
|
blockSelector: string | null;
|
||||||
|
maskTextClass: string | RegExp;
|
||||||
|
maskTextSelector: string | null;
|
||||||
skipChild: boolean;
|
skipChild: boolean;
|
||||||
inlineStylesheet: boolean;
|
inlineStylesheet: boolean;
|
||||||
maskInputOptions?: MaskInputOptions;
|
maskInputOptions?: MaskInputOptions;
|
||||||
|
maskTextFn: MaskTextFn | undefined;
|
||||||
slimDOMOptions: SlimDOMOptions;
|
slimDOMOptions: SlimDOMOptions;
|
||||||
recordCanvas?: boolean;
|
recordCanvas?: boolean;
|
||||||
preserveWhiteSpace?: boolean;
|
preserveWhiteSpace?: boolean;
|
||||||
@@ -556,9 +611,12 @@ export function serializeNodeWithId(
|
|||||||
map,
|
map,
|
||||||
blockClass,
|
blockClass,
|
||||||
blockSelector,
|
blockSelector,
|
||||||
|
maskTextClass,
|
||||||
|
maskTextSelector,
|
||||||
skipChild = false,
|
skipChild = false,
|
||||||
inlineStylesheet = true,
|
inlineStylesheet = true,
|
||||||
maskInputOptions = {},
|
maskInputOptions = {},
|
||||||
|
maskTextFn,
|
||||||
slimDOMOptions,
|
slimDOMOptions,
|
||||||
recordCanvas = false,
|
recordCanvas = false,
|
||||||
onSerialize,
|
onSerialize,
|
||||||
@@ -570,8 +628,11 @@ export function serializeNodeWithId(
|
|||||||
doc,
|
doc,
|
||||||
blockClass,
|
blockClass,
|
||||||
blockSelector,
|
blockSelector,
|
||||||
|
maskTextClass,
|
||||||
|
maskTextSelector,
|
||||||
inlineStylesheet,
|
inlineStylesheet,
|
||||||
maskInputOptions,
|
maskInputOptions,
|
||||||
|
maskTextFn,
|
||||||
recordCanvas,
|
recordCanvas,
|
||||||
});
|
});
|
||||||
if (!_serializedNode) {
|
if (!_serializedNode) {
|
||||||
@@ -628,9 +689,12 @@ export function serializeNodeWithId(
|
|||||||
map,
|
map,
|
||||||
blockClass,
|
blockClass,
|
||||||
blockSelector,
|
blockSelector,
|
||||||
|
maskTextClass,
|
||||||
|
maskTextSelector,
|
||||||
skipChild,
|
skipChild,
|
||||||
inlineStylesheet,
|
inlineStylesheet,
|
||||||
maskInputOptions,
|
maskInputOptions,
|
||||||
|
maskTextFn,
|
||||||
slimDOMOptions,
|
slimDOMOptions,
|
||||||
recordCanvas,
|
recordCanvas,
|
||||||
preserveWhiteSpace,
|
preserveWhiteSpace,
|
||||||
@@ -675,9 +739,12 @@ export function serializeNodeWithId(
|
|||||||
map,
|
map,
|
||||||
blockClass,
|
blockClass,
|
||||||
blockSelector,
|
blockSelector,
|
||||||
|
maskTextClass,
|
||||||
|
maskTextSelector,
|
||||||
skipChild: false,
|
skipChild: false,
|
||||||
inlineStylesheet,
|
inlineStylesheet,
|
||||||
maskInputOptions,
|
maskInputOptions,
|
||||||
|
maskTextFn,
|
||||||
slimDOMOptions,
|
slimDOMOptions,
|
||||||
recordCanvas,
|
recordCanvas,
|
||||||
preserveWhiteSpace,
|
preserveWhiteSpace,
|
||||||
@@ -702,11 +769,14 @@ function snapshot(
|
|||||||
n: Document,
|
n: Document,
|
||||||
options?: {
|
options?: {
|
||||||
blockClass?: string | RegExp;
|
blockClass?: string | RegExp;
|
||||||
|
blockSelector?: string | null;
|
||||||
|
maskTextClass?: string | RegExp;
|
||||||
|
maskTextSelector?: string | null;
|
||||||
inlineStylesheet?: boolean;
|
inlineStylesheet?: boolean;
|
||||||
maskAllInputs?: boolean | MaskInputOptions;
|
maskAllInputs?: boolean | MaskInputOptions;
|
||||||
|
maskTextFn?: MaskTextFn;
|
||||||
slimDOM?: boolean | SlimDOMOptions;
|
slimDOM?: boolean | SlimDOMOptions;
|
||||||
recordCanvas?: boolean;
|
recordCanvas?: boolean;
|
||||||
blockSelector?: string | null;
|
|
||||||
preserveWhiteSpace?: boolean;
|
preserveWhiteSpace?: boolean;
|
||||||
onSerialize?: (n: INode) => unknown;
|
onSerialize?: (n: INode) => unknown;
|
||||||
onIframeLoad?: (iframeINode: INode, node: serializedNodeWithId) => unknown;
|
onIframeLoad?: (iframeINode: INode, node: serializedNodeWithId) => unknown;
|
||||||
@@ -715,10 +785,13 @@ function snapshot(
|
|||||||
): [serializedNodeWithId | null, idNodeMap] {
|
): [serializedNodeWithId | null, idNodeMap] {
|
||||||
const {
|
const {
|
||||||
blockClass = 'rr-block',
|
blockClass = 'rr-block',
|
||||||
|
blockSelector = null,
|
||||||
|
maskTextClass = 'rr-mask',
|
||||||
|
maskTextSelector = null,
|
||||||
inlineStylesheet = true,
|
inlineStylesheet = true,
|
||||||
recordCanvas = false,
|
recordCanvas = false,
|
||||||
blockSelector = null,
|
|
||||||
maskAllInputs = false,
|
maskAllInputs = false,
|
||||||
|
maskTextFn,
|
||||||
slimDOM = false,
|
slimDOM = false,
|
||||||
preserveWhiteSpace,
|
preserveWhiteSpace,
|
||||||
onSerialize,
|
onSerialize,
|
||||||
@@ -772,9 +845,12 @@ function snapshot(
|
|||||||
map: idNodeMap,
|
map: idNodeMap,
|
||||||
blockClass,
|
blockClass,
|
||||||
blockSelector,
|
blockSelector,
|
||||||
|
maskTextClass,
|
||||||
|
maskTextSelector,
|
||||||
skipChild: false,
|
skipChild: false,
|
||||||
inlineStylesheet,
|
inlineStylesheet,
|
||||||
maskInputOptions,
|
maskInputOptions,
|
||||||
|
maskTextFn,
|
||||||
slimDOMOptions,
|
slimDOMOptions,
|
||||||
recordCanvas,
|
recordCanvas,
|
||||||
preserveWhiteSpace,
|
preserveWhiteSpace,
|
||||||
|
|||||||
@@ -105,3 +105,5 @@ export type SlimDOMOptions = Partial<{
|
|||||||
headMetaAuthorship: boolean;
|
headMetaAuthorship: boolean;
|
||||||
headMetaVerification: boolean;
|
headMetaVerification: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
export type MaskTextFn = (text: string) => string;
|
||||||
|
|||||||
@@ -202,6 +202,21 @@ exports[`[html file]: invalid-tagname.html 1`] = `
|
|||||||
</body></html>"
|
</body></html>"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`[html file]: mask-text.html 1`] = `
|
||||||
|
"<!DOCTYPE html><html xmlns=\\"http://www.w3.org/1999/xhtml\\" lang=\\"en\\"><head>
|
||||||
|
<meta charset=\\"UTF-8\\" />
|
||||||
|
<meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1.0\\" />
|
||||||
|
<meta http-equiv=\\"X-UA-Compatible\\" content=\\"ie=edge\\" />
|
||||||
|
<title>Document</title>
|
||||||
|
</head> <body>
|
||||||
|
<p class=\\"rr-mask\\">**** *</p>
|
||||||
|
<div class=\\"rr-mask\\">
|
||||||
|
<span>**** *</span>
|
||||||
|
</div>
|
||||||
|
<div class=\\"rr-mask\\">**** *</div>
|
||||||
|
</body></html>"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`[html file]: picture.html 1`] = `
|
exports[`[html file]: picture.html 1`] = `
|
||||||
"<html xmlns=\\"http://www.w3.org/1999/xhtml\\"><head></head><body>
|
"<html xmlns=\\"http://www.w3.org/1999/xhtml\\"><head></head><body>
|
||||||
<picture>
|
<picture>
|
||||||
|
|||||||
17
test/html/mask-text.html
Normal file
17
test/html/mask-text.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||||
|
<title>Document</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<p class="rr-mask">mask 1</p>
|
||||||
|
<div class="rr-mask">
|
||||||
|
<span>mask 2</span>
|
||||||
|
</div>
|
||||||
|
<div class="rr-mask">mask 3</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
4
typings/index.d.ts
vendored
4
typings/index.d.ts
vendored
@@ -1,5 +1,5 @@
|
|||||||
import snapshot, { serializeNodeWithId, transformAttribute, visitSnapshot, cleanupSnapshot, IGNORED_NODE } from './snapshot';
|
import snapshot, { serializeNodeWithId, transformAttribute, visitSnapshot, cleanupSnapshot, needMaskingText, IGNORED_NODE } from './snapshot';
|
||||||
import rebuild, { buildNodeWithSN, addHoverClass } from './rebuild';
|
import rebuild, { buildNodeWithSN, addHoverClass } from './rebuild';
|
||||||
export * from './types';
|
export * from './types';
|
||||||
export * from './utils';
|
export * from './utils';
|
||||||
export { snapshot, serializeNodeWithId, rebuild, buildNodeWithSN, addHoverClass, transformAttribute, visitSnapshot, cleanupSnapshot, IGNORED_NODE, };
|
export { snapshot, serializeNodeWithId, rebuild, buildNodeWithSN, addHoverClass, transformAttribute, visitSnapshot, cleanupSnapshot, needMaskingText, IGNORED_NODE, };
|
||||||
|
|||||||
11
typings/snapshot.d.ts
vendored
11
typings/snapshot.d.ts
vendored
@@ -1,17 +1,21 @@
|
|||||||
import { serializedNodeWithId, INode, idNodeMap, MaskInputOptions, SlimDOMOptions } from './types';
|
import { serializedNodeWithId, INode, idNodeMap, MaskInputOptions, MaskTextFn, SlimDOMOptions } from './types';
|
||||||
export declare const IGNORED_NODE = -2;
|
export declare const IGNORED_NODE = -2;
|
||||||
export declare function absoluteToStylesheet(cssText: string | null, href: string): string;
|
export declare function absoluteToStylesheet(cssText: string | null, href: string): string;
|
||||||
export declare function absoluteToDoc(doc: Document, attributeValue: string): string;
|
export declare function absoluteToDoc(doc: Document, attributeValue: string): string;
|
||||||
export declare function transformAttribute(doc: Document, tagName: string, name: string, value: string): string;
|
export declare function transformAttribute(doc: Document, tagName: string, name: string, value: string): string;
|
||||||
export declare function _isBlockedElement(element: HTMLElement, blockClass: string | RegExp, blockSelector: string | null): boolean;
|
export declare function _isBlockedElement(element: HTMLElement, blockClass: string | RegExp, blockSelector: string | null): boolean;
|
||||||
|
export declare function needMaskingText(node: Node | null, maskTextClass: string | RegExp, maskTextSelector: string | null): boolean;
|
||||||
export declare function serializeNodeWithId(n: Node | INode, options: {
|
export declare function serializeNodeWithId(n: Node | INode, options: {
|
||||||
doc: Document;
|
doc: Document;
|
||||||
map: idNodeMap;
|
map: idNodeMap;
|
||||||
blockClass: string | RegExp;
|
blockClass: string | RegExp;
|
||||||
blockSelector: string | null;
|
blockSelector: string | null;
|
||||||
|
maskTextClass: string | RegExp;
|
||||||
|
maskTextSelector: string | null;
|
||||||
skipChild: boolean;
|
skipChild: boolean;
|
||||||
inlineStylesheet: boolean;
|
inlineStylesheet: boolean;
|
||||||
maskInputOptions?: MaskInputOptions;
|
maskInputOptions?: MaskInputOptions;
|
||||||
|
maskTextFn?: MaskTextFn;
|
||||||
slimDOMOptions: SlimDOMOptions;
|
slimDOMOptions: SlimDOMOptions;
|
||||||
recordCanvas?: boolean;
|
recordCanvas?: boolean;
|
||||||
preserveWhiteSpace?: boolean;
|
preserveWhiteSpace?: boolean;
|
||||||
@@ -21,11 +25,14 @@ export declare function serializeNodeWithId(n: Node | INode, options: {
|
|||||||
}): serializedNodeWithId | null;
|
}): serializedNodeWithId | null;
|
||||||
declare function snapshot(n: Document, options?: {
|
declare function snapshot(n: Document, options?: {
|
||||||
blockClass?: string | RegExp;
|
blockClass?: string | RegExp;
|
||||||
|
blockSelector?: string | null;
|
||||||
|
maskTextClass?: string | RegExp;
|
||||||
|
maskTextSelector?: string | null;
|
||||||
inlineStylesheet?: boolean;
|
inlineStylesheet?: boolean;
|
||||||
maskAllInputs?: boolean | MaskInputOptions;
|
maskAllInputs?: boolean | MaskInputOptions;
|
||||||
|
maskTextFn?: MaskTextFn;
|
||||||
slimDOM?: boolean | SlimDOMOptions;
|
slimDOM?: boolean | SlimDOMOptions;
|
||||||
recordCanvas?: boolean;
|
recordCanvas?: boolean;
|
||||||
blockSelector?: string | null;
|
|
||||||
preserveWhiteSpace?: boolean;
|
preserveWhiteSpace?: boolean;
|
||||||
onSerialize?: (n: INode) => unknown;
|
onSerialize?: (n: INode) => unknown;
|
||||||
onIframeLoad?: (iframeINode: INode, node: serializedNodeWithId) => unknown;
|
onIframeLoad?: (iframeINode: INode, node: serializedNodeWithId) => unknown;
|
||||||
|
|||||||
1
typings/types.d.ts
vendored
1
typings/types.d.ts
vendored
@@ -86,3 +86,4 @@ export declare type SlimDOMOptions = Partial<{
|
|||||||
headMetaAuthorship: boolean;
|
headMetaAuthorship: boolean;
|
||||||
headMetaVerification: boolean;
|
headMetaVerification: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
export declare type MaskTextFn = (text: string) => string;
|
||||||
|
|||||||
Reference in New Issue
Block a user