plugin API (#598)
* temp: plugin API * fix a bug in the replay handler and rename some type names. * update integration test * improve plugin types and handle legacy log data * use different naming in record and replay bundles * delete unreferenced types Co-authored-by: Lucky Feng <294889365@qq.com>
This commit is contained in:
@@ -33,6 +33,13 @@ function toAllPath(path) {
|
|||||||
return path.replace('rrweb', 'rrweb-all');
|
return path.replace('rrweb', 'rrweb-all');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toPluginPath(pluginName, stage) {
|
||||||
|
return (path) =>
|
||||||
|
path
|
||||||
|
.replace(/^([\w]+)\//, '$1/plugins/')
|
||||||
|
.replace('rrweb', `${pluginName}-${stage}`);
|
||||||
|
}
|
||||||
|
|
||||||
function toMinPath(path) {
|
function toMinPath(path) {
|
||||||
return path.replace(/\.js$/, '.min.js');
|
return path.replace(/\.js$/, '.min.js');
|
||||||
}
|
}
|
||||||
@@ -74,6 +81,16 @@ const baseConfigs = [
|
|||||||
name: 'rrweb',
|
name: 'rrweb',
|
||||||
pathFn: toAllPath,
|
pathFn: toAllPath,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
input: './src/plugins/console/record/index.ts',
|
||||||
|
name: 'rrwebConsoleRecord',
|
||||||
|
pathFn: toPluginPath('console', 'record'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: './src/plugins/console/replay/index.ts',
|
||||||
|
name: 'rrwebConsoleReplay',
|
||||||
|
pathFn: toPluginPath('console', 'replay'),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
let configs = [];
|
let configs = [];
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
export * from '../index';
|
export * from '../index';
|
||||||
export * from '../packer';
|
export * from '../packer';
|
||||||
|
export * from '../plugins/console/record';
|
||||||
|
export * from '../plugins/console/replay';
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// tslint:disable
|
||||||
/**
|
/**
|
||||||
* Class StackFrame is a fork of https://github.com/stacktracejs/stackframe/blob/master/stackframe.js
|
* Class StackFrame is a fork of https://github.com/stacktracejs/stackframe/blob/master/stackframe.js
|
||||||
* I fork it because:
|
* I fork it because:
|
||||||
203
src/plugins/console/record/index.ts
Normal file
203
src/plugins/console/record/index.ts
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
import { listenerHandler, RecordPlugin } from '../../../types';
|
||||||
|
import { stringify } from './stringify';
|
||||||
|
import { StackFrame, ErrorStackParser } from './error-stack-parser';
|
||||||
|
import { patch } from '../../../utils';
|
||||||
|
|
||||||
|
export type StringifyOptions = {
|
||||||
|
// limit of string length
|
||||||
|
stringLengthLimit?: number;
|
||||||
|
/**
|
||||||
|
* limit of number of keys in an object
|
||||||
|
* if an object contains more keys than this limit, we would call its toString function directly
|
||||||
|
*/
|
||||||
|
numOfKeysLimit: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type LogRecordOptions = {
|
||||||
|
level?: LogLevel[] | undefined;
|
||||||
|
lengthThreshold?: number;
|
||||||
|
stringifyOptions?: StringifyOptions;
|
||||||
|
logger?: Logger;
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultLogOptions: LogRecordOptions = {
|
||||||
|
level: [
|
||||||
|
'assert',
|
||||||
|
'clear',
|
||||||
|
'count',
|
||||||
|
'countReset',
|
||||||
|
'debug',
|
||||||
|
'dir',
|
||||||
|
'dirxml',
|
||||||
|
'error',
|
||||||
|
'group',
|
||||||
|
'groupCollapsed',
|
||||||
|
'groupEnd',
|
||||||
|
'info',
|
||||||
|
'log',
|
||||||
|
'table',
|
||||||
|
'time',
|
||||||
|
'timeEnd',
|
||||||
|
'timeLog',
|
||||||
|
'trace',
|
||||||
|
'warn',
|
||||||
|
],
|
||||||
|
lengthThreshold: 1000,
|
||||||
|
logger: console,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LogData = {
|
||||||
|
level: LogLevel;
|
||||||
|
trace: string[];
|
||||||
|
payload: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type logCallback = (p: LogData) => void;
|
||||||
|
|
||||||
|
export type LogLevel =
|
||||||
|
| 'assert'
|
||||||
|
| 'clear'
|
||||||
|
| 'count'
|
||||||
|
| 'countReset'
|
||||||
|
| 'debug'
|
||||||
|
| 'dir'
|
||||||
|
| 'dirxml'
|
||||||
|
| 'error'
|
||||||
|
| 'group'
|
||||||
|
| 'groupCollapsed'
|
||||||
|
| 'groupEnd'
|
||||||
|
| 'info'
|
||||||
|
| 'log'
|
||||||
|
| 'table'
|
||||||
|
| 'time'
|
||||||
|
| 'timeEnd'
|
||||||
|
| 'timeLog'
|
||||||
|
| 'trace'
|
||||||
|
| 'warn';
|
||||||
|
|
||||||
|
/* fork from interface Console */
|
||||||
|
// all kinds of console functions
|
||||||
|
export type Logger = {
|
||||||
|
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?: 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
function initLogObserver(
|
||||||
|
cb: logCallback,
|
||||||
|
logOptions: LogRecordOptions,
|
||||||
|
): listenerHandler {
|
||||||
|
const logger = logOptions.logger;
|
||||||
|
if (!logger) {
|
||||||
|
return () => {};
|
||||||
|
}
|
||||||
|
let logCount = 0;
|
||||||
|
const cancelHandlers: listenerHandler[] = [];
|
||||||
|
// add listener to thrown errors
|
||||||
|
if (logOptions.level!.includes('error')) {
|
||||||
|
if (window) {
|
||||||
|
const originalOnError = window.onerror;
|
||||||
|
window.onerror = (
|
||||||
|
msg: Event | string,
|
||||||
|
file: string,
|
||||||
|
line: number,
|
||||||
|
col: number,
|
||||||
|
error: Error,
|
||||||
|
) => {
|
||||||
|
if (originalOnError) {
|
||||||
|
originalOnError.apply(this, [msg, file, line, col, error]);
|
||||||
|
}
|
||||||
|
const trace: string[] = ErrorStackParser.parse(
|
||||||
|
error,
|
||||||
|
).map((stackFrame: StackFrame) => stackFrame.toString());
|
||||||
|
const payload = [stringify(msg, logOptions.stringifyOptions)];
|
||||||
|
cb({
|
||||||
|
level: 'error',
|
||||||
|
trace,
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
cancelHandlers.push(() => {
|
||||||
|
window.onerror = originalOnError;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const levelType of logOptions.level!) {
|
||||||
|
cancelHandlers.push(replace(logger, levelType));
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
cancelHandlers.forEach((h) => h());
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* replace the original console function and record logs
|
||||||
|
* @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 () => {};
|
||||||
|
}
|
||||||
|
// replace the logger.{level}. return a restore function
|
||||||
|
return patch(_logger, level, (original) => {
|
||||||
|
return (...args: Array<unknown>) => {
|
||||||
|
original.apply(this, args);
|
||||||
|
try {
|
||||||
|
const trace = ErrorStackParser.parse(new Error())
|
||||||
|
.map((stackFrame: StackFrame) => stackFrame.toString())
|
||||||
|
.splice(1); // splice(1) to omit the hijacked log function
|
||||||
|
const payload = args.map((s) =>
|
||||||
|
stringify(s, logOptions.stringifyOptions),
|
||||||
|
);
|
||||||
|
logCount++;
|
||||||
|
if (logCount < logOptions.lengthThreshold!) {
|
||||||
|
cb({
|
||||||
|
level,
|
||||||
|
trace,
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
} else if (logCount === logOptions.lengthThreshold) {
|
||||||
|
// notify the user
|
||||||
|
cb({
|
||||||
|
level: 'warn',
|
||||||
|
trace: [],
|
||||||
|
payload: [
|
||||||
|
stringify('The number of log records reached the threshold.'),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
original('rrweb logger error:', error, ...args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PLUGIN_NAME = 'rrweb/console@1';
|
||||||
|
|
||||||
|
export const getRecordConsolePlugin: (
|
||||||
|
options?: LogRecordOptions,
|
||||||
|
) => RecordPlugin = (options) => ({
|
||||||
|
name: PLUGIN_NAME,
|
||||||
|
observer: initLogObserver,
|
||||||
|
options: options
|
||||||
|
? Object.assign({}, defaultLogOptions, options)
|
||||||
|
: defaultLogOptions,
|
||||||
|
});
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { StringifyOptions } from '../types';
|
import { StringifyOptions } from './index';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* transfer the node path in Event to string
|
* transfer the node path in Event to string
|
||||||
144
src/plugins/console/replay/index.ts
Normal file
144
src/plugins/console/replay/index.ts
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
import { LogLevel, LogData, PLUGIN_NAME } from '../record';
|
||||||
|
import {
|
||||||
|
eventWithTime,
|
||||||
|
EventType,
|
||||||
|
IncrementalSource,
|
||||||
|
ReplayPlugin,
|
||||||
|
} from '../../../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* define an interface to replay log records
|
||||||
|
* (data: logData) => void> function to display the log data
|
||||||
|
*/
|
||||||
|
type ReplayLogger = Partial<Record<LogLevel, (data: LogData) => void>>;
|
||||||
|
|
||||||
|
type LogReplayConfig = {
|
||||||
|
level?: LogLevel[] | undefined;
|
||||||
|
replayLogger: ReplayLogger | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ORIGINAL_ATTRIBUTE_NAME = '__rrweb_original__';
|
||||||
|
type PatchedConsoleLog = {
|
||||||
|
[ORIGINAL_ATTRIBUTE_NAME]: typeof console.log;
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultLogConfig: LogReplayConfig = {
|
||||||
|
level: [
|
||||||
|
'assert',
|
||||||
|
'clear',
|
||||||
|
'count',
|
||||||
|
'countReset',
|
||||||
|
'debug',
|
||||||
|
'dir',
|
||||||
|
'dirxml',
|
||||||
|
'error',
|
||||||
|
'group',
|
||||||
|
'groupCollapsed',
|
||||||
|
'groupEnd',
|
||||||
|
'info',
|
||||||
|
'log',
|
||||||
|
'table',
|
||||||
|
'time',
|
||||||
|
'timeEnd',
|
||||||
|
'timeLog',
|
||||||
|
'trace',
|
||||||
|
'warn',
|
||||||
|
],
|
||||||
|
replayLogger: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
class LogReplayPlugin {
|
||||||
|
private config: LogReplayConfig;
|
||||||
|
|
||||||
|
constructor(config?: LogReplayConfig) {
|
||||||
|
this.config = Object.assign(defaultLogConfig, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* generate a console log replayer which implement the interface ReplayLogger
|
||||||
|
*/
|
||||||
|
public getConsoleLogger(): ReplayLogger {
|
||||||
|
const replayLogger: ReplayLogger = {};
|
||||||
|
for (const level of this.config.level!) {
|
||||||
|
if (level === 'trace') {
|
||||||
|
replayLogger[level] = (data: LogData) => {
|
||||||
|
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 {
|
||||||
|
replayLogger[level] = (data: LogData) => {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* format the trace data to a string
|
||||||
|
* @param data the log data
|
||||||
|
*/
|
||||||
|
private formatMessage(data: LogData): string {
|
||||||
|
if (data.trace.length === 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const stackPrefix = '\n\tat ';
|
||||||
|
let result = stackPrefix;
|
||||||
|
result += data.trace.join(stackPrefix);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getLogReplayPlugin: (options?: LogReplayConfig) => ReplayPlugin = (
|
||||||
|
options,
|
||||||
|
) => {
|
||||||
|
const replayLogger =
|
||||||
|
options?.replayLogger || new LogReplayPlugin(options).getConsoleLogger();
|
||||||
|
|
||||||
|
return {
|
||||||
|
handler(event: eventWithTime, _isSync, context) {
|
||||||
|
let logData: LogData | null = null;
|
||||||
|
if (
|
||||||
|
event.type === EventType.IncrementalSnapshot &&
|
||||||
|
event.data.source === (IncrementalSource.Log as IncrementalSource)
|
||||||
|
) {
|
||||||
|
logData = (event.data as unknown) as LogData;
|
||||||
|
} else if (
|
||||||
|
event.type === EventType.Plugin &&
|
||||||
|
event.data.plugin === PLUGIN_NAME
|
||||||
|
) {
|
||||||
|
logData = event.data.payload as LogData;
|
||||||
|
}
|
||||||
|
if (logData) {
|
||||||
|
try {
|
||||||
|
if (typeof replayLogger[logData.level] === 'function') {
|
||||||
|
replayLogger[logData.level]!(logData);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (context.replayer.config.showWarning) {
|
||||||
|
console.warn(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -16,7 +16,6 @@ import {
|
|||||||
recordOptions,
|
recordOptions,
|
||||||
IncrementalSource,
|
IncrementalSource,
|
||||||
listenerHandler,
|
listenerHandler,
|
||||||
LogRecordOptions,
|
|
||||||
mutationCallbackParam,
|
mutationCallbackParam,
|
||||||
scrollCallback,
|
scrollCallback,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
@@ -59,7 +58,7 @@ function record<T = eventWithTime>(
|
|||||||
mousemoveWait,
|
mousemoveWait,
|
||||||
recordCanvas = false,
|
recordCanvas = false,
|
||||||
collectFonts = false,
|
collectFonts = false,
|
||||||
recordLog = false,
|
plugins,
|
||||||
} = options;
|
} = options;
|
||||||
// runtime checks for user options
|
// runtime checks for user options
|
||||||
if (!emit) {
|
if (!emit) {
|
||||||
@@ -113,37 +112,6 @@ function record<T = eventWithTime>(
|
|||||||
: _slimDOMOptions
|
: _slimDOMOptions
|
||||||
? _slimDOMOptions
|
? _slimDOMOptions
|
||||||
: {};
|
: {};
|
||||||
const defaultLogOptions: LogRecordOptions = {
|
|
||||||
level: [
|
|
||||||
'assert',
|
|
||||||
'clear',
|
|
||||||
'count',
|
|
||||||
'countReset',
|
|
||||||
'debug',
|
|
||||||
'dir',
|
|
||||||
'dirxml',
|
|
||||||
'error',
|
|
||||||
'group',
|
|
||||||
'groupCollapsed',
|
|
||||||
'groupEnd',
|
|
||||||
'info',
|
|
||||||
'log',
|
|
||||||
'table',
|
|
||||||
'time',
|
|
||||||
'timeEnd',
|
|
||||||
'timeLog',
|
|
||||||
'trace',
|
|
||||||
'warn',
|
|
||||||
],
|
|
||||||
lengthThreshold: 1000,
|
|
||||||
logger: console,
|
|
||||||
};
|
|
||||||
|
|
||||||
const logOptions: LogRecordOptions = recordLog
|
|
||||||
? recordLog === true
|
|
||||||
? defaultLogOptions
|
|
||||||
: Object.assign({}, defaultLogOptions, recordLog)
|
|
||||||
: {};
|
|
||||||
|
|
||||||
polyfill();
|
polyfill();
|
||||||
|
|
||||||
@@ -400,16 +368,6 @@ function record<T = eventWithTime>(
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
logCb: (p) =>
|
|
||||||
wrappedEmit(
|
|
||||||
wrapEvent({
|
|
||||||
type: EventType.IncrementalSnapshot,
|
|
||||||
data: {
|
|
||||||
source: IncrementalSource.Log,
|
|
||||||
...p,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
blockClass,
|
blockClass,
|
||||||
ignoreClass,
|
ignoreClass,
|
||||||
maskTextClass,
|
maskTextClass,
|
||||||
@@ -422,12 +380,26 @@ function record<T = eventWithTime>(
|
|||||||
doc,
|
doc,
|
||||||
maskInputFn,
|
maskInputFn,
|
||||||
maskTextFn,
|
maskTextFn,
|
||||||
logOptions,
|
|
||||||
blockSelector,
|
blockSelector,
|
||||||
slimDOMOptions,
|
slimDOMOptions,
|
||||||
mirror,
|
mirror,
|
||||||
iframeManager,
|
iframeManager,
|
||||||
shadowDomManager,
|
shadowDomManager,
|
||||||
|
plugins:
|
||||||
|
plugins?.map((p) => ({
|
||||||
|
observer: p.observer,
|
||||||
|
options: p.options,
|
||||||
|
callback: (payload: object) =>
|
||||||
|
wrappedEmit(
|
||||||
|
wrapEvent({
|
||||||
|
type: EventType.Plugin,
|
||||||
|
data: {
|
||||||
|
plugin: p.name,
|
||||||
|
payload,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
})) || [],
|
||||||
},
|
},
|
||||||
hooks,
|
hooks,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -37,17 +37,11 @@ import {
|
|||||||
fontParam,
|
fontParam,
|
||||||
MaskInputFn,
|
MaskInputFn,
|
||||||
MaskTextFn,
|
MaskTextFn,
|
||||||
logCallback,
|
|
||||||
LogRecordOptions,
|
|
||||||
Logger,
|
|
||||||
LogLevel,
|
|
||||||
Mirror,
|
Mirror,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import MutationBuffer from './mutation';
|
import MutationBuffer from './mutation';
|
||||||
import { stringify } from './stringify';
|
|
||||||
import { IframeManager } from './iframe-manager';
|
import { IframeManager } from './iframe-manager';
|
||||||
import { ShadowDomManager } from './shadow-dom-manager';
|
import { ShadowDomManager } from './shadow-dom-manager';
|
||||||
import { StackFrame, ErrorStackParser } from './error-stack-parser';
|
|
||||||
|
|
||||||
type WindowWithStoredMutationObserver = Window & {
|
type WindowWithStoredMutationObserver = Window & {
|
||||||
__rrMutationObserver?: MutationObserver;
|
__rrMutationObserver?: MutationObserver;
|
||||||
@@ -535,11 +529,16 @@ function initCanvasMutationObserver(
|
|||||||
recordArgs[0] &&
|
recordArgs[0] &&
|
||||||
recordArgs[0] instanceof HTMLCanvasElement
|
recordArgs[0] instanceof HTMLCanvasElement
|
||||||
) {
|
) {
|
||||||
const canvas = recordArgs[0]
|
const canvas = recordArgs[0];
|
||||||
const ctx = canvas.getContext('2d')
|
const ctx = canvas.getContext('2d');
|
||||||
let imgd = ctx?.getImageData(0, 0, canvas.width, canvas.height)
|
let imgd = ctx?.getImageData(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
canvas.width,
|
||||||
|
canvas.height,
|
||||||
|
);
|
||||||
let pix = imgd?.data;
|
let pix = imgd?.data;
|
||||||
recordArgs[0] = JSON.stringify(pix)
|
recordArgs[0] = JSON.stringify(pix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cb({
|
cb({
|
||||||
@@ -627,97 +626,6 @@ function initFontObserver(cb: fontCallback): listenerHandler {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function initLogObserver(
|
|
||||||
cb: logCallback,
|
|
||||||
logOptions: LogRecordOptions,
|
|
||||||
): listenerHandler {
|
|
||||||
const logger = logOptions.logger;
|
|
||||||
if (!logger) {
|
|
||||||
return () => {};
|
|
||||||
}
|
|
||||||
let logCount = 0;
|
|
||||||
const cancelHandlers: listenerHandler[] = [];
|
|
||||||
// add listener to thrown errors
|
|
||||||
if (logOptions.level!.includes('error')) {
|
|
||||||
if (window) {
|
|
||||||
const originalOnError = window.onerror;
|
|
||||||
window.onerror = (
|
|
||||||
msg: Event | string,
|
|
||||||
file: string,
|
|
||||||
line: number,
|
|
||||||
col: number,
|
|
||||||
error: Error,
|
|
||||||
) => {
|
|
||||||
if (originalOnError) {
|
|
||||||
originalOnError.apply(this, [msg, file, line, col, error]);
|
|
||||||
}
|
|
||||||
const trace: string[] = ErrorStackParser.parse(
|
|
||||||
error,
|
|
||||||
).map((stackFrame: StackFrame) => stackFrame.toString());
|
|
||||||
const payload = [stringify(msg, logOptions.stringifyOptions)];
|
|
||||||
cb({
|
|
||||||
level: 'error',
|
|
||||||
trace,
|
|
||||||
payload,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
cancelHandlers.push(() => {
|
|
||||||
window.onerror = originalOnError;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const levelType of logOptions.level!) {
|
|
||||||
cancelHandlers.push(replace(logger, levelType));
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
cancelHandlers.forEach((h) => h());
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* replace the original console function and record logs
|
|
||||||
* @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 () => {};
|
|
||||||
}
|
|
||||||
// replace the logger.{level}. return a restore function
|
|
||||||
return patch(_logger, level, (original) => {
|
|
||||||
return (...args: unknown[]) => {
|
|
||||||
original.apply(this, args);
|
|
||||||
try {
|
|
||||||
const trace = ErrorStackParser.parse(new Error())
|
|
||||||
.map((stackFrame: StackFrame) => stackFrame.toString())
|
|
||||||
.splice(1); // splice(1) to omit the hijacked log function
|
|
||||||
const payload = args.map((s) =>
|
|
||||||
stringify(s, logOptions.stringifyOptions),
|
|
||||||
);
|
|
||||||
logCount++;
|
|
||||||
if (logCount < logOptions.lengthThreshold!) {
|
|
||||||
cb({
|
|
||||||
level,
|
|
||||||
trace,
|
|
||||||
payload,
|
|
||||||
});
|
|
||||||
} else if (logCount === logOptions.lengthThreshold) {
|
|
||||||
// notify the user
|
|
||||||
cb({
|
|
||||||
level: 'warn',
|
|
||||||
trace: [],
|
|
||||||
payload: [
|
|
||||||
stringify('The number of log records reached the threshold.'),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
original('rrweb logger error:', error, ...args);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function mergeHooks(o: observerParam, hooks: hooksParam) {
|
function mergeHooks(o: observerParam, hooks: hooksParam) {
|
||||||
const {
|
const {
|
||||||
mutationCb,
|
mutationCb,
|
||||||
@@ -730,7 +638,6 @@ function mergeHooks(o: observerParam, hooks: hooksParam) {
|
|||||||
styleSheetRuleCb,
|
styleSheetRuleCb,
|
||||||
canvasMutationCb,
|
canvasMutationCb,
|
||||||
fontCb,
|
fontCb,
|
||||||
logCb,
|
|
||||||
} = o;
|
} = o;
|
||||||
o.mutationCb = (...p: Arguments<mutationCallBack>) => {
|
o.mutationCb = (...p: Arguments<mutationCallBack>) => {
|
||||||
if (hooks.mutation) {
|
if (hooks.mutation) {
|
||||||
@@ -792,12 +699,6 @@ function mergeHooks(o: observerParam, hooks: hooksParam) {
|
|||||||
}
|
}
|
||||||
fontCb(...p);
|
fontCb(...p);
|
||||||
};
|
};
|
||||||
o.logCb = (...p: Arguments<logCallback>) => {
|
|
||||||
if (hooks.log) {
|
|
||||||
hooks.log(...p);
|
|
||||||
}
|
|
||||||
logCb(...p);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initObservers(
|
export function initObservers(
|
||||||
@@ -866,9 +767,11 @@ export function initObservers(
|
|||||||
? initCanvasMutationObserver(o.canvasMutationCb, o.blockClass, o.mirror)
|
? initCanvasMutationObserver(o.canvasMutationCb, o.blockClass, o.mirror)
|
||||||
: () => {};
|
: () => {};
|
||||||
const fontObserver = o.collectFonts ? initFontObserver(o.fontCb) : () => {};
|
const fontObserver = o.collectFonts ? initFontObserver(o.fontCb) : () => {};
|
||||||
const logObserver = o.logOptions
|
// plugins
|
||||||
? initLogObserver(o.logCb, o.logOptions)
|
const pluginHandlers: listenerHandler[] = [];
|
||||||
: () => {};
|
for (const plugin of o.plugins) {
|
||||||
|
pluginHandlers.push(plugin.observer(plugin.callback, plugin.options));
|
||||||
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
mutationObserver.disconnect();
|
mutationObserver.disconnect();
|
||||||
@@ -881,6 +784,6 @@ export function initObservers(
|
|||||||
styleSheetObserver();
|
styleSheetObserver();
|
||||||
canvasMutationObserver();
|
canvasMutationObserver();
|
||||||
fontObserver();
|
fontObserver();
|
||||||
logObserver();
|
pluginHandlers.forEach((h) => h());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,9 +28,6 @@ import {
|
|||||||
canvasMutationData,
|
canvasMutationData,
|
||||||
Mirror,
|
Mirror,
|
||||||
ElementState,
|
ElementState,
|
||||||
LogReplayConfig,
|
|
||||||
logData,
|
|
||||||
ReplayLogger,
|
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import {
|
import {
|
||||||
createMirror,
|
createMirror,
|
||||||
@@ -54,11 +51,6 @@ const SKIP_TIME_INTERVAL = 5 * 1000;
|
|||||||
const mitt = (mittProxy as any).default || mittProxy;
|
const mitt = (mittProxy as any).default || mittProxy;
|
||||||
|
|
||||||
const REPLAY_CONSOLE_PREFIX = '[replayer]';
|
const REPLAY_CONSOLE_PREFIX = '[replayer]';
|
||||||
const ORIGINAL_ATTRIBUTE_NAME = '__rrweb_original__';
|
|
||||||
|
|
||||||
type PatchedConsoleLog = {
|
|
||||||
[ORIGINAL_ATTRIBUTE_NAME]: typeof console.log;
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultMouseTailConfig = {
|
const defaultMouseTailConfig = {
|
||||||
duration: 500,
|
duration: 500,
|
||||||
@@ -67,31 +59,6 @@ const defaultMouseTailConfig = {
|
|||||||
strokeStyle: 'red',
|
strokeStyle: 'red',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const defaultLogConfig: LogReplayConfig = {
|
|
||||||
level: [
|
|
||||||
'assert',
|
|
||||||
'clear',
|
|
||||||
'count',
|
|
||||||
'countReset',
|
|
||||||
'debug',
|
|
||||||
'dir',
|
|
||||||
'dirxml',
|
|
||||||
'error',
|
|
||||||
'group',
|
|
||||||
'groupCollapsed',
|
|
||||||
'groupEnd',
|
|
||||||
'info',
|
|
||||||
'log',
|
|
||||||
'table',
|
|
||||||
'time',
|
|
||||||
'timeEnd',
|
|
||||||
'timeLog',
|
|
||||||
'trace',
|
|
||||||
'warn',
|
|
||||||
],
|
|
||||||
replayLogger: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
export class Replayer {
|
export class Replayer {
|
||||||
public wrapper: HTMLDivElement;
|
public wrapper: HTMLDivElement;
|
||||||
public iframe: HTMLIFrameElement;
|
public iframe: HTMLIFrameElement;
|
||||||
@@ -149,12 +116,8 @@ export class Replayer {
|
|||||||
UNSAFE_replayCanvas: false,
|
UNSAFE_replayCanvas: false,
|
||||||
pauseAnimation: true,
|
pauseAnimation: true,
|
||||||
mouseTail: defaultMouseTailConfig,
|
mouseTail: defaultMouseTailConfig,
|
||||||
logConfig: defaultLogConfig,
|
|
||||||
};
|
};
|
||||||
this.config = Object.assign({}, defaultConfig, config);
|
this.config = Object.assign({}, defaultConfig, config);
|
||||||
if (!this.config.logConfig.replayLogger) {
|
|
||||||
this.config.logConfig.replayLogger = this.getConsoleLogger();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.handleResize = this.handleResize.bind(this);
|
this.handleResize = this.handleResize.bind(this);
|
||||||
this.getCastFn = this.getCastFn.bind(this);
|
this.getCastFn = this.getCastFn.bind(this);
|
||||||
@@ -522,6 +485,11 @@ export class Replayer {
|
|||||||
if (castFn) {
|
if (castFn) {
|
||||||
castFn();
|
castFn();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const plugin of this.config.plugins || []) {
|
||||||
|
plugin.handler(event, isSync, { replayer: this });
|
||||||
|
}
|
||||||
|
|
||||||
this.service.send({ type: 'CAST_EVENT', payload: { event } });
|
this.service.send({ type: 'CAST_EVENT', payload: { event } });
|
||||||
|
|
||||||
// events are kept sorted by timestamp, check if this is the last event
|
// events are kept sorted by timestamp, check if this is the last event
|
||||||
@@ -1046,19 +1014,6 @@ export class Replayer {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case IncrementalSource.Log: {
|
|
||||||
try {
|
|
||||||
const logData = e.data as logData;
|
|
||||||
const replayLogger = this.config.logConfig.replayLogger!;
|
|
||||||
if (typeof replayLogger[logData.level] === 'function') {
|
|
||||||
replayLogger[logData.level]!(logData);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (this.config.showWarning) {
|
|
||||||
console.warn(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1378,59 +1333,6 @@ export class Replayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* format the trace data to a string
|
|
||||||
* @param data the log data
|
|
||||||
*/
|
|
||||||
private formatMessage(data: logData): string {
|
|
||||||
if (data.trace.length === 0) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
const stackPrefix = '\n\tat ';
|
|
||||||
let result = stackPrefix;
|
|
||||||
result += data.trace.join(stackPrefix);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* generate a console log replayer which implement the interface ReplayLogger
|
|
||||||
*/
|
|
||||||
private getConsoleLogger(): ReplayLogger {
|
|
||||||
const replayLogger: ReplayLogger = {};
|
|
||||||
for (const level of this.config.logConfig.level!) {
|
|
||||||
if (level === 'trace') {
|
|
||||||
replayLogger[level] = (data: logData) => {
|
|
||||||
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 {
|
|
||||||
replayLogger[level] = (data: logData) => {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
private legacy_resolveMissingNode(
|
private legacy_resolveMissingNode(
|
||||||
map: missingNodeMap,
|
map: missingNodeMap,
|
||||||
parent: Node,
|
parent: Node,
|
||||||
|
|||||||
134
src/types.ts
134
src/types.ts
@@ -9,6 +9,7 @@ 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';
|
||||||
import { ShadowDomManager } from './record/shadow-dom-manager';
|
import { ShadowDomManager } from './record/shadow-dom-manager';
|
||||||
|
import type { Replayer } from './replay';
|
||||||
|
|
||||||
export enum EventType {
|
export enum EventType {
|
||||||
DomContentLoaded,
|
DomContentLoaded,
|
||||||
@@ -17,6 +18,7 @@ export enum EventType {
|
|||||||
IncrementalSnapshot,
|
IncrementalSnapshot,
|
||||||
Meta,
|
Meta,
|
||||||
Custom,
|
Custom,
|
||||||
|
Plugin,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type domContentLoadedEvent = {
|
export type domContentLoadedEvent = {
|
||||||
@@ -54,11 +56,6 @@ export type metaEvent = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type logEvent = {
|
|
||||||
type: EventType.IncrementalSnapshot;
|
|
||||||
data: incrementalData;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type customEvent<T = unknown> = {
|
export type customEvent<T = unknown> = {
|
||||||
type: EventType.Custom;
|
type: EventType.Custom;
|
||||||
data: {
|
data: {
|
||||||
@@ -67,6 +64,14 @@ export type customEvent<T = unknown> = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type pluginEvent<T = unknown> = {
|
||||||
|
type: EventType.Plugin;
|
||||||
|
data: {
|
||||||
|
plugin: string;
|
||||||
|
payload: T;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export type styleSheetEvent = {};
|
export type styleSheetEvent = {};
|
||||||
|
|
||||||
export enum IncrementalSource {
|
export enum IncrementalSource {
|
||||||
@@ -130,10 +135,6 @@ export type fontData = {
|
|||||||
source: IncrementalSource.Font;
|
source: IncrementalSource.Font;
|
||||||
} & fontParam;
|
} & fontParam;
|
||||||
|
|
||||||
export type logData = {
|
|
||||||
source: IncrementalSource.Log;
|
|
||||||
} & LogParam;
|
|
||||||
|
|
||||||
export type incrementalData =
|
export type incrementalData =
|
||||||
| mutationData
|
| mutationData
|
||||||
| mousemoveData
|
| mousemoveData
|
||||||
@@ -144,8 +145,7 @@ export type incrementalData =
|
|||||||
| mediaInteractionData
|
| mediaInteractionData
|
||||||
| styleSheetRuleData
|
| styleSheetRuleData
|
||||||
| canvasMutationData
|
| canvasMutationData
|
||||||
| fontData
|
| fontData;
|
||||||
| logData;
|
|
||||||
|
|
||||||
export type event =
|
export type event =
|
||||||
| domContentLoadedEvent
|
| domContentLoadedEvent
|
||||||
@@ -153,8 +153,8 @@ export type event =
|
|||||||
| fullSnapshotEvent
|
| fullSnapshotEvent
|
||||||
| incrementalSnapshotEvent
|
| incrementalSnapshotEvent
|
||||||
| metaEvent
|
| metaEvent
|
||||||
| logEvent
|
| customEvent
|
||||||
| customEvent;
|
| pluginEvent;
|
||||||
|
|
||||||
export type eventWithTime = event & {
|
export type eventWithTime = event & {
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
@@ -191,6 +191,12 @@ export type SamplingStrategy = Partial<{
|
|||||||
input: 'all' | 'last';
|
input: 'all' | 'last';
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
export type RecordPlugin<TOptions = unknown> = {
|
||||||
|
name: string;
|
||||||
|
observer: (cb: Function, options: TOptions) => listenerHandler;
|
||||||
|
options: TOptions;
|
||||||
|
};
|
||||||
|
|
||||||
export type recordOptions<T> = {
|
export type recordOptions<T> = {
|
||||||
emit?: (e: T, isCheckout?: boolean) => void;
|
emit?: (e: T, isCheckout?: boolean) => void;
|
||||||
checkoutEveryNth?: number;
|
checkoutEveryNth?: number;
|
||||||
@@ -211,9 +217,9 @@ export type recordOptions<T> = {
|
|||||||
sampling?: SamplingStrategy;
|
sampling?: SamplingStrategy;
|
||||||
recordCanvas?: boolean;
|
recordCanvas?: boolean;
|
||||||
collectFonts?: boolean;
|
collectFonts?: boolean;
|
||||||
|
plugins?: RecordPlugin[];
|
||||||
// departed, please use sampling options
|
// departed, please use sampling options
|
||||||
mousemoveWait?: number;
|
mousemoveWait?: number;
|
||||||
recordLog?: boolean | LogRecordOptions;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type observerParam = {
|
export type observerParam = {
|
||||||
@@ -236,8 +242,6 @@ export type observerParam = {
|
|||||||
styleSheetRuleCb: styleSheetRuleCallback;
|
styleSheetRuleCb: styleSheetRuleCallback;
|
||||||
canvasMutationCb: canvasMutationCallback;
|
canvasMutationCb: canvasMutationCallback;
|
||||||
fontCb: fontCallback;
|
fontCb: fontCallback;
|
||||||
logCb: logCallback;
|
|
||||||
logOptions: LogRecordOptions;
|
|
||||||
sampling: SamplingStrategy;
|
sampling: SamplingStrategy;
|
||||||
recordCanvas: boolean;
|
recordCanvas: boolean;
|
||||||
collectFonts: boolean;
|
collectFonts: boolean;
|
||||||
@@ -246,6 +250,11 @@ export type observerParam = {
|
|||||||
mirror: Mirror;
|
mirror: Mirror;
|
||||||
iframeManager: IframeManager;
|
iframeManager: IframeManager;
|
||||||
shadowDomManager: ShadowDomManager;
|
shadowDomManager: ShadowDomManager;
|
||||||
|
plugins: Array<{
|
||||||
|
observer: Function;
|
||||||
|
callback: Function;
|
||||||
|
options: unknown;
|
||||||
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type hooksParam = {
|
export type hooksParam = {
|
||||||
@@ -259,7 +268,6 @@ export type hooksParam = {
|
|||||||
styleSheetRule?: styleSheetRuleCallback;
|
styleSheetRule?: styleSheetRuleCallback;
|
||||||
canvasMutation?: canvasMutationCallback;
|
canvasMutation?: canvasMutationCallback;
|
||||||
font?: fontCallback;
|
font?: fontCallback;
|
||||||
log?: logCallback;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://dom.spec.whatwg.org/#interface-mutationrecord
|
// https://dom.spec.whatwg.org/#interface-mutationrecord
|
||||||
@@ -396,67 +404,8 @@ export type fontParam = {
|
|||||||
descriptors?: FontFaceDescriptors;
|
descriptors?: FontFaceDescriptors;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LogLevel =
|
|
||||||
| 'assert'
|
|
||||||
| 'clear'
|
|
||||||
| 'count'
|
|
||||||
| 'countReset'
|
|
||||||
| 'debug'
|
|
||||||
| 'dir'
|
|
||||||
| 'dirxml'
|
|
||||||
| 'error'
|
|
||||||
| 'group'
|
|
||||||
| 'groupCollapsed'
|
|
||||||
| 'groupEnd'
|
|
||||||
| 'info'
|
|
||||||
| 'log'
|
|
||||||
| 'table'
|
|
||||||
| 'time'
|
|
||||||
| 'timeEnd'
|
|
||||||
| 'timeLog'
|
|
||||||
| 'trace'
|
|
||||||
| 'warn';
|
|
||||||
|
|
||||||
/* fork from interface Console */
|
|
||||||
// all kinds of console functions
|
|
||||||
export type Logger = {
|
|
||||||
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?: 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;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* define an interface to replay log records
|
|
||||||
* (data: logData) => void> function to display the log data
|
|
||||||
*/
|
|
||||||
export type ReplayLogger = Partial<Record<LogLevel, (data: logData) => void>>;
|
|
||||||
|
|
||||||
export type LogParam = {
|
|
||||||
level: LogLevel;
|
|
||||||
trace: string[];
|
|
||||||
payload: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type fontCallback = (p: fontParam) => void;
|
export type fontCallback = (p: fontParam) => void;
|
||||||
|
|
||||||
export type logCallback = (p: LogParam) => void;
|
|
||||||
|
|
||||||
export type viewportResizeDimension = {
|
export type viewportResizeDimension = {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
@@ -480,7 +429,7 @@ export const enum MediaInteractions {
|
|||||||
export type mediaInteractionParam = {
|
export type mediaInteractionParam = {
|
||||||
type: MediaInteractions;
|
type: MediaInteractions;
|
||||||
id: number;
|
id: number;
|
||||||
currentTime?: number
|
currentTime?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type mediaInteractionCallback = (p: mediaInteractionParam) => void;
|
export type mediaInteractionCallback = (p: mediaInteractionParam) => void;
|
||||||
@@ -511,6 +460,13 @@ export type throttleOptions = {
|
|||||||
export type listenerHandler = () => void;
|
export type listenerHandler = () => void;
|
||||||
export type hookResetter = () => void;
|
export type hookResetter = () => void;
|
||||||
|
|
||||||
|
export type ReplayPlugin = {
|
||||||
|
handler: (
|
||||||
|
event: eventWithTime,
|
||||||
|
isSync: boolean,
|
||||||
|
context: { replayer: Replayer },
|
||||||
|
) => void;
|
||||||
|
};
|
||||||
export type playerConfig = {
|
export type playerConfig = {
|
||||||
speed: number;
|
speed: number;
|
||||||
maxSpeed: number;
|
maxSpeed: number;
|
||||||
@@ -534,12 +490,7 @@ export type playerConfig = {
|
|||||||
strokeStyle?: string;
|
strokeStyle?: string;
|
||||||
};
|
};
|
||||||
unpackFn?: UnpackFn;
|
unpackFn?: UnpackFn;
|
||||||
logConfig: LogReplayConfig;
|
plugins?: ReplayPlugin[];
|
||||||
};
|
|
||||||
|
|
||||||
export type LogReplayConfig = {
|
|
||||||
level?: LogLevel[] | undefined;
|
|
||||||
replayLogger: ReplayLogger | undefined;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type playerMetaData = {
|
export type playerMetaData = {
|
||||||
@@ -601,20 +552,3 @@ export type ElementState = {
|
|||||||
// [scrollLeft,scrollTop]
|
// [scrollLeft,scrollTop]
|
||||||
scroll?: [number, number];
|
scroll?: [number, number];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type StringifyOptions = {
|
|
||||||
// limit of string length
|
|
||||||
stringLengthLimit?: number;
|
|
||||||
/**
|
|
||||||
* limit of number of keys in an object
|
|
||||||
* if an object contains more keys than this limit, we would call its toString function directly
|
|
||||||
*/
|
|
||||||
numOfKeysLimit: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type LogRecordOptions = {
|
|
||||||
level?: LogLevel[] | undefined;
|
|
||||||
lengthThreshold?: number;
|
|
||||||
stringifyOptions?: StringifyOptions;
|
|
||||||
logger?: Logger;
|
|
||||||
};
|
|
||||||
|
|||||||
25
src/utils.ts
25
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
|
||||||
@@ -322,6 +322,7 @@ export function needCastInSyncMode(event: eventWithTime): boolean {
|
|||||||
return false;
|
return false;
|
||||||
case EventType.FullSnapshot:
|
case EventType.FullSnapshot:
|
||||||
case EventType.Meta:
|
case EventType.Meta:
|
||||||
|
case EventType.Plugin:
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -395,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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -459,12 +460,8 @@ export class TreeIndex {
|
|||||||
scrollMap: TreeIndex['scrollMap'];
|
scrollMap: TreeIndex['scrollMap'];
|
||||||
inputMap: TreeIndex['inputMap'];
|
inputMap: TreeIndex['inputMap'];
|
||||||
} {
|
} {
|
||||||
const {
|
const { tree, removeNodeMutations, textMutations, attributeMutations } =
|
||||||
tree,
|
this;
|
||||||
removeNodeMutations,
|
|
||||||
textMutations,
|
|
||||||
attributeMutations,
|
|
||||||
} = this;
|
|
||||||
|
|
||||||
const batchMutationData: mutationData = {
|
const batchMutationData: mutationData = {
|
||||||
source: IncrementalSource.Mutation,
|
source: IncrementalSource.Mutation,
|
||||||
@@ -653,5 +650,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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2855,213 +2855,247 @@ exports[`log 1`] = `
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
\\"type\\": 3,
|
\\"type\\": 6,
|
||||||
\\"data\\": {
|
\\"data\\": {
|
||||||
\\"source\\": 11,
|
\\"plugin\\": \\"rrweb/console@1\\",
|
||||||
\\"level\\": \\"assert\\",
|
\\"payload\\": {
|
||||||
\\"trace\\": [
|
\\"level\\": \\"assert\\",
|
||||||
\\"__puppeteer_evaluation_script__:2:37\\"
|
\\"trace\\": [
|
||||||
],
|
\\"__puppeteer_evaluation_script__:2:37\\"
|
||||||
\\"payload\\": [
|
],
|
||||||
\\"true\\",
|
\\"payload\\": [
|
||||||
\\"\\\\\\"assert\\\\\\"\\"
|
\\"true\\",
|
||||||
]
|
\\"\\\\\\"assert\\\\\\"\\"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
\\"type\\": 3,
|
\\"type\\": 6,
|
||||||
\\"data\\": {
|
\\"data\\": {
|
||||||
\\"source\\": 11,
|
\\"plugin\\": \\"rrweb/console@1\\",
|
||||||
\\"level\\": \\"count\\",
|
\\"payload\\": {
|
||||||
\\"trace\\": [
|
\\"level\\": \\"count\\",
|
||||||
\\"__puppeteer_evaluation_script__:3:37\\"
|
\\"trace\\": [
|
||||||
],
|
\\"__puppeteer_evaluation_script__:3:37\\"
|
||||||
\\"payload\\": [
|
],
|
||||||
\\"\\\\\\"count\\\\\\"\\"
|
\\"payload\\": [
|
||||||
]
|
\\"\\\\\\"count\\\\\\"\\"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
\\"type\\": 3,
|
\\"type\\": 6,
|
||||||
\\"data\\": {
|
\\"data\\": {
|
||||||
\\"source\\": 11,
|
\\"plugin\\": \\"rrweb/console@1\\",
|
||||||
\\"level\\": \\"countReset\\",
|
\\"payload\\": {
|
||||||
\\"trace\\": [
|
\\"level\\": \\"countReset\\",
|
||||||
\\"__puppeteer_evaluation_script__:4:37\\"
|
\\"trace\\": [
|
||||||
],
|
\\"__puppeteer_evaluation_script__:4:37\\"
|
||||||
\\"payload\\": [
|
],
|
||||||
\\"\\\\\\"count\\\\\\"\\"
|
\\"payload\\": [
|
||||||
]
|
\\"\\\\\\"count\\\\\\"\\"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
\\"type\\": 3,
|
\\"type\\": 6,
|
||||||
\\"data\\": {
|
\\"data\\": {
|
||||||
\\"source\\": 11,
|
\\"plugin\\": \\"rrweb/console@1\\",
|
||||||
\\"level\\": \\"debug\\",
|
\\"payload\\": {
|
||||||
\\"trace\\": [
|
\\"level\\": \\"debug\\",
|
||||||
\\"__puppeteer_evaluation_script__:5:37\\"
|
\\"trace\\": [
|
||||||
],
|
\\"__puppeteer_evaluation_script__:5:37\\"
|
||||||
\\"payload\\": [
|
],
|
||||||
\\"\\\\\\"debug\\\\\\"\\"
|
\\"payload\\": [
|
||||||
]
|
\\"\\\\\\"debug\\\\\\"\\"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
\\"type\\": 3,
|
\\"type\\": 6,
|
||||||
\\"data\\": {
|
\\"data\\": {
|
||||||
\\"source\\": 11,
|
\\"plugin\\": \\"rrweb/console@1\\",
|
||||||
\\"level\\": \\"dir\\",
|
\\"payload\\": {
|
||||||
\\"trace\\": [
|
\\"level\\": \\"dir\\",
|
||||||
\\"__puppeteer_evaluation_script__:6:37\\"
|
\\"trace\\": [
|
||||||
],
|
\\"__puppeteer_evaluation_script__:6:37\\"
|
||||||
\\"payload\\": [
|
],
|
||||||
\\"\\\\\\"dir\\\\\\"\\"
|
\\"payload\\": [
|
||||||
]
|
\\"\\\\\\"dir\\\\\\"\\"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
\\"type\\": 3,
|
\\"type\\": 6,
|
||||||
\\"data\\": {
|
\\"data\\": {
|
||||||
\\"source\\": 11,
|
\\"plugin\\": \\"rrweb/console@1\\",
|
||||||
\\"level\\": \\"dirxml\\",
|
\\"payload\\": {
|
||||||
\\"trace\\": [
|
\\"level\\": \\"dirxml\\",
|
||||||
\\"__puppeteer_evaluation_script__:7:37\\"
|
\\"trace\\": [
|
||||||
],
|
\\"__puppeteer_evaluation_script__:7:37\\"
|
||||||
\\"payload\\": [
|
],
|
||||||
\\"\\\\\\"dirxml\\\\\\"\\"
|
\\"payload\\": [
|
||||||
]
|
\\"\\\\\\"dirxml\\\\\\"\\"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
\\"type\\": 3,
|
\\"type\\": 6,
|
||||||
\\"data\\": {
|
\\"data\\": {
|
||||||
\\"source\\": 11,
|
\\"plugin\\": \\"rrweb/console@1\\",
|
||||||
\\"level\\": \\"group\\",
|
\\"payload\\": {
|
||||||
\\"trace\\": [
|
\\"level\\": \\"group\\",
|
||||||
\\"__puppeteer_evaluation_script__:8:37\\"
|
\\"trace\\": [
|
||||||
],
|
\\"__puppeteer_evaluation_script__:8:37\\"
|
||||||
\\"payload\\": []
|
],
|
||||||
|
\\"payload\\": []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
\\"type\\": 3,
|
\\"type\\": 6,
|
||||||
\\"data\\": {
|
\\"data\\": {
|
||||||
\\"source\\": 11,
|
\\"plugin\\": \\"rrweb/console@1\\",
|
||||||
\\"level\\": \\"groupCollapsed\\",
|
\\"payload\\": {
|
||||||
\\"trace\\": [
|
\\"level\\": \\"groupCollapsed\\",
|
||||||
\\"__puppeteer_evaluation_script__:9:37\\"
|
\\"trace\\": [
|
||||||
],
|
\\"__puppeteer_evaluation_script__:9:37\\"
|
||||||
\\"payload\\": []
|
],
|
||||||
|
\\"payload\\": []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
\\"type\\": 3,
|
\\"type\\": 6,
|
||||||
\\"data\\": {
|
\\"data\\": {
|
||||||
\\"source\\": 11,
|
\\"plugin\\": \\"rrweb/console@1\\",
|
||||||
\\"level\\": \\"info\\",
|
\\"payload\\": {
|
||||||
\\"trace\\": [
|
\\"level\\": \\"info\\",
|
||||||
\\"__puppeteer_evaluation_script__:10:37\\"
|
\\"trace\\": [
|
||||||
],
|
\\"__puppeteer_evaluation_script__:10:37\\"
|
||||||
\\"payload\\": [
|
],
|
||||||
\\"\\\\\\"info\\\\\\"\\"
|
\\"payload\\": [
|
||||||
]
|
\\"\\\\\\"info\\\\\\"\\"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
\\"type\\": 3,
|
\\"type\\": 6,
|
||||||
\\"data\\": {
|
\\"data\\": {
|
||||||
\\"source\\": 11,
|
\\"plugin\\": \\"rrweb/console@1\\",
|
||||||
\\"level\\": \\"log\\",
|
\\"payload\\": {
|
||||||
\\"trace\\": [
|
\\"level\\": \\"log\\",
|
||||||
\\"__puppeteer_evaluation_script__:11:37\\"
|
\\"trace\\": [
|
||||||
],
|
\\"__puppeteer_evaluation_script__:11:37\\"
|
||||||
\\"payload\\": [
|
],
|
||||||
\\"\\\\\\"log\\\\\\"\\"
|
\\"payload\\": [
|
||||||
]
|
\\"\\\\\\"log\\\\\\"\\"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
\\"type\\": 3,
|
\\"type\\": 6,
|
||||||
\\"data\\": {
|
\\"data\\": {
|
||||||
\\"source\\": 11,
|
\\"plugin\\": \\"rrweb/console@1\\",
|
||||||
\\"level\\": \\"table\\",
|
\\"payload\\": {
|
||||||
\\"trace\\": [
|
\\"level\\": \\"table\\",
|
||||||
\\"__puppeteer_evaluation_script__:12:37\\"
|
\\"trace\\": [
|
||||||
],
|
\\"__puppeteer_evaluation_script__:12:37\\"
|
||||||
\\"payload\\": [
|
],
|
||||||
\\"\\\\\\"table\\\\\\"\\"
|
\\"payload\\": [
|
||||||
]
|
\\"\\\\\\"table\\\\\\"\\"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
\\"type\\": 3,
|
\\"type\\": 6,
|
||||||
\\"data\\": {
|
\\"data\\": {
|
||||||
\\"source\\": 11,
|
\\"plugin\\": \\"rrweb/console@1\\",
|
||||||
\\"level\\": \\"time\\",
|
\\"payload\\": {
|
||||||
\\"trace\\": [
|
\\"level\\": \\"time\\",
|
||||||
\\"__puppeteer_evaluation_script__:13:37\\"
|
\\"trace\\": [
|
||||||
],
|
\\"__puppeteer_evaluation_script__:13:37\\"
|
||||||
\\"payload\\": []
|
],
|
||||||
|
\\"payload\\": []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
\\"type\\": 3,
|
\\"type\\": 6,
|
||||||
\\"data\\": {
|
\\"data\\": {
|
||||||
\\"source\\": 11,
|
\\"plugin\\": \\"rrweb/console@1\\",
|
||||||
\\"level\\": \\"timeEnd\\",
|
\\"payload\\": {
|
||||||
\\"trace\\": [
|
\\"level\\": \\"timeEnd\\",
|
||||||
\\"__puppeteer_evaluation_script__:14:37\\"
|
\\"trace\\": [
|
||||||
],
|
\\"__puppeteer_evaluation_script__:14:37\\"
|
||||||
\\"payload\\": []
|
],
|
||||||
|
\\"payload\\": []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
\\"type\\": 3,
|
\\"type\\": 6,
|
||||||
\\"data\\": {
|
\\"data\\": {
|
||||||
\\"source\\": 11,
|
\\"plugin\\": \\"rrweb/console@1\\",
|
||||||
\\"level\\": \\"timeLog\\",
|
\\"payload\\": {
|
||||||
\\"trace\\": [
|
\\"level\\": \\"timeLog\\",
|
||||||
\\"__puppeteer_evaluation_script__:15:37\\"
|
\\"trace\\": [
|
||||||
],
|
\\"__puppeteer_evaluation_script__:15:37\\"
|
||||||
\\"payload\\": []
|
],
|
||||||
|
\\"payload\\": []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
\\"type\\": 3,
|
\\"type\\": 6,
|
||||||
\\"data\\": {
|
\\"data\\": {
|
||||||
\\"source\\": 11,
|
\\"plugin\\": \\"rrweb/console@1\\",
|
||||||
\\"level\\": \\"trace\\",
|
\\"payload\\": {
|
||||||
\\"trace\\": [
|
\\"level\\": \\"trace\\",
|
||||||
\\"__puppeteer_evaluation_script__:16:37\\"
|
\\"trace\\": [
|
||||||
],
|
\\"__puppeteer_evaluation_script__:16:37\\"
|
||||||
\\"payload\\": [
|
],
|
||||||
\\"\\\\\\"trace\\\\\\"\\"
|
\\"payload\\": [
|
||||||
]
|
\\"\\\\\\"trace\\\\\\"\\"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
\\"type\\": 3,
|
\\"type\\": 6,
|
||||||
\\"data\\": {
|
\\"data\\": {
|
||||||
\\"source\\": 11,
|
\\"plugin\\": \\"rrweb/console@1\\",
|
||||||
\\"level\\": \\"warn\\",
|
\\"payload\\": {
|
||||||
\\"trace\\": [
|
\\"level\\": \\"warn\\",
|
||||||
\\"__puppeteer_evaluation_script__:17:37\\"
|
\\"trace\\": [
|
||||||
],
|
\\"__puppeteer_evaluation_script__:17:37\\"
|
||||||
\\"payload\\": [
|
],
|
||||||
\\"\\\\\\"warn\\\\\\"\\"
|
\\"payload\\": [
|
||||||
]
|
\\"\\\\\\"warn\\\\\\"\\"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
\\"type\\": 3,
|
\\"type\\": 6,
|
||||||
\\"data\\": {
|
\\"data\\": {
|
||||||
\\"source\\": 11,
|
\\"plugin\\": \\"rrweb/console@1\\",
|
||||||
\\"level\\": \\"clear\\",
|
\\"payload\\": {
|
||||||
\\"trace\\": [
|
\\"level\\": \\"clear\\",
|
||||||
\\"__puppeteer_evaluation_script__:18:37\\"
|
\\"trace\\": [
|
||||||
],
|
\\"__puppeteer_evaluation_script__:18:37\\"
|
||||||
\\"payload\\": []
|
],
|
||||||
|
\\"payload\\": []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]"
|
]"
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ describe('record integration tests', function (this: ISuite) {
|
|||||||
maskInputOptions: ${JSON.stringify(options.maskAllInputs)},
|
maskInputOptions: ${JSON.stringify(options.maskAllInputs)},
|
||||||
maskTextFn: ${options.maskTextFn},
|
maskTextFn: ${options.maskTextFn},
|
||||||
recordCanvas: ${options.recordCanvas},
|
recordCanvas: ${options.recordCanvas},
|
||||||
recordLog: ${options.recordLog},
|
plugins: ${options.plugins}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
@@ -90,7 +90,12 @@ describe('record integration tests', function (this: ISuite) {
|
|||||||
this.browser = await launchPuppeteer();
|
this.browser = await launchPuppeteer();
|
||||||
|
|
||||||
const bundlePath = path.resolve(__dirname, '../dist/rrweb.min.js');
|
const bundlePath = path.resolve(__dirname, '../dist/rrweb.min.js');
|
||||||
this.code = fs.readFileSync(bundlePath, 'utf8');
|
const pluginsCode = [
|
||||||
|
path.resolve(__dirname, '../dist/plugins/console-record.min.js'),
|
||||||
|
]
|
||||||
|
.map((path) => fs.readFileSync(path, 'utf8'))
|
||||||
|
.join();
|
||||||
|
this.code = fs.readFileSync(bundlePath, 'utf8') + pluginsCode;
|
||||||
});
|
});
|
||||||
|
|
||||||
after(async () => {
|
after(async () => {
|
||||||
@@ -385,7 +390,7 @@ describe('record integration tests', function (this: ISuite) {
|
|||||||
await page.goto('about:blank');
|
await page.goto('about:blank');
|
||||||
await page.setContent(
|
await page.setContent(
|
||||||
getHtml.call(this, 'log.html', {
|
getHtml.call(this, 'log.html', {
|
||||||
recordLog: true,
|
plugins: '[rrwebConsoleRecord.getRecordConsolePlugin()]',
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
2
typings/entries/all.d.ts
vendored
2
typings/entries/all.d.ts
vendored
@@ -1,2 +1,4 @@
|
|||||||
export * from '../index';
|
export * from '../index';
|
||||||
export * from '../packer';
|
export * from '../packer';
|
||||||
|
export * from '../plugins/console/record';
|
||||||
|
export * from '../plugins/console/replay';
|
||||||
|
|||||||
37
typings/plugins/console/record/error-stack-parser.d.ts
vendored
Normal file
37
typings/plugins/console/record/error-stack-parser.d.ts
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
export declare class StackFrame {
|
||||||
|
private fileName;
|
||||||
|
private functionName;
|
||||||
|
private lineNumber?;
|
||||||
|
private columnNumber?;
|
||||||
|
constructor(obj: {
|
||||||
|
fileName?: string;
|
||||||
|
functionName?: string;
|
||||||
|
lineNumber?: number;
|
||||||
|
columnNumber?: number;
|
||||||
|
});
|
||||||
|
toString(): string;
|
||||||
|
}
|
||||||
|
export declare const ErrorStackParser: {
|
||||||
|
parse: (error: Error) => StackFrame[];
|
||||||
|
extractLocation: (urlLike: string) => (string | undefined)[];
|
||||||
|
parseV8OrIE: (error: {
|
||||||
|
stack: string;
|
||||||
|
}) => StackFrame[];
|
||||||
|
parseFFOrSafari: (error: {
|
||||||
|
stack: string;
|
||||||
|
}) => StackFrame[];
|
||||||
|
parseOpera: (e: {
|
||||||
|
stacktrace?: string;
|
||||||
|
message: string;
|
||||||
|
stack?: string;
|
||||||
|
}) => StackFrame[];
|
||||||
|
parseOpera9: (e: {
|
||||||
|
message: string;
|
||||||
|
}) => StackFrame[];
|
||||||
|
parseOpera10: (e: {
|
||||||
|
stacktrace: string;
|
||||||
|
}) => StackFrame[];
|
||||||
|
parseOpera11: (error: {
|
||||||
|
stack: string;
|
||||||
|
}) => StackFrame[];
|
||||||
|
};
|
||||||
41
typings/plugins/console/record/index.d.ts
vendored
Normal file
41
typings/plugins/console/record/index.d.ts
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { RecordPlugin } from '../../../types';
|
||||||
|
export declare type StringifyOptions = {
|
||||||
|
stringLengthLimit?: number;
|
||||||
|
numOfKeysLimit: number;
|
||||||
|
};
|
||||||
|
declare type LogRecordOptions = {
|
||||||
|
level?: LogLevel[] | undefined;
|
||||||
|
lengthThreshold?: number;
|
||||||
|
stringifyOptions?: StringifyOptions;
|
||||||
|
logger?: Logger;
|
||||||
|
};
|
||||||
|
export declare type LogData = {
|
||||||
|
level: LogLevel;
|
||||||
|
trace: string[];
|
||||||
|
payload: string[];
|
||||||
|
};
|
||||||
|
export declare type LogLevel = 'assert' | 'clear' | 'count' | 'countReset' | 'debug' | 'dir' | 'dirxml' | 'error' | 'group' | 'groupCollapsed' | 'groupEnd' | 'info' | 'log' | 'table' | 'time' | 'timeEnd' | 'timeLog' | 'trace' | 'warn';
|
||||||
|
export declare type Logger = {
|
||||||
|
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?: 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;
|
||||||
|
};
|
||||||
|
export declare const PLUGIN_NAME = "rrweb/console@1";
|
||||||
|
export declare const getRecordConsolePlugin: (options?: LogRecordOptions) => RecordPlugin;
|
||||||
|
export {};
|
||||||
2
typings/plugins/console/record/stringify.d.ts
vendored
Normal file
2
typings/plugins/console/record/stringify.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import { StringifyOptions } from './index';
|
||||||
|
export declare function stringify(obj: any, stringifyOptions?: StringifyOptions): string;
|
||||||
9
typings/plugins/console/replay/index.d.ts
vendored
Normal file
9
typings/plugins/console/replay/index.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { LogLevel, LogData } from '../record';
|
||||||
|
import { ReplayPlugin } from '../../../types';
|
||||||
|
declare type ReplayLogger = Partial<Record<LogLevel, (data: LogData) => void>>;
|
||||||
|
declare type LogReplayConfig = {
|
||||||
|
level?: LogLevel[] | undefined;
|
||||||
|
replayLogger: ReplayLogger | undefined;
|
||||||
|
};
|
||||||
|
export declare const getLogReplayPlugin: (options?: LogReplayConfig) => ReplayPlugin;
|
||||||
|
export {};
|
||||||
2
typings/replay/index.d.ts
vendored
2
typings/replay/index.d.ts
vendored
@@ -50,8 +50,6 @@ export declare class Replayer {
|
|||||||
private applyMutation;
|
private applyMutation;
|
||||||
private applyScroll;
|
private applyScroll;
|
||||||
private applyInput;
|
private applyInput;
|
||||||
private formatMessage;
|
|
||||||
private getConsoleLogger;
|
|
||||||
private legacy_resolveMissingNode;
|
private legacy_resolveMissingNode;
|
||||||
private moveAndHover;
|
private moveAndHover;
|
||||||
private drawMouseTail;
|
private drawMouseTail;
|
||||||
|
|||||||
87
typings/types.d.ts
vendored
87
typings/types.d.ts
vendored
@@ -3,13 +3,15 @@ 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';
|
||||||
import { ShadowDomManager } from './record/shadow-dom-manager';
|
import { ShadowDomManager } from './record/shadow-dom-manager';
|
||||||
|
import type { Replayer } from './replay';
|
||||||
export declare enum EventType {
|
export declare enum EventType {
|
||||||
DomContentLoaded = 0,
|
DomContentLoaded = 0,
|
||||||
Load = 1,
|
Load = 1,
|
||||||
FullSnapshot = 2,
|
FullSnapshot = 2,
|
||||||
IncrementalSnapshot = 3,
|
IncrementalSnapshot = 3,
|
||||||
Meta = 4,
|
Meta = 4,
|
||||||
Custom = 5
|
Custom = 5,
|
||||||
|
Plugin = 6
|
||||||
}
|
}
|
||||||
export declare type domContentLoadedEvent = {
|
export declare type domContentLoadedEvent = {
|
||||||
type: EventType.DomContentLoaded;
|
type: EventType.DomContentLoaded;
|
||||||
@@ -41,10 +43,6 @@ export declare type metaEvent = {
|
|||||||
height: number;
|
height: number;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export declare type logEvent = {
|
|
||||||
type: EventType.IncrementalSnapshot;
|
|
||||||
data: incrementalData;
|
|
||||||
};
|
|
||||||
export declare type customEvent<T = unknown> = {
|
export declare type customEvent<T = unknown> = {
|
||||||
type: EventType.Custom;
|
type: EventType.Custom;
|
||||||
data: {
|
data: {
|
||||||
@@ -52,6 +50,13 @@ export declare type customEvent<T = unknown> = {
|
|||||||
payload: T;
|
payload: T;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
export declare type pluginEvent<T = unknown> = {
|
||||||
|
type: EventType.Plugin;
|
||||||
|
data: {
|
||||||
|
plugin: string;
|
||||||
|
payload: T;
|
||||||
|
};
|
||||||
|
};
|
||||||
export declare type styleSheetEvent = {};
|
export declare type styleSheetEvent = {};
|
||||||
export declare enum IncrementalSource {
|
export declare enum IncrementalSource {
|
||||||
Mutation = 0,
|
Mutation = 0,
|
||||||
@@ -100,11 +105,8 @@ export declare type canvasMutationData = {
|
|||||||
export declare type fontData = {
|
export declare type fontData = {
|
||||||
source: IncrementalSource.Font;
|
source: IncrementalSource.Font;
|
||||||
} & fontParam;
|
} & fontParam;
|
||||||
export declare type logData = {
|
export declare type incrementalData = mutationData | mousemoveData | mouseInteractionData | scrollData | viewportResizeData | inputData | mediaInteractionData | styleSheetRuleData | canvasMutationData | fontData;
|
||||||
source: IncrementalSource.Log;
|
export declare type event = domContentLoadedEvent | loadedEvent | fullSnapshotEvent | incrementalSnapshotEvent | metaEvent | customEvent | pluginEvent;
|
||||||
} & LogParam;
|
|
||||||
export declare type incrementalData = mutationData | mousemoveData | mouseInteractionData | scrollData | viewportResizeData | inputData | mediaInteractionData | styleSheetRuleData | canvasMutationData | fontData | logData;
|
|
||||||
export declare type event = domContentLoadedEvent | loadedEvent | fullSnapshotEvent | incrementalSnapshotEvent | metaEvent | logEvent | customEvent;
|
|
||||||
export declare type eventWithTime = event & {
|
export declare type eventWithTime = event & {
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
delay?: number;
|
delay?: number;
|
||||||
@@ -118,6 +120,11 @@ export declare type SamplingStrategy = Partial<{
|
|||||||
scroll: number;
|
scroll: number;
|
||||||
input: 'all' | 'last';
|
input: 'all' | 'last';
|
||||||
}>;
|
}>;
|
||||||
|
export declare type RecordPlugin<TOptions = unknown> = {
|
||||||
|
name: string;
|
||||||
|
observer: (cb: Function, options: TOptions) => listenerHandler;
|
||||||
|
options: TOptions;
|
||||||
|
};
|
||||||
export declare type recordOptions<T> = {
|
export declare type recordOptions<T> = {
|
||||||
emit?: (e: T, isCheckout?: boolean) => void;
|
emit?: (e: T, isCheckout?: boolean) => void;
|
||||||
checkoutEveryNth?: number;
|
checkoutEveryNth?: number;
|
||||||
@@ -138,8 +145,8 @@ export declare type recordOptions<T> = {
|
|||||||
sampling?: SamplingStrategy;
|
sampling?: SamplingStrategy;
|
||||||
recordCanvas?: boolean;
|
recordCanvas?: boolean;
|
||||||
collectFonts?: boolean;
|
collectFonts?: boolean;
|
||||||
|
plugins?: RecordPlugin[];
|
||||||
mousemoveWait?: number;
|
mousemoveWait?: number;
|
||||||
recordLog?: boolean | LogRecordOptions;
|
|
||||||
};
|
};
|
||||||
export declare type observerParam = {
|
export declare type observerParam = {
|
||||||
mutationCb: mutationCallBack;
|
mutationCb: mutationCallBack;
|
||||||
@@ -161,8 +168,6 @@ export declare type observerParam = {
|
|||||||
styleSheetRuleCb: styleSheetRuleCallback;
|
styleSheetRuleCb: styleSheetRuleCallback;
|
||||||
canvasMutationCb: canvasMutationCallback;
|
canvasMutationCb: canvasMutationCallback;
|
||||||
fontCb: fontCallback;
|
fontCb: fontCallback;
|
||||||
logCb: logCallback;
|
|
||||||
logOptions: LogRecordOptions;
|
|
||||||
sampling: SamplingStrategy;
|
sampling: SamplingStrategy;
|
||||||
recordCanvas: boolean;
|
recordCanvas: boolean;
|
||||||
collectFonts: boolean;
|
collectFonts: boolean;
|
||||||
@@ -171,6 +176,11 @@ export declare type observerParam = {
|
|||||||
mirror: Mirror;
|
mirror: Mirror;
|
||||||
iframeManager: IframeManager;
|
iframeManager: IframeManager;
|
||||||
shadowDomManager: ShadowDomManager;
|
shadowDomManager: ShadowDomManager;
|
||||||
|
plugins: Array<{
|
||||||
|
observer: Function;
|
||||||
|
callback: Function;
|
||||||
|
options: unknown;
|
||||||
|
}>;
|
||||||
};
|
};
|
||||||
export declare type hooksParam = {
|
export declare type hooksParam = {
|
||||||
mutation?: mutationCallBack;
|
mutation?: mutationCallBack;
|
||||||
@@ -183,7 +193,6 @@ export declare type hooksParam = {
|
|||||||
styleSheetRule?: styleSheetRuleCallback;
|
styleSheetRule?: styleSheetRuleCallback;
|
||||||
canvasMutation?: canvasMutationCallback;
|
canvasMutation?: canvasMutationCallback;
|
||||||
font?: fontCallback;
|
font?: fontCallback;
|
||||||
log?: logCallback;
|
|
||||||
};
|
};
|
||||||
export declare type mutationRecord = {
|
export declare type mutationRecord = {
|
||||||
type: string;
|
type: string;
|
||||||
@@ -290,36 +299,7 @@ export declare type fontParam = {
|
|||||||
buffer: boolean;
|
buffer: boolean;
|
||||||
descriptors?: FontFaceDescriptors;
|
descriptors?: FontFaceDescriptors;
|
||||||
};
|
};
|
||||||
export declare type LogLevel = 'assert' | 'clear' | 'count' | 'countReset' | 'debug' | 'dir' | 'dirxml' | 'error' | 'group' | 'groupCollapsed' | 'groupEnd' | 'info' | 'log' | 'table' | 'time' | 'timeEnd' | 'timeLog' | 'trace' | 'warn';
|
|
||||||
export declare type Logger = {
|
|
||||||
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?: 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;
|
|
||||||
};
|
|
||||||
export declare type ReplayLogger = Partial<Record<LogLevel, (data: logData) => void>>;
|
|
||||||
export declare type LogParam = {
|
|
||||||
level: LogLevel;
|
|
||||||
trace: string[];
|
|
||||||
payload: string[];
|
|
||||||
};
|
|
||||||
export declare type fontCallback = (p: fontParam) => void;
|
export declare type fontCallback = (p: fontParam) => void;
|
||||||
export declare type logCallback = (p: LogParam) => void;
|
|
||||||
export declare type viewportResizeDimension = {
|
export declare type viewportResizeDimension = {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
@@ -363,6 +343,11 @@ export declare type throttleOptions = {
|
|||||||
};
|
};
|
||||||
export declare type listenerHandler = () => void;
|
export declare type listenerHandler = () => void;
|
||||||
export declare type hookResetter = () => void;
|
export declare type hookResetter = () => void;
|
||||||
|
export declare type ReplayPlugin = {
|
||||||
|
handler: (event: eventWithTime, isSync: boolean, context: {
|
||||||
|
replayer: Replayer;
|
||||||
|
}) => void;
|
||||||
|
};
|
||||||
export declare type playerConfig = {
|
export declare type playerConfig = {
|
||||||
speed: number;
|
speed: number;
|
||||||
maxSpeed: number;
|
maxSpeed: number;
|
||||||
@@ -384,11 +369,7 @@ export declare type playerConfig = {
|
|||||||
strokeStyle?: string;
|
strokeStyle?: string;
|
||||||
};
|
};
|
||||||
unpackFn?: UnpackFn;
|
unpackFn?: UnpackFn;
|
||||||
logConfig: LogReplayConfig;
|
plugins?: ReplayPlugin[];
|
||||||
};
|
|
||||||
export declare type LogReplayConfig = {
|
|
||||||
level?: LogLevel[] | undefined;
|
|
||||||
replayLogger: ReplayLogger | undefined;
|
|
||||||
};
|
};
|
||||||
export declare type playerMetaData = {
|
export declare type playerMetaData = {
|
||||||
startTime: number;
|
startTime: number;
|
||||||
@@ -436,14 +417,4 @@ export declare type MaskTextFn = (text: string) => string;
|
|||||||
export declare type ElementState = {
|
export declare type ElementState = {
|
||||||
scroll?: [number, number];
|
scroll?: [number, number];
|
||||||
};
|
};
|
||||||
export declare type StringifyOptions = {
|
|
||||||
stringLengthLimit?: number;
|
|
||||||
numOfKeysLimit: number;
|
|
||||||
};
|
|
||||||
export declare type LogRecordOptions = {
|
|
||||||
level?: LogLevel[] | undefined;
|
|
||||||
lengthThreshold?: number;
|
|
||||||
stringifyOptions?: StringifyOptions;
|
|
||||||
logger?: Logger;
|
|
||||||
};
|
|
||||||
export {};
|
export {};
|
||||||
|
|||||||
Reference in New Issue
Block a user