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