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:
yz-yu
2021-06-28 00:09:09 +08:00
committed by GitHub
parent 4e025c84ce
commit 5fc6c193fd
20 changed files with 753 additions and 579 deletions

View File

@@ -33,6 +33,13 @@ function toAllPath(path) {
return path.replace('rrweb', 'rrweb-all');
}
function toPluginPath(pluginName, stage) {
return (path) =>
path
.replace(/^([\w]+)\//, '$1/plugins/')
.replace('rrweb', `${pluginName}-${stage}`);
}
function toMinPath(path) {
return path.replace(/\.js$/, '.min.js');
}
@@ -74,6 +81,16 @@ const baseConfigs = [
name: 'rrweb',
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 = [];

View File

@@ -1,2 +1,4 @@
export * from '../index';
export * from '../packer';
export * from '../plugins/console/record';
export * from '../plugins/console/replay';

View File

@@ -1,3 +1,4 @@
// tslint:disable
/**
* Class StackFrame is a fork of https://github.com/stacktracejs/stackframe/blob/master/stackframe.js
* I fork it because:

View 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,
});

View File

@@ -4,7 +4,7 @@
*
*/
import { StringifyOptions } from '../types';
import { StringifyOptions } from './index';
/**
* transfer the node path in Event to string

View 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);
}
}
}
},
};
};

View File

@@ -16,7 +16,6 @@ import {
recordOptions,
IncrementalSource,
listenerHandler,
LogRecordOptions,
mutationCallbackParam,
scrollCallback,
} from '../types';
@@ -59,7 +58,7 @@ function record<T = eventWithTime>(
mousemoveWait,
recordCanvas = false,
collectFonts = false,
recordLog = false,
plugins,
} = options;
// runtime checks for user options
if (!emit) {
@@ -113,37 +112,6 @@ function record<T = eventWithTime>(
: _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();
@@ -400,16 +368,6 @@ function record<T = eventWithTime>(
},
}),
),
logCb: (p) =>
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.Log,
...p,
},
}),
),
blockClass,
ignoreClass,
maskTextClass,
@@ -422,12 +380,26 @@ function record<T = eventWithTime>(
doc,
maskInputFn,
maskTextFn,
logOptions,
blockSelector,
slimDOMOptions,
mirror,
iframeManager,
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,
);

View File

@@ -37,17 +37,11 @@ import {
fontParam,
MaskInputFn,
MaskTextFn,
logCallback,
LogRecordOptions,
Logger,
LogLevel,
Mirror,
} from '../types';
import MutationBuffer from './mutation';
import { stringify } from './stringify';
import { IframeManager } from './iframe-manager';
import { ShadowDomManager } from './shadow-dom-manager';
import { StackFrame, ErrorStackParser } from './error-stack-parser';
type WindowWithStoredMutationObserver = Window & {
__rrMutationObserver?: MutationObserver;
@@ -535,11 +529,16 @@ function initCanvasMutationObserver(
recordArgs[0] &&
recordArgs[0] instanceof HTMLCanvasElement
) {
const canvas = recordArgs[0]
const ctx = canvas.getContext('2d')
let imgd = ctx?.getImageData(0, 0, canvas.width, canvas.height)
const canvas = recordArgs[0];
const ctx = canvas.getContext('2d');
let imgd = ctx?.getImageData(
0,
0,
canvas.width,
canvas.height,
);
let pix = imgd?.data;
recordArgs[0] = JSON.stringify(pix)
recordArgs[0] = JSON.stringify(pix);
}
}
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) {
const {
mutationCb,
@@ -730,7 +638,6 @@ function mergeHooks(o: observerParam, hooks: hooksParam) {
styleSheetRuleCb,
canvasMutationCb,
fontCb,
logCb,
} = o;
o.mutationCb = (...p: Arguments<mutationCallBack>) => {
if (hooks.mutation) {
@@ -792,12 +699,6 @@ function mergeHooks(o: observerParam, hooks: hooksParam) {
}
fontCb(...p);
};
o.logCb = (...p: Arguments<logCallback>) => {
if (hooks.log) {
hooks.log(...p);
}
logCb(...p);
};
}
export function initObservers(
@@ -866,9 +767,11 @@ export function initObservers(
? initCanvasMutationObserver(o.canvasMutationCb, o.blockClass, o.mirror)
: () => {};
const fontObserver = o.collectFonts ? initFontObserver(o.fontCb) : () => {};
const logObserver = o.logOptions
? initLogObserver(o.logCb, o.logOptions)
: () => {};
// plugins
const pluginHandlers: listenerHandler[] = [];
for (const plugin of o.plugins) {
pluginHandlers.push(plugin.observer(plugin.callback, plugin.options));
}
return () => {
mutationObserver.disconnect();
@@ -881,6 +784,6 @@ export function initObservers(
styleSheetObserver();
canvasMutationObserver();
fontObserver();
logObserver();
pluginHandlers.forEach((h) => h());
};
}

View File

@@ -28,9 +28,6 @@ import {
canvasMutationData,
Mirror,
ElementState,
LogReplayConfig,
logData,
ReplayLogger,
} from '../types';
import {
createMirror,
@@ -54,11 +51,6 @@ const SKIP_TIME_INTERVAL = 5 * 1000;
const mitt = (mittProxy as any).default || mittProxy;
const REPLAY_CONSOLE_PREFIX = '[replayer]';
const ORIGINAL_ATTRIBUTE_NAME = '__rrweb_original__';
type PatchedConsoleLog = {
[ORIGINAL_ATTRIBUTE_NAME]: typeof console.log;
};
const defaultMouseTailConfig = {
duration: 500,
@@ -67,31 +59,6 @@ const defaultMouseTailConfig = {
strokeStyle: 'red',
} 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 {
public wrapper: HTMLDivElement;
public iframe: HTMLIFrameElement;
@@ -149,12 +116,8 @@ export class Replayer {
UNSAFE_replayCanvas: false,
pauseAnimation: true,
mouseTail: defaultMouseTailConfig,
logConfig: defaultLogConfig,
};
this.config = Object.assign({}, defaultConfig, config);
if (!this.config.logConfig.replayLogger) {
this.config.logConfig.replayLogger = this.getConsoleLogger();
}
this.handleResize = this.handleResize.bind(this);
this.getCastFn = this.getCastFn.bind(this);
@@ -522,6 +485,11 @@ export class Replayer {
if (castFn) {
castFn();
}
for (const plugin of this.config.plugins || []) {
plugin.handler(event, isSync, { replayer: this });
}
this.service.send({ type: 'CAST_EVENT', payload: { event } });
// events are kept sorted by timestamp, check if this is the last event
@@ -1046,19 +1014,6 @@ export class Replayer {
}
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:
}
}
@@ -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(
map: missingNodeMap,
parent: Node,

View File

@@ -9,6 +9,7 @@ import { PackFn, UnpackFn } from './packer/base';
import { FontFaceDescriptors } from 'css-font-loading-module';
import { IframeManager } from './record/iframe-manager';
import { ShadowDomManager } from './record/shadow-dom-manager';
import type { Replayer } from './replay';
export enum EventType {
DomContentLoaded,
@@ -17,6 +18,7 @@ export enum EventType {
IncrementalSnapshot,
Meta,
Custom,
Plugin,
}
export type domContentLoadedEvent = {
@@ -54,11 +56,6 @@ export type metaEvent = {
};
};
export type logEvent = {
type: EventType.IncrementalSnapshot;
data: incrementalData;
};
export type customEvent<T = unknown> = {
type: EventType.Custom;
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 enum IncrementalSource {
@@ -130,10 +135,6 @@ export type fontData = {
source: IncrementalSource.Font;
} & fontParam;
export type logData = {
source: IncrementalSource.Log;
} & LogParam;
export type incrementalData =
| mutationData
| mousemoveData
@@ -144,8 +145,7 @@ export type incrementalData =
| mediaInteractionData
| styleSheetRuleData
| canvasMutationData
| fontData
| logData;
| fontData;
export type event =
| domContentLoadedEvent
@@ -153,8 +153,8 @@ export type event =
| fullSnapshotEvent
| incrementalSnapshotEvent
| metaEvent
| logEvent
| customEvent;
| customEvent
| pluginEvent;
export type eventWithTime = event & {
timestamp: number;
@@ -191,6 +191,12 @@ export type SamplingStrategy = Partial<{
input: 'all' | 'last';
}>;
export type RecordPlugin<TOptions = unknown> = {
name: string;
observer: (cb: Function, options: TOptions) => listenerHandler;
options: TOptions;
};
export type recordOptions<T> = {
emit?: (e: T, isCheckout?: boolean) => void;
checkoutEveryNth?: number;
@@ -211,9 +217,9 @@ export type recordOptions<T> = {
sampling?: SamplingStrategy;
recordCanvas?: boolean;
collectFonts?: boolean;
plugins?: RecordPlugin[];
// departed, please use sampling options
mousemoveWait?: number;
recordLog?: boolean | LogRecordOptions;
};
export type observerParam = {
@@ -236,8 +242,6 @@ export type observerParam = {
styleSheetRuleCb: styleSheetRuleCallback;
canvasMutationCb: canvasMutationCallback;
fontCb: fontCallback;
logCb: logCallback;
logOptions: LogRecordOptions;
sampling: SamplingStrategy;
recordCanvas: boolean;
collectFonts: boolean;
@@ -246,6 +250,11 @@ export type observerParam = {
mirror: Mirror;
iframeManager: IframeManager;
shadowDomManager: ShadowDomManager;
plugins: Array<{
observer: Function;
callback: Function;
options: unknown;
}>;
};
export type hooksParam = {
@@ -259,7 +268,6 @@ export type hooksParam = {
styleSheetRule?: styleSheetRuleCallback;
canvasMutation?: canvasMutationCallback;
font?: fontCallback;
log?: logCallback;
};
// https://dom.spec.whatwg.org/#interface-mutationrecord
@@ -396,67 +404,8 @@ export type fontParam = {
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 logCallback = (p: LogParam) => void;
export type viewportResizeDimension = {
width: number;
height: number;
@@ -480,7 +429,7 @@ export const enum MediaInteractions {
export type mediaInteractionParam = {
type: MediaInteractions;
id: number;
currentTime?: number
currentTime?: number;
};
export type mediaInteractionCallback = (p: mediaInteractionParam) => void;
@@ -511,6 +460,13 @@ export type throttleOptions = {
export type listenerHandler = () => void;
export type hookResetter = () => void;
export type ReplayPlugin = {
handler: (
event: eventWithTime,
isSync: boolean,
context: { replayer: Replayer },
) => void;
};
export type playerConfig = {
speed: number;
maxSpeed: number;
@@ -534,12 +490,7 @@ export type playerConfig = {
strokeStyle?: string;
};
unpackFn?: UnpackFn;
logConfig: LogReplayConfig;
};
export type LogReplayConfig = {
level?: LogLevel[] | undefined;
replayLogger: ReplayLogger | undefined;
plugins?: ReplayPlugin[];
};
export type playerMetaData = {
@@ -601,20 +552,3 @@ export type ElementState = {
// [scrollLeft,scrollTop]
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;
};

View File

@@ -53,7 +53,7 @@ export function createMirror(): Mirror {
delete this.map[id];
if (n.childNodes) {
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) {
return true;
}
return isAncestorRemoved((target.parentNode as unknown) as INode, mirror);
return isAncestorRemoved(target.parentNode as unknown as INode, mirror);
}
export function isTouchEvent(
@@ -286,13 +286,13 @@ export function isTouchEvent(
export function polyfill(win = window) {
if ('NodeList' in win && !win.NodeList.prototype.forEach) {
win.NodeList.prototype.forEach = (Array.prototype
.forEach as unknown) as NodeList['forEach'];
win.NodeList.prototype.forEach = Array.prototype
.forEach as unknown as NodeList['forEach'];
}
if ('DOMTokenList' in win && !win.DOMTokenList.prototype.forEach) {
win.DOMTokenList.prototype.forEach = (Array.prototype
.forEach as unknown) as DOMTokenList['forEach'];
win.DOMTokenList.prototype.forEach = Array.prototype
.forEach as unknown as DOMTokenList['forEach'];
}
// https://github.com/Financial-Times/polyfill-service/pull/183
@@ -322,6 +322,7 @@ export function needCastInSyncMode(event: eventWithTime): boolean {
return false;
case EventType.FullSnapshot:
case EventType.Meta:
case EventType.Plugin:
return true;
default:
break;
@@ -395,7 +396,7 @@ export class TreeIndex {
const node = mirror.getNode(id);
node?.childNodes.forEach((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'];
inputMap: TreeIndex['inputMap'];
} {
const {
tree,
removeNodeMutations,
textMutations,
attributeMutations,
} = this;
const { tree, removeNodeMutations, textMutations, attributeMutations } =
this;
const batchMutationData: mutationData = {
source: IncrementalSource.Mutation,
@@ -653,5 +650,5 @@ export function getBaseDimension(
export function hasShadowRoot<T extends Node>(
n: T,
): n is T & { shadowRoot: ShadowRoot } {
return Boolean(((n as unknown) as Element)?.shadowRoot);
return Boolean((n as unknown as Element)?.shadowRoot);
}

View File

@@ -2855,213 +2855,247 @@ exports[`log 1`] = `
}
},
{
\\"type\\": 3,
\\"type\\": 6,
\\"data\\": {
\\"source\\": 11,
\\"level\\": \\"assert\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:2:37\\"
],
\\"payload\\": [
\\"true\\",
\\"\\\\\\"assert\\\\\\"\\"
]
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"assert\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:2:37\\"
],
\\"payload\\": [
\\"true\\",
\\"\\\\\\"assert\\\\\\"\\"
]
}
}
},
{
\\"type\\": 3,
\\"type\\": 6,
\\"data\\": {
\\"source\\": 11,
\\"level\\": \\"count\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:3:37\\"
],
\\"payload\\": [
\\"\\\\\\"count\\\\\\"\\"
]
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"count\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:3:37\\"
],
\\"payload\\": [
\\"\\\\\\"count\\\\\\"\\"
]
}
}
},
{
\\"type\\": 3,
\\"type\\": 6,
\\"data\\": {
\\"source\\": 11,
\\"level\\": \\"countReset\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:4:37\\"
],
\\"payload\\": [
\\"\\\\\\"count\\\\\\"\\"
]
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"countReset\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:4:37\\"
],
\\"payload\\": [
\\"\\\\\\"count\\\\\\"\\"
]
}
}
},
{
\\"type\\": 3,
\\"type\\": 6,
\\"data\\": {
\\"source\\": 11,
\\"level\\": \\"debug\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:5:37\\"
],
\\"payload\\": [
\\"\\\\\\"debug\\\\\\"\\"
]
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"debug\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:5:37\\"
],
\\"payload\\": [
\\"\\\\\\"debug\\\\\\"\\"
]
}
}
},
{
\\"type\\": 3,
\\"type\\": 6,
\\"data\\": {
\\"source\\": 11,
\\"level\\": \\"dir\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:6:37\\"
],
\\"payload\\": [
\\"\\\\\\"dir\\\\\\"\\"
]
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"dir\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:6:37\\"
],
\\"payload\\": [
\\"\\\\\\"dir\\\\\\"\\"
]
}
}
},
{
\\"type\\": 3,
\\"type\\": 6,
\\"data\\": {
\\"source\\": 11,
\\"level\\": \\"dirxml\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:7:37\\"
],
\\"payload\\": [
\\"\\\\\\"dirxml\\\\\\"\\"
]
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"dirxml\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:7:37\\"
],
\\"payload\\": [
\\"\\\\\\"dirxml\\\\\\"\\"
]
}
}
},
{
\\"type\\": 3,
\\"type\\": 6,
\\"data\\": {
\\"source\\": 11,
\\"level\\": \\"group\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:8:37\\"
],
\\"payload\\": []
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"group\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:8:37\\"
],
\\"payload\\": []
}
}
},
{
\\"type\\": 3,
\\"type\\": 6,
\\"data\\": {
\\"source\\": 11,
\\"level\\": \\"groupCollapsed\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:9:37\\"
],
\\"payload\\": []
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"groupCollapsed\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:9:37\\"
],
\\"payload\\": []
}
}
},
{
\\"type\\": 3,
\\"type\\": 6,
\\"data\\": {
\\"source\\": 11,
\\"level\\": \\"info\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:10:37\\"
],
\\"payload\\": [
\\"\\\\\\"info\\\\\\"\\"
]
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"info\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:10:37\\"
],
\\"payload\\": [
\\"\\\\\\"info\\\\\\"\\"
]
}
}
},
{
\\"type\\": 3,
\\"type\\": 6,
\\"data\\": {
\\"source\\": 11,
\\"level\\": \\"log\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:11:37\\"
],
\\"payload\\": [
\\"\\\\\\"log\\\\\\"\\"
]
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"log\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:11:37\\"
],
\\"payload\\": [
\\"\\\\\\"log\\\\\\"\\"
]
}
}
},
{
\\"type\\": 3,
\\"type\\": 6,
\\"data\\": {
\\"source\\": 11,
\\"level\\": \\"table\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:12:37\\"
],
\\"payload\\": [
\\"\\\\\\"table\\\\\\"\\"
]
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"table\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:12:37\\"
],
\\"payload\\": [
\\"\\\\\\"table\\\\\\"\\"
]
}
}
},
{
\\"type\\": 3,
\\"type\\": 6,
\\"data\\": {
\\"source\\": 11,
\\"level\\": \\"time\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:13:37\\"
],
\\"payload\\": []
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"time\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:13:37\\"
],
\\"payload\\": []
}
}
},
{
\\"type\\": 3,
\\"type\\": 6,
\\"data\\": {
\\"source\\": 11,
\\"level\\": \\"timeEnd\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:14:37\\"
],
\\"payload\\": []
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"timeEnd\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:14:37\\"
],
\\"payload\\": []
}
}
},
{
\\"type\\": 3,
\\"type\\": 6,
\\"data\\": {
\\"source\\": 11,
\\"level\\": \\"timeLog\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:15:37\\"
],
\\"payload\\": []
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"timeLog\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:15:37\\"
],
\\"payload\\": []
}
}
},
{
\\"type\\": 3,
\\"type\\": 6,
\\"data\\": {
\\"source\\": 11,
\\"level\\": \\"trace\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:16:37\\"
],
\\"payload\\": [
\\"\\\\\\"trace\\\\\\"\\"
]
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"trace\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:16:37\\"
],
\\"payload\\": [
\\"\\\\\\"trace\\\\\\"\\"
]
}
}
},
{
\\"type\\": 3,
\\"type\\": 6,
\\"data\\": {
\\"source\\": 11,
\\"level\\": \\"warn\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:17:37\\"
],
\\"payload\\": [
\\"\\\\\\"warn\\\\\\"\\"
]
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"warn\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:17:37\\"
],
\\"payload\\": [
\\"\\\\\\"warn\\\\\\"\\"
]
}
}
},
{
\\"type\\": 3,
\\"type\\": 6,
\\"data\\": {
\\"source\\": 11,
\\"level\\": \\"clear\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:18:37\\"
],
\\"payload\\": []
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"clear\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:18:37\\"
],
\\"payload\\": []
}
}
}
]"

View File

@@ -77,7 +77,7 @@ describe('record integration tests', function (this: ISuite) {
maskInputOptions: ${JSON.stringify(options.maskAllInputs)},
maskTextFn: ${options.maskTextFn},
recordCanvas: ${options.recordCanvas},
recordLog: ${options.recordLog},
plugins: ${options.plugins}
});
</script>
</body>
@@ -90,7 +90,12 @@ describe('record integration tests', function (this: ISuite) {
this.browser = await launchPuppeteer();
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 () => {
@@ -385,7 +390,7 @@ describe('record integration tests', function (this: ISuite) {
await page.goto('about:blank');
await page.setContent(
getHtml.call(this, 'log.html', {
recordLog: true,
plugins: '[rrwebConsoleRecord.getRecordConsolePlugin()]',
}),
);

View File

@@ -1,2 +1,4 @@
export * from '../index';
export * from '../packer';
export * from '../plugins/console/record';
export * from '../plugins/console/replay';

View 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[];
};

View 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 {};

View File

@@ -0,0 +1,2 @@
import { StringifyOptions } from './index';
export declare function stringify(obj: any, stringifyOptions?: StringifyOptions): string;

View 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 {};

View File

@@ -50,8 +50,6 @@ export declare class Replayer {
private applyMutation;
private applyScroll;
private applyInput;
private formatMessage;
private getConsoleLogger;
private legacy_resolveMissingNode;
private moveAndHover;
private drawMouseTail;

87
typings/types.d.ts vendored
View File

@@ -3,13 +3,15 @@ import { PackFn, UnpackFn } from './packer/base';
import { FontFaceDescriptors } from 'css-font-loading-module';
import { IframeManager } from './record/iframe-manager';
import { ShadowDomManager } from './record/shadow-dom-manager';
import type { Replayer } from './replay';
export declare enum EventType {
DomContentLoaded = 0,
Load = 1,
FullSnapshot = 2,
IncrementalSnapshot = 3,
Meta = 4,
Custom = 5
Custom = 5,
Plugin = 6
}
export declare type domContentLoadedEvent = {
type: EventType.DomContentLoaded;
@@ -41,10 +43,6 @@ export declare type metaEvent = {
height: number;
};
};
export declare type logEvent = {
type: EventType.IncrementalSnapshot;
data: incrementalData;
};
export declare type customEvent<T = unknown> = {
type: EventType.Custom;
data: {
@@ -52,6 +50,13 @@ export declare type customEvent<T = unknown> = {
payload: T;
};
};
export declare type pluginEvent<T = unknown> = {
type: EventType.Plugin;
data: {
plugin: string;
payload: T;
};
};
export declare type styleSheetEvent = {};
export declare enum IncrementalSource {
Mutation = 0,
@@ -100,11 +105,8 @@ export declare type canvasMutationData = {
export declare type fontData = {
source: IncrementalSource.Font;
} & fontParam;
export declare type logData = {
source: IncrementalSource.Log;
} & 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 incrementalData = mutationData | mousemoveData | mouseInteractionData | scrollData | viewportResizeData | inputData | mediaInteractionData | styleSheetRuleData | canvasMutationData | fontData;
export declare type event = domContentLoadedEvent | loadedEvent | fullSnapshotEvent | incrementalSnapshotEvent | metaEvent | customEvent | pluginEvent;
export declare type eventWithTime = event & {
timestamp: number;
delay?: number;
@@ -118,6 +120,11 @@ export declare type SamplingStrategy = Partial<{
scroll: number;
input: 'all' | 'last';
}>;
export declare type RecordPlugin<TOptions = unknown> = {
name: string;
observer: (cb: Function, options: TOptions) => listenerHandler;
options: TOptions;
};
export declare type recordOptions<T> = {
emit?: (e: T, isCheckout?: boolean) => void;
checkoutEveryNth?: number;
@@ -138,8 +145,8 @@ export declare type recordOptions<T> = {
sampling?: SamplingStrategy;
recordCanvas?: boolean;
collectFonts?: boolean;
plugins?: RecordPlugin[];
mousemoveWait?: number;
recordLog?: boolean | LogRecordOptions;
};
export declare type observerParam = {
mutationCb: mutationCallBack;
@@ -161,8 +168,6 @@ export declare type observerParam = {
styleSheetRuleCb: styleSheetRuleCallback;
canvasMutationCb: canvasMutationCallback;
fontCb: fontCallback;
logCb: logCallback;
logOptions: LogRecordOptions;
sampling: SamplingStrategy;
recordCanvas: boolean;
collectFonts: boolean;
@@ -171,6 +176,11 @@ export declare type observerParam = {
mirror: Mirror;
iframeManager: IframeManager;
shadowDomManager: ShadowDomManager;
plugins: Array<{
observer: Function;
callback: Function;
options: unknown;
}>;
};
export declare type hooksParam = {
mutation?: mutationCallBack;
@@ -183,7 +193,6 @@ export declare type hooksParam = {
styleSheetRule?: styleSheetRuleCallback;
canvasMutation?: canvasMutationCallback;
font?: fontCallback;
log?: logCallback;
};
export declare type mutationRecord = {
type: string;
@@ -290,36 +299,7 @@ export declare type fontParam = {
buffer: boolean;
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 logCallback = (p: LogParam) => void;
export declare type viewportResizeDimension = {
width: number;
height: number;
@@ -363,6 +343,11 @@ export declare type throttleOptions = {
};
export declare type listenerHandler = () => void;
export declare type hookResetter = () => void;
export declare type ReplayPlugin = {
handler: (event: eventWithTime, isSync: boolean, context: {
replayer: Replayer;
}) => void;
};
export declare type playerConfig = {
speed: number;
maxSpeed: number;
@@ -384,11 +369,7 @@ export declare type playerConfig = {
strokeStyle?: string;
};
unpackFn?: UnpackFn;
logConfig: LogReplayConfig;
};
export declare type LogReplayConfig = {
level?: LogLevel[] | undefined;
replayLogger: ReplayLogger | undefined;
plugins?: ReplayPlugin[];
};
export declare type playerMetaData = {
startTime: number;
@@ -436,14 +417,4 @@ export declare type MaskTextFn = (text: string) => string;
export declare type ElementState = {
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 {};