From 8f3ea2b76a7fa02685ae0d1f1be40be9a050b343 Mon Sep 17 00:00:00 2001 From: Yanzhen Yu Date: Wed, 1 Apr 2026 12:00:00 +0800 Subject: [PATCH] close #330 implement more accurate finish event --- src/replay/index.ts | 27 +++++++++++++++++++++++---- typings/replay/index.d.ts | 3 +++ typings/types.d.ts | 8 +++++++- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/replay/index.ts b/src/replay/index.ts index 72f0bf8e..915dbf29 100644 --- a/src/replay/index.ts +++ b/src/replay/index.ts @@ -417,9 +417,23 @@ export class Replayer { this.service.state.context.events.length - 1 ] ) { - this.backToNormal(); - this.service.send('END'); - this.emitter.emit(ReplayerEvents.Finish); + const finish = () => { + this.backToNormal(); + this.service.send('END'); + this.emitter.emit(ReplayerEvents.Finish); + }; + if ( + event.type === EventType.IncrementalSnapshot && + event.data.source === IncrementalSource.MouseMove && + event.data.positions.length + ) { + // defer finish event if the last event is a mouse move + setTimeout(() => { + finish(); + }, Math.max(0, -event.data.positions[0].timeOffset)); + } else { + finish(); + } } }; return wrappedCastFn; @@ -559,7 +573,7 @@ export class Replayer { } private applyIncremental( - e: incrementalSnapshotEvent & { timestamp: number }, + e: incrementalSnapshotEvent & { timestamp: number; delay?: number }, isSync: boolean, ) { const { data: d } = e; @@ -591,6 +605,11 @@ export class Replayer { }; this.timer.addAction(action); }); + // add a dummy action to keep timer alive + this.timer.addAction({ + doAction() {}, + delay: e.delay! - d.positions[0]?.timeOffset, + }); } break; case IncrementalSource.MouseInteraction: { diff --git a/typings/replay/index.d.ts b/typings/replay/index.d.ts index 30a733c3..23348143 100644 --- a/typings/replay/index.d.ts +++ b/typings/replay/index.d.ts @@ -10,6 +10,8 @@ export declare class Replayer { get timer(): Timer; config: playerConfig; private mouse; + private mouseTail; + private tailPositions; private emitter; private nextUserInteractionEvent; private legacy_missingNodeRetryMap; @@ -41,6 +43,7 @@ export declare class Replayer { private applyInput; private legacy_resolveMissingNode; private moveAndHover; + private drawMouseTail; private hoverElements; private isUserInteraction; private backToNormal; diff --git a/typings/types.d.ts b/typings/types.d.ts index ca668327..5406e0be 100644 --- a/typings/types.d.ts +++ b/typings/types.d.ts @@ -286,6 +286,12 @@ export declare type playerConfig = { insertStyleRules: string[]; triggerFocus: boolean; UNSAFE_replayCanvas: boolean; + mouseTail: boolean | { + duration?: number; + lineCap?: string; + lineWidth?: number; + strokeStyle?: string; + }; unpackFn?: UnpackFn; }; export declare type playerMetaData = { @@ -301,7 +307,7 @@ export declare type missingNodeMap = { [id: number]: missingNode; }; export declare type actionWithDelay = { - doAction: () => void; + doAction: () => Promise; delay: number; }; export declare type Handler = (event?: unknown) => void;