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',
|
name: 'rrwebConsoleReplay',
|
||||||
pathFn: toPluginPath('console', 'replay'),
|
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 = [];
|
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 lastFullSnapshotEvent: eventWithTime;
|
||||||
let incrementalSnapshotCount = 0;
|
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) => {
|
wrappedEmit = (e: eventWithTime, isCheckout?: boolean) => {
|
||||||
if (
|
if (
|
||||||
mutationBuffers[0]?.isFrozen() &&
|
mutationBuffers[0]?.isFrozen() &&
|
||||||
@@ -136,7 +147,7 @@ function record<T = eventWithTime>(
|
|||||||
mutationBuffers.forEach((buf) => buf.unfreeze());
|
mutationBuffers.forEach((buf) => buf.unfreeze());
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(((packFn ? packFn(e) : e) as unknown) as T, isCheckout);
|
emit(eventProcessor(e), isCheckout);
|
||||||
if (e.type === EventType.FullSnapshot) {
|
if (e.type === EventType.FullSnapshot) {
|
||||||
lastFullSnapshotEvent = e;
|
lastFullSnapshotEvent = e;
|
||||||
incrementalSnapshotCount = 0;
|
incrementalSnapshotCount = 0;
|
||||||
@@ -417,20 +428,22 @@ function record<T = eventWithTime>(
|
|||||||
shadowDomManager,
|
shadowDomManager,
|
||||||
canvasManager,
|
canvasManager,
|
||||||
plugins:
|
plugins:
|
||||||
plugins?.map((p) => ({
|
plugins
|
||||||
observer: p.observer,
|
?.filter((p) => p.observer)
|
||||||
options: p.options,
|
?.map((p) => ({
|
||||||
callback: (payload: object) =>
|
observer: p.observer!,
|
||||||
wrappedEmit(
|
options: p.options,
|
||||||
wrapEvent({
|
callback: (payload: object) =>
|
||||||
type: EventType.Plugin,
|
wrappedEmit(
|
||||||
data: {
|
wrapEvent({
|
||||||
plugin: p.name,
|
type: EventType.Plugin,
|
||||||
payload,
|
data: {
|
||||||
},
|
plugin: p.name,
|
||||||
}),
|
payload,
|
||||||
),
|
},
|
||||||
})) || [],
|
}),
|
||||||
|
),
|
||||||
|
})) || [],
|
||||||
},
|
},
|
||||||
hooks,
|
hooks,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -205,7 +205,8 @@ export type SamplingStrategy = Partial<{
|
|||||||
|
|
||||||
export type RecordPlugin<TOptions = unknown> = {
|
export type RecordPlugin<TOptions = unknown> = {
|
||||||
name: string;
|
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;
|
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> = {
|
export declare type RecordPlugin<TOptions = unknown> = {
|
||||||
name: string;
|
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;
|
options: TOptions;
|
||||||
};
|
};
|
||||||
export declare type recordOptions<T> = {
|
export declare type recordOptions<T> = {
|
||||||
|
|||||||
Reference in New Issue
Block a user