From a8b493799b3d4c0be65d6ef6e2f5511418a9500d Mon Sep 17 00:00:00 2001 From: yz-yu Date: Wed, 1 Apr 2026 12:00:00 +0800 Subject: [PATCH] fix the skip event calculation (#242) --- src/replay/index.ts | 6 +++--- src/replay/machine.ts | 37 ++++++++++++++++++++++++------------- src/types.ts | 2 ++ test/machine.test.ts | 35 ++++++++++++++++++++++++++++++++--- test/replayer.test.ts | 7 ++++++- 5 files changed, 67 insertions(+), 20 deletions(-) diff --git a/src/replay/index.ts b/src/replay/index.ts index c189da1b..d39c0739 100644 --- a/src/replay/index.ts +++ b/src/replay/index.ts @@ -216,10 +216,10 @@ export class Replayer { * @param timeOffset number */ public play(timeOffset = 0) { - if (this.service.state.value == 'ended') { - this.service.send({ type: 'REPLAY'}); + if (this.service.state.value === 'ended') { + this.service.send({ type: 'REPLAY' }); } - if (this.service.state.value == 'paused') { + if (this.service.state.value === 'paused') { this.service.send({ type: 'RESUME', payload: { timeOffset } }); } else { this.service.send({ type: 'PLAY', payload: { timeOffset } }); diff --git a/src/replay/machine.ts b/src/replay/machine.ts index a30a9a55..cdc794f9 100644 --- a/src/replay/machine.ts +++ b/src/replay/machine.ts @@ -79,27 +79,37 @@ export type PlayerState = * If the array have multiple meta and fullsnapshot events, * return the events from last meta to the end. */ -export function getLastSession(events: eventWithTime[]): eventWithTime[] { - const lastSession: eventWithTime[] = []; - - let hasFullSnapshot = false; - let hasMeta = false; +export function getLastSession( + events: eventWithTime[], + baselineTime: number, +): eventWithTime[] { + let startMetaIdx: number | null = null; + let endMetaIdx: number | null = null; for (let idx = events.length - 1; idx >= 0; idx--) { const event = events[idx]; - lastSession.unshift(event); - if (event.type === EventType.FullSnapshot) { - hasFullSnapshot = true; - } if (event.type === EventType.Meta) { - hasMeta = true; + if (event.timestamp > baselineTime) { + endMetaIdx = idx; + } else { + startMetaIdx = idx; + } } - if (hasFullSnapshot && hasMeta) { + if (startMetaIdx !== null) { break; } } - return lastSession; + // baseline time is less than first meta event + if (startMetaIdx === null && endMetaIdx !== null) { + startMetaIdx = endMetaIdx; + endMetaIdx = null; + } + + return events.slice( + startMetaIdx ?? 0, + endMetaIdx === null ? events.length : endMetaIdx + 1, + ); } type PlayerAssets = { @@ -198,12 +208,13 @@ export function createPlayerService( play(ctx) { const { timer, events, baselineTime, lastPlayedEvent } = ctx; timer.clear(); - const neededEvents = getLastSession(events); + const neededEvents = getLastSession(events, baselineTime); const actions = new Array(); for (const event of neededEvents) { if ( lastPlayedEvent && + lastPlayedEvent.timestamp > baselineTime && (event.timestamp <= lastPlayedEvent.timestamp || event === lastPlayedEvent) ) { diff --git a/src/types.ts b/src/types.ts index 749f3d33..455a7032 100644 --- a/src/types.ts +++ b/src/types.ts @@ -336,6 +336,8 @@ export type playerConfig = { }; export type playerMetaData = { + startTime: number; + endTime: number; totalTime: number; }; diff --git a/test/machine.test.ts b/test/machine.test.ts index 261a9a06..24a8179f 100644 --- a/test/machine.test.ts +++ b/test/machine.test.ts @@ -6,14 +6,43 @@ import { EventType } from '../src/types'; const events = sampleEvents.filter( (e) => ![EventType.DomContentLoaded, EventType.Load].includes(e.type), ); +const nextEvents = events.map((e) => ({ + ...e, + timestamp: e.timestamp + 1000, +})); +const nextNextEvents = nextEvents.map((e) => ({ + ...e, + timestamp: e.timestamp + 1000, +})); describe('get last session', () => { it('will return all the events when there is only one session', () => { - expect(getLastSession(events)).to.deep.equal(events); + expect(getLastSession(events, events[0].timestamp)).to.deep.equal(events); }); it('will return last session when there is more than one in the events', () => { - const multiple = events.concat(events).concat(events); - expect(getLastSession(multiple)).to.deep.equal(events); + const multiple = events.concat(nextEvents).concat(nextNextEvents); + expect( + getLastSession( + multiple, + nextNextEvents[nextNextEvents.length - 1].timestamp, + ), + ).to.deep.equal(nextNextEvents); + }); + + it('will return last session when baseline time is future time', () => { + const multiple = events.concat(nextEvents).concat(nextNextEvents); + expect( + getLastSession( + multiple, + nextNextEvents[nextNextEvents.length - 1].timestamp + 1000, + ), + ).to.deep.equal(nextNextEvents); + }); + + it('will return first session when baseline time is previous time', () => { + expect(getLastSession(events, events[0].timestamp - 1000)).to.deep.equal( + events, + ); }); }); diff --git a/test/replayer.test.ts b/test/replayer.test.ts index 896cda61..5a199ad2 100644 --- a/test/replayer.test.ts +++ b/test/replayer.test.ts @@ -6,6 +6,7 @@ import * as puppeteer from 'puppeteer'; import { expect } from 'chai'; import { Suite } from 'mocha'; import { launchPuppeteer, sampleEvents as events } from './utils'; +import { EventType } from '../src/types'; interface ISuite extends Suite { code: string; @@ -59,7 +60,11 @@ describe('replayer', function (this: ISuite) { replayer.play(); replayer['timer']['actions'].length; `); - expect(actionLength).to.equal(events.length); + expect(actionLength).to.equal( + events.filter( + (e) => ![EventType.DomContentLoaded, EventType.Load].includes(e.type), + ).length, + ); }); it('will clean actions when pause', async () => {