{"version":3,"file":"EmbraceSpanSessionManager.cjs","names":["diag","OTelPerformanceManager","trace","BasicTracerProvider","KEY_PREFIX_EMB_PROPERTIES","KEY_EMB_SESSION_REASON_ENDED","KEY_EMB_SDK_STARTUP_DURATION","KEY_EMB_FROM_STORAGE","generateUUID","KEY_EMB_TYPE","KEY_EMB_STATE","getVisibilityState","ATTR_SESSION_ID","KEY_EMB_COLD_START","KEY_EMB_SESSION_NUMBER","getIncrementedCount","EMBRACE_SESSION_NUMBER_STORAGE_KEY","KEY_EMB_SESSION_REASON_STARTED","EmbraceExtendedSpan"],"sources":["../../../src/managers/EmbraceSpanSessionManager/EmbraceSpanSessionManager.ts"],"sourcesContent":["import type {\n  Attributes,\n  DiagLogger,\n  HrTime,\n  Tracer,\n  TracerProvider,\n} from '@opentelemetry/api';\nimport { diag, trace } from '@opentelemetry/api';\nimport type { ReadableSpan } from '@opentelemetry/sdk-trace-web';\nimport { BasicTracerProvider } from '@opentelemetry/sdk-trace-web';\nimport { ATTR_SESSION_ID } from '@opentelemetry/semantic-conventions/incubating';\nimport type {\n  PropertyOptions,\n  ReasonSessionEnded,\n  StartSessionOptions,\n} from '../../api-sessions/index.ts';\nimport type { VisibilityStateDocument } from '../../common/index.ts';\nimport {\n  EMB_TYPES,\n  KEY_EMB_COLD_START,\n  KEY_EMB_FROM_STORAGE,\n  KEY_EMB_SDK_STARTUP_DURATION,\n  KEY_EMB_SESSION_NUMBER,\n  KEY_EMB_SESSION_REASON_ENDED,\n  KEY_EMB_SESSION_REASON_STARTED,\n  KEY_EMB_STATE,\n  KEY_EMB_TYPE,\n  KEY_PREFIX_EMB_PROPERTIES,\n} from '../../constants/index.ts';\nimport type { ExtendedSpan } from '../../index.ts';\nimport type { PerformanceManager } from '../../utils/index.ts';\nimport {\n  generateUUID,\n  getIncrementedCount,\n  getVisibilityState,\n  OTelPerformanceManager,\n} from '../../utils/index.ts';\nimport type { LimitManagerInternal } from '../EmbraceLimitManager/index.ts';\nimport { EmbraceExtendedSpan } from '../EmbraceTraceManager/EmbraceExtendedSpan.ts';\nimport { EMBRACE_SESSION_NUMBER_STORAGE_KEY } from './constants.ts';\nimport type {\n  EmbraceSpanSessionManagerArgs,\n  SessionEndedListener,\n  SessionStartedListener,\n  SpanSessionManagerInternal,\n} from './types.ts';\n\nexport class EmbraceSpanSessionManager implements SpanSessionManagerInternal {\n  private _previousSessionId: string | null = null;\n  private _activeSessionId: string | null = null;\n  private _activeSessionStartTime: HrTime | null = null;\n  private _sessionSpan: ExtendedSpan | null = null;\n  private _activeSessionCounts: Record<string, number> | null = null;\n  private _nextSessionCounts: Record<string, number> = {};\n  private _coldStart = true; // Whether the session was started from a new page load or not.\n  private _sdkStartupDuration = 0;\n  private readonly _sessionStartedListeners: Array<SessionStartedListener> = [];\n  private readonly _sessionEndedListeners: Array<SessionEndedListener> = [];\n  private _tracer: Tracer;\n  private readonly _noExportTracer: Tracer;\n  private readonly _diag: DiagLogger;\n  private readonly _perf: PerformanceManager;\n  private readonly _visibilityDoc: VisibilityStateDocument;\n  private readonly _storage: Storage;\n  private readonly _limitManager: LimitManagerInternal;\n\n  public constructor({\n    diag: diagParam,\n    perf,\n    visibilityDoc = window.document,\n    storage = window.localStorage,\n    limitManager,\n  }: EmbraceSpanSessionManagerArgs) {\n    this._diag =\n      diagParam ??\n      diag.createComponentLogger({\n        namespace: 'EmbraceSpanSessionManager',\n      });\n    this._perf = perf ?? new OTelPerformanceManager();\n    this._visibilityDoc = visibilityDoc;\n    this._storage = storage;\n    this._limitManager = limitManager;\n    this._tracer = trace.getTracer('embrace-web-sdk-sessions');\n    this._noExportTracer = new BasicTracerProvider().getTracer(\n      'embrace-web-sdk-sessions',\n    );\n  }\n\n  // Collects all permanent session properties from localStorage\n  private _getPermanentAttributes(): Attributes {\n    const permanentAttributes = new Map();\n    try {\n      for (let i = 0; i < this._storage.length; i++) {\n        const key = this._storage.key(i);\n        if (key?.startsWith(KEY_PREFIX_EMB_PROPERTIES)) {\n          const value = this._storage.getItem(key);\n          if (value) {\n            permanentAttributes.set(key, value);\n          }\n        }\n      }\n    } catch (error) {\n      this._diag.warn('Error loading permanent session properties', error);\n    }\n    return Object.fromEntries(permanentAttributes.entries()) as Attributes;\n  }\n\n  public addBreadcrumb(name: string) {\n    if (!this._sessionSpan) {\n      this._diag.debug(\n        'trying to add breadcrumb to a session, but there is no session in progress. This is a no-op.',\n      );\n      return;\n    }\n\n    const limitedBreadcrumb = this._limitManager.limitBreadcrumb(name);\n\n    if (limitedBreadcrumb === 'dropped') {\n      return;\n    }\n\n    this._sessionSpan.addEvent(\n      'emb-breadcrumb',\n      {\n        message: limitedBreadcrumb.name,\n      },\n      this._perf.getNowMillis(),\n    );\n  }\n\n  public addProperty(\n    propertyKey: string,\n    value: string,\n    options?: PropertyOptions,\n  ) {\n    if (!this._sessionSpan) {\n      this._diag.debug(\n        'trying to add properties to a session, but there is no session in progress. This is a no-op.',\n      );\n      return;\n    }\n\n    const limitedSessionProperty = this._limitManager.limitSessionProperty(\n      propertyKey,\n      value,\n    );\n\n    if (limitedSessionProperty === 'dropped') {\n      return;\n    }\n\n    const attributeKey = KEY_PREFIX_EMB_PROPERTIES + limitedSessionProperty.key;\n    this._sessionSpan.setAttribute(attributeKey, limitedSessionProperty.value);\n\n    if (options?.lifespan === 'permanent') {\n      try {\n        this._storage.setItem(attributeKey, value);\n      } catch (error) {\n        this._diag.warn('Failed to set permanent session property', error);\n      }\n    }\n  }\n\n  public removeProperty(propertyKey: string) {\n    if (!this._sessionSpan) {\n      this._diag.debug(\n        'trying to remove a session property, but there is no session in progress. This is a no-op.',\n      );\n      return;\n    }\n\n    // We truncate long session property keys on addProperty so need to apply the same logic here\n    const attributeKey =\n      KEY_PREFIX_EMB_PROPERTIES +\n      this._limitManager.truncateString('session_property_key', propertyKey);\n    this._sessionSpan.removeAttribute(attributeKey);\n\n    try {\n      if (this._storage.getItem(attributeKey)) {\n        this._storage.removeItem(attributeKey);\n      }\n    } catch (error) {\n      this._diag.warn('Error removing permanent session property', error);\n    }\n  }\n\n  // the external api doesn't include a reason, and if a users uses it to end a session, the reason will be 'manual'\n  // note: don't use this internally, this is just for user facing APIs. Use this.endSessionSpanInternal instead.\n  public endSessionSpan() {\n    this.endSessionSpanInternal('manual');\n  }\n\n  // endSessionSpanInternal is not part of the public API, but is used internally to end a session span adding a specific reason\n  public endSessionSpanInternal(reason: ReasonSessionEnded) {\n    if (!this._sessionSpan) {\n      this._diag.debug(\n        'trying to end a session, but there is no session in progress. This is a no-op.',\n      );\n      return;\n    }\n\n    for (const listener of this._sessionEndedListeners) {\n      try {\n        listener();\n      } catch (error) {\n        this._diag.warn('Error while executing session ended listener', error);\n      }\n    }\n\n    this._sessionSpan.setAttributes(this._endSessionSpanAttributes(reason));\n    this._sessionSpan.end();\n    this._sessionSpan = null;\n    this._activeSessionStartTime = null;\n    this._previousSessionId = this._activeSessionId;\n    this._activeSessionId = null;\n    this._activeSessionCounts = null;\n\n    // For the limit manager to add a session ended listener it would need a reference to this\n    // session manager which would create a circular dependency\n    this._limitManager.reset();\n  }\n\n  private _endSessionSpanAttributes(reason: ReasonSessionEnded): Attributes {\n    return {\n      ...this._getPermanentAttributes(),\n      [KEY_EMB_SESSION_REASON_ENDED]: reason,\n      ...this._activeSessionCounts,\n      ...this._limitManager.getDiagnosticCounts(),\n      [KEY_EMB_SDK_STARTUP_DURATION]: this._sdkStartupDuration,\n    };\n  }\n\n  // currentSessionAsReadableSpan creates a copy of the current session span with the same attributes\n  // that endSessionSpanInternal would add, but does not affect the original session span which remains active.\n  public currentSessionAsReadableSpan(\n    reason: ReasonSessionEnded,\n  ): ReadableSpan | null {\n    if (!this._sessionSpan || !this._activeSessionStartTime) {\n      this._diag.debug(\n        'trying to end a session, but there is no session in progress. This is a no-op.',\n      );\n      return null;\n    }\n\n    // Create a new span with the same name and start time as the original session span,\n    // but using a new tracer so that it does not get exported.\n    const span = this._noExportTracer.startSpan('emb-session', {\n      startTime: this._activeSessionStartTime,\n      attributes: {\n        // Copy all current attributes from the original session span, plus the ending attributes\n        ...this._sessionSpan.attributes,\n        ...this._endSessionSpanAttributes(reason),\n        [KEY_EMB_FROM_STORAGE]: true,\n      },\n    });\n    span.end();\n    return span as unknown as ReadableSpan;\n  }\n\n  public getSessionId(): string | null {\n    return this._activeSessionId;\n  }\n\n  public getPreviousSessionId(): string | null {\n    return this._previousSessionId;\n  }\n\n  public getSessionSpan(): ExtendedSpan | null {\n    return this._sessionSpan;\n  }\n\n  public getSessionStartTime(): HrTime | null {\n    return this._activeSessionStartTime;\n  }\n\n  public startSessionSpan(options?: StartSessionOptions) {\n    // if there is a session already in progress, end it first\n    if (this._sessionSpan) {\n      this.endSessionSpanInternal('manual');\n    }\n\n    this._activeSessionId = generateUUID();\n    this._activeSessionStartTime = this._perf.getNowHRTime();\n    this._activeSessionCounts = {};\n    const previouslyRecordedCounts = this._nextSessionCounts;\n    this._nextSessionCounts = {};\n\n    const attributes: Attributes = {\n      ...this._getPermanentAttributes(),\n      [KEY_EMB_TYPE]: EMB_TYPES.Session,\n      [KEY_EMB_STATE]: getVisibilityState(this._visibilityDoc),\n      [ATTR_SESSION_ID]: this._activeSessionId,\n      [KEY_EMB_COLD_START]: this._coldStart,\n      [KEY_EMB_SESSION_NUMBER]: getIncrementedCount(\n        this._storage,\n        EMBRACE_SESSION_NUMBER_STORAGE_KEY,\n        this._diag,\n      ),\n      ...previouslyRecordedCounts,\n    };\n\n    if (options?.reason) {\n      attributes[KEY_EMB_SESSION_REASON_STARTED] = options.reason;\n    }\n\n    this._sessionSpan = new EmbraceExtendedSpan(\n      this._tracer.startSpan('emb-session', {\n        attributes,\n      }),\n    );\n\n    this._coldStart = false;\n\n    for (const listener of this._sessionStartedListeners) {\n      try {\n        listener();\n      } catch (error) {\n        this._diag.warn(\n          'Error while executing session started listener',\n          error,\n        );\n      }\n    }\n  }\n\n  public incrSessionCountForKey(key: string) {\n    if (!this._sessionSpan || !this._activeSessionCounts) {\n      this._diag.debug(\n        'trying to increment a count for the active session, but there is no session in progress. This is a no-op.',\n      );\n      return;\n    }\n\n    this._activeSessionCounts[key] = (this._activeSessionCounts[key] || 0) + 1;\n  }\n\n  public incrNextSessionCountForKey(key: string) {\n    this._nextSessionCounts[key] = (this._nextSessionCounts[key] || 0) + 1;\n  }\n\n  public addSessionStartedListener(listener: SessionStartedListener) {\n    const listenerIndex = this._sessionStartedListeners.push(listener);\n\n    return () => {\n      this._sessionStartedListeners.splice(listenerIndex - 1, 1);\n    };\n  }\n\n  public addSessionEndedListener(listener: SessionEndedListener) {\n    const listenerIndex = this._sessionEndedListeners.push(listener);\n\n    return () => {\n      this._sessionEndedListeners.splice(listenerIndex - 1, 1);\n    };\n  }\n\n  public recordSDKStartupDuration(duration: number) {\n    this._sdkStartupDuration = Math.ceil(duration);\n  }\n\n  public setTracerProvider(tracerProvider: TracerProvider) {\n    this._tracer = tracerProvider.getTracer('embrace-web-sdk-sessions');\n  }\n}\n"],"mappings":"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"}