impl #23 add custom privacy selectors
This commit is contained in:
@@ -24,8 +24,7 @@
|
|||||||
"dist",
|
"dist",
|
||||||
"lib",
|
"lib",
|
||||||
"es",
|
"es",
|
||||||
"index.d.ts",
|
"typings"
|
||||||
"src/types.ts"
|
|
||||||
],
|
],
|
||||||
"author": "yanzhen@smartx.com",
|
"author": "yanzhen@smartx.com",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -59,7 +58,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/smoothscroll-polyfill": "^0.3.0",
|
"@types/smoothscroll-polyfill": "^0.3.0",
|
||||||
"mitt": "^1.1.3",
|
"mitt": "^1.1.3",
|
||||||
"rrweb-snapshot": "^0.7.5",
|
"rrweb-snapshot": "^0.7.6",
|
||||||
"smoothscroll-polyfill": "^0.4.3"
|
"smoothscroll-polyfill": "^0.4.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,13 @@ function wrapEvent(e: event): eventWithTime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function record(options: recordOptions = {}): listenerHandler | undefined {
|
function record(options: recordOptions = {}): listenerHandler | undefined {
|
||||||
const { emit, checkoutEveryNms, checkoutEveryNth } = options;
|
const {
|
||||||
|
emit,
|
||||||
|
checkoutEveryNms,
|
||||||
|
checkoutEveryNth,
|
||||||
|
blockClass = 'rr-block',
|
||||||
|
ignoreClass = 'rr-ignore',
|
||||||
|
} = options;
|
||||||
// runtime checks for user options
|
// runtime checks for user options
|
||||||
if (!emit) {
|
if (!emit) {
|
||||||
throw new Error('emit function is required');
|
throw new Error('emit function is required');
|
||||||
@@ -56,7 +62,7 @@ function record(options: recordOptions = {}): listenerHandler | undefined {
|
|||||||
}),
|
}),
|
||||||
isCheckout,
|
isCheckout,
|
||||||
);
|
);
|
||||||
const [node, idNodeMap] = snapshot(document);
|
const [node, idNodeMap] = snapshot(document, blockClass);
|
||||||
if (!node) {
|
if (!node) {
|
||||||
return console.warn('Failed to snapshot the document');
|
return console.warn('Failed to snapshot the document');
|
||||||
}
|
}
|
||||||
@@ -152,6 +158,8 @@ function record(options: recordOptions = {}): listenerHandler | undefined {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
blockClass,
|
||||||
|
ignoreClass,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,7 +46,10 @@ import { deepDelete, isParentRemoved, isParentDropped } from './collection';
|
|||||||
* which means all the id related calculation should be lazy too.
|
* which means all the id related calculation should be lazy too.
|
||||||
* @param cb mutationCallBack
|
* @param cb mutationCallBack
|
||||||
*/
|
*/
|
||||||
function initMutationObserver(cb: mutationCallBack): MutationObserver {
|
function initMutationObserver(
|
||||||
|
cb: mutationCallBack,
|
||||||
|
blockClass: string,
|
||||||
|
): MutationObserver {
|
||||||
const observer = new MutationObserver(mutations => {
|
const observer = new MutationObserver(mutations => {
|
||||||
const texts: textCursor[] = [];
|
const texts: textCursor[] = [];
|
||||||
const attributes: attributeCursor[] = [];
|
const attributes: attributeCursor[] = [];
|
||||||
@@ -57,7 +60,7 @@ function initMutationObserver(cb: mutationCallBack): MutationObserver {
|
|||||||
const droppedSet = new Set<Node>();
|
const droppedSet = new Set<Node>();
|
||||||
|
|
||||||
const genAdds = (n: Node) => {
|
const genAdds = (n: Node) => {
|
||||||
if (isBlocked(n)) {
|
if (isBlocked(n, blockClass)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
addsSet.add(n);
|
addsSet.add(n);
|
||||||
@@ -76,7 +79,7 @@ function initMutationObserver(cb: mutationCallBack): MutationObserver {
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case 'characterData': {
|
case 'characterData': {
|
||||||
const value = target.textContent;
|
const value = target.textContent;
|
||||||
if (!isBlocked(target) && value !== oldValue) {
|
if (!isBlocked(target, blockClass) && value !== oldValue) {
|
||||||
texts.push({
|
texts.push({
|
||||||
value,
|
value,
|
||||||
node: target,
|
node: target,
|
||||||
@@ -86,7 +89,7 @@ function initMutationObserver(cb: mutationCallBack): MutationObserver {
|
|||||||
}
|
}
|
||||||
case 'attributes': {
|
case 'attributes': {
|
||||||
const value = (target as HTMLElement).getAttribute(attributeName!);
|
const value = (target as HTMLElement).getAttribute(attributeName!);
|
||||||
if (isBlocked(target) || value === oldValue) {
|
if (isBlocked(target, blockClass) || value === oldValue) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let item: attributeCursor | undefined = attributes.find(
|
let item: attributeCursor | undefined = attributes.find(
|
||||||
@@ -108,7 +111,7 @@ function initMutationObserver(cb: mutationCallBack): MutationObserver {
|
|||||||
removedNodes.forEach(n => {
|
removedNodes.forEach(n => {
|
||||||
const nodeId = mirror.getId(n as INode);
|
const nodeId = mirror.getId(n as INode);
|
||||||
const parentId = mirror.getId(target as INode);
|
const parentId = mirror.getId(target as INode);
|
||||||
if (isBlocked(n)) {
|
if (isBlocked(n, blockClass)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// removed node has not been serialized yet, just remove it from the Set
|
// removed node has not been serialized yet, just remove it from the Set
|
||||||
@@ -154,7 +157,7 @@ function initMutationObserver(cb: mutationCallBack): MutationObserver {
|
|||||||
nextId: !n.nextSibling
|
nextId: !n.nextSibling
|
||||||
? n.nextSibling
|
? n.nextSibling
|
||||||
: mirror.getId(n.nextSibling as INode),
|
: mirror.getId(n.nextSibling as INode),
|
||||||
node: serializeNodeWithId(n, document, mirror.map, true)!,
|
node: serializeNodeWithId(n, document, mirror.map, blockClass, true)!,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
droppedSet.add(n);
|
droppedSet.add(n);
|
||||||
@@ -239,11 +242,12 @@ function initMousemoveObserver(cb: mousemoveCallBack): listenerHandler {
|
|||||||
|
|
||||||
function initMouseInteractionObserver(
|
function initMouseInteractionObserver(
|
||||||
cb: mouseInteractionCallBack,
|
cb: mouseInteractionCallBack,
|
||||||
|
blockClass: string,
|
||||||
): listenerHandler {
|
): listenerHandler {
|
||||||
const handlers: listenerHandler[] = [];
|
const handlers: listenerHandler[] = [];
|
||||||
const getHandler = (eventKey: keyof typeof MouseInteractions) => {
|
const getHandler = (eventKey: keyof typeof MouseInteractions) => {
|
||||||
return (event: MouseEvent) => {
|
return (event: MouseEvent) => {
|
||||||
if (isBlocked(event.target as Node)) {
|
if (isBlocked(event.target as Node, blockClass)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const id = mirror.getId(event.target as INode);
|
const id = mirror.getId(event.target as INode);
|
||||||
@@ -268,9 +272,12 @@ function initMouseInteractionObserver(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function initScrollObserver(cb: scrollCallback): listenerHandler {
|
function initScrollObserver(
|
||||||
|
cb: scrollCallback,
|
||||||
|
blockClass: string,
|
||||||
|
): listenerHandler {
|
||||||
const updatePosition = throttle<UIEvent>(evt => {
|
const updatePosition = throttle<UIEvent>(evt => {
|
||||||
if (!evt.target || isBlocked(evt.target as Node)) {
|
if (!evt.target || isBlocked(evt.target as Node, blockClass)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const id = mirror.getId(evt.target as INode);
|
const id = mirror.getId(evt.target as INode);
|
||||||
@@ -313,23 +320,26 @@ const HOOK_PROPERTIES: Array<[HTMLElement, string]> = [
|
|||||||
[HTMLSelectElement.prototype, 'value'],
|
[HTMLSelectElement.prototype, 'value'],
|
||||||
[HTMLTextAreaElement.prototype, 'value'],
|
[HTMLTextAreaElement.prototype, 'value'],
|
||||||
];
|
];
|
||||||
const IGNORE_CLASS = 'rr-ignore';
|
|
||||||
const lastInputValueMap: WeakMap<EventTarget, inputValue> = new WeakMap();
|
const lastInputValueMap: WeakMap<EventTarget, inputValue> = new WeakMap();
|
||||||
function initInputObserver(cb: inputCallback): listenerHandler {
|
function initInputObserver(
|
||||||
|
cb: inputCallback,
|
||||||
|
blockClass: string,
|
||||||
|
ignoreClass: string,
|
||||||
|
): listenerHandler {
|
||||||
function eventHandler(event: Event) {
|
function eventHandler(event: Event) {
|
||||||
const { target } = event;
|
const { target } = event;
|
||||||
if (
|
if (
|
||||||
!target ||
|
!target ||
|
||||||
!(target as Element).tagName ||
|
!(target as Element).tagName ||
|
||||||
INPUT_TAGS.indexOf((target as Element).tagName) < 0 ||
|
INPUT_TAGS.indexOf((target as Element).tagName) < 0 ||
|
||||||
isBlocked(target as Node)
|
isBlocked(target as Node, blockClass)
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const type: string | undefined = (target as HTMLInputElement).type;
|
const type: string | undefined = (target as HTMLInputElement).type;
|
||||||
if (
|
if (
|
||||||
type === 'password' ||
|
type === 'password' ||
|
||||||
(target as HTMLElement).classList.contains(IGNORE_CLASS)
|
(target as HTMLElement).classList.contains(ignoreClass)
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -396,14 +406,19 @@ function initInputObserver(cb: inputCallback): listenerHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function initObservers(o: observerParam): listenerHandler {
|
export default function initObservers(o: observerParam): listenerHandler {
|
||||||
const mutationObserver = initMutationObserver(o.mutationCb);
|
const mutationObserver = initMutationObserver(o.mutationCb, o.blockClass);
|
||||||
const mousemoveHandler = initMousemoveObserver(o.mousemoveCb);
|
const mousemoveHandler = initMousemoveObserver(o.mousemoveCb);
|
||||||
const mouseInteractionHandler = initMouseInteractionObserver(
|
const mouseInteractionHandler = initMouseInteractionObserver(
|
||||||
o.mouseInteractionCb,
|
o.mouseInteractionCb,
|
||||||
|
o.blockClass,
|
||||||
);
|
);
|
||||||
const scrollHandler = initScrollObserver(o.scrollCb);
|
const scrollHandler = initScrollObserver(o.scrollCb, o.blockClass);
|
||||||
const viewportResizeHandler = initViewportResizeObserver(o.viewportResizeCb);
|
const viewportResizeHandler = initViewportResizeObserver(o.viewportResizeCb);
|
||||||
const inputHandler = initInputObserver(o.inputCb);
|
const inputHandler = initInputObserver(
|
||||||
|
o.inputCb,
|
||||||
|
o.blockClass,
|
||||||
|
o.ignoreClass,
|
||||||
|
);
|
||||||
return () => {
|
return () => {
|
||||||
mutationObserver.disconnect();
|
mutationObserver.disconnect();
|
||||||
mousemoveHandler();
|
mousemoveHandler();
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
ReplayerEvents,
|
ReplayerEvents,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { mirror } from '../utils';
|
import { mirror } from '../utils';
|
||||||
import injectStyleRules from './styles/inject-style';
|
import getInjectStyleRules from './styles/inject-style';
|
||||||
import './styles/style.css';
|
import './styles/style.css';
|
||||||
|
|
||||||
const SKIP_TIME_THRESHOLD = 10 * 1000;
|
const SKIP_TIME_THRESHOLD = 10 * 1000;
|
||||||
@@ -70,6 +70,7 @@ export class Replayer {
|
|||||||
skipInactive: false,
|
skipInactive: false,
|
||||||
showWarning: true,
|
showWarning: true,
|
||||||
showDebug: false,
|
showDebug: false,
|
||||||
|
blockClass: 'rr-block',
|
||||||
};
|
};
|
||||||
this.config = Object.assign({}, defaultConfig, config);
|
this.config = Object.assign({}, defaultConfig, config);
|
||||||
|
|
||||||
@@ -278,6 +279,7 @@ export class Replayer {
|
|||||||
const styleEl = document.createElement('style');
|
const styleEl = document.createElement('style');
|
||||||
const { documentElement, head } = this.iframe.contentDocument!;
|
const { documentElement, head } = this.iframe.contentDocument!;
|
||||||
documentElement!.insertBefore(styleEl, head);
|
documentElement!.insertBefore(styleEl, head);
|
||||||
|
const injectStyleRules = getInjectStyleRules(this.config.blockClass);
|
||||||
for (let idx = 0; idx < injectStyleRules.length; idx++) {
|
for (let idx = 0; idx < injectStyleRules.length; idx++) {
|
||||||
(styleEl.sheet! as CSSStyleSheet).insertRule(injectStyleRules[idx], idx);
|
(styleEl.sheet! as CSSStyleSheet).insertRule(injectStyleRules[idx], idx);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const rules: string[] = [
|
const rules: (blockClass: string) => string[] = (blockClass: string) => [
|
||||||
'iframe, .rr-block { background: #ccc }',
|
`iframe, .${blockClass} { background: #ccc }`,
|
||||||
'noscript { display: none !important; }',
|
'noscript { display: none !important; }',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
11
src/types.ts
11
src/types.ts
@@ -102,6 +102,8 @@ export type recordOptions = {
|
|||||||
emit?: (e: eventWithTime, isCheckout?: boolean) => void;
|
emit?: (e: eventWithTime, isCheckout?: boolean) => void;
|
||||||
checkoutEveryNth?: number;
|
checkoutEveryNth?: number;
|
||||||
checkoutEveryNms?: number;
|
checkoutEveryNms?: number;
|
||||||
|
blockClass?: string;
|
||||||
|
ignoreClass?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type observerParam = {
|
export type observerParam = {
|
||||||
@@ -111,6 +113,8 @@ export type observerParam = {
|
|||||||
scrollCb: scrollCallback;
|
scrollCb: scrollCallback;
|
||||||
viewportResizeCb: viewportResizeCallback;
|
viewportResizeCb: viewportResizeCallback;
|
||||||
inputCb: inputCallback;
|
inputCb: inputCallback;
|
||||||
|
blockClass: string;
|
||||||
|
ignoreClass: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type textCursor = {
|
export type textCursor = {
|
||||||
@@ -229,9 +233,10 @@ export type playerConfig = {
|
|||||||
speed: number;
|
speed: number;
|
||||||
root: Element;
|
root: Element;
|
||||||
loadTimeout: number;
|
loadTimeout: number;
|
||||||
skipInactive: Boolean;
|
skipInactive: boolean;
|
||||||
showWarning: Boolean;
|
showWarning: boolean;
|
||||||
showDebug: Boolean;
|
showDebug: boolean;
|
||||||
|
blockClass: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type playerMetaData = {
|
export type playerMetaData = {
|
||||||
|
|||||||
@@ -113,18 +113,17 @@ export function getWindowWidth(): number {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const BLOCK_CLASS = 'rr-block';
|
export function isBlocked(node: Node | null, blockClass: string): boolean {
|
||||||
export function isBlocked(node: Node | null): boolean {
|
|
||||||
if (!node) {
|
if (!node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (node.nodeType === node.ELEMENT_NODE) {
|
if (node.nodeType === node.ELEMENT_NODE) {
|
||||||
return (
|
return (
|
||||||
(node as HTMLElement).classList.contains(BLOCK_CLASS) ||
|
(node as HTMLElement).classList.contains(blockClass) ||
|
||||||
isBlocked(node.parentNode)
|
isBlocked(node.parentNode, blockClass)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return isBlocked(node.parentNode);
|
return isBlocked(node.parentNode, blockClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isAncestorRemoved(target: INode): boolean {
|
export function isAncestorRemoved(target: INode): boolean {
|
||||||
|
|||||||
Reference in New Issue
Block a user