Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/core/src/domain/session/sessionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export interface SessionContext<TrackingType extends string> extends Context {
trackingType: TrackingType
isReplayForced: boolean
anonymousId: string | undefined
expire: string | undefined
}

export const VISIBILITY_CHECK_DELAY = ONE_MINUTE
Expand Down Expand Up @@ -88,6 +89,15 @@ export function startSessionManager<TrackingType extends string>(
}
}

sessionStore.sessionStateUpdateObservable.subscribe(({ previousState, newState }) => {
if (previousState.id === newState.id && previousState.expire !== newState.expire) {
const entry = sessionContextHistory.find()
if (entry) {
entry.expire = newState.expire
}
}
})

trackingConsentState.observable.subscribe(() => {
if (trackingConsentState.isGranted()) {
sessionStore.expandOrRenewSession()
Expand Down Expand Up @@ -115,6 +125,7 @@ export function startSessionManager<TrackingType extends string>(
trackingType: SESSION_NOT_TRACKED as TrackingType,
isReplayForced: false,
anonymousId: undefined,
expire: undefined,
}
}

Expand All @@ -123,6 +134,7 @@ export function startSessionManager<TrackingType extends string>(
trackingType: session[productKey] as TrackingType,
isReplayForced: !!session.forcedReplay,
anonymousId: session.anonymousId,
expire: session.expire,
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/rum-core/src/boot/startRum.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ function startRumStub(
const hooks = createHooks()
const viewHistory = startViewHistory(lifeCycle)
const urlContexts = startUrlContexts(lifeCycle, hooks, locationChangeObservable, location)
startSessionContext(hooks, sessionManager, noopRecorderApi, viewHistory)
startSessionContext(hooks, sessionManager, noopRecorderApi, viewHistory, pageStateHistory)

const { stop: rumEventCollectionStop } = startRumEventCollection(
lifeCycle,
Expand Down
2 changes: 1 addition & 1 deletion packages/rum-core/src/boot/startRum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export function startRum(
const urlContexts = startUrlContexts(lifeCycle, hooks, locationChangeObservable, location)
cleanupTasks.push(() => urlContexts.stop())
const featureFlagContexts = startFeatureFlagContexts(lifeCycle, hooks, configuration)
startSessionContext(hooks, session, recorderApi, viewHistory)
startSessionContext(hooks, session, recorderApi, viewHistory, pageStateHistory)
startConnectivityContext(hooks)
startTrackingConsentContext(hooks, trackingConsentState)
const globalContext = startGlobalContext(hooks, configuration, 'rum', true)
Expand Down
3 changes: 2 additions & 1 deletion packages/rum-core/src/domain/assembly.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
mockRumConfiguration,
mockViewHistory,
noopRecorderApi,
mockPageStateHistory,
} from '../../test'
import type { RumEventDomainContext } from '../domainContext.types'
import type { RawRumEvent } from '../rawRumEvent.types'
Expand Down Expand Up @@ -574,7 +575,7 @@ function setupAssemblyTestWithDefaults({
const recorderApi = noopRecorderApi
const viewHistory = { ...mockViewHistory(), findView: () => findView() }
startGlobalContext(hooks, mockRumConfiguration(), 'rum', true)
startSessionContext(hooks, rumSessionManager, recorderApi, viewHistory)
startSessionContext(hooks, rumSessionManager, recorderApi, viewHistory, mockPageStateHistory())
startRumAssembly(mockRumConfiguration(partialConfiguration), lifeCycle, hooks, reportErrorSpy)

registerCleanupTask(() => {
Expand Down
10 changes: 8 additions & 2 deletions packages/rum-core/src/domain/contexts/pageStateHistory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export interface PageStateEntry {
export interface PageStateHistory {
wasInPageStateDuringPeriod: (state: PageState, startTime: RelativeTime, duration: Duration) => boolean
addPageState(nextPageState: PageState, startTime?: RelativeTime): void
findPageStatesForPeriod: (startTime: RelativeTime, duration: Duration) => PageStateServerEntry[] | undefined
stop: () => void
}

Expand Down Expand Up @@ -99,14 +100,18 @@ export function startPageStateHistory(
return pageStateEntryHistory.findAll(startTime, duration).some((pageState) => pageState.state === state)
}

function findPageStatesForPeriod(startTime: RelativeTime, duration: Duration) {
const pageStateEntries = pageStateEntryHistory.findAll(startTime, duration)
return processPageStates(pageStateEntries, startTime, maxPageStateEntriesSelectable)
}

hooks.register(
HookNames.Assemble,
({ startTime, duration = 0 as Duration, eventType }): DefaultRumEventAttributes | SKIPPED => {
if (eventType === RumEventType.VIEW) {
const pageStates = pageStateEntryHistory.findAll(startTime, duration)
return {
type: eventType,
_dd: { page_states: processPageStates(pageStates, startTime, maxPageStateEntriesSelectable) },
_dd: { page_states: findPageStatesForPeriod(startTime, duration) },
}
}

Expand All @@ -124,6 +129,7 @@ export function startPageStateHistory(
return {
wasInPageStateDuringPeriod,
addPageState,
findPageStatesForPeriod,
stop: () => {
stopEventListeners()
pageStateEntryHistory.stop()
Expand Down
10 changes: 7 additions & 3 deletions packages/rum-core/src/domain/contexts/sessionContext.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import type { RelativeTime } from '@datadog/browser-core'
import { clocksNow, DISCARDED, HookNames } from '@datadog/browser-core'
import type { RumSessionManagerMock } from '../../../test'
import { createRumSessionManagerMock, noopRecorderApi } from '../../../test'
import {
type RumSessionManagerMock,
mockPageStateHistory,
createRumSessionManagerMock,
noopRecorderApi,
} from '../../../test'
import { SessionType } from '../rumSessionManager'
import type { DefaultRumEventAttributes, DefaultTelemetryEventAttributes, Hooks } from '../hooks'
import { createHooks } from '../hooks'
Expand Down Expand Up @@ -37,7 +41,7 @@ describe('session context', () => {
getReplayStatsSpy = spyOn(recorderApi, 'getReplayStats')
findViewSpy = spyOn(viewHistory, 'findView').and.returnValue(fakeView)

startSessionContext(hooks, sessionManager, recorderApi, viewHistory)
startSessionContext(hooks, sessionManager, recorderApi, viewHistory, mockPageStateHistory())
})

it('should set id and type', () => {
Expand Down
58 changes: 55 additions & 3 deletions packages/rum-core/src/domain/contexts/sessionContext.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,32 @@
import { DISCARDED, HookNames, SKIPPED } from '@datadog/browser-core'
import { SessionReplayState, SessionType } from '../rumSessionManager'
import type { ContextValue } from '@datadog/browser-core'
import {
toServerDuration,
DISCARDED,
HookNames,
SKIPPED,
dateNow,
ONE_MINUTE,
addTelemetryDebug,
elapsed,
relativeNow,
relativeToClocks,
} from '@datadog/browser-core'
import type { RumSessionManager } from '../rumSessionManager'
import { SessionReplayState, SessionType } from '../rumSessionManager'
import type { PageStateServerEntry } from '../../rawRumEvent.types'
import { RumEventType } from '../../rawRumEvent.types'
import type { RecorderApi } from '../../boot/rumPublicApi'
import type { DefaultRumEventAttributes, DefaultTelemetryEventAttributes, Hooks } from '../hooks'
import type { ViewHistory } from './viewHistory'
import type { PageStateHistory } from './pageStateHistory'
import { PageState } from './pageStateHistory'

export function startSessionContext(
hooks: Hooks,
sessionManager: RumSessionManager,
recorderApi: RecorderApi,
viewHistory: ViewHistory
viewHistory: ViewHistory,
pageStateHistory: PageStateHistory
) {
hooks.register(HookNames.Assemble, ({ eventType, startTime }): DefaultRumEventAttributes | DISCARDED => {
const session = sessionManager.findTrackedSession(startTime)
Expand All @@ -19,6 +35,42 @@ export function startSessionContext(
if (!session || !view) {
return DISCARDED
}
// TODO share constant
const isSessionExpired = dateNow() - Number(session.expire) > 15 * ONE_MINUTE
const eventDuration = elapsed(startTime, relativeNow())
const wasInFrozenState = pageStateHistory.wasInPageStateDuringPeriod(PageState.FROZEN, startTime, eventDuration)
if (isSessionExpired || (wasInFrozenState && eventType !== RumEventType.VIEW)) {
const pageStatesForPeriod = pageStateHistory.findPageStatesForPeriod(startTime, eventDuration)!
// monitor-until: 2026-01-01
addTelemetryDebug('Event sent after session expiration or frozen page state', {
debug: {
eventDuration,
eventType,
isSessionExpired,
sessionExpiredSince: isSessionExpired ? dateNow() - Number(session.expire) : undefined,
elapsedBetweenStartAndExpire: isSessionExpired
? Number(session.expire) - relativeToClocks(startTime).timeStamp
: undefined,
wasInFrozenState,
pageStateDuringEventDuration: pageStatesForPeriod as ContextValue,
sumFrozenDuration: wasInFrozenState ? computeSumFrozenDuration(pageStatesForPeriod) : undefined,
},
})
}

/**
* Compute a frozen period duration by looking at the frozen entry start time and the next entry start time
*/
function computeSumFrozenDuration(pageStates: PageStateServerEntry[]) {
let sum = 0
for (let i = 0; i < pageStates.length; i++) {
if (pageStates[i].state === PageState.FROZEN) {
const nextStateStart = pageStates[i + 1] ? pageStates[i + 1].start : toServerDuration(relativeNow())
sum += nextStateStart - pageStates[i].start
}
}
return sum
}

let hasReplay
let sampledForReplay
Expand Down
2 changes: 2 additions & 0 deletions packages/rum-core/src/domain/rumSessionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export interface RumSession {
id: string
sessionReplay: SessionReplayState
anonymousId?: string
expire?: string
}

export const enum RumTrackingType {
Expand Down Expand Up @@ -88,6 +89,7 @@ export function startRumSessionManager(
? SessionReplayState.FORCED
: SessionReplayState.OFF,
anonymousId: session.anonymousId,
expire: session.expire,
}
},
expire: sessionManager.expire,
Expand Down
1 change: 1 addition & 0 deletions packages/rum-core/test/mockPageStateHistory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export function mockPageStateHistory(partialPageStateHistory?: Partial<PageState
const pageStateHistory: PageStateHistory = {
addPageState: noop,
stop: noop,
findPageStatesForPeriod: () => [],
wasInPageStateDuringPeriod: () => false,
...partialPageStateHistory,
}
Expand Down