Mask value attribute changes for elements in maskInputOptions (#602)
* mask value attribute changes for elements in maskInputOptions * refactor initInputObserver to use maskInputValue * add todo * Fix typo * upgrade rrweb-snapshot to 1.1.6 * move maskInputValue to rrweb-snapshot
This commit is contained in:
@@ -66,6 +66,6 @@
|
|||||||
"@xstate/fsm": "^1.4.0",
|
"@xstate/fsm": "^1.4.0",
|
||||||
"fflate": "^0.4.4",
|
"fflate": "^0.4.4",
|
||||||
"mitt": "^1.1.3",
|
"mitt": "^1.1.3",
|
||||||
"rrweb-snapshot": "^1.1.5"
|
"rrweb-snapshot": "^1.1.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,6 +194,7 @@ function record<T = eventWithTime>(
|
|||||||
inlineStylesheet,
|
inlineStylesheet,
|
||||||
maskInputOptions,
|
maskInputOptions,
|
||||||
maskTextFn,
|
maskTextFn,
|
||||||
|
maskInputFn,
|
||||||
recordCanvas,
|
recordCanvas,
|
||||||
sampling,
|
sampling,
|
||||||
slimDOMOptions,
|
slimDOMOptions,
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import {
|
|||||||
IGNORED_NODE,
|
IGNORED_NODE,
|
||||||
isShadowRoot,
|
isShadowRoot,
|
||||||
needMaskingText,
|
needMaskingText,
|
||||||
|
maskInputValue,
|
||||||
|
MaskTextFn,
|
||||||
|
MaskInputFn,
|
||||||
} from 'rrweb-snapshot';
|
} from 'rrweb-snapshot';
|
||||||
import {
|
import {
|
||||||
mutationRecord,
|
mutationRecord,
|
||||||
@@ -17,7 +20,6 @@ import {
|
|||||||
attributeCursor,
|
attributeCursor,
|
||||||
removedNodeMutation,
|
removedNodeMutation,
|
||||||
addedNodeMutation,
|
addedNodeMutation,
|
||||||
MaskTextFn,
|
|
||||||
Mirror,
|
Mirror,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import {
|
import {
|
||||||
@@ -167,6 +169,7 @@ export default class MutationBuffer {
|
|||||||
private inlineStylesheet: boolean;
|
private inlineStylesheet: boolean;
|
||||||
private maskInputOptions: MaskInputOptions;
|
private maskInputOptions: MaskInputOptions;
|
||||||
private maskTextFn: MaskTextFn | undefined;
|
private maskTextFn: MaskTextFn | undefined;
|
||||||
|
private maskInputFn: MaskInputFn | undefined;
|
||||||
private recordCanvas: boolean;
|
private recordCanvas: boolean;
|
||||||
private slimDOMOptions: SlimDOMOptions;
|
private slimDOMOptions: SlimDOMOptions;
|
||||||
private doc: Document;
|
private doc: Document;
|
||||||
@@ -184,6 +187,7 @@ export default class MutationBuffer {
|
|||||||
inlineStylesheet: boolean,
|
inlineStylesheet: boolean,
|
||||||
maskInputOptions: MaskInputOptions,
|
maskInputOptions: MaskInputOptions,
|
||||||
maskTextFn: MaskTextFn | undefined,
|
maskTextFn: MaskTextFn | undefined,
|
||||||
|
maskInputFn: MaskInputFn | undefined,
|
||||||
recordCanvas: boolean,
|
recordCanvas: boolean,
|
||||||
slimDOMOptions: SlimDOMOptions,
|
slimDOMOptions: SlimDOMOptions,
|
||||||
doc: Document,
|
doc: Document,
|
||||||
@@ -198,6 +202,7 @@ export default class MutationBuffer {
|
|||||||
this.inlineStylesheet = inlineStylesheet;
|
this.inlineStylesheet = inlineStylesheet;
|
||||||
this.maskInputOptions = maskInputOptions;
|
this.maskInputOptions = maskInputOptions;
|
||||||
this.maskTextFn = maskTextFn;
|
this.maskTextFn = maskTextFn;
|
||||||
|
this.maskInputFn = maskInputFn;
|
||||||
this.recordCanvas = recordCanvas;
|
this.recordCanvas = recordCanvas;
|
||||||
this.slimDOMOptions = slimDOMOptions;
|
this.slimDOMOptions = slimDOMOptions;
|
||||||
this.emissionCallback = cb;
|
this.emissionCallback = cb;
|
||||||
@@ -287,6 +292,7 @@ export default class MutationBuffer {
|
|||||||
inlineStylesheet: this.inlineStylesheet,
|
inlineStylesheet: this.inlineStylesheet,
|
||||||
maskInputOptions: this.maskInputOptions,
|
maskInputOptions: this.maskInputOptions,
|
||||||
maskTextFn: this.maskTextFn,
|
maskTextFn: this.maskTextFn,
|
||||||
|
maskInputFn: this.maskInputFn,
|
||||||
slimDOMOptions: this.slimDOMOptions,
|
slimDOMOptions: this.slimDOMOptions,
|
||||||
recordCanvas: this.recordCanvas,
|
recordCanvas: this.recordCanvas,
|
||||||
onSerialize: (currentN) => {
|
onSerialize: (currentN) => {
|
||||||
@@ -443,7 +449,16 @@ export default class MutationBuffer {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'attributes': {
|
case 'attributes': {
|
||||||
const value = (m.target as HTMLElement).getAttribute(m.attributeName!);
|
let value = (m.target as HTMLElement).getAttribute(m.attributeName!);
|
||||||
|
if (m.attributeName === 'value') {
|
||||||
|
value = maskInputValue({
|
||||||
|
maskInputOptions: this.maskInputOptions,
|
||||||
|
tagName: (m.target as HTMLElement).tagName,
|
||||||
|
type: (m.target as HTMLElement).getAttribute('type'),
|
||||||
|
value,
|
||||||
|
maskInputFn: this.maskInputFn,
|
||||||
|
});
|
||||||
|
}
|
||||||
if (isBlocked(m.target, this.blockClass) || value === m.oldValue) {
|
if (isBlocked(m.target, this.blockClass) || value === m.oldValue) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
import { INode, MaskInputOptions, SlimDOMOptions } from 'rrweb-snapshot';
|
import {
|
||||||
|
INode,
|
||||||
|
MaskInputOptions,
|
||||||
|
SlimDOMOptions,
|
||||||
|
maskInputValue,
|
||||||
|
MaskInputFn,
|
||||||
|
MaskTextFn,
|
||||||
|
} from 'rrweb-snapshot';
|
||||||
import { FontFaceDescriptors, FontFaceSet } from 'css-font-loading-module';
|
import { FontFaceDescriptors, FontFaceSet } from 'css-font-loading-module';
|
||||||
import {
|
import {
|
||||||
throttle,
|
throttle,
|
||||||
@@ -35,8 +42,6 @@ import {
|
|||||||
canvasMutationCallback,
|
canvasMutationCallback,
|
||||||
fontCallback,
|
fontCallback,
|
||||||
fontParam,
|
fontParam,
|
||||||
MaskInputFn,
|
|
||||||
MaskTextFn,
|
|
||||||
Mirror,
|
Mirror,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import MutationBuffer from './mutation';
|
import MutationBuffer from './mutation';
|
||||||
@@ -83,6 +88,7 @@ export function initMutationObserver(
|
|||||||
inlineStylesheet: boolean,
|
inlineStylesheet: boolean,
|
||||||
maskInputOptions: MaskInputOptions,
|
maskInputOptions: MaskInputOptions,
|
||||||
maskTextFn: MaskTextFn | undefined,
|
maskTextFn: MaskTextFn | undefined,
|
||||||
|
maskInputFn: MaskInputFn | undefined,
|
||||||
recordCanvas: boolean,
|
recordCanvas: boolean,
|
||||||
slimDOMOptions: SlimDOMOptions,
|
slimDOMOptions: SlimDOMOptions,
|
||||||
mirror: Mirror,
|
mirror: Mirror,
|
||||||
@@ -102,6 +108,7 @@ export function initMutationObserver(
|
|||||||
inlineStylesheet,
|
inlineStylesheet,
|
||||||
maskInputOptions,
|
maskInputOptions,
|
||||||
maskTextFn,
|
maskTextFn,
|
||||||
|
maskInputFn,
|
||||||
recordCanvas,
|
recordCanvas,
|
||||||
slimDOMOptions,
|
slimDOMOptions,
|
||||||
doc,
|
doc,
|
||||||
@@ -366,11 +373,13 @@ function initInputObserver(
|
|||||||
] ||
|
] ||
|
||||||
maskInputOptions[type as keyof MaskInputOptions]
|
maskInputOptions[type as keyof MaskInputOptions]
|
||||||
) {
|
) {
|
||||||
if (maskInputFn) {
|
text = maskInputValue({
|
||||||
text = maskInputFn(text);
|
maskInputOptions,
|
||||||
} else {
|
tagName: (target as HTMLElement).tagName,
|
||||||
text = '*'.repeat(text.length);
|
type,
|
||||||
}
|
value: text,
|
||||||
|
maskInputFn,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
cbWithDedup(target, { text, isChecked });
|
cbWithDedup(target, { text, isChecked });
|
||||||
// if a radio was checked
|
// if a radio was checked
|
||||||
@@ -476,7 +485,7 @@ function initMediaInteractionObserver(
|
|||||||
blockClass: blockClass,
|
blockClass: blockClass,
|
||||||
mirror: Mirror,
|
mirror: Mirror,
|
||||||
): listenerHandler {
|
): listenerHandler {
|
||||||
const handler = (type: MediaInteractions ) => (event: Event) => {
|
const handler = (type: MediaInteractions) => (event: Event) => {
|
||||||
const target = getEventTarget(event);
|
const target = getEventTarget(event);
|
||||||
if (!target || isBlocked(target as Node, blockClass)) {
|
if (!target || isBlocked(target as Node, blockClass)) {
|
||||||
return;
|
return;
|
||||||
@@ -484,13 +493,13 @@ function initMediaInteractionObserver(
|
|||||||
mediaInteractionCb({
|
mediaInteractionCb({
|
||||||
type,
|
type,
|
||||||
id: mirror.getId(target as INode),
|
id: mirror.getId(target as INode),
|
||||||
currentTime: (target as HTMLMediaElement).currentTime
|
currentTime: (target as HTMLMediaElement).currentTime,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const handlers = [
|
const handlers = [
|
||||||
on('play', handler(MediaInteractions.Play)),
|
on('play', handler(MediaInteractions.Play)),
|
||||||
on('pause', handler(MediaInteractions.Pause)),
|
on('pause', handler(MediaInteractions.Pause)),
|
||||||
on('seeked', handler(MediaInteractions.Seeked))
|
on('seeked', handler(MediaInteractions.Seeked)),
|
||||||
];
|
];
|
||||||
return () => {
|
return () => {
|
||||||
handlers.forEach((h) => h());
|
handlers.forEach((h) => h());
|
||||||
@@ -716,6 +725,7 @@ export function initObservers(
|
|||||||
o.inlineStylesheet,
|
o.inlineStylesheet,
|
||||||
o.maskInputOptions,
|
o.maskInputOptions,
|
||||||
o.maskTextFn,
|
o.maskTextFn,
|
||||||
|
o.maskInputFn,
|
||||||
o.recordCanvas,
|
o.recordCanvas,
|
||||||
o.slimDOMOptions,
|
o.slimDOMOptions,
|
||||||
o.mirror,
|
o.mirror,
|
||||||
|
|||||||
@@ -2,12 +2,16 @@ import {
|
|||||||
mutationCallBack,
|
mutationCallBack,
|
||||||
blockClass,
|
blockClass,
|
||||||
maskTextClass,
|
maskTextClass,
|
||||||
MaskTextFn,
|
|
||||||
Mirror,
|
Mirror,
|
||||||
scrollCallback,
|
scrollCallback,
|
||||||
SamplingStrategy,
|
SamplingStrategy,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { MaskInputOptions, SlimDOMOptions } from 'rrweb-snapshot';
|
import {
|
||||||
|
MaskInputOptions,
|
||||||
|
SlimDOMOptions,
|
||||||
|
MaskTextFn,
|
||||||
|
MaskInputFn,
|
||||||
|
} from 'rrweb-snapshot';
|
||||||
import { IframeManager } from './iframe-manager';
|
import { IframeManager } from './iframe-manager';
|
||||||
import { initMutationObserver, initScrollObserver } from './observer';
|
import { initMutationObserver, initScrollObserver } from './observer';
|
||||||
|
|
||||||
@@ -19,6 +23,7 @@ type BypassOptions = {
|
|||||||
inlineStylesheet: boolean;
|
inlineStylesheet: boolean;
|
||||||
maskInputOptions: MaskInputOptions;
|
maskInputOptions: MaskInputOptions;
|
||||||
maskTextFn: MaskTextFn | undefined;
|
maskTextFn: MaskTextFn | undefined;
|
||||||
|
maskInputFn: MaskInputFn | undefined;
|
||||||
recordCanvas: boolean;
|
recordCanvas: boolean;
|
||||||
sampling: SamplingStrategy;
|
sampling: SamplingStrategy;
|
||||||
slimDOMOptions: SlimDOMOptions;
|
slimDOMOptions: SlimDOMOptions;
|
||||||
@@ -54,6 +59,7 @@ export class ShadowDomManager {
|
|||||||
this.bypassOptions.inlineStylesheet,
|
this.bypassOptions.inlineStylesheet,
|
||||||
this.bypassOptions.maskInputOptions,
|
this.bypassOptions.maskInputOptions,
|
||||||
this.bypassOptions.maskTextFn,
|
this.bypassOptions.maskTextFn,
|
||||||
|
this.bypassOptions.maskInputFn,
|
||||||
this.bypassOptions.recordCanvas,
|
this.bypassOptions.recordCanvas,
|
||||||
this.bypassOptions.slimDOMOptions,
|
this.bypassOptions.slimDOMOptions,
|
||||||
this.mirror,
|
this.mirror,
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import {
|
|||||||
INode,
|
INode,
|
||||||
MaskInputOptions,
|
MaskInputOptions,
|
||||||
SlimDOMOptions,
|
SlimDOMOptions,
|
||||||
|
MaskInputFn,
|
||||||
|
MaskTextFn,
|
||||||
} from 'rrweb-snapshot';
|
} from 'rrweb-snapshot';
|
||||||
import { PackFn, UnpackFn } from './packer/base';
|
import { PackFn, UnpackFn } from './packer/base';
|
||||||
import { FontFaceDescriptors } from 'css-font-loading-module';
|
import { FontFaceDescriptors } from 'css-font-loading-module';
|
||||||
@@ -544,10 +546,6 @@ export enum ReplayerEvents {
|
|||||||
PlayBack = 'play-back',
|
PlayBack = 'play-back',
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
// store the state that would be changed during the process(unmount from dom and mount again)
|
||||||
export type ElementState = {
|
export type ElementState = {
|
||||||
// [scrollLeft,scrollTop]
|
// [scrollLeft,scrollTop]
|
||||||
|
|||||||
24
src/utils.ts
24
src/utils.ts
@@ -53,7 +53,7 @@ export function createMirror(): Mirror {
|
|||||||
delete this.map[id];
|
delete this.map[id];
|
||||||
if (n.childNodes) {
|
if (n.childNodes) {
|
||||||
n.childNodes.forEach((child) =>
|
n.childNodes.forEach((child) =>
|
||||||
this.removeNodeFromMap(child as Node as INode),
|
this.removeNodeFromMap((child as Node) as INode),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -275,7 +275,7 @@ export function isAncestorRemoved(target: INode, mirror: Mirror): boolean {
|
|||||||
if (!target.parentNode) {
|
if (!target.parentNode) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return isAncestorRemoved(target.parentNode as unknown as INode, mirror);
|
return isAncestorRemoved((target.parentNode as unknown) as INode, mirror);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isTouchEvent(
|
export function isTouchEvent(
|
||||||
@@ -286,13 +286,13 @@ export function isTouchEvent(
|
|||||||
|
|
||||||
export function polyfill(win = window) {
|
export function polyfill(win = window) {
|
||||||
if ('NodeList' in win && !win.NodeList.prototype.forEach) {
|
if ('NodeList' in win && !win.NodeList.prototype.forEach) {
|
||||||
win.NodeList.prototype.forEach = Array.prototype
|
win.NodeList.prototype.forEach = (Array.prototype
|
||||||
.forEach as unknown as NodeList['forEach'];
|
.forEach as unknown) as NodeList['forEach'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('DOMTokenList' in win && !win.DOMTokenList.prototype.forEach) {
|
if ('DOMTokenList' in win && !win.DOMTokenList.prototype.forEach) {
|
||||||
win.DOMTokenList.prototype.forEach = Array.prototype
|
win.DOMTokenList.prototype.forEach = (Array.prototype
|
||||||
.forEach as unknown as DOMTokenList['forEach'];
|
.forEach as unknown) as DOMTokenList['forEach'];
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/Financial-Times/polyfill-service/pull/183
|
// https://github.com/Financial-Times/polyfill-service/pull/183
|
||||||
@@ -396,7 +396,7 @@ export class TreeIndex {
|
|||||||
const node = mirror.getNode(id);
|
const node = mirror.getNode(id);
|
||||||
node?.childNodes.forEach((childNode) => {
|
node?.childNodes.forEach((childNode) => {
|
||||||
if ('__sn' in childNode) {
|
if ('__sn' in childNode) {
|
||||||
deepRemoveFromMirror((childNode as unknown as INode).__sn.id);
|
deepRemoveFromMirror(((childNode as unknown) as INode).__sn.id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -460,8 +460,12 @@ export class TreeIndex {
|
|||||||
scrollMap: TreeIndex['scrollMap'];
|
scrollMap: TreeIndex['scrollMap'];
|
||||||
inputMap: TreeIndex['inputMap'];
|
inputMap: TreeIndex['inputMap'];
|
||||||
} {
|
} {
|
||||||
const { tree, removeNodeMutations, textMutations, attributeMutations } =
|
const {
|
||||||
this;
|
tree,
|
||||||
|
removeNodeMutations,
|
||||||
|
textMutations,
|
||||||
|
attributeMutations,
|
||||||
|
} = this;
|
||||||
|
|
||||||
const batchMutationData: mutationData = {
|
const batchMutationData: mutationData = {
|
||||||
source: IncrementalSource.Mutation,
|
source: IncrementalSource.Mutation,
|
||||||
@@ -650,5 +654,5 @@ export function getBaseDimension(
|
|||||||
export function hasShadowRoot<T extends Node>(
|
export function hasShadowRoot<T extends Node>(
|
||||||
n: T,
|
n: T,
|
||||||
): n is T & { shadowRoot: ShadowRoot } {
|
): n is T & { shadowRoot: ShadowRoot } {
|
||||||
return Boolean((n as unknown as Element)?.shadowRoot);
|
return Boolean(((n as unknown) as Element)?.shadowRoot);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5461,6 +5461,360 @@ exports[`maskInputOptions 1`] = `
|
|||||||
]"
|
]"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`maskPassword 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\\": {
|
||||||
|
\\"lang\\": \\"en\\"
|
||||||
|
},
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"head\\",
|
||||||
|
\\"attributes\\": {},
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\",
|
||||||
|
\\"id\\": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"meta\\",
|
||||||
|
\\"attributes\\": {
|
||||||
|
\\"charset\\": \\"UTF-8\\"
|
||||||
|
},
|
||||||
|
\\"childNodes\\": [],
|
||||||
|
\\"id\\": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\",
|
||||||
|
\\"id\\": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"meta\\",
|
||||||
|
\\"attributes\\": {
|
||||||
|
\\"http-equiv\\": \\"X-UA-Compatible\\",
|
||||||
|
\\"content\\": \\"IE=edge\\"
|
||||||
|
},
|
||||||
|
\\"childNodes\\": [],
|
||||||
|
\\"id\\": 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\",
|
||||||
|
\\"id\\": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"meta\\",
|
||||||
|
\\"attributes\\": {
|
||||||
|
\\"name\\": \\"viewport\\",
|
||||||
|
\\"content\\": \\"width=device-width, initial-scale=1.0\\"
|
||||||
|
},
|
||||||
|
\\"childNodes\\": [],
|
||||||
|
\\"id\\": 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\",
|
||||||
|
\\"id\\": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"title\\",
|
||||||
|
\\"attributes\\": {},
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"Document\\",
|
||||||
|
\\"id\\": 12
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"id\\": 11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\",
|
||||||
|
\\"id\\": 13
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"id\\": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\",
|
||||||
|
\\"id\\": 14
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"body\\",
|
||||||
|
\\"attributes\\": {},
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\",
|
||||||
|
\\"id\\": 16
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"input\\",
|
||||||
|
\\"attributes\\": {
|
||||||
|
\\"type\\": \\"password\\",
|
||||||
|
\\"id\\": \\"password\\"
|
||||||
|
},
|
||||||
|
\\"childNodes\\": [],
|
||||||
|
\\"id\\": 17
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\",
|
||||||
|
\\"id\\": 18
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"script\\",
|
||||||
|
\\"attributes\\": {},
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"SCRIPT_PLACEHOLDER\\",
|
||||||
|
\\"id\\": 20
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"id\\": 19
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\\\n \\",
|
||||||
|
\\"id\\": 21
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"script\\",
|
||||||
|
\\"attributes\\": {},
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"SCRIPT_PLACEHOLDER\\",
|
||||||
|
\\"id\\": 23
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"id\\": 22
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\",
|
||||||
|
\\"id\\": 24
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"id\\": 15
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"id\\": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"id\\": 1
|
||||||
|
},
|
||||||
|
\\"initialOffset\\": {
|
||||||
|
\\"left\\": 0,
|
||||||
|
\\"top\\": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"data\\": {
|
||||||
|
\\"source\\": 2,
|
||||||
|
\\"type\\": 5,
|
||||||
|
\\"id\\": 17
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"data\\": {
|
||||||
|
\\"source\\": 5,
|
||||||
|
\\"text\\": \\"*\\",
|
||||||
|
\\"isChecked\\": false,
|
||||||
|
\\"id\\": 17
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"data\\": {
|
||||||
|
\\"source\\": 0,
|
||||||
|
\\"texts\\": [],
|
||||||
|
\\"attributes\\": [
|
||||||
|
{
|
||||||
|
\\"id\\": 17,
|
||||||
|
\\"attributes\\": {
|
||||||
|
\\"value\\": \\"*\\"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"removes\\": [],
|
||||||
|
\\"adds\\": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"data\\": {
|
||||||
|
\\"source\\": 5,
|
||||||
|
\\"text\\": \\"**\\",
|
||||||
|
\\"isChecked\\": false,
|
||||||
|
\\"id\\": 17
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"data\\": {
|
||||||
|
\\"source\\": 0,
|
||||||
|
\\"texts\\": [],
|
||||||
|
\\"attributes\\": [
|
||||||
|
{
|
||||||
|
\\"id\\": 17,
|
||||||
|
\\"attributes\\": {
|
||||||
|
\\"value\\": \\"**\\"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"removes\\": [],
|
||||||
|
\\"adds\\": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"data\\": {
|
||||||
|
\\"source\\": 5,
|
||||||
|
\\"text\\": \\"***\\",
|
||||||
|
\\"isChecked\\": false,
|
||||||
|
\\"id\\": 17
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"data\\": {
|
||||||
|
\\"source\\": 0,
|
||||||
|
\\"texts\\": [],
|
||||||
|
\\"attributes\\": [
|
||||||
|
{
|
||||||
|
\\"id\\": 17,
|
||||||
|
\\"attributes\\": {
|
||||||
|
\\"value\\": \\"***\\"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"removes\\": [],
|
||||||
|
\\"adds\\": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"data\\": {
|
||||||
|
\\"source\\": 5,
|
||||||
|
\\"text\\": \\"****\\",
|
||||||
|
\\"isChecked\\": false,
|
||||||
|
\\"id\\": 17
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"data\\": {
|
||||||
|
\\"source\\": 0,
|
||||||
|
\\"texts\\": [],
|
||||||
|
\\"attributes\\": [
|
||||||
|
{
|
||||||
|
\\"id\\": 17,
|
||||||
|
\\"attributes\\": {
|
||||||
|
\\"value\\": \\"****\\"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"removes\\": [],
|
||||||
|
\\"adds\\": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"data\\": {
|
||||||
|
\\"source\\": 5,
|
||||||
|
\\"text\\": \\"*****\\",
|
||||||
|
\\"isChecked\\": false,
|
||||||
|
\\"id\\": 17
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"data\\": {
|
||||||
|
\\"source\\": 0,
|
||||||
|
\\"texts\\": [],
|
||||||
|
\\"attributes\\": [
|
||||||
|
{
|
||||||
|
\\"id\\": 17,
|
||||||
|
\\"attributes\\": {
|
||||||
|
\\"value\\": \\"*****\\"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"removes\\": [],
|
||||||
|
\\"adds\\": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"data\\": {
|
||||||
|
\\"source\\": 5,
|
||||||
|
\\"text\\": \\"******\\",
|
||||||
|
\\"isChecked\\": false,
|
||||||
|
\\"id\\": 17
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"data\\": {
|
||||||
|
\\"source\\": 0,
|
||||||
|
\\"texts\\": [],
|
||||||
|
\\"attributes\\": [
|
||||||
|
{
|
||||||
|
\\"id\\": 17,
|
||||||
|
\\"attributes\\": {
|
||||||
|
\\"value\\": \\"******\\"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"removes\\": [],
|
||||||
|
\\"adds\\": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`move-node-1 1`] = `
|
exports[`move-node-1 1`] = `
|
||||||
"[
|
"[
|
||||||
{
|
{
|
||||||
|
|||||||
17
test/html/password.html
Normal file
17
test/html/password.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Document</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<input type="password" id="password" />
|
||||||
|
<script>
|
||||||
|
const password = document.getElementById('password');
|
||||||
|
password.addEventListener('keyup', (event) => {
|
||||||
|
password.setAttribute('value', password.value);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -267,6 +267,23 @@ describe('record integration tests', function (this: ISuite) {
|
|||||||
assertSnapshot(snapshots, __filename, 'maskInputOptions');
|
assertSnapshot(snapshots, __filename, 'maskInputOptions');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should mask value attribute with maskInputOptions', async () => {
|
||||||
|
const page: puppeteer.Page = await this.browser.newPage();
|
||||||
|
await page.goto('about:blank');
|
||||||
|
await page.setContent(
|
||||||
|
getHtml.call(this, 'password.html', {
|
||||||
|
maskInputOptions: {
|
||||||
|
password: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await page.type('input[type="password"]', 'secr3t');
|
||||||
|
|
||||||
|
const snapshots = await page.evaluate('window.snapshots');
|
||||||
|
assertSnapshot(snapshots, __filename, 'maskPassword');
|
||||||
|
});
|
||||||
|
|
||||||
it('should not record blocked elements and its child nodes', async () => {
|
it('should not record blocked elements and its child nodes', async () => {
|
||||||
const page: puppeteer.Page = await this.browser.newPage();
|
const page: puppeteer.Page = await this.browser.newPage();
|
||||||
await page.goto('about:blank');
|
await page.goto('about:blank');
|
||||||
|
|||||||
7
typings/record/mutation.d.ts
vendored
7
typings/record/mutation.d.ts
vendored
@@ -1,5 +1,5 @@
|
|||||||
import { MaskInputOptions, SlimDOMOptions } from 'rrweb-snapshot';
|
import { MaskInputOptions, SlimDOMOptions, MaskTextFn, MaskInputFn } from 'rrweb-snapshot';
|
||||||
import { mutationRecord, blockClass, maskTextClass, mutationCallBack, MaskTextFn, Mirror } from '../types';
|
import { mutationRecord, blockClass, maskTextClass, mutationCallBack, Mirror } from '../types';
|
||||||
import { IframeManager } from './iframe-manager';
|
import { IframeManager } from './iframe-manager';
|
||||||
import { ShadowDomManager } from './shadow-dom-manager';
|
import { ShadowDomManager } from './shadow-dom-manager';
|
||||||
export default class MutationBuffer {
|
export default class MutationBuffer {
|
||||||
@@ -21,13 +21,14 @@ export default class MutationBuffer {
|
|||||||
private inlineStylesheet;
|
private inlineStylesheet;
|
||||||
private maskInputOptions;
|
private maskInputOptions;
|
||||||
private maskTextFn;
|
private maskTextFn;
|
||||||
|
private maskInputFn;
|
||||||
private recordCanvas;
|
private recordCanvas;
|
||||||
private slimDOMOptions;
|
private slimDOMOptions;
|
||||||
private doc;
|
private doc;
|
||||||
private mirror;
|
private mirror;
|
||||||
private iframeManager;
|
private iframeManager;
|
||||||
private shadowDomManager;
|
private shadowDomManager;
|
||||||
init(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, mirror: Mirror, iframeManager: IframeManager, shadowDomManager: ShadowDomManager): void;
|
init(cb: mutationCallBack, blockClass: blockClass, blockSelector: string | null, maskTextClass: maskTextClass, maskTextSelector: string | null, inlineStylesheet: boolean, maskInputOptions: MaskInputOptions, maskTextFn: MaskTextFn | undefined, maskInputFn: MaskInputFn | undefined, recordCanvas: boolean, slimDOMOptions: SlimDOMOptions, doc: Document, mirror: Mirror, iframeManager: IframeManager, shadowDomManager: ShadowDomManager): void;
|
||||||
freeze(): void;
|
freeze(): void;
|
||||||
unfreeze(): void;
|
unfreeze(): void;
|
||||||
isFrozen(): boolean;
|
isFrozen(): boolean;
|
||||||
|
|||||||
6
typings/record/observer.d.ts
vendored
6
typings/record/observer.d.ts
vendored
@@ -1,10 +1,10 @@
|
|||||||
import { MaskInputOptions, SlimDOMOptions } from 'rrweb-snapshot';
|
import { MaskInputOptions, SlimDOMOptions, MaskInputFn, MaskTextFn } from 'rrweb-snapshot';
|
||||||
import { mutationCallBack, observerParam, listenerHandler, scrollCallback, blockClass, maskTextClass, hooksParam, SamplingStrategy, MaskTextFn, Mirror } from '../types';
|
import { mutationCallBack, observerParam, listenerHandler, scrollCallback, blockClass, maskTextClass, hooksParam, SamplingStrategy, Mirror } from '../types';
|
||||||
import MutationBuffer from './mutation';
|
import MutationBuffer from './mutation';
|
||||||
import { IframeManager } from './iframe-manager';
|
import { IframeManager } from './iframe-manager';
|
||||||
import { ShadowDomManager } from './shadow-dom-manager';
|
import { ShadowDomManager } from './shadow-dom-manager';
|
||||||
export declare const mutationBuffers: MutationBuffer[];
|
export declare const mutationBuffers: MutationBuffer[];
|
||||||
export declare function initMutationObserver(cb: mutationCallBack, doc: Document, blockClass: blockClass, blockSelector: string | null, maskTextClass: maskTextClass, maskTextSelector: string | null, inlineStylesheet: boolean, maskInputOptions: MaskInputOptions, maskTextFn: MaskTextFn | undefined, recordCanvas: boolean, slimDOMOptions: SlimDOMOptions, mirror: Mirror, iframeManager: IframeManager, shadowDomManager: ShadowDomManager, rootEl: Node): MutationObserver;
|
export declare function initMutationObserver(cb: mutationCallBack, doc: Document, blockClass: blockClass, blockSelector: string | null, maskTextClass: maskTextClass, maskTextSelector: string | null, inlineStylesheet: boolean, maskInputOptions: MaskInputOptions, maskTextFn: MaskTextFn | undefined, maskInputFn: MaskInputFn | undefined, recordCanvas: boolean, slimDOMOptions: SlimDOMOptions, mirror: Mirror, iframeManager: IframeManager, shadowDomManager: ShadowDomManager, rootEl: Node): MutationObserver;
|
||||||
export declare function initScrollObserver(cb: scrollCallback, doc: Document, mirror: Mirror, blockClass: blockClass, sampling: SamplingStrategy): listenerHandler;
|
export declare function initScrollObserver(cb: scrollCallback, doc: Document, mirror: Mirror, blockClass: blockClass, sampling: SamplingStrategy): listenerHandler;
|
||||||
export declare const INPUT_TAGS: string[];
|
export declare const INPUT_TAGS: string[];
|
||||||
export declare function initObservers(o: observerParam, hooks?: hooksParam): listenerHandler;
|
export declare function initObservers(o: observerParam, hooks?: hooksParam): listenerHandler;
|
||||||
|
|||||||
5
typings/record/shadow-dom-manager.d.ts
vendored
5
typings/record/shadow-dom-manager.d.ts
vendored
@@ -1,5 +1,5 @@
|
|||||||
import { mutationCallBack, blockClass, maskTextClass, MaskTextFn, Mirror, scrollCallback, SamplingStrategy } from '../types';
|
import { mutationCallBack, blockClass, maskTextClass, Mirror, scrollCallback, SamplingStrategy } from '../types';
|
||||||
import { MaskInputOptions, SlimDOMOptions } from 'rrweb-snapshot';
|
import { MaskInputOptions, SlimDOMOptions, MaskTextFn, MaskInputFn } from 'rrweb-snapshot';
|
||||||
import { IframeManager } from './iframe-manager';
|
import { IframeManager } from './iframe-manager';
|
||||||
declare type BypassOptions = {
|
declare type BypassOptions = {
|
||||||
blockClass: blockClass;
|
blockClass: blockClass;
|
||||||
@@ -9,6 +9,7 @@ declare type BypassOptions = {
|
|||||||
inlineStylesheet: boolean;
|
inlineStylesheet: boolean;
|
||||||
maskInputOptions: MaskInputOptions;
|
maskInputOptions: MaskInputOptions;
|
||||||
maskTextFn: MaskTextFn | undefined;
|
maskTextFn: MaskTextFn | undefined;
|
||||||
|
maskInputFn: MaskInputFn | undefined;
|
||||||
recordCanvas: boolean;
|
recordCanvas: boolean;
|
||||||
sampling: SamplingStrategy;
|
sampling: SamplingStrategy;
|
||||||
slimDOMOptions: SlimDOMOptions;
|
slimDOMOptions: SlimDOMOptions;
|
||||||
|
|||||||
4
typings/types.d.ts
vendored
4
typings/types.d.ts
vendored
@@ -1,4 +1,4 @@
|
|||||||
import { serializedNodeWithId, idNodeMap, INode, MaskInputOptions, SlimDOMOptions } from 'rrweb-snapshot';
|
import { serializedNodeWithId, idNodeMap, INode, MaskInputOptions, SlimDOMOptions, MaskInputFn, MaskTextFn } from 'rrweb-snapshot';
|
||||||
import { PackFn, UnpackFn } from './packer/base';
|
import { PackFn, UnpackFn } from './packer/base';
|
||||||
import { FontFaceDescriptors } from 'css-font-loading-module';
|
import { FontFaceDescriptors } from 'css-font-loading-module';
|
||||||
import { IframeManager } from './record/iframe-manager';
|
import { IframeManager } from './record/iframe-manager';
|
||||||
@@ -413,8 +413,6 @@ export declare enum ReplayerEvents {
|
|||||||
StateChange = "state-change",
|
StateChange = "state-change",
|
||||||
PlayBack = "play-back"
|
PlayBack = "play-back"
|
||||||
}
|
}
|
||||||
export declare type MaskInputFn = (text: string) => string;
|
|
||||||
export declare type MaskTextFn = (text: string) => string;
|
|
||||||
export declare type ElementState = {
|
export declare type ElementState = {
|
||||||
scroll?: [number, number];
|
scroll?: [number, number];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2845,10 +2845,10 @@ rollup@^2.3.3:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "~2.1.2"
|
fsevents "~2.1.2"
|
||||||
|
|
||||||
rrweb-snapshot@^1.1.5:
|
rrweb-snapshot@^1.1.6:
|
||||||
version "1.1.5"
|
version "1.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/rrweb-snapshot/-/rrweb-snapshot-1.1.5.tgz#7784a01e7d2ccd067ec56cdec38a1fc2a77cbe86"
|
resolved "https://registry.yarnpkg.com/rrweb-snapshot/-/rrweb-snapshot-1.1.6.tgz#e598510e387b9b2c62ce0cfc586a37c89549b228"
|
||||||
integrity sha512-U5mfiySoK85WkLCljFTh+ShgTDrOMXWrh1qpou8WwW3AEXba7CSv/sTtoXp3QfG5oxe1YOc+fC3tB4kHMdI00w==
|
integrity sha512-twvqV1WRuFwodO5UV0tzS4Q2w+ZYi5kwOx/uwbRkplfazcnbphDLFvy8x9IwMOZFhyRg1jDJjbnlC8wy4xq5bQ==
|
||||||
|
|
||||||
run-async@^2.2.0:
|
run-async@^2.2.0:
|
||||||
version "2.4.1"
|
version "2.4.1"
|
||||||
|
|||||||
Reference in New Issue
Block a user