Skip to content

Commit aac3b00

Browse files
committed
fix: Assign timeKeeper before storeTiming
1 parent 1296653 commit aac3b00

2 files changed

Lines changed: 84 additions & 2 deletions

File tree

src/features/session_trace/aggregate/index.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export class Aggregate extends AggregateBase {
4141
this.entitled ??= stEntitled
4242
if (!this.entitled) this.blocked = true
4343
if (this.blocked) return this.deregisterDrain()
44+
this.timeKeeper ??= this.agentRef.runtime.timeKeeper
4445

4546
if (!this.initialized) {
4647
this.initialized = true
@@ -78,8 +79,6 @@ export class Aggregate extends AggregateBase {
7879
* If it drains later (due to a mode change), data and handlers will instantly drain instead of waiting for the registry. */
7980
if (this.mode === MODE.OFF) return this.deregisterDrain()
8081

81-
this.timeKeeper ??= this.agentRef.runtime.timeKeeper
82-
8382
/** The handlers set up by the Inst file */
8483
registerHandler('bst', (...args) => this.traceStorage.storeEvent(...args), this.featureName, this.ee)
8584
registerHandler('bstResource', (...args) => this.traceStorage.storeResources(...args), this.featureName, this.ee)

tests/components/session_trace/aggregate.test.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,89 @@ test('initialization - should still use L2 timings when responseStart is 0', asy
187187
resetAgent(testAgent)
188188
})
189189

190+
test('initialization - should still store in relative timestamps when called with L1/absolute timings', async () => {
191+
const testAgent = setupAgent()
192+
193+
// Save original values
194+
const originalPerformanceNavigationTiming = global.PerformanceNavigationTiming
195+
const originalPerformanceTiming = performance.timing
196+
197+
// Simulate legacy browser without PerformanceNavigationTiming
198+
delete global.PerformanceNavigationTiming
199+
200+
// Mock performance.timing with absolute timestamps (legacy API)
201+
const mockAbsoluteTiming = {
202+
navigationStart: 1234567890000,
203+
domContentLoadedEventEnd: 1234567890250,
204+
loadEventEnd: 1234567890300
205+
}
206+
Object.defineProperty(performance, 'timing', {
207+
value: mockAbsoluteTiming,
208+
writable: true,
209+
configurable: true
210+
})
211+
212+
const sessionTraceInstrument = new SessionTrace(testAgent)
213+
await new Promise(process.nextTick)
214+
const testAggregate = sessionTraceInstrument.featAggregate
215+
216+
// Mock timeKeeper to have deterministic timestamp conversion
217+
testAggregate.agentRef.runtime.timeKeeper = {
218+
ready: true,
219+
correctAbsoluteTimestamp: (val) => val, // No correction
220+
convertAbsoluteTimestamp: (val) => val - 1234567890000 // Convert to relative by subtracting navigationStart
221+
}
222+
223+
// Mock drain to prevent buffer clearing
224+
const drainSpy = jest.spyOn(testAggregate, 'drain').mockImplementation(() => {})
225+
const storeTimingSpy = jest.spyOn(testAggregate.traceStorage, 'storeTiming')
226+
227+
// Act - triggers initialization, should use legacy path with absolute timestamps
228+
testAggregate.ee.emit('rumresp', [{ st: 1, sts: MODE.FULL }])
229+
await new Promise((resolve) => setTimeout(resolve, 100))
230+
231+
// Assertions
232+
// - Should be called with performance.timing and isAbsoluteTimestamp=true
233+
expect(storeTimingSpy).toHaveBeenCalledWith(mockAbsoluteTiming, true)
234+
235+
// - Verify that timing nodes were stored with timestamps converted from absolute to relative
236+
const bufferedEvents = testAggregate.events.get()
237+
238+
expect(bufferedEvents).toContainEqual(expect.objectContaining({
239+
n: 'navigationStart',
240+
s: 0, // 1234567890000 - 1234567890000 = 0
241+
e: 0,
242+
o: 'document',
243+
t: 'timing'
244+
}))
245+
246+
expect(bufferedEvents).toContainEqual(expect.objectContaining({
247+
n: 'domContentLoadedEventEnd',
248+
s: 250, // 1234567890250 - 1234567890000 = 250
249+
e: 250,
250+
o: 'document',
251+
t: 'timing'
252+
}))
253+
254+
expect(bufferedEvents).toContainEqual(expect.objectContaining({
255+
n: 'loadEventEnd',
256+
s: 300, // 1234567890300 - 1234567890000 = 300
257+
e: 300,
258+
o: 'document',
259+
t: 'timing'
260+
}))
261+
262+
// Cleanup
263+
drainSpy.mockRestore()
264+
Object.defineProperty(performance, 'timing', {
265+
value: originalPerformanceTiming,
266+
writable: true,
267+
configurable: true
268+
})
269+
global.PerformanceNavigationTiming = originalPerformanceNavigationTiming
270+
resetAgent(testAgent)
271+
})
272+
190273
test('tracks previously stored events and processes them once per occurrence', done => {
191274
document.addEventListener('visibilitychange', () => 1)
192275
document.addEventListener('visibilitychange', () => 2)

0 commit comments

Comments
 (0)