diff --git a/src/record/observer.ts b/src/record/observer.ts index a3f1624b09..bfbf1a8a17 100644 --- a/src/record/observer.ts +++ b/src/record/observer.ts @@ -132,7 +132,9 @@ function initMoveObserver( const threshold = typeof sampling.mousemove === 'number' ? sampling.mousemove : 50; const callbackThreshold = - typeof sampling.mousemoveCallback === 'number' ? sampling.mousemoveCallback : 500; + typeof sampling.mousemoveCallback === 'number' + ? sampling.mousemoveCallback + : 500; let positions: mousePosition[] = []; let timeBaseline: number | null; @@ -261,18 +263,18 @@ function initScrollObserver( function initViewportResizeObserver( cb: viewportResizeCallback, ): listenerHandler { - let last_h = -1; - let last_w = -1; + let lastH = -1; + let lastW = -1; const updateDimension = throttle(() => { const height = getWindowHeight(); const width = getWindowWidth(); - if (last_h !== height || last_w != width) { + if (lastH !== height || lastW !== width) { cb({ width: Number(width), height: Number(height), }); - last_h = height; - last_w = width; + lastH = height; + lastW = width; } }, 200); return on('resize', updateDimension, window); @@ -562,24 +564,30 @@ function initLogObserver( logOptions: LogRecordOptions, ): listenerHandler { const logger = logOptions.logger; - if (!logger) return () => {}; + if (!logger) { + return () => {}; + } let logCount = 0; - const cancelHandlers: any[] = []; + const cancelHandlers: listenerHandler[] = []; // add listener to thrown errors if (logOptions.level!.includes('error')) { if (window) { const originalOnError = window.onerror; + // tslint:disable-next-line:no-any window.onerror = (...args: any[]) => { - originalOnError && originalOnError.apply(this, args); - let stack: Array = []; - if (args[args.length - 1] instanceof Error) + if (originalOnError) { + originalOnError.apply(this, args); + } + let stack: string[] = []; + if (args[args.length - 1] instanceof Error) { // 0(the second parameter) tells parseStack that every stack in Error is useful stack = parseStack(args[args.length - 1].stack, 0); + } const payload = [stringify(args[0], logOptions.stringifyOptions)]; cb({ level: 'error', trace: stack, - payload: payload, + payload, }); }; cancelHandlers.push(() => { @@ -587,8 +595,9 @@ function initLogObserver( }); } } - for (const levelType of logOptions.level!) + for (const levelType of logOptions.level!) { cancelHandlers.push(replace(logger, levelType)); + } return () => { cancelHandlers.forEach((h) => h()); }; @@ -598,10 +607,13 @@ function initLogObserver( * @param logger the logger object such as Console * @param level the name of log function to be replaced */ - function replace(logger: Logger, level: LogLevel) { - if (!logger[level]) return () => {}; + function replace(_logger: Logger, level: LogLevel) { + if (!_logger[level]) { + return () => {}; + } // replace the logger.{level}. return a restore function - return patch(logger, level, (original) => { + return patch(_logger, level, (original) => { + // tslint:disable-next-line:no-any return (...args: any[]) => { original.apply(this, args); try { @@ -610,13 +622,13 @@ function initLogObserver( stringify(s, logOptions.stringifyOptions), ); logCount++; - if (logCount < logOptions.lengthThreshold!) + if (logCount < logOptions.lengthThreshold!) { cb({ - level: level, + level, trace: stack, - payload: payload, + payload, }); - else if (logCount === logOptions.lengthThreshold) + } else if (logCount === logOptions.lengthThreshold) { // notify the user cb({ level: 'warn', @@ -625,6 +637,7 @@ function initLogObserver( stringify('The number of log records reached the threshold.'), ], }); + } } catch (error) { original('rrweb logger error:', error, ...args); } @@ -639,7 +652,7 @@ function initLogObserver( function parseStack( stack: string | undefined, omitDepth: number = 1, - ): Array { + ): string[] { let stacks: string[] = []; if (stack) { stacks = stack diff --git a/src/record/stringify.ts b/src/record/stringify.ts index 0547f2f32a..b1c69b81b9 100644 --- a/src/record/stringify.ts +++ b/src/record/stringify.ts @@ -1,3 +1,4 @@ +// tslint:disable:no-any no-bitwise forin /** * this file is used to serialize log message to string * @@ -14,18 +15,21 @@ function pathToSelector(node: HTMLElement): string | '' { return ''; } - var path = ''; + let path = ''; while (node.parentElement) { - var name = node.localName; - if (!name) break; + let name = node.localName; + if (!name) { + break; + } name = name.toLowerCase(); - var parent = node.parentElement; + let parent = node.parentElement; - var domSiblings = []; + let domSiblings = []; if (parent.children && parent.children.length > 0) { - for (var i = 0; i < parent.children.length; i++) { - var sibling = parent.children[i]; + // tslint:disable-next-line:prefer-for-of + for (let i = 0; i < parent.children.length; i++) { + let sibling = parent.children[i]; if (sibling.localName && sibling.localName.toLowerCase) { if (sibling.localName.toLowerCase() === name) { domSiblings.push(sibling); @@ -56,45 +60,55 @@ export function stringify( numOfKeysLimit: 50, }; Object.assign(options, stringifyOptions); - let stack: any[] = [], - keys: any[] = []; + const stack: any[] = []; + const keys: any[] = []; return JSON.stringify(obj, function (key, value) { /** * forked from https://github.com/moll/json-stringify-safe/blob/master/stringify.js * to deCycle the object */ if (stack.length > 0) { - var thisPos = stack.indexOf(this); + const thisPos = stack.indexOf(this); ~thisPos ? stack.splice(thisPos + 1) : stack.push(this); ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key); if (~stack.indexOf(value)) { - if (stack[0] === value) value = '[Circular ~]'; - else + if (stack[0] === value) { + value = '[Circular ~]'; + } else { value = '[Circular ~.' + keys.slice(0, stack.indexOf(value)).join('.') + ']'; + } } - } else stack.push(value); + } else { + stack.push(value); + } /* END of the FORK */ - if (value === null || value === undefined) return value; + if (value === null || value === undefined) { + return value; + } if (shouldToString(value)) { return toString(value); } if (value instanceof Event) { const eventResult: any = {}; - for (const key in value) { - const eventValue = (value as any)[key]; - if (Array.isArray(eventValue)) - eventResult[key] = pathToSelector( + for (const eventKey in value) { + const eventValue = (value as any)[eventKey]; + if (Array.isArray(eventValue)) { + eventResult[eventKey] = pathToSelector( eventValue.length ? eventValue[0] : null, ); - else eventResult[key] = eventValue; + } else { + eventResult[eventKey] = eventValue; + } } return eventResult; } else if (value instanceof Node) { - if (value instanceof HTMLElement) return value ? value.outerHTML : ''; + if (value instanceof HTMLElement) { + return value ? value.outerHTML : ''; + } return value.nodeName; } return value; @@ -103,21 +117,24 @@ export function stringify( /** * whether we should call toString function of this object */ - function shouldToString(obj: object): boolean { + function shouldToString(_obj: object): boolean { if ( - typeof obj === 'object' && - Object.keys(obj).length > options.numOfKeysLimit - ) + typeof _obj === 'object' && + Object.keys(_obj).length > options.numOfKeysLimit + ) { + return true; + } + if (typeof _obj === 'function') { return true; - if (typeof obj === 'function') return true; + } return false; } /** * limit the toString() result according to option */ - function toString(obj: object): string { - let str = obj.toString(); + function toString(_obj: object): string { + let str = _obj.toString(); if (options.stringLengthLimit && str.length > options.stringLengthLimit) { str = `${str.slice(0, options.stringLengthLimit)}...`; } diff --git a/src/replay/index.ts b/src/replay/index.ts index 1a79d5f982..719b17a677 100644 --- a/src/replay/index.ts +++ b/src/replay/index.ts @@ -52,7 +52,11 @@ const SKIP_TIME_INTERVAL = 5 * 1000; const mitt = (mittProxy as any).default || mittProxy; const REPLAY_CONSOLE_PREFIX = '[replayer]'; -const SCROLL_ATTRIBUTE_NAME = '__rrweb_scroll__'; +const ORIGINAL_ATTRIBUTE_NAME = '__rrweb_original__'; + +type PatchedConsoleLog = { + [ORIGINAL_ATTRIBUTE_NAME]: typeof console.log; +}; const defaultMouseTailConfig = { duration: 500, @@ -141,8 +145,9 @@ export class Replayer { logConfig: defaultLogConfig, }; this.config = Object.assign({}, defaultConfig, config); - if (!this.config.logConfig.replayLogger) + if (!this.config.logConfig.replayLogger) { this.config.logConfig.replayLogger = this.getConsoleLogger(); + } this.handleResize = this.handleResize.bind(this); this.getCastFn = this.getCastFn.bind(this); @@ -1020,8 +1025,9 @@ export class Replayer { try { const logData = e.data as logData; const replayLogger = this.config.logConfig.replayLogger!; - if (typeof replayLogger[logData.level] === 'function') + if (typeof replayLogger[logData.level] === 'function') { replayLogger[logData.level]!(logData); + } } catch (error) { if (this.config.showWarning) { console.warn(error); @@ -1319,7 +1325,9 @@ export class Replayer { * @param data the log data */ private formatMessage(data: logData): string { - if (data.trace.length === 0) return ''; + if (data.trace.length === 0) { + return ''; + } const stackPrefix = '\n\tat '; let result = stackPrefix; result += data.trace.join(stackPrefix); @@ -1330,29 +1338,38 @@ export class Replayer { * generate a console log replayer which implement the interface ReplayLogger */ private getConsoleLogger(): ReplayLogger { - const rrwebOriginal = SCROLL_ATTRIBUTE_NAME; const replayLogger: ReplayLogger = {}; - for (const level of this.config.logConfig.level!) - if (level === 'trace') + for (const level of this.config.logConfig.level!) { + if (level === 'trace') { replayLogger[level] = (data: logData) => { - const logger = (console.log as any)[rrwebOriginal] - ? (console.log as any)[rrwebOriginal] + const logger = ((console.log as unknown) as PatchedConsoleLog)[ + ORIGINAL_ATTRIBUTE_NAME + ] + ? ((console.log as unknown) as PatchedConsoleLog)[ + ORIGINAL_ATTRIBUTE_NAME + ] : console.log; logger( ...data.payload.map((s) => JSON.parse(s)), this.formatMessage(data), ); }; - else + } else { replayLogger[level] = (data: logData) => { - const logger = (console[level] as any)[rrwebOriginal] - ? (console[level] as any)[rrwebOriginal] + const logger = ((console[level] as unknown) as PatchedConsoleLog)[ + ORIGINAL_ATTRIBUTE_NAME + ] + ? ((console[level] as unknown) as PatchedConsoleLog)[ + ORIGINAL_ATTRIBUTE_NAME + ] : console[level]; logger( ...data.payload.map((s) => JSON.parse(s)), this.formatMessage(data), ); }; + } + } return replayLogger; } diff --git a/src/types.ts b/src/types.ts index e9fa13d689..432e29a682 100644 --- a/src/types.ts +++ b/src/types.ts @@ -401,25 +401,25 @@ export type LogLevel = /* fork from interface Console */ // all kinds of console functions export type Logger = { - assert?: (value: any, message?: string, ...optionalParams: any[]) => void; - clear?: () => void; - count?: (label?: string) => void; - countReset?: (label?: string) => void; - debug?: (message?: any, ...optionalParams: any[]) => void; - dir?: (obj: any, options?: NodeJS.InspectOptions) => void; - dirxml?: (...data: any[]) => void; - error?: (message?: any, ...optionalParams: any[]) => void; - group?: (...label: any[]) => void; - groupCollapsed?: (label?: any[]) => void; + assert?: typeof console.assert; + clear?: typeof console.clear; + count?: typeof console.count; + countReset?: typeof console.countReset; + debug?: typeof console.debug; + dir?: typeof console.dir; + dirxml?: typeof console.dirxml; + error?: typeof console.error; + group?: typeof console.group; + groupCollapsed?: typeof console.groupCollapsed; groupEnd?: () => void; - info?: (message?: any, ...optionalParams: any[]) => void; - log?: (message?: any, ...optionalParams: any[]) => void; - table?: (tabularData: any, properties?: ReadonlyArray) => void; - time?: (label?: string) => void; - timeEnd?: (label?: string) => void; - timeLog?: (label?: string, ...data: any[]) => void; - trace?: (message?: any, ...optionalParams: any[]) => void; - warn?: (message?: any, ...optionalParams: any[]) => void; + info?: typeof console.info; + log?: typeof console.log; + table?: typeof console.table; + time?: typeof console.time; + timeEnd?: typeof console.timeEnd; + timeLog?: typeof console.timeLog; + trace?: typeof console.trace; + warn?: typeof console.warn; }; /** @@ -430,8 +430,8 @@ export type ReplayLogger = Partial void>>; export type LogParam = { level: LogLevel; - trace: Array; - payload: Array; + trace: string[]; + payload: string[]; }; export type fontCallback = (p: fontParam) => void; @@ -511,7 +511,7 @@ export type playerConfig = { }; export type LogReplayConfig = { - level?: Array | undefined; + level?: LogLevel[] | undefined; replayLogger: ReplayLogger | undefined; }; @@ -583,7 +583,7 @@ export type StringifyOptions = { }; export type LogRecordOptions = { - level?: Array | undefined; + level?: LogLevel[] | undefined; lengthThreshold?: number; stringifyOptions?: StringifyOptions; logger?: Logger;