diff --git a/src/index.ts b/src/index.ts index 3da6831d..ccc0e8e8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,11 @@ -import record from './record'; +import record, { addCustomEvent } from './record'; import { Replayer } from './replay'; import { mirror } from './utils'; -export { EventType, IncrementalSource, MouseInteractions, ReplayerEvents } from './types' -export { record, Replayer, mirror }; +export { + EventType, + IncrementalSource, + MouseInteractions, + ReplayerEvents, +} from './types'; +export { record, addCustomEvent, Replayer, mirror }; diff --git a/src/record/index.ts b/src/record/index.ts index d61ce4a2..66f633eb 100644 --- a/src/record/index.ts +++ b/src/record/index.ts @@ -17,6 +17,23 @@ function wrapEvent(e: event): eventWithTime { }; } +let wrappedEmit!: (e: eventWithTime, isCheckout?: boolean) => void; + +export function addCustomEvent(tag: string, payload: T) { + if (!wrappedEmit) { + throw new Error('please add custom event after start recording'); + } + wrappedEmit( + wrapEvent({ + type: EventType.Custom, + data: { + tag, + payload, + }, + }), + ); +} + function record(options: recordOptions = {}): listenerHandler | undefined { const { emit, @@ -34,7 +51,7 @@ function record(options: recordOptions = {}): listenerHandler | undefined { let lastFullSnapshotEvent: eventWithTime; let incrementalSnapshotCount = 0; - const wrappedEmit = (e: eventWithTime, isCheckout?: boolean) => { + wrappedEmit = (e: eventWithTime, isCheckout?: boolean) => { emit(e, isCheckout); if (e.type === EventType.FullSnapshot) { lastFullSnapshotEvent = e; diff --git a/src/types.ts b/src/types.ts index ff1c1e89..807c81af 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,6 +6,7 @@ export enum EventType { FullSnapshot, IncrementalSnapshot, Meta, + Custom, } export type domContentLoadedEvent = { @@ -43,6 +44,14 @@ export type metaEvent = { }; }; +export type customEvent = { + type: EventType.Custom; + data: { + tag: string; + payload: T; + }; +}; + export enum IncrementalSource { Mutation, MouseMove, @@ -91,7 +100,8 @@ export type event = | loadedEvent | fullSnapshotEvent | incrementalSnapshotEvent - | metaEvent; + | metaEvent + | customEvent; export type eventWithTime = event & { timestamp: number; diff --git a/test/__snapshots__/record.test.ts.snap b/test/__snapshots__/record.test.ts.snap index 22b67cdd..7ecfb700 100644 --- a/test/__snapshots__/record.test.ts.snap +++ b/test/__snapshots__/record.test.ts.snap @@ -245,3 +245,89 @@ exports[`async-checkout 1`] = ` } ]" `; + +exports[`custom-event 1`] = ` +"[ + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 800, + \\"height\\": 600 + } + }, + { + \\"type\\": 2, + \\"data\\": { + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 3 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 5 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"text\\" + }, + \\"childNodes\\": [], + \\"id\\": 6 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", + \\"id\\": 7 + } + ], + \\"id\\": 4 + } + ], + \\"id\\": 2 + } + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 5, + \\"data\\": { + \\"tag\\": \\"tag1\\", + \\"payload\\": 1 + } + }, + { + \\"type\\": 5, + \\"data\\": { + \\"tag\\": \\"tag2\\", + \\"payload\\": { + \\"a\\": \\"b\\" + } + } + } +]" +`; diff --git a/test/record.test.ts b/test/record.test.ts index ca56a6d0..e3213144 100644 --- a/test/record.test.ts +++ b/test/record.test.ts @@ -23,6 +23,7 @@ interface ISuite extends Suite { interface IWindow extends Window { rrweb: { record: (options: recordOptions) => listenerHandler | undefined; + addCustomEvent(tag: string, payload: T): void; }; emit: (e: eventWithTime) => undefined; } @@ -180,4 +181,19 @@ describe('record', function(this: ISuite) { await this.page.waitFor(50); assertSnapshot(this.events, __filename, 'async-checkout'); }); + + it('can add custom event', async () => { + await this.page.evaluate(() => { + const { record, addCustomEvent } = (window as IWindow).rrweb; + record({ + emit: (window as IWindow).emit, + }); + addCustomEvent('tag1', 1); + addCustomEvent<{ a: string }>('tag2', { + a: 'b', + }); + }); + await this.page.waitFor(50); + assertSnapshot(this.events, __filename, 'custom-event'); + }); });