Skip to content

Commit 3a68385

Browse files
committed
feat: allow replaying a single event
working add events refactor
1 parent 53b83bb commit 3a68385

File tree

3 files changed

+82
-1
lines changed

3 files changed

+82
-1
lines changed

packages/rrweb/src/replay/index.ts

+29-1
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ export class Replayer {
134134
private mouseTail: HTMLCanvasElement | null = null;
135135
private tailPositions: Array<{ x: number; y: number }> = [];
136136

137-
private emitter: Emitter = mitt();
137+
private emitter: Emitter = mitt() as Emitter;
138138

139139
private nextUserInteractionEvent: eventWithTime | null;
140140

@@ -331,6 +331,8 @@ export class Replayer {
331331
this.applySelection(this.lastSelectionData);
332332
this.lastSelectionData = null;
333333
}
334+
335+
this.emitter.emit(ReplayerEvents.FlushEnd);
334336
});
335337
this.emitter.on(ReplayerEvents.PlayBack, () => {
336338
this.firstFullSnapshot = null;
@@ -525,6 +527,31 @@ export class Replayer {
525527
this.emitter.emit(ReplayerEvents.Start);
526528
}
527529

530+
public playSingleEvent(eventIndex: number) {
531+
const handleFinish = () => {
532+
this.service.send('END');
533+
this.emitter.off(ReplayerEvents.FlushEnd, handleFinish);
534+
};
535+
this.emitter.on(ReplayerEvents.FlushEnd, handleFinish);
536+
537+
if (this.service.state.matches('paused')) {
538+
this.service.send({
539+
type: 'PLAY_SINGLE_EVENT',
540+
payload: { singleEvent: eventIndex },
541+
});
542+
} else {
543+
this.service.send({ type: 'PAUSE' });
544+
this.service.send({
545+
type: 'PLAY_SINGLE_EVENT',
546+
payload: { singleEvent: eventIndex },
547+
});
548+
}
549+
this.iframe.contentDocument
550+
?.getElementsByTagName('html')[0]
551+
?.classList.remove('rrweb-paused');
552+
this.emitter.emit(ReplayerEvents.Start);
553+
}
554+
528555
public pause(timeOffset?: number) {
529556
if (timeOffset === undefined && this.service.state.matches('playing')) {
530557
this.service.send({ type: 'PAUSE' });
@@ -558,6 +585,7 @@ export class Replayer {
558585
this.mediaManager.reset();
559586
this.config.root.removeChild(this.wrapper);
560587
this.emitter.emit(ReplayerEvents.Destroy);
588+
this.emitter.all.clear();
561589
}
562590

563591
public startLive(baselineTime?: number) {

packages/rrweb/src/replay/machine.ts

+51
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ export type PlayerEvent =
2828
timeOffset: number;
2929
};
3030
}
31+
| {
32+
type: 'PLAY_SINGLE_EVENT';
33+
payload: {
34+
singleEvent: number;
35+
};
36+
}
3137
| {
3238
type: 'CAST_EVENT';
3339
payload: {
@@ -78,6 +84,30 @@ export function discardPriorSnapshots(
7884
return events;
7985
}
8086

87+
function discardPriorSnapshotsToEvent(
88+
events: eventWithTime[],
89+
targetIndex: number,
90+
) {
91+
const targetEvent = events[targetIndex];
92+
93+
if (!targetEvent) {
94+
return [];
95+
}
96+
97+
for (let idx = targetIndex; idx >= 0; idx--) {
98+
const event = events[idx];
99+
100+
if (!event) {
101+
continue;
102+
}
103+
104+
if (event.type === EventType.Meta) {
105+
return events.slice(idx, targetIndex + 1);
106+
}
107+
}
108+
return events;
109+
}
110+
81111
type PlayerAssets = {
82112
emitter: Emitter;
83113
applyEventsSynchronously(events: Array<eventWithTime>): void;
@@ -119,6 +149,10 @@ export function createPlayerService(
119149
target: 'playing',
120150
actions: ['recordTimeOffset', 'play'],
121151
},
152+
PLAY_SINGLE_EVENT: {
153+
target: 'playing',
154+
actions: ['playSingleEvent'],
155+
},
122156
CAST_EVENT: {
123157
target: 'paused',
124158
actions: 'castEvent',
@@ -168,6 +202,23 @@ export function createPlayerService(
168202
baselineTime: ctx.events[0].timestamp + timeOffset,
169203
};
170204
}),
205+
206+
playSingleEvent(ctx, event) {
207+
if (event.type !== 'PLAY_SINGLE_EVENT') {
208+
return;
209+
}
210+
211+
const { singleEvent } = event.payload;
212+
213+
const neededEvents = discardPriorSnapshotsToEvent(
214+
ctx.events,
215+
singleEvent,
216+
);
217+
218+
applyEventsSynchronously(neededEvents);
219+
emitter.emit(ReplayerEvents.Flush);
220+
},
221+
171222
play(ctx) {
172223
const { timer, events, baselineTime, lastPlayedEvent } = ctx;
173224
timer.clear();

packages/types/src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,7 @@ export type Emitter = {
651651
on(type: string, handler: Handler): void;
652652
emit(type: string, event?: unknown): void;
653653
off(type: string, handler: Handler): void;
654+
all: Map<string | symbol, Handler[]>;
654655
};
655656

656657
export type Arguments<T> = T extends (...payload: infer U) => unknown
@@ -675,6 +676,7 @@ export enum ReplayerEvents {
675676
EventCast = 'event-cast',
676677
CustomEvent = 'custom-event',
677678
Flush = 'flush',
679+
FlushEnd = 'flush-end',
678680
StateChange = 'state-change',
679681
PlayBack = 'play-back',
680682
Destroy = 'destroy',

0 commit comments

Comments
 (0)