impl sequential id plugins (#819)

Also introduce a new kind of plugin: event processor
This commit is contained in:
yz-yu
2026-04-01 12:00:00 +08:00
committed by GitHub
parent 7cd03662a4
commit 2b969804a9
8 changed files with 125 additions and 17 deletions

View File

@@ -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 = [];

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

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

View File

@@ -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,
);

View File

@@ -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;
};

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

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

View File

@@ -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> = {