impl sequential id plugins (#819)
Also introduce a new kind of plugin: event processor
This commit is contained in:
@@ -93,6 +93,16 @@ const baseConfigs = [
|
||||
name: 'rrwebConsoleReplay',
|
||||
pathFn: toPluginPath('console', 'replay'),
|
||||
},
|
||||
{
|
||||
input: './src/plugins/sequential-id/record/index.ts',
|
||||
name: 'rrwebSequentialIdRecord',
|
||||
pathFn: toPluginPath('sequential-id', 'record'),
|
||||
},
|
||||
{
|
||||
input: './src/plugins/sequential-id/replay/index.ts',
|
||||
name: 'rrwebSequentialIdReplay',
|
||||
pathFn: toPluginPath('sequential-id', 'replay'),
|
||||
},
|
||||
];
|
||||
|
||||
let configs = [];
|
||||
|
||||
31
packages/rrweb/src/plugins/sequential-id/record/index.ts
Normal file
31
packages/rrweb/src/plugins/sequential-id/record/index.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { RecordPlugin } from '../../../types';
|
||||
|
||||
export type SequentialIdOptions = {
|
||||
key: string;
|
||||
};
|
||||
|
||||
const defaultOptions: SequentialIdOptions = {
|
||||
key: '_sid',
|
||||
};
|
||||
|
||||
export const PLUGIN_NAME = 'rrweb/sequential-id@1';
|
||||
|
||||
export const getRecordSequentialIdPlugin: (
|
||||
options?: Partial<SequentialIdOptions>,
|
||||
) => RecordPlugin = (options) => {
|
||||
const _options = options
|
||||
? Object.assign({}, defaultOptions, options)
|
||||
: defaultOptions;
|
||||
let id = 0;
|
||||
|
||||
return {
|
||||
name: PLUGIN_NAME,
|
||||
eventProcessor(event) {
|
||||
Object.assign(event, {
|
||||
[_options.key]: ++id,
|
||||
});
|
||||
return event;
|
||||
},
|
||||
options: _options,
|
||||
};
|
||||
};
|
||||
39
packages/rrweb/src/plugins/sequential-id/replay/index.ts
Normal file
39
packages/rrweb/src/plugins/sequential-id/replay/index.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import type { SequentialIdOptions } from '../record';
|
||||
import { ReplayPlugin, eventWithTime } from '../../../types';
|
||||
|
||||
type Options = SequentialIdOptions & {
|
||||
warnOnMissingId: boolean;
|
||||
};
|
||||
|
||||
const defaultOptions: Options = {
|
||||
key: '_sid',
|
||||
warnOnMissingId: true,
|
||||
};
|
||||
|
||||
export const getReplaySequentialIdPlugin: (
|
||||
options?: Partial<Options>,
|
||||
) => ReplayPlugin = (options) => {
|
||||
const { key, warnOnMissingId } = options
|
||||
? Object.assign({}, defaultOptions, options)
|
||||
: defaultOptions;
|
||||
let currentId = 1;
|
||||
|
||||
return {
|
||||
handler(event: eventWithTime) {
|
||||
if (key in event) {
|
||||
const id = ((event as unknown) as Record<string, number>)[key];
|
||||
if (id !== currentId) {
|
||||
console.error(
|
||||
`[sequential-id-plugin]: expect to get an id with value "${currentId}", but got "${id}"`,
|
||||
);
|
||||
} else {
|
||||
currentId++;
|
||||
}
|
||||
} else if (warnOnMissingId) {
|
||||
console.warn(
|
||||
`[sequential-id-plugin]: failed to get id in key: "${key}"`,
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -122,6 +122,17 @@ function record<T = eventWithTime>(
|
||||
|
||||
let lastFullSnapshotEvent: eventWithTime;
|
||||
let incrementalSnapshotCount = 0;
|
||||
const eventProcessor = (e: eventWithTime): T => {
|
||||
for (const plugin of plugins || []) {
|
||||
if (plugin.eventProcessor) {
|
||||
e = plugin.eventProcessor(e);
|
||||
}
|
||||
}
|
||||
if (packFn) {
|
||||
e = (packFn(e) as unknown) as eventWithTime;
|
||||
}
|
||||
return (e as unknown) as T;
|
||||
};
|
||||
wrappedEmit = (e: eventWithTime, isCheckout?: boolean) => {
|
||||
if (
|
||||
mutationBuffers[0]?.isFrozen() &&
|
||||
@@ -136,7 +147,7 @@ function record<T = eventWithTime>(
|
||||
mutationBuffers.forEach((buf) => buf.unfreeze());
|
||||
}
|
||||
|
||||
emit(((packFn ? packFn(e) : e) as unknown) as T, isCheckout);
|
||||
emit(eventProcessor(e), isCheckout);
|
||||
if (e.type === EventType.FullSnapshot) {
|
||||
lastFullSnapshotEvent = e;
|
||||
incrementalSnapshotCount = 0;
|
||||
@@ -417,20 +428,22 @@ function record<T = eventWithTime>(
|
||||
shadowDomManager,
|
||||
canvasManager,
|
||||
plugins:
|
||||
plugins?.map((p) => ({
|
||||
observer: p.observer,
|
||||
options: p.options,
|
||||
callback: (payload: object) =>
|
||||
wrappedEmit(
|
||||
wrapEvent({
|
||||
type: EventType.Plugin,
|
||||
data: {
|
||||
plugin: p.name,
|
||||
payload,
|
||||
},
|
||||
}),
|
||||
),
|
||||
})) || [],
|
||||
plugins
|
||||
?.filter((p) => p.observer)
|
||||
?.map((p) => ({
|
||||
observer: p.observer!,
|
||||
options: p.options,
|
||||
callback: (payload: object) =>
|
||||
wrappedEmit(
|
||||
wrapEvent({
|
||||
type: EventType.Plugin,
|
||||
data: {
|
||||
plugin: p.name,
|
||||
payload,
|
||||
},
|
||||
}),
|
||||
),
|
||||
})) || [],
|
||||
},
|
||||
hooks,
|
||||
);
|
||||
|
||||
@@ -205,7 +205,8 @@ export type SamplingStrategy = Partial<{
|
||||
|
||||
export type RecordPlugin<TOptions = unknown> = {
|
||||
name: string;
|
||||
observer: (cb: Function, win: IWindow, options: TOptions) => listenerHandler;
|
||||
observer?: (cb: Function, win: IWindow, options: TOptions) => listenerHandler;
|
||||
eventProcessor?: <TExtend>(event: eventWithTime) => eventWithTime & TExtend;
|
||||
options: TOptions;
|
||||
};
|
||||
|
||||
|
||||
6
packages/rrweb/typings/plugins/sequential-id/record/index.d.ts
vendored
Normal file
6
packages/rrweb/typings/plugins/sequential-id/record/index.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
import { RecordPlugin } from '../../../types';
|
||||
export declare type SequentialIdOptions = {
|
||||
key: string;
|
||||
};
|
||||
export declare const PLUGIN_NAME = "rrweb/sequential-id@1";
|
||||
export declare const getRecordSequentialIdPlugin: (options?: Partial<SequentialIdOptions>) => RecordPlugin;
|
||||
7
packages/rrweb/typings/plugins/sequential-id/replay/index.d.ts
vendored
Normal file
7
packages/rrweb/typings/plugins/sequential-id/replay/index.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { SequentialIdOptions } from '../record';
|
||||
import { ReplayPlugin } from '../../../types';
|
||||
declare type Options = SequentialIdOptions & {
|
||||
warnOnMissingId: boolean;
|
||||
};
|
||||
export declare const getReplaySequentialIdPlugin: (options?: Partial<Options>) => ReplayPlugin;
|
||||
export {};
|
||||
3
packages/rrweb/typings/types.d.ts
vendored
3
packages/rrweb/typings/types.d.ts
vendored
@@ -128,7 +128,8 @@ export declare type SamplingStrategy = Partial<{
|
||||
}>;
|
||||
export declare type RecordPlugin<TOptions = unknown> = {
|
||||
name: string;
|
||||
observer: (cb: Function, win: IWindow, options: TOptions) => listenerHandler;
|
||||
observer?: (cb: Function, win: IWindow, options: TOptions) => listenerHandler;
|
||||
eventProcessor?: <TExtend>(event: eventWithTime) => eventWithTime & TExtend;
|
||||
options: TOptions;
|
||||
};
|
||||
export declare type recordOptions<T> = {
|
||||
|
||||
Reference in New Issue
Block a user