@@ -2,7 +2,7 @@ import { Observable } from '../../tools/observable'
22import type { Context } from '../../tools/serialisation/context'
33import { createValueHistory } from '../../tools/valueHistory'
44import type { RelativeTime } from '../../tools/utils/timeUtils'
5- import { clocksOrigin , ONE_MINUTE , relativeNow } from '../../tools/utils/timeUtils'
5+ import { clocksOrigin , dateNow , ONE_MINUTE , relativeNow } from '../../tools/utils/timeUtils'
66import { addEventListener , addEventListeners , DOM_EVENT } from '../../browser/addEventListener'
77import { clearInterval , setInterval } from '../../tools/timer'
88import type { Configuration } from '../configuration'
@@ -11,10 +11,15 @@ import { addTelemetryDebug } from '../telemetry'
1111import { isSyntheticsTest } from '../synthetics/syntheticsWorkerValues'
1212import type { CookieStore } from '../../browser/browser.types'
1313import { getCurrentSite } from '../../browser/cookie'
14+ import { ExperimentalFeature , isExperimentalFeatureEnabled } from '../../tools/experimentalFeatures'
15+ import { findLast } from '../../tools/utils/polyfills'
16+ import { monitorError } from '../../tools/monitor'
1417import { SESSION_NOT_TRACKED , SESSION_TIME_OUT_DELAY } from './sessionConstants'
1518import { startSessionStore } from './sessionStore'
1619import type { SessionState } from './sessionState'
20+ import { toSessionState } from './sessionState'
1721import { retrieveSessionCookie } from './storeStrategies/sessionInCookie'
22+ import { SESSION_STORE_KEY } from './storeStrategies/sessionStoreStrategy'
1823
1924export interface SessionManager < TrackingType extends string > {
2025 findSession : (
@@ -75,6 +80,12 @@ export function startSessionManager<TrackingType extends string>(
7580 // manager is started.
7681 sessionStore . expandOrRenewSession ( )
7782 sessionContextHistory . add ( buildSessionContext ( ) , clocksOrigin ( ) . relative )
83+ if ( isExperimentalFeatureEnabled ( ExperimentalFeature . SHORT_SESSION_INVESTIGATION ) ) {
84+ const session = sessionStore . getSession ( )
85+ if ( session ) {
86+ detectSessionIdChange ( configuration , session )
87+ }
88+ }
7889
7990 trackingConsentState . observable . subscribe ( ( ) => {
8091 if ( trackingConsentState . isGranted ( ) ) {
@@ -163,24 +174,69 @@ function trackResume(configuration: Configuration, cb: () => void) {
163174
164175async function reportUnexpectedSessionState ( ) {
165176 const rawSession = retrieveSessionCookie ( )
166- let sessionCookies : string [ ] | Awaited < ReturnType < CookieStore [ 'getAll' ] > > = [ ]
167-
168- if ( 'cookieStore' in window ) {
169- sessionCookies = await ( window as { cookieStore : CookieStore } ) . cookieStore . getAll ( '_dd_s' )
170- } else {
171- sessionCookies = document . cookie . split ( / \s * ; \s * / ) . filter ( ( cookie ) => cookie . startsWith ( '_dd_s' ) )
172- }
173-
174177 addTelemetryDebug ( 'Unexpected session state' , {
175178 session : rawSession ,
176179 isSyntheticsTest : isSyntheticsTest ( ) ,
177180 createdTimestamp : rawSession ?. created ,
178181 expireTimestamp : rawSession ?. expire ,
179- cookie : {
180- count : sessionCookies . length ,
181- domain : getCurrentSite ( ) ,
182- ...sessionCookies ,
183- } ,
182+ cookie : await getSessionCookies ( ) ,
184183 currentDomain : `${ window . location . protocol } //${ window . location . hostname } ` ,
185184 } )
186185}
186+
187+ function detectSessionIdChange ( configuration : Configuration , initialSessionState : SessionState ) {
188+ if ( ! window . cookieStore || ! initialSessionState . created ) {
189+ return
190+ }
191+
192+ const sessionCreatedTime = Number ( initialSessionState . created )
193+ const sdkInitTime = dateNow ( )
194+
195+ const { stop } = addEventListener ( configuration , cookieStore as CookieStore , DOM_EVENT . CHANGE , listener )
196+ stopCallbacks . push ( stop )
197+
198+ function listener ( event : CookieChangeEvent ) {
199+ const changed = findLast ( event . changed , ( change ) : change is CookieListItem => change . name === SESSION_STORE_KEY )
200+ if ( ! changed ) {
201+ return
202+ }
203+
204+ const sessionAge = dateNow ( ) - sessionCreatedTime
205+ if ( sessionAge > 14 * ONE_MINUTE ) {
206+ // The session might have expired just because it's too old or lack activity
207+ stop ( )
208+ } else {
209+ const newSessionState = toSessionState ( changed . value )
210+ if ( newSessionState . id && newSessionState . id !== initialSessionState . id ) {
211+ stop ( )
212+ const time = dateNow ( ) - sdkInitTime
213+ getSessionCookies ( )
214+ . then ( ( cookie ) => {
215+ addTelemetryDebug ( 'Session cookie changed' , {
216+ time,
217+ session_age : sessionAge ,
218+ old : initialSessionState ,
219+ new : newSessionState ,
220+ cookie,
221+ } )
222+ } )
223+ . catch ( monitorError )
224+ }
225+ }
226+ }
227+ }
228+
229+ async function getSessionCookies ( ) : Promise < { count : number ; domain : string } > {
230+ let sessionCookies : string [ ] | Awaited < ReturnType < CookieStore [ 'getAll' ] > >
231+ if ( 'cookieStore' in window ) {
232+ sessionCookies = await ( window as { cookieStore : CookieStore } ) . cookieStore . getAll ( SESSION_STORE_KEY )
233+ } else {
234+ sessionCookies = document . cookie . split ( / \s * ; \s * / ) . filter ( ( cookie ) => cookie . startsWith ( SESSION_STORE_KEY ) )
235+ }
236+
237+ return {
238+ count : sessionCookies . length ,
239+ domain : getCurrentSite ( ) ,
240+ ...sessionCookies ,
241+ }
242+ }
0 commit comments