{"version":3,"file":"sessionManager.mjs","names":[],"sources":["../../../src/domain/session/sessionManager.ts"],"sourcesContent":["import {\n  ONE_HOUR,\n  ONE_MINUTE,\n  ONE_SECOND,\n  dateNow,\n  elapsed,\n  timeStampNow,\n  clocksOrigin,\n  relativeNow,\n} from '@openobserve/js-core/time'\nimport type { TimeStamp, RelativeTime } from '@openobserve/js-core/time'\nimport { Observable } from '../../tools/observable'\nimport { createValueHistory } from '../../tools/valueHistory'\nimport { addEventListener, addEventListeners, DOM_EVENT } from '../../browser/addEventListener'\nimport { clearInterval, clearTimeout, setInterval, setTimeout } from '../../tools/timer'\nimport { mockable } from '../../tools/mockable'\nimport { noop, throttle } from '../../tools/utils/functionUtils'\nimport { generateUUID } from '../../tools/utils/stringUtils'\nimport type { Configuration } from '../configuration'\nimport type { TrackingConsentState } from '../trackingConsent'\nimport { isWorkerEnvironment } from '../../tools/globalObject'\nimport { display } from '../../tools/display'\nimport { isSampled } from '../sampler'\nimport { TelemetryMetrics, addTelemetryMetrics } from '../telemetry'\nimport { monitorError } from '../../tools/monitor'\nimport { SESSION_TIME_OUT_DELAY } from './sessionConstants'\nimport type { SessionState } from './sessionState'\nimport {\n  expandOnly,\n  expandOrRenew,\n  getCreatedDate,\n  getExpireDate,\n  getExpiredSessionState,\n  initializeSession,\n  isSessionInExpiredState,\n} from './sessionState'\nimport { getSessionStoreStrategy, selectSessionStoreStrategyType } from './sessionStore'\n\nexport interface SessionManager {\n  findSession: (startTime?: RelativeTime, options?: { returnInactive: boolean }) => SessionContext | undefined\n  findTrackedSession: (startTime?: RelativeTime, options?: { returnInactive: boolean }) => SessionContext | undefined\n  renewObservable: Observable<void>\n  expireObservable: Observable<void>\n  expire: () => void\n  updateSessionState: (state: Partial<SessionState>) => void\n}\n\nexport interface SessionContext {\n  id: string\n  anonymousId?: string | undefined\n  isReplayForced?: boolean\n  createdAt: TimeStamp\n}\n\nexport const VISIBILITY_CHECK_DELAY = ONE_MINUTE\nconst SESSION_CONTEXT_TIMEOUT_DELAY = SESSION_TIME_OUT_DELAY\n\n// Maximum duration for which we can send data related to a session.\n//\n// The backend behavior depends on how old the session is when it receives an event:\n// - Session started < 4h ago: the backend updates the session normally.\n// - Session started between 4h and 24h ago: the backend ignores the event (safe).\n// - Session started > 24h ago: the backend recreates a session with that id (problematic).\n//\n// We choose 12h as a threshold — safely between 4h and 24h — to avoid both recreating\n// sessions and discarding too many legitimate late events.\nexport const TRACKED_SESSION_MAX_AGE = ONE_HOUR * 12\nlet stopCallbacks: Array<() => void> = []\n\nexport async function startSessionManager(\n  configuration: Configuration,\n  trackingConsentState: TrackingConsentState\n): Promise<SessionManager | undefined> {\n  const startTime = relativeNow()\n  const renewObservable = new Observable<void>()\n  const expireObservable = new Observable<void>()\n\n  const sessionStoreStrategyType = await mockable(selectSessionStoreStrategyType)(configuration)\n  if (!sessionStoreStrategyType) {\n    display.warn('No storage available for session. We will not send any data.')\n    return\n  }\n\n  const strategy = mockable(getSessionStoreStrategy)(sessionStoreStrategyType, configuration)\n\n  const sessionContextHistory = createValueHistory<SessionContext>({\n    expireDelay: SESSION_CONTEXT_TIMEOUT_DELAY,\n  })\n  stopCallbacks.push(() => sessionContextHistory.stop())\n\n  let sessionExpired = false\n\n  const { throttled: throttledExpandOrRenew, cancel: cancelExpandOrRenew } = throttle(() => {\n    sessionExpired = false\n    strategy\n      .setSessionState((state) => expandOrRenew(state, configuration), 'expandOrRenewOnActivity')\n      .catch(monitorError)\n  }, ONE_SECOND)\n  stopCallbacks.push(cancelExpandOrRenew)\n\n  let expirationTimeoutId: ReturnType<typeof setTimeout> | undefined\n  stopCallbacks.push(() => clearTimeout(expirationTimeoutId))\n\n  let stopped = false\n  stopCallbacks.push(() => {\n    stopped = true\n  })\n\n  let initialState = await resolveInitialState().catch((error) =>\n    monitorError(new Error(`Error while resolving initial session state: ${error}`))\n  )\n  if (!initialState || stopped) {\n    return\n  }\n\n  // Consent is always granted when the session manager is started, but it may\n  // be revoked during the async initialization (e.g., while waiting for cookie lock).\n  // Mirror setupSessionTracking's revoke/grant handler until the manager is installed:\n  // expire the session in storage, wait for the next grant, then re-resolve.\n  while (!trackingConsentState.isGranted()) {\n    expire()\n    await new Promise<void>((resolve) => trackingConsentState.onGrantedOnce(resolve))\n    if (stopped) {\n      return\n    }\n    initialState = await resolveInitialState().catch(monitorError)\n    if (!initialState || stopped) {\n      return\n    }\n  }\n\n  sessionContextHistory.add(buildSessionContext(initialState), clocksOrigin().relative)\n  scheduleExpirationTimeout(initialState)\n  subscribeToSessionChanges()\n  setupSessionTracking()\n\n  // monitor-until: 2026-10-15\n  addTelemetryMetrics(TelemetryMetrics.SESSION_MANAGER_INIT_METRICS_TELEMETRY_NAME, {\n    metrics: { duration: elapsed(startTime, relativeNow()) },\n  })\n\n  return buildSessionManager()\n\n  async function resolveInitialState() {\n    let state: SessionState = {}\n    await strategy.setSessionState((currentState) => {\n      const initialState = initializeSession(currentState, configuration)\n      state = expandOrRenew(initialState, configuration)\n      return state\n    }, 'initialize')\n    return state\n  }\n\n  function subscribeToSessionChanges() {\n    const subscription = strategy.sessionObservable.subscribe((sessionState) => {\n      scheduleExpirationTimeout(sessionState)\n      handleStateChange(sessionState)\n    })\n    stopCallbacks.push(() => subscription.unsubscribe())\n  }\n\n  function scheduleExpirationTimeout(state: SessionState) {\n    clearTimeout(expirationTimeoutId)\n    const expireDate = getExpireDate(state)\n    if (expireDate) {\n      const delay = expireDate - dateNow()\n      expirationTimeoutId = setTimeout(() => {\n        strategy\n          .setSessionState((state) => {\n            if (isSessionInExpiredState(state)) {\n              if (!trackingConsentState.isGranted()) {\n                delete state.anonymousId\n              }\n\n              return getExpiredSessionState(state, configuration)\n            }\n\n            return state\n          }, 'expireOnTimeout')\n          .catch(monitorError)\n      }, delay)\n    }\n  }\n\n  function setupSessionTracking() {\n    trackingConsentState.observable.subscribe(() => {\n      if (trackingConsentState.isGranted()) {\n        sessionExpired = false\n        strategy\n          .setSessionState((state) => expandOrRenew(state, configuration), 'expandOrRenewOnConsent')\n          .catch(monitorError)\n      } else {\n        expire()\n      }\n    })\n\n    if (!isWorkerEnvironment) {\n      trackActivity(() => {\n        if (trackingConsentState.isGranted()) {\n          throttledExpandOrRenew()\n        }\n      })\n      trackVisibility(() => {\n        if (!sessionExpired) {\n          strategy.setSessionState((state) => expandOnly(state), 'expandOnVisibility').catch(monitorError)\n        }\n      })\n      trackResume(() => {\n        strategy\n          .setSessionState((state) => initializeSession(state, configuration), 'initializeOnResume')\n          .catch(monitorError)\n      })\n    }\n  }\n\n  function buildSessionManager(): SessionManager {\n    return {\n      findSession: (startTime, options) => sessionContextHistory.find(startTime, options),\n      findTrackedSession: (startTime, options) => {\n        const session = sessionContextHistory.find(startTime, options)\n\n        if (!session || session.id === 'invalid' || !isSampled(session.id, configuration.sessionSampleRate)) {\n          return\n        }\n\n        if (dateNow() - session.createdAt > TRACKED_SESSION_MAX_AGE) {\n          return\n        }\n\n        return session\n      },\n      renewObservable,\n      expireObservable,\n      expire,\n      updateSessionState: (partialState) => {\n        strategy.setSessionState((state) => ({ ...state, ...partialState }), 'updateState').catch(monitorError)\n      },\n    }\n  }\n\n  function handleStateChange(newState: SessionState) {\n    const previousSession = sessionContextHistory.find()\n    const hadSession = previousSession?.id !== undefined\n    const hasSession = newState.id !== undefined\n    const sessionIdChanged = hadSession && hasSession && previousSession.id !== newState.id\n\n    if (hadSession && (!hasSession || sessionIdChanged)) {\n      // Session expired or replaced\n      sessionExpired = true\n      expireObservable.notify()\n      sessionContextHistory.closeActive(relativeNow())\n    }\n\n    if (hasSession && (!hadSession || sessionIdChanged)) {\n      if (sessionExpired) {\n        // Don't adopt another tab's session — this tab needs its own user interaction to renew\n        return\n      }\n      // New session appeared\n      sessionContextHistory.add(buildSessionContext(newState), relativeNow())\n      renewObservable.notify()\n    } else if (hadSession && hasSession && !sessionIdChanged) {\n      // Same session,\n      // Mutate the session context in the history for replay forced changes\n\n      previousSession.isReplayForced = !!newState.forcedReplay\n    }\n  }\n\n  function expire() {\n    cancelExpandOrRenew()\n    // Update in-memory state synchronously so events stop being collected immediately\n    const expiredState = getExpiredSessionState(sessionContextHistory.find(), configuration)\n    if (!trackingConsentState.isGranted()) {\n      delete expiredState.anonymousId\n    }\n    handleStateChange(expiredState)\n    // Persist to storage asynchronously\n    strategy\n      .setSessionState((state) => {\n        if (!trackingConsentState.isGranted()) {\n          delete state.anonymousId\n        }\n        return getExpiredSessionState(state, configuration)\n      }, 'expire')\n      .catch(monitorError)\n  }\n\n  function buildSessionContext(sessionState: SessionState): SessionContext {\n    const createdAt = getCreatedDate(sessionState) ?? timeStampNow()\n\n    if (!sessionState.id) {\n      return {\n        id: 'invalid',\n        isReplayForced: false,\n        anonymousId: undefined,\n        createdAt,\n      }\n    }\n\n    return {\n      id: sessionState.id,\n      isReplayForced: !!sessionState.forcedReplay,\n      anonymousId: sessionState.anonymousId,\n      createdAt,\n    }\n  }\n}\n\nexport function startSessionManagerStub(): Promise<SessionManager> {\n  const stubSessionId = generateUUID()\n  let sessionContext: SessionContext = {\n    id: stubSessionId,\n    isReplayForced: false,\n    anonymousId: undefined,\n    createdAt: timeStampNow(),\n  }\n  return Promise.resolve({\n    findSession: () => sessionContext,\n    findTrackedSession: () => sessionContext,\n    renewObservable: new Observable(),\n    expireObservable: new Observable(),\n    expire: noop,\n    updateSessionState: (state) => {\n      sessionContext = {\n        ...sessionContext,\n        ...state,\n      }\n    },\n  })\n}\n\nexport function stopSessionManager() {\n  stopCallbacks.forEach((e) => e())\n  stopCallbacks = []\n}\n\nfunction trackActivity(expandOrRenewSession: () => void) {\n  const { stop } = addEventListeners(\n    window,\n    [DOM_EVENT.CLICK, DOM_EVENT.TOUCH_START, DOM_EVENT.KEY_DOWN, DOM_EVENT.SCROLL],\n    expandOrRenewSession,\n    { capture: true, passive: true }\n  )\n  stopCallbacks.push(stop)\n}\n\nfunction trackVisibility(expandSession: () => void) {\n  const expandSessionWhenVisible = () => {\n    if (document.visibilityState === 'visible') {\n      expandSession()\n    }\n  }\n\n  const { stop } = addEventListener(document, DOM_EVENT.VISIBILITY_CHANGE, expandSessionWhenVisible)\n  stopCallbacks.push(stop)\n\n  const visibilityCheckInterval = setInterval(expandSessionWhenVisible, VISIBILITY_CHECK_DELAY)\n  stopCallbacks.push(() => {\n    clearInterval(visibilityCheckInterval)\n  })\n}\n\nfunction trackResume(cb: () => void) {\n  const { stop } = addEventListener(window, DOM_EVENT.RESUME, cb, { capture: true })\n  stopCallbacks.push(stop)\n}\n"],"mappings":"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"}