UNPKG

@embrace-io/web-sdk

Version:
1 lines 49.2 kB
{"version":3,"file":"EmbraceUserSessionManager.cjs","names":["DEFAULT_ACTIVITY_THROTTLE_MS","DEFAULT_ACTIVITY_EVENTS","diag","readPermanentProperties","trace","throttle","clampNumber","DEFAULT_USER_SESSION_MAX_DURATION_SECONDS","MIN_USER_SESSION_MAX_DURATION_SECONDS","MAX_USER_SESSION_MAX_DURATION_SECONDS","DEFAULT_USER_SESSION_INACTIVITY_TIMEOUT_SECONDS","MAX_USER_SESSION_INACTIVITY_TIMEOUT_SECONDS","DEFAULT_USER_SESSION_FOREGROUND_INACTIVITY_TIMEOUT_SECONDS","MAX_USER_SESSION_FOREGROUND_INACTIVITY_TIMEOUT_SECONDS","KEY_EMB_USER_SESSION_ID","ATTR_SESSION_ID","KEY_EMB_USER_SESSION_PREVIOUS_ID","ATTR_SESSION_PREVIOUS_ID","KEY_EMB_USER_SESSION_NUMBER","KEY_EMB_USER_SESSION_PART_INDEX","KEY_EMB_SESSION_PART_NUMBER","KEY_EMB_USER_SESSION_START_TS","KEY_EMB_USER_SESSION_MAX_DURATION_SECONDS","KEY_EMB_USER_SESSION_INACTIVITY_TIMEOUT_SECONDS","KEY_EMB_USER_SESSION_FOREGROUND_INACTIVITY_TIMEOUT_SECONDS","EMBRACE_LAST_END_USER_SESSION_TS_KEY","getVisibilityState","generateUUID","KEY_EMB_TYPE","KEY_EMB_STATE","KEY_EMB_SESSION_PART_ID","KEY_EMB_SESSION_PART_START_REASON","KEY_EMB_COLD_START","EmbraceExtendedSpan","SESSION_PART_SPAN_NAME","FINAL_SESSION_PART_END_REASONS","KEY_EMB_SESSION_PART_END_REASON","KEY_EMB_SDK_STARTUP_DURATION","KEY_EMB_PAGE_LOAD","KEY_EMB_IS_FINAL_SESSION_PART","KEY_EMB_USER_SESSION_TERMINATION_REASON","KEY_PREFIX_EMB_PROPERTIES","EMBRACE_USER_SESSION_STATE_KEY","storePermanentProperties","readUserSessionState","isUserSessionExpired","getIncrementedCount","EMBRACE_USER_SESSION_NUMBER_KEY","createUserSessionState","EMBRACE_SESSION_PART_NUMBER_KEY","isTabEngaged"],"sources":["../../../src/managers/EmbraceUserSessionManager/EmbraceUserSessionManager.ts"],"sourcesContent":["import type {\n Attributes,\n DiagLogger,\n Tracer,\n TracerProvider,\n} from '@opentelemetry/api';\nimport { diag, trace } from '@opentelemetry/api';\nimport {\n ATTR_SESSION_ID,\n ATTR_SESSION_PREVIOUS_ID,\n} from '@opentelemetry/semantic-conventions/incubating';\nimport type {\n PropertyOptions,\n SessionPartStartedEvent,\n UserSessionEndReason,\n} from '../../api-sessions/manager/types.ts';\nimport type { VisibilityStateDocument } from '../../common/index.ts';\nimport {\n EMB_STATES,\n EMB_TYPES,\n KEY_EMB_COLD_START,\n KEY_EMB_IS_FINAL_SESSION_PART,\n KEY_EMB_PAGE_LOAD,\n KEY_EMB_SDK_STARTUP_DURATION,\n KEY_EMB_SESSION_PART_END_REASON,\n KEY_EMB_SESSION_PART_ID,\n KEY_EMB_SESSION_PART_NUMBER,\n KEY_EMB_SESSION_PART_START_REASON,\n KEY_EMB_STATE,\n KEY_EMB_TYPE,\n KEY_EMB_USER_SESSION_FOREGROUND_INACTIVITY_TIMEOUT_SECONDS,\n KEY_EMB_USER_SESSION_ID,\n KEY_EMB_USER_SESSION_INACTIVITY_TIMEOUT_SECONDS,\n KEY_EMB_USER_SESSION_MAX_DURATION_SECONDS,\n KEY_EMB_USER_SESSION_NUMBER,\n KEY_EMB_USER_SESSION_PART_INDEX,\n KEY_EMB_USER_SESSION_PREVIOUS_ID,\n KEY_EMB_USER_SESSION_START_TS,\n KEY_EMB_USER_SESSION_TERMINATION_REASON,\n KEY_PREFIX_EMB_PROPERTIES,\n} from '../../constants/index.ts';\nimport type { ExtendedSpan } from '../../index.ts';\nimport type { DynamicConfigManager } from '../../sdk/index.ts';\nimport type {\n NamespacedStorage,\n PerformanceManager,\n TimeoutRef,\n} from '../../utils/index.ts';\nimport {\n clampNumber,\n generateUUID,\n getIncrementedCount,\n getVisibilityState,\n throttle,\n} from '../../utils/index.ts';\nimport type { LimitManagerInternal } from '../EmbraceLimitManager/index.ts';\nimport { EmbraceExtendedSpan } from '../EmbraceTraceManager/EmbraceExtendedSpan.ts';\nimport {\n DEFAULT_ACTIVITY_EVENTS,\n DEFAULT_ACTIVITY_THROTTLE_MS,\n DEFAULT_USER_SESSION_FOREGROUND_INACTIVITY_TIMEOUT_SECONDS,\n DEFAULT_USER_SESSION_INACTIVITY_TIMEOUT_SECONDS,\n DEFAULT_USER_SESSION_MAX_DURATION_SECONDS,\n EMBRACE_LAST_END_USER_SESSION_TS_KEY,\n EMBRACE_SESSION_PART_NUMBER_KEY,\n EMBRACE_USER_SESSION_NUMBER_KEY,\n EMBRACE_USER_SESSION_STATE_KEY,\n END_USER_SESSION_COOLDOWN_MS,\n FINAL_SESSION_PART_END_REASONS,\n MAX_USER_SESSION_FOREGROUND_INACTIVITY_TIMEOUT_SECONDS,\n MAX_USER_SESSION_INACTIVITY_TIMEOUT_SECONDS,\n MAX_USER_SESSION_MAX_DURATION_SECONDS,\n MIN_USER_SESSION_FOREGROUND_INACTIVITY_TIMEOUT_SECONDS,\n MIN_USER_SESSION_INACTIVITY_TIMEOUT_SECONDS,\n MIN_USER_SESSION_MAX_DURATION_SECONDS,\n SESSION_PART_SPAN_NAME,\n} from './constants.ts';\nimport type {\n EmbraceUserSessionManagerArgs,\n EndSessionPartOptions,\n RolloverSessionPartOptions,\n StartSessionPartOptions,\n UserSessionAttributes,\n UserSessionManagerInternal,\n UserSessionState,\n} from './types.ts';\nimport {\n addActivityListeners,\n createUserSessionState,\n isTabEngaged,\n isUserSessionExpired,\n readPermanentProperties,\n readUserSessionState,\n removeActivityListeners,\n storePermanentProperties,\n} from './utils/index.ts';\n\n/**\n * Parts are engagement-gated (visible AND focused), so only one part can\n * be active at a time. Engagement exclusivity lets us treat storage as\n * a plain shared row read on engagement and written on changes.\n */\nexport class EmbraceUserSessionManager implements UserSessionManagerInternal {\n private _state: UserSessionState | null = null;\n private _previousUserSessionId: string | null = null;\n private _maxDurationTimeout: ReturnType<typeof setTimeout> | null = null;\n // Bare keys; the wire-format prefix is applied at stamp time.\n private _permanentProperties: Record<string, string> = {};\n // Distinguishes \"another tab wiped storage after we wrote\" from \"we\n // never managed to write in the first place\"; see\n // _continueUserSessionAfterPartEnd.\n private _hasStoredState = false;\n\n private _activeSessionPartId: string | null = null;\n // Held for the part's lifetime so getUserSessionAttributes() reads\n // during the part return its starting value, even if another tab bumps\n // the shared counter while this tab is mid-disengagement.\n private _currentSessionPartNumber: number | null = null;\n private _sessionPartSpan: ExtendedSpan | null = null;\n private _activeSessionPartCounts: Record<string, number> | null = null;\n // Marks the first part of the page lifetime so it can be reported as a\n // cold start.\n private _coldStart = true;\n private _nextSessionPartCounts: Record<string, number> = {};\n private _sdkStartupDuration = 0;\n private readonly _sessionPartStartedListeners: Array<\n (event: SessionPartStartedEvent) => void\n > = [];\n private readonly _sessionPartEndedListeners: Array<() => void> = [];\n private _tracer: Tracer;\n\n private readonly _diag: DiagLogger;\n private readonly _perf: PerformanceManager;\n private readonly _storage: NamespacedStorage;\n private readonly _visibilityDoc: VisibilityStateDocument;\n private readonly _limitManager: LimitManagerInternal;\n private readonly _dynamicConfigManager: DynamicConfigManager;\n private readonly _target: EventTarget;\n private readonly _activityEvents: ReadonlyArray<string>;\n private readonly _onActivityThrottled: (event: Event) => void;\n private _sessionPartInactivityTimer: TimeoutRef | null = null;\n\n public constructor({\n dynamicConfigManager,\n diag: diagParam,\n perf,\n visibilityDoc,\n storage,\n limitManager,\n target = window,\n activityThrottleMs = DEFAULT_ACTIVITY_THROTTLE_MS,\n activityEvents = DEFAULT_ACTIVITY_EVENTS,\n }: EmbraceUserSessionManagerArgs) {\n this._diag =\n diagParam ??\n diag.createComponentLogger({\n namespace: 'EmbraceUserSessionManager',\n });\n this._perf = perf;\n this._visibilityDoc = visibilityDoc;\n this._storage = storage;\n this._limitManager = limitManager;\n this._dynamicConfigManager = dynamicConfigManager;\n this._permanentProperties = readPermanentProperties(\n this._storage,\n this._diag,\n );\n this._tracer = trace.getTracer('embrace-web-sdk-session-parts');\n this._target = target;\n this._activityEvents = activityEvents;\n this._onActivityThrottled = throttle(this._onActivity, activityThrottleMs);\n }\n\n /**\n * Reads the user-session durations from remote config, clamps each to its\n * allowed range, and enforces inactivity <= max duration. Called once per\n * user-session creation; the resolved values are frozen into that session's\n * state.\n */\n private _resolveUserSessionDurations(): {\n userSessionMaxDurationSeconds: number;\n userSessionInactivityTimeoutSeconds: number;\n userSessionForegroundInactivityTimeoutSeconds: number;\n } {\n const config = this._dynamicConfigManager.getConfig();\n\n const userSessionMaxDurationSeconds = clampNumber({\n diag: this._diag,\n value: config.userSessionMaxDurationSeconds,\n defaultValue: DEFAULT_USER_SESSION_MAX_DURATION_SECONDS,\n min: MIN_USER_SESSION_MAX_DURATION_SECONDS,\n max: MAX_USER_SESSION_MAX_DURATION_SECONDS,\n });\n const userSessionInactivityTimeoutSeconds = clampNumber({\n diag: this._diag,\n value: config.userSessionInactivityTimeoutSeconds,\n defaultValue: DEFAULT_USER_SESSION_INACTIVITY_TIMEOUT_SECONDS,\n min: MIN_USER_SESSION_INACTIVITY_TIMEOUT_SECONDS,\n max: MAX_USER_SESSION_INACTIVITY_TIMEOUT_SECONDS,\n });\n const userSessionForegroundInactivityTimeoutSeconds = clampNumber({\n diag: this._diag,\n value: config.userSessionForegroundInactivityTimeoutSeconds,\n defaultValue: DEFAULT_USER_SESSION_FOREGROUND_INACTIVITY_TIMEOUT_SECONDS,\n min: MIN_USER_SESSION_FOREGROUND_INACTIVITY_TIMEOUT_SECONDS,\n max: MAX_USER_SESSION_FOREGROUND_INACTIVITY_TIMEOUT_SECONDS,\n });\n\n // Each inactivity timeout must fit inside the max duration; if remote\n // config violates that, the offending value falls back to its own default\n // rather than to the max-duration value.\n const inactivityWithinMax =\n userSessionInactivityTimeoutSeconds <= userSessionMaxDurationSeconds;\n const foregroundWithinMax =\n userSessionForegroundInactivityTimeoutSeconds <=\n userSessionMaxDurationSeconds;\n\n if (!inactivityWithinMax) {\n this._diag.warn(\n `userSessionInactivityTimeoutSeconds (${userSessionInactivityTimeoutSeconds.toString()}s) ` +\n `exceeds userSessionMaxDurationSeconds (${userSessionMaxDurationSeconds.toString()}s); ` +\n `falling back to default inactivity timeout (${DEFAULT_USER_SESSION_INACTIVITY_TIMEOUT_SECONDS.toString()}s).`,\n );\n }\n if (!foregroundWithinMax) {\n this._diag.warn(\n `userSessionForegroundInactivityTimeoutSeconds (${userSessionForegroundInactivityTimeoutSeconds.toString()}s) ` +\n `exceeds userSessionMaxDurationSeconds (${userSessionMaxDurationSeconds.toString()}s); ` +\n `falling back to default foreground inactivity timeout (${DEFAULT_USER_SESSION_FOREGROUND_INACTIVITY_TIMEOUT_SECONDS.toString()}s).`,\n );\n }\n\n return {\n userSessionMaxDurationSeconds,\n userSessionInactivityTimeoutSeconds: inactivityWithinMax\n ? userSessionInactivityTimeoutSeconds\n : DEFAULT_USER_SESSION_INACTIVITY_TIMEOUT_SECONDS,\n userSessionForegroundInactivityTimeoutSeconds: foregroundWithinMax\n ? userSessionForegroundInactivityTimeoutSeconds\n : DEFAULT_USER_SESSION_FOREGROUND_INACTIVITY_TIMEOUT_SECONDS,\n };\n }\n\n public setTracerProvider(tracerProvider: TracerProvider): void {\n this._tracer = tracerProvider.getTracer('embrace-web-sdk-session-parts');\n // Listeners attach here rather than in the constructor so that tests\n // that construct a manager without going through SDK init don't\n // pollute window with listeners, and so that browser-activity-driven\n // parts only start once a real tracer is wired.\n addActivityListeners({\n target: this._target,\n visibilityDoc: this._visibilityDoc,\n activityEvents: this._activityEvents,\n onActivity: this._onActivityThrottled,\n onVisibilityChange: this._onVisibilityChange,\n onFocus: this._onFocus,\n onBlur: this._onBlur,\n });\n // Eager state init so getUserSessionId() returns a real id before the\n // first part starts, and so other tabs see the row when we create a\n // fresh session here.\n const { state, created } = this._loadOrCreateUserSessionState(\n this._perf.getNowMillis(),\n );\n this._state = state;\n if (created) {\n this._storeState();\n }\n }\n\n public recordSDKStartupDuration(duration: number): void {\n this._sdkStartupDuration = Math.ceil(duration);\n }\n\n public getUserSessionId(): string | null {\n return this._state?.userSessionId ?? null;\n }\n\n public getPreviousUserSessionId(): string | null {\n // Fall back to in-memory for the brief window during user-session end\n // where storage is cleared but the just-ended id is still held.\n return this._state?.previousUserSessionId ?? this._previousUserSessionId;\n }\n\n public getUserSessionStartTime(): number | null {\n return this._state?.userSessionStartTs ?? null;\n }\n\n public getUserSessionAttributes(): UserSessionAttributes | null {\n if (!this._state) {\n return null;\n }\n const previousUserSessionId = this.getPreviousUserSessionId() ?? '';\n return {\n [KEY_EMB_USER_SESSION_ID]: this._state.userSessionId,\n [ATTR_SESSION_ID]: this._state.userSessionId,\n [KEY_EMB_USER_SESSION_PREVIOUS_ID]: previousUserSessionId,\n [ATTR_SESSION_PREVIOUS_ID]: previousUserSessionId,\n [KEY_EMB_USER_SESSION_NUMBER]: this._state.userSessionNumber,\n [KEY_EMB_USER_SESSION_PART_INDEX]: this._state.userSessionPartIndex,\n [KEY_EMB_SESSION_PART_NUMBER]: this._currentSessionPartNumber ?? 0,\n [KEY_EMB_USER_SESSION_START_TS]: this._state.userSessionStartTs,\n [KEY_EMB_USER_SESSION_MAX_DURATION_SECONDS]:\n this._state.userSessionMaxDurationSeconds,\n [KEY_EMB_USER_SESSION_INACTIVITY_TIMEOUT_SECONDS]:\n this._state.userSessionInactivityTimeoutSeconds,\n [KEY_EMB_USER_SESSION_FOREGROUND_INACTIVITY_TIMEOUT_SECONDS]:\n this._state.userSessionForegroundInactivityTimeoutSeconds,\n };\n }\n\n public endUserSession(): void {\n if (!this._state) {\n this._diag.debug(\n 'Trying to end user session, but there is no active session. This is a no-op.',\n );\n return;\n }\n\n const now = this._perf.getNowMillis();\n const lastEndRaw = this._storage.getItem(\n EMBRACE_LAST_END_USER_SESSION_TS_KEY,\n );\n const lastEndTs = lastEndRaw === null ? NaN : Number(lastEndRaw);\n if (\n Number.isFinite(lastEndTs) &&\n now - lastEndTs < END_USER_SESSION_COOLDOWN_MS\n ) {\n this._diag.warn(\n 'endUserSession called within cooldown period, ignoring.',\n );\n return;\n }\n\n this._rolloverUserSession('manual');\n this._storage.setItem(EMBRACE_LAST_END_USER_SESSION_TS_KEY, String(now));\n }\n\n public getSessionPartId(): string | null {\n return this._activeSessionPartId;\n }\n\n public getSessionPartSpan(): ExtendedSpan | null {\n return this._sessionPartSpan;\n }\n\n public startSessionPartInternal({\n reason,\n timestamp,\n }: StartSessionPartOptions): void {\n if (this._sessionPartSpan) {\n this._diag.warn(\n `startSessionPartInternal called while a part is active (reason: ${reason}); ignoring`,\n );\n return;\n }\n\n // Parts are foreground-only: a part corresponds to a tab that is both\n // visible AND focused.\n if (\n getVisibilityState(this._visibilityDoc) === EMB_STATES.Background ||\n !this._visibilityDoc.hasFocus()\n ) {\n this._diag.debug(\n `skipping session part start (reason: ${reason}) because the tab is not engaged`,\n );\n return;\n }\n\n const activeSessionPartId = generateUUID();\n const previouslyRecordedCounts = this._nextSessionPartCounts;\n\n this._beginUserSessionForPartStart();\n\n const attributes: Attributes = {\n ...this.getUserSessionAttributes(),\n [KEY_EMB_TYPE]: EMB_TYPES.SessionPart,\n [KEY_EMB_STATE]: EMB_STATES.Foreground,\n [KEY_EMB_SESSION_PART_ID]: activeSessionPartId,\n [KEY_EMB_SESSION_PART_START_REASON]: reason,\n [KEY_EMB_COLD_START]: this._coldStart,\n ...previouslyRecordedCounts,\n };\n\n this._activeSessionPartId = activeSessionPartId;\n let span: EmbraceExtendedSpan;\n try {\n span = new EmbraceExtendedSpan(\n this._tracer.startSpan(SESSION_PART_SPAN_NAME, {\n attributes,\n startTime: timestamp,\n }),\n );\n } catch (error) {\n this._activeSessionPartId = null;\n this._diag.error('Error starting session part span', error);\n return;\n }\n\n this._activeSessionPartCounts = {};\n this._nextSessionPartCounts = {};\n this._sessionPartSpan = span;\n\n this._startSessionPartInactivityTimer();\n\n this._fireListeners(this._sessionPartStartedListeners, 'started', {\n reason,\n });\n }\n\n public endSessionPartInternal({\n reason,\n userSessionEndReason,\n timestamp,\n }: EndSessionPartOptions): void {\n if (!this._sessionPartSpan) {\n this._diag.debug(\n 'trying to end a session part, but there is no session part in progress. This is a no-op.',\n );\n return;\n }\n\n this._clearSessionPartInactivityTimer();\n\n const isFinalSessionPart = FINAL_SESSION_PART_END_REASONS.has(reason);\n\n // Two stamps, captured upfront so an unbounded SpanProcessor.onEnd\n // cannot push them forward: the caller's timestamp (when given) becomes\n // the span end time, while the inactivity deadline is computed from the\n // actual call time so an anchored end cannot shorten the expiry window.\n const now = this._perf.getNowMillis();\n const partEndTs = timestamp ?? now;\n const span = this._sessionPartSpan;\n try {\n const endAttrs: Attributes = {\n [KEY_EMB_SESSION_PART_END_REASON]: reason,\n ...this._activeSessionPartCounts,\n ...this._limitManager.getDiagnosticCounts(),\n [KEY_EMB_SDK_STARTUP_DURATION]: this._sdkStartupDuration,\n };\n if (this._coldStart) {\n endAttrs[KEY_EMB_PAGE_LOAD] =\n this._visibilityDoc.readyState === 'complete';\n }\n if (isFinalSessionPart) {\n endAttrs[KEY_EMB_IS_FINAL_SESSION_PART] = 1;\n if (userSessionEndReason) {\n endAttrs[KEY_EMB_USER_SESSION_TERMINATION_REASON] =\n userSessionEndReason;\n }\n }\n\n const propertyAttrs: Attributes = {};\n for (const [bareKey, value] of Object.entries(\n this.getSessionPartProperties(),\n )) {\n propertyAttrs[KEY_PREFIX_EMB_PROPERTIES + bareKey] = value;\n }\n\n try {\n span.setAttributes({\n ...propertyAttrs,\n ...endAttrs,\n });\n } catch (error) {\n this._diag.warn(\n 'Error setting end attributes on session part span; ending span without them',\n error,\n );\n }\n } finally {\n // Fire listeners after end attributes are stamped (so subscribers can\n // read them) and before span.end() (so anything they emit still\n // attaches to the part that is conceptually still active).\n this._fireListeners(this._sessionPartEndedListeners, 'ended', undefined);\n try {\n span.end(partEndTs);\n } catch (error) {\n this._diag.warn('Error ending session part span', error);\n }\n this._sessionPartSpan = null;\n this._coldStart = false;\n this._activeSessionPartId = null;\n this._activeSessionPartCounts = null;\n this._currentSessionPartNumber = null;\n this._limitManager.reset();\n }\n\n if (isFinalSessionPart) {\n this._previousUserSessionId = this._state?.userSessionId ?? null;\n this._state = null;\n this._storage.removeItem(EMBRACE_USER_SESSION_STATE_KEY);\n this._clearMaxDurationTimer();\n } else {\n this._continueUserSessionAfterPartEnd(now);\n }\n }\n\n public addBreadcrumb(name: string): void {\n if (!this._sessionPartSpan) {\n this._diag.debug(\n 'trying to add breadcrumb, but there is no session part in progress. This is a no-op.',\n );\n return;\n }\n\n const limitedBreadcrumb = this._limitManager.limitBreadcrumb(name);\n if (limitedBreadcrumb === 'dropped') {\n return;\n }\n\n this._sessionPartSpan.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 ): void {\n const limitedSessionProperty = this._limitManager.limitUserSessionProperty(\n propertyKey,\n value,\n );\n\n if (limitedSessionProperty === 'dropped') {\n return;\n }\n\n const bareKey = limitedSessionProperty.key;\n const bareValue = limitedSessionProperty.value;\n\n if (options?.lifespan === 'permanent') {\n const candidate = {\n ...this._permanentProperties,\n [bareKey]: bareValue,\n };\n // Reject the write if storage is unavailable: a property that can't\n // persist would silently diverge from what other tabs (and the next\n // page load) see, which is worse than no property at all.\n if (!storePermanentProperties(this._storage, candidate)) {\n this._diag.warn(\n 'Storage unavailable; rejecting permanent property write.',\n );\n return;\n }\n this._permanentProperties = candidate;\n this._removeFromUserSessionProperties(bareKey);\n return;\n }\n\n // Ensure a state row exists so the write has somewhere to land;\n // addProperty can fire before any part has started.\n const { state } = this._loadOrCreateUserSessionState(\n this._perf.getNowMillis(),\n );\n this._state = {\n ...state,\n userSessionProperties: {\n ...state.userSessionProperties,\n [bareKey]: bareValue,\n },\n };\n if (!this._storeState()) {\n // Roll back so callers don't observe a value that won't persist.\n // The user session itself stays in memory.\n this._state = state;\n this._diag.warn(\n 'Storage unavailable; rejecting user-session property write.',\n );\n }\n }\n\n public removeProperty(propertyKey: string): void {\n const bareKey = this._limitManager.truncateString(\n 'session_property_key',\n propertyKey,\n );\n\n // Try both stores so a key that was flipped between scopes doesn't\n // linger in the other one.\n this._removeFromPermanentProperties(bareKey);\n this._removeFromUserSessionProperties(bareKey);\n }\n\n /**\n * Bare-keyed view of properties for the active part. User-session-scoped\n * entries shadow permanent ones, matching the cross-scope flip rule\n * enforced in `addProperty`. Callers apply the wire-format prefix at\n * stamp time.\n */\n public getSessionPartProperties(): Record<string, string> {\n return {\n ...this._permanentProperties,\n ...(this._state?.userSessionProperties ?? {}),\n };\n }\n\n public incrSessionPartCountForKey(key: string): void {\n if (!this._sessionPartSpan || !this._activeSessionPartCounts) {\n this._diag.debug(\n 'trying to increment a count for the active session part, but there is no session part in progress. This is a no-op.',\n );\n return;\n }\n this._activeSessionPartCounts[key] =\n (this._activeSessionPartCounts[key] || 0) + 1;\n }\n\n public incrNextSessionPartCountForKey(key: string): void {\n this._nextSessionPartCounts[key] =\n (this._nextSessionPartCounts[key] || 0) + 1;\n }\n\n public addSessionPartStartedListener(\n listener: (event: SessionPartStartedEvent) => void,\n ): () => void {\n return this._addListener(this._sessionPartStartedListeners, listener);\n }\n\n public addSessionPartEndedListener(listener: () => void): () => void {\n return this._addListener(this._sessionPartEndedListeners, listener);\n }\n\n public getSessionId(): string | null {\n return this.getUserSessionId();\n }\n\n public getPreviousSessionId(): null {\n return null;\n }\n\n public getSessionStartTime(): number | null {\n return this.getUserSessionStartTime();\n }\n\n public endSessionSpan(): void {\n this.endUserSession();\n }\n\n public startSessionSpan(): void {}\n\n public getSessionSpan(): ExtendedSpan | null {\n return this.getSessionPartSpan();\n }\n\n public currentSessionAsReadableSpan(): null {\n return null;\n }\n\n public addSessionStartedListener(_listener: () => void): () => void {\n return () => {};\n }\n\n public addSessionEndedListener(_listener: () => void): () => void {\n return () => {};\n }\n\n /**\n * Returns the persisted user-session state, rotating to a fresh one if\n * the persisted row is missing or expired. Arms the max-duration timer\n * when needed. The caller is responsible for assigning the result to\n * `_state` and for persisting (the helper does not write to storage).\n */\n private _loadOrCreateUserSessionState(now: number): {\n state: UserSessionState;\n created: boolean;\n } {\n let state = readUserSessionState(this._storage, this._diag);\n // Never persisted (writes disabled): keep the in-memory session rather than\n // mint a fresh one per part. After a persist, an empty read is a real clear.\n if (!state && !this._hasStoredState && this._state) {\n state = this._state;\n }\n let created = false;\n if (!state || isUserSessionExpired(state, now)) {\n if (state) {\n this._previousUserSessionId = state.userSessionId;\n }\n const userSessionNumber = getIncrementedCount(\n this._storage,\n EMBRACE_USER_SESSION_NUMBER_KEY,\n this._diag,\n );\n // Refresh remote config for the next user session. The fetch is async,\n // so it lands in the cache after this session's durations are frozen\n // below; this session uses the currently cached config. Skipped on cold\n // start, where initSDK already refreshes at startup.\n if (!this._coldStart) {\n void this._dynamicConfigManager.refreshRemoteConfig();\n }\n const {\n userSessionMaxDurationSeconds,\n userSessionInactivityTimeoutSeconds,\n userSessionForegroundInactivityTimeoutSeconds,\n } = this._resolveUserSessionDurations();\n state = createUserSessionState({\n now,\n previousUserSessionId: this._previousUserSessionId,\n userSessionMaxDurationSeconds,\n userSessionInactivityTimeoutSeconds,\n userSessionForegroundInactivityTimeoutSeconds,\n userSessionNumber,\n });\n created = true;\n }\n // Arm on a freshly created state, and on the page-reload case where a\n // new manager loads existing state and has no timer running yet.\n // userSessionMaxEndTs is fixed at creation, so re-arming the same value\n // on every load/start call is wasteful.\n if (created || this._maxDurationTimeout === null) {\n this._setupMaxDurationTimer(state, now);\n }\n return { state, created };\n }\n\n /**\n * Bumps the part counter and arms the max-duration timer. Relies on\n * `_loadOrCreateUserSessionState` to provide a non-null, non-expired state,\n * so `getUserSessionAttributes()` is guaranteed non-null after this.\n */\n private _beginUserSessionForPartStart(): void {\n const now = this._perf.getNowMillis();\n const { state } = this._loadOrCreateUserSessionState(now);\n\n this._currentSessionPartNumber = getIncrementedCount(\n this._storage,\n EMBRACE_SESSION_PART_NUMBER_KEY,\n this._diag,\n );\n\n this._state = {\n ...state,\n userSessionPartIndex: state.userSessionPartIndex + 1,\n // A part is active, so any pending inactivity deadline no longer\n // applies; it gets re-armed when the part ends.\n inactivityDeadlineTs: null,\n };\n this._storeState();\n }\n\n /**\n * Records the inactivity deadline on the user-session row so the next\n * part start can detect lazy expiry. The max-duration timer stays valid\n * because userSessionMaxEndTs doesn't change between parts.\n */\n private _continueUserSessionAfterPartEnd(now: number): void {\n if (!this._state) {\n this._clearMaxDurationTimer();\n return;\n }\n\n // If the state row was present at part start but is gone now (user\n // wiped site data, quota eviction, sibling tab cleared), don't\n // resurrect it from memory. Drop in-memory state so the next part\n // start creates a fresh user session; carry the just-ended id\n // forward so the next session can back-link to it.\n // _hasStoredState gates this: if we never persisted in the first\n // place, the in-memory copy stays authoritative.\n if (\n this._hasStoredState &&\n this._storage.getItem(EMBRACE_USER_SESSION_STATE_KEY) === null\n ) {\n this._previousUserSessionId = this._state.userSessionId;\n this._state = null;\n this._clearMaxDurationTimer();\n return;\n }\n\n this._state = {\n ...this._state,\n inactivityDeadlineTs:\n now + this._state.userSessionInactivityTimeoutSeconds * 1000,\n };\n this._storeState();\n }\n\n /**\n * Ends the active part as final and starts a fresh part for the next\n * user session. State clearing for the engaged path is handled by\n * endSessionPartInternal's isFinal branch. When no part is active\n * (tab disengaged), the same state-clearing is performed inline so\n * the next engagement creates a fresh user session rather than\n * resuming the just-ended one.\n */\n private _rolloverUserSession(\n userSessionEndReason: UserSessionEndReason,\n ): void {\n if (this._sessionPartSpan) {\n // The paired end/start share one boundary timestamp so the consecutive\n // part spans tile without an artificial gap. Real gaps in the part\n // timeline (page load, hidden tab) stay meaningful because those paths\n // start a part without passing a timestamp. The timestamp anchors the\n // spans only; user-session state and timers always use the actual call\n // time.\n const boundaryTimestamp = this._perf.getNowMillis();\n this.endSessionPartInternal({\n reason: 'user_session_ended',\n userSessionEndReason,\n timestamp: boundaryTimestamp,\n });\n this.startSessionPartInternal({\n reason: 'user_session_rollover',\n timestamp: boundaryTimestamp,\n });\n return;\n }\n\n this._previousUserSessionId = this._state?.userSessionId ?? null;\n this._state = null;\n this._storage.removeItem(EMBRACE_USER_SESSION_STATE_KEY);\n this._clearMaxDurationTimer();\n }\n\n /**\n * Ends the active part and starts a new one within the same user session.\n * The user session is preserved while the part counters advance, so the\n * caller's `endReason`/`startReason` must both be non-final. Both spans share\n * one boundary timestamp so they tile without a gap; the caller may supply it\n * (for example the timestamp of the triggering event), defaulting to now. A\n * no-op when no part is active: there is nothing to roll over and the next\n * engagement starts one.\n */\n\n public rolloverSessionPartInternal({\n endReason,\n startReason,\n timestamp,\n }: RolloverSessionPartOptions): void {\n if (!this._sessionPartSpan) {\n return;\n }\n const boundaryTimestamp = timestamp ?? this._perf.getNowMillis();\n this.endSessionPartInternal({\n reason: endReason,\n timestamp: boundaryTimestamp,\n });\n this.startSessionPartInternal({\n reason: startReason,\n timestamp: boundaryTimestamp,\n });\n }\n\n private _setupMaxDurationTimer(state: UserSessionState, now: number): void {\n this._clearMaxDurationTimer();\n\n const remaining = state.userSessionMaxEndTs - now;\n if (remaining <= 0) {\n return;\n }\n\n this._maxDurationTimeout = setTimeout(() => {\n this._maxDurationTimeout = null;\n this._rolloverUserSession('max_duration_reached');\n }, remaining);\n }\n\n /**\n * @internal Would be private but exposed for tests to simulate a page reload\n */\n public _clearMaxDurationTimer(): void {\n if (this._maxDurationTimeout !== null) {\n clearTimeout(this._maxDurationTimeout);\n this._maxDurationTimeout = null;\n }\n }\n\n /**\n * Detaches browser-activity listeners and clears the part-inactivity\n * timer. Intended for SDK teardown and tests; in production the manager\n * lives for the page's lifetime.\n */\n public _shutdown(): void {\n removeActivityListeners({\n target: this._target,\n visibilityDoc: this._visibilityDoc,\n activityEvents: this._activityEvents,\n onActivity: this._onActivityThrottled,\n onVisibilityChange: this._onVisibilityChange,\n onFocus: this._onFocus,\n onBlur: this._onBlur,\n });\n this._clearSessionPartInactivityTimer();\n this._clearMaxDurationTimer();\n }\n\n private readonly _onActivity = (): void => {\n try {\n if (!isTabEngaged(this._visibilityDoc)) {\n return;\n }\n if (this._activeSessionPartId === null) {\n this.startSessionPartInternal({ reason: 'web_activity' });\n return;\n }\n this._startSessionPartInactivityTimer();\n } catch (e) {\n this._diag.warn('Error handling activity event', e);\n }\n };\n\n // Tab visibility flipped (switch tabs, minimize, OS app switch). Drives\n // the engagement transition off the current visibilityState/hasFocus pair.\n private readonly _onVisibilityChange = (): void => {\n this._handleEngagementTransition('visibilitychange');\n };\n\n // Window focus arrived (alt-tab back, clicked into the document). hasFocus\n // is now true; engagement transition resolves whether to start a part.\n private readonly _onFocus = (): void => {\n this._handleEngagementTransition('focus');\n };\n\n // Window focus lost (alt-tab, DevTools, another window). Engagement\n // transition ends the active part as background. pagehide/pageshow\n // are not listened to: blur or visibilitychange-to-hidden end the part\n // first (see README \"Unload and BFCache handling\" for the verified\n // cross-engine ordering). The part-end doubles as the unload flush via\n // EmbraceSessionPartBatchedSpanProcessor.onEnd's synchronous keepalive export.\n // https://developer.chrome.com/docs/web-platform/page-lifecycle-api\n private readonly _onBlur = (): void => {\n this._handleEngagementTransition('blur');\n };\n\n private _handleEngagementTransition(source: string): void {\n try {\n const engaged = isTabEngaged(this._visibilityDoc);\n const active = this._activeSessionPartId !== null;\n if (!engaged && active) {\n this._diag.debug(`tab disengaged via ${source}; ending current part`);\n this.endSessionPartInternal({ reason: 'background' });\n return;\n }\n if (engaged && !active) {\n this._diag.debug(`tab engaged via ${source}; starting new part`);\n this.startSessionPartInternal({ reason: 'foreground' });\n return;\n }\n if (engaged && active) {\n // Redundant engagement event while a part is already active (browser\n // quirk: an extra focus/visibility fire that isn't a real transition).\n // Re-arm the inactivity timer since the event still implies presence.\n this._startSessionPartInactivityTimer();\n }\n } catch (e) {\n this._diag.warn('Error handling engagement change', e);\n }\n }\n\n private readonly _onSessionPartInactivity = (): void => {\n this._sessionPartInactivityTimer = null;\n try {\n if (this._activeSessionPartId === null) {\n return;\n }\n this._diag.debug(\n 'inactivity timer fired; ending current part and user session',\n );\n this.endSessionPartInternal({\n reason: 'web_foreground_inactivity',\n userSessionEndReason: 'inactivity',\n });\n } catch (e) {\n this._diag.warn('Error handling inactivity timer', e);\n }\n };\n\n private _startSessionPartInactivityTimer(): void {\n this._clearSessionPartInactivityTimer();\n // Callers only arm this with an active part, so the early return is a type\n // guard rather than a live path.\n if (!this._state) {\n return;\n }\n this._sessionPartInactivityTimer = setTimeout(\n this._onSessionPartInactivity,\n this._state.userSessionForegroundInactivityTimeoutSeconds * 1000,\n );\n }\n\n private _clearSessionPartInactivityTimer(): void {\n if (this._sessionPartInactivityTimer !== null) {\n clearTimeout(this._sessionPartInactivityTimer);\n this._sessionPartInactivityTimer = null;\n }\n }\n\n private _addListener<T>(list: T[], listener: T): () => void {\n list.push(listener);\n return () => {\n const i = list.indexOf(listener);\n if (i !== -1) {\n list.splice(i, 1);\n }\n };\n }\n\n private _fireListeners<T>(\n list: Array<(arg: T) => void>,\n kind: string,\n arg: T,\n ): void {\n for (const listener of list) {\n try {\n listener(arg);\n } catch (error) {\n this._diag.warn(\n `Error while executing session part ${kind} listener`,\n error,\n );\n }\n }\n }\n\n private _storeState(): boolean {\n if (!this._state) {\n return false;\n }\n const stored = this._storage.setItem(\n EMBRACE_USER_SESSION_STATE_KEY,\n JSON.stringify(this._state),\n );\n if (stored) {\n this._hasStoredState = true;\n }\n return stored;\n }\n\n private _removeFromPermanentProperties(bareKey: string): void {\n if (!(bareKey in this._permanentProperties)) {\n return;\n }\n const pruned = { ...this._permanentProperties };\n delete pruned[bareKey];\n this._permanentProperties = pruned;\n storePermanentProperties(this._storage, pruned);\n }\n\n private _removeFromUserSessionProperties(bareKey: string): void {\n if (!this._state || !(bareKey in this._state.userSessionProperties)) {\n return;\n }\n const pruned = { ...this._state.userSessionProperties };\n delete pruned[bareKey];\n this._state = {\n ...this._state,\n userSessionProperties: pruned,\n };\n this._storeState();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAsGA,IAAa,4BAAb,MAA6E;CAC3E,SAA0C;CAC1C,yBAAgD;CAChD,sBAAoE;CAEpE,uBAAuD,CAAC;CAIxD,kBAA0B;CAE1B,uBAA8C;CAI9C,4BAAmD;CACnD,mBAAgD;CAChD,2BAAkE;CAGlE,aAAqB;CACrB,yBAAyD,CAAC;CAC1D,sBAA8B;CAC9B,+BAEI,CAAC;CACL,6BAAiE,CAAC;CAClE;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA,8BAAyD;CAEzD,YAAmB,EACjB,sBACA,MAAM,WACN,MACA,eACA,SACA,cACA,SAAS,QACT,qBAAqBA,qDAAAA,8BACrB,iBAAiBC,qDAAAA,2BACe;EAChC,KAAK,QACH,aACAC,mBAAAA,KAAK,sBAAsB,EACzB,WAAW,4BACb,CAAC;EACH,KAAK,QAAQ;EACb,KAAK,iBAAiB;EACtB,KAAK,WAAW;EAChB,KAAK,gBAAgB;EACrB,KAAK,wBAAwB;EAC7B,KAAK,uBAAuBC,uDAAAA,wBAC1B,KAAK,UACL,KAAK,KACP;EACA,KAAK,UAAUC,mBAAAA,MAAM,UAAU,+BAA+B;EAC9D,KAAK,UAAU;EACf,KAAK,kBAAkB;EACvB,KAAK,uBAAuBC,uBAAAA,SAAS,KAAK,aAAa,kBAAkB;CAC3E;;;;;;;CAQA,+BAIE;EACA,MAAM,SAAS,KAAK,sBAAsB,UAAU;EAEpD,MAAM,gCAAgCC,0BAAAA,YAAY;GAChD,MAAM,KAAK;GACX,OAAO,OAAO;GACd,cAAcC,qDAAAA;GACd,KAAKC,qDAAAA;GACL,KAAKC,qDAAAA;EACP,CAAC;EACD,MAAM,sCAAsCH,0BAAAA,YAAY;GACtD,MAAM,KAAK;GACX,OAAO,OAAO;GACd,cAAcI,qDAAAA;GACd,KAAA;GACA,KAAKC,qDAAAA;EACP,CAAC;EACD,MAAM,gDAAgDL,0BAAAA,YAAY;GAChE,MAAM,KAAK;GACX,OAAO,OAAO;GACd,cAAcM,qDAAAA;GACd,KAAA;GACA,KAAKC,qDAAAA;EACP,CAAC;EAKD,MAAM,sBACJ,uCAAuC;EACzC,MAAM,sBACJ,iDACA;EAEF,IAAI,CAAC,qBACH,KAAK,MAAM,KACT,wCAAwC,oCAAoC,SAAS,EAAE,4CAC3C,8BAA8B,SAAS,EAAE,kDACpCH,qDAAAA,gDAAgD,SAAS,EAAE,IAC9G;EAEF,IAAI,CAAC,qBACH,KAAK,MAAM,KACT,kDAAkD,8CAA8C,SAAS,EAAE,4CAC/D,8BAA8B,SAAS,EAAE,6DACzBE,qDAAAA,2DAA2D,SAAS,EAAE,IACpI;EAGF,OAAO;GACL;GACA,qCAAqC,sBACjC,sCACAF,qDAAAA;GACJ,+CAA+C,sBAC3C,gDACAE,qDAAAA;EACN;CACF;CAEA,kBAAyB,gBAAsC;EAC7D,KAAK,UAAU,eAAe,UAAU,+BAA+B;EAKvE,0DAAA,qBAAqB;GACnB,QAAQ,KAAK;GACb,eAAe,KAAK;GACpB,gBAAgB,KAAK;GACrB,YAAY,KAAK;GACjB,oBAAoB,KAAK;GACzB,SAAS,KAAK;GACd,QAAQ,KAAK;EACf,CAAC;EAID,MAAM,EAAE,OAAO,YAAY,KAAK,8BAC9B,KAAK,MAAM,aAAa,CAC1B;EACA,KAAK,SAAS;EACd,IAAI,SACF,KAAK,YAAY;CAErB;CAEA,yBAAgC,UAAwB;EACtD,KAAK,sBAAsB,KAAK,KAAK,QAAQ;CAC/C;CAEA,mBAAyC;EACvC,OAAO,KAAK,QAAQ,iBAAiB;CACvC;CAEA,2BAAiD;EAG/C,OAAO,KAAK,QAAQ,yBAAyB,KAAK;CACpD;CAEA,0BAAgD;EAC9C,OAAO,KAAK,QAAQ,sBAAsB;CAC5C;CAEA,2BAAgE;EAC9D,IAAI,CAAC,KAAK,QACR,OAAO;EAET,MAAM,wBAAwB,KAAK,yBAAyB,KAAK;EACjE,OAAO;IACJE,6BAAAA,0BAA0B,KAAK,OAAO;IACtCC,+CAAAA,kBAAkB,KAAK,OAAO;IAC9BC,6BAAAA,mCAAmC;IACnCC,+CAAAA,2BAA2B;IAC3BC,6BAAAA,8BAA8B,KAAK,OAAO;IAC1CC,6BAAAA,kCAAkC,KAAK,OAAO;IAC9CC,6BAAAA,8BAA8B,KAAK,6BAA6B;IAChEC,6BAAAA,gCAAgC,KAAK,OAAO;IAC5CC,6BAAAA,4CACC,KAAK,OAAO;IACbC,6BAAAA,kDACC,KAAK,OAAO;IACbC,6BAAAA,6DACC,KAAK,OAAO;EAChB;CACF;CAEA,iBAA8B;EAC5B,IAAI,CAAC,KAAK,QAAQ;GAChB,KAAK,MAAM,MACT,8EACF;GACA;EACF;EAEA,MAAM,MAAM,KAAK,MAAM,aAAa;EACpC,MAAM,aAAa,KAAK,SAAS,QAC/BC,qDAAAA,oCACF;EACA,MAAM,YAAY,eAAe,OAAO,MAAM,OAAO,UAAU;EAC/D,IACE,OAAO,SAAS,SAAS,KACzB,MAAM,YAAA,KACN;GACA,KAAK,MAAM,KACT,yDACF;GACA;EACF;EAEA,KAAK,qBAAqB,QAAQ;EAClC,KAAK,SAAS,QAAQA,qDAAAA,sCAAsC,OAAO,GAAG,CAAC;CACzE;CAEA,mBAAyC;EACvC,OAAO,KAAK;CACd;CAEA,qBAAiD;EAC/C,OAAO,KAAK;CACd;CAEA,yBAAgC,EAC9B,QACA,aACgC;EAChC,IAAI,KAAK,kBAAkB;GACzB,KAAK,MAAM,KACT,mEAAmE,OAAO,YAC5E;GACA;EACF;EAIA,IACEC,iCAAAA,mBAAmB,KAAK,cAAc,MAAA,gBACtC,CAAC,KAAK,eAAe,SAAS,GAC9B;GACA,KAAK,MAAM,MACT,wCAAwC,OAAO,iCACjD;GACA;EACF;EAEA,MAAM,sBAAsBC,2BAAAA,aAAa;EACzC,MAAM,2BAA2B,KAAK;EAEtC,KAAK,8BAA8B;EAEnC,MAAM,aAAyB;GAC7B,GAAG,KAAK,yBAAyB;IAChCC,6BAAAA,eAAAA;IACAC,6BAAAA,gBAAAA;IACAC,6BAAAA,0BAA0B;IAC1BC,6BAAAA,oCAAoC;IACpCC,6BAAAA,qBAAqB,KAAK;GAC3B,GAAG;EACL;EAEA,KAAK,uBAAuB;EAC5B,IAAI;EACJ,IAAI;GACF,OAAO,IAAIC,yDAAAA,oBACT,KAAK,QAAQ,UAAUC,qDAAAA,wBAAwB;IAC7C;IACA,WAAW;GACb,CAAC,CACH;EACF,SAAS,OAAO;GACd,KAAK,uBAAuB;GAC5B,KAAK,MAAM,MAAM,oCAAoC,KAAK;GAC1D;EACF;EAEA,KAAK,2BAA2B,CAAC;EACjC,KAAK,yBAAyB,CAAC;EAC/B,KAAK,mBAAmB;EAExB,KAAK,iCAAiC;EAEtC,KAAK,eAAe,KAAK,8BAA8B,WAAW,EAChE,OACF,CAAC;CACH;CAEA,uBAA8B,EAC5B,QACA,sBACA,aAC8B;EAC9B,IAAI,CAAC,KAAK,kBAAkB;GAC1B,KAAK,MAAM,MACT,0FACF;GACA;EACF;EAEA,KAAK,iCAAiC;EAEtC,MAAM,qBAAqBC,qDAAAA,+BAA+B,IAAI,MAAM;EAMpE,MAAM,MAAM,KAAK,MAAM,aAAa;EACpC,MAAM,YAAY,aAAa;EAC/B,MAAM,OAAO,KAAK;EAClB,IAAI;GACF,MAAM,WAAuB;KAC1BC,6BAAAA,kCAAkC;IACnC,GAAG,KAAK;IACR,GAAG,KAAK,cAAc,oBAAoB;KACzCC,6BAAAA,+BAA+B,KAAK;GACvC;GACA,IAAI,KAAK,YACP,SAASC,6BAAAA,qBACP,KAAK,eAAe,eAAe;GAEvC,IAAI,oBAAoB;IACtB,SAASC,6BAAAA,iCAAiC;IAC1C,IAAI,sBACF,SAASC,6BAAAA,2CACP;GAEN;GAEA,MAAM,gBAA4B,CAAC;GACnC,KAAK,MAAM,CAAC,SAAS,UAAU,OAAO,QACpC,KAAK,yBAAyB,CAChC,GACE,cAAcC,6BAAAA,4BAA4B,WAAW;GAGvD,IAAI;IACF,KAAK,cAAc;KACjB,GAAG;KACH,GAAG;IACL,CAAC;GACH,SAAS,OAAO;IACd,KAAK,MAAM,KACT,+EACA,KACF;GACF;EACF,UAAU;GAIR,KAAK,eAAe,KAAK,4BAA4B,SAAS,KAAA,CAAS;GACvE,IAAI;IACF,KAAK,IAAI,SAAS;GACpB,SAAS,OAAO;IACd,KAAK,MAAM,KAAK,kCAAkC,KAAK;GACzD;GACA,KAAK,mBAAmB;GACxB,KAAK,aAAa;GAClB,KAAK,uBAAuB;GAC5B,KAAK,2BAA2B;GAChC,KAAK,4BAA4B;GACjC,KAAK,cAAc,MAAM;EAC3B;EAEA,IAAI,oBAAoB;GACtB,KAAK,yBAAyB,KAAK,QAAQ,iBAAiB;GAC5D,KAAK,SAAS;GACd,KAAK,SAAS,WAAWC,qDAAAA,8BAA8B;GACvD,KAAK,uBAAuB;EAC9B,OACE,KAAK,iCAAiC,GAAG;CAE7C;CAEA,cAAqB,MAAoB;EACvC,IAAI,CAAC,KAAK,kBAAkB;GAC1B,KAAK,MAAM,MACT,sFACF;GACA;EACF;EAEA,MAAM,oBAAoB,KAAK,cAAc,gBAAgB,IAAI;EACjE,IAAI,sBAAsB,WACxB;EAGF,KAAK,iBAAiB,SACpB,kBACA,EACE,SAAS,kBAAkB,KAC7B,GACA,KAAK,MAAM,aAAa,CAC1B;CACF;CAEA,YACE,aACA,OACA,SACM;EACN,MAAM,yBAAyB,KAAK,cAAc,yBAChD,aACA,KACF;EAEA,IAAI,2BAA2B,WAC7B;EAGF,MAAM,UAAU,uBAAuB;EACvC,MAAM,YAAY,uBAAuB;EAEzC,IAAI,SAAS,aAAa,aAAa;GACrC,MAAM,YAAY;IAChB,GAAG,KAAK;KACP,UAAU;GACb;GAIA,IAAI,CAACC,uDAAAA,yBAAyB,KAAK,UAAU,SAAS,GAAG;IACvD,KAAK,MAAM,KACT,0DACF;IACA;GACF;GACA,KAAK,uBAAuB;GAC5B,KAAK,iCAAiC,OAAO;GAC7C;EACF;EAIA,MAAM,EAAE,UAAU,KAAK,8BACrB,KAAK,MAAM,aAAa,CAC1B;EACA,KAAK,SAAS;GACZ,GAAG;GACH,uBAAuB;IACrB,GAAG,MAAM;KACR,UAAU;GACb;EACF;EACA,IAAI,CAAC,KAAK,YAAY,GAAG;GAGvB,KAAK,SAAS;GACd,KAAK,MAAM,KACT,6DACF;EACF;CACF;CAEA,eAAsB,aAA2B;EAC/C,MAAM,UAAU,KAAK,cAAc,eACjC,wBACA,WACF;EAIA,KAAK,+BAA+B,OAAO;EAC3C,KAAK,iCAAiC,OAAO;CAC/C;;;;;;;CAQA,2BAA0D;EACxD,OAAO;GACL,GAAG,KAAK;GACR,GAAI,KAAK,QAAQ,yBAAyB,CAAC;EAC7C;CACF;CAEA,2BAAkC,KAAmB;EACnD,IAAI,CAAC,KAAK,oBAAoB,CAAC,KAAK,0BAA0B;GAC5D,KAAK,MAAM,MACT,qHACF;GACA;EACF;EACA,KAAK,yBAAyB,QAC3B,KAAK,yBAAyB,QAAQ,KAAK;CAChD;CAEA,+BAAsC,KAAmB;EACvD,KAAK,uBAAuB,QACzB,KAAK,uBAAuB,QAAQ,KAAK;CAC9C;CAEA,8BACE,UACY;EACZ,OAAO,KAAK,aAAa,KAAK,8BAA8B,QAAQ;CACtE;CAEA,4BAAmC,UAAkC;EACnE,OAAO,KAAK,aAAa,KAAK,4BAA4B,QAAQ;CACpE;CAEA,eAAqC;EACnC,OAAO,KAAK,iBAAiB;CAC/B;CAEA,uBAAoC;EAClC,OAAO;CACT;CAEA,sBAA4C;EAC1C,OAAO,KAAK,wBAAwB;CACtC;CAEA,iBAA8B;EAC5B,KAAK,eAAe;CACtB;CAEA,mBAAgC,CAAC;CAEjC,iBAA6C;EAC3C,OAAO,KAAK,mBAAmB;CACjC;CAEA,+BAA4C;EAC1C,OAAO;CACT;CAEA,0BAAiC,WAAmC;EAClE,aAAa,CAAC;CAChB;CAEA,wBAA+B,WAAmC;EAChE,aAAa,CAAC;CAChB;;;;;;;CAQA,8BAAsC,KAGpC;EACA,IAAI,QAAQC,uDAAAA,qBAAqB,KAAK,UAAU,KAAK,KAAK;EAG1D,IAAI,CAAC,SAAS,CAAC,KAAK,mBAAmB,KAAK,QAC1C,QAAQ,KAAK;EAEf,IAAI,UAAU;EACd,IAAI,CAAC,SAASC,uDAAAA,qBAAqB,OAAO,GAAG,GAAG;GAC9C,IAAI,OACF,KAAK,yBAAyB,MAAM;GAEtC,MAAM,oBAAoBC,kCAAAA,oBACxB,KAAK,UACLC,qDAAAA,iCACA,KAAK,KACP;GAKA,IAAI,CAAC,KAAK,YACR,KAAU,sBAAsB,oBAAoB;GAEtD,MAAM,EACJ,+BACA,qCACA,kDACE,KAAK,6BAA6B;GACtC,QAAQC,uDAAAA,uBAAuB;IAC7B;IACA,uBAAuB,KAAK;IAC5B;IACA;IACA;IACA;GACF,CAAC;GACD,UAAU;EACZ;EAKA,IAAI,WAAW,KAAK,wBAAwB,MAC1C,KAAK,uBAAuB,OAAO,GAAG;EAExC,OAAO;GAAE;GAAO;EAAQ;CAC1B;;;;;;CAOA,gCAA8C;EAC5C,MAAM,MAAM,KAAK,MAAM,aAAa;EACpC,MAAM,EAAE,UAAU,KAAK,8BAA8B,GAAG;EAExD,KAAK,4BAA4BF,kCAAAA,oBAC/B,KAAK,UACLG,qDAAAA,iCACA,KAAK,KACP;EAEA,KAAK,SAAS;GACZ,GAAG;GACH,sBAAsB,MAAM,uBAAuB;GAGnD,sBAAsB;EACxB;EACA,KAAK,YAAY;CACnB;;;;;;CAOA,iCAAyC,KAAmB;EAC1D,IAAI,CAAC,KAAK,QAAQ;GAChB,KAAK,uBAAuB;GAC5B;EACF;EASA,IACE,KAAK,mBACL,KAAK,SAAS,QAAA,4BAAsC,MAAM,MAC1D;GACA,KAAK,yBAAyB,KAAK,OAAO;GAC1C,KAAK,SAAS;GACd,KAAK,uBAAuB;GAC5B;EACF;EAEA,KAAK,SAAS;GACZ,GAAG,KAAK;GACR,sBACE,MAAM,KAAK,OAAO,sCAAsC;EAC5D;EACA,KAAK,YAAY;CACnB;;;;;;;;;CAUA,qBACE,sBACM;EACN,IAAI,KAAK,kBAAkB;GAOzB,MAAM,oBAAoB,KAAK,MAAM,aAAa;GAClD,KAAK,uBAAuB;IAC1B,QAAQ;IACR;IACA,WAAW;GACb,CAAC;GACD,KAAK,yBAAyB;IAC5B,QAAQ;IACR,WAAW;GACb,CAAC;GACD;EACF;EAEA,KAAK,yBAAyB,KAAK,QAAQ,iBAAiB;EAC5D,KAAK,SAAS;EACd,KAAK,SAAS,WAAWP,qDAAAA,8BAA8B;EACvD,KAAK,uBAAuB;CAC9B;;;;;;;;;;CAYA,4BAAmC,EACjC,WACA,aACA,aACmC;EACnC,IAAI,CAAC,KAAK,kBACR;EAEF,MAAM,oBAAoB,aAAa,KAAK,MAAM,aAAa;EAC/D,KAAK,uBAAuB;GAC1B,QAAQ;GACR,WAAW;EACb,CAAC;EACD,KAAK,yBAAyB;GAC5B,QAAQ;GACR,WAAW;EACb,CAAC;CACH;CAEA,uBAA+B,OAAyB,KAAmB;EACzE,KAAK,uBAAuB;EAE5B,MAAM,YAAY,MAAM,sBAAsB;EAC9C,IAAI,aAAa,GACf;EAGF,KAAK,sBAAsB,iBAAiB;GAC1C,KAAK,sBAAsB;GAC3B,KAAK,qBAAqB,sBAAsB;EAClD,GAAG,SAAS;CACd;;;;CAKA,yBAAsC;EACpC,IAAI,KAAK,wBAAwB,MAAM;GACrC,aAAa,KAAK,mBAAmB;GACrC,KAAK,sBAAsB;EAC7B;CACF;;;;;;CAOA,YAAyB;EACvB,0DAAA,wBAAwB;GACtB,QAAQ,KAAK;GACb,eAAe,KAAK;GACpB,gBAAgB,KAAK;GACrB,YAAY,KAAK;GACjB,oBAAoB,KAAK;GACzB,SAAS,KAAK;GACd,QAAQ,KAAK;EACf,CAAC;EACD,KAAK,iCAAiC;EACtC,KAAK,uBAAuB;CAC9B;CAEA,oBAA2C;EACzC,IAAI;GACF,IAAI,CAACQ,0DAAAA,aAAa,KAAK,cAAc,GACnC;GAEF,IAAI,KAAK,yBAAyB,MAAM;IACtC,KAAK,yBAAyB,EAAE,QAAQ,eAAe,CAAC;IACxD;GACF;GACA,KAAK,iCAAiC;EACxC,SAAS,GAAG;GACV,KAAK,MAAM,KAAK,iCAAiC,CAAC;EACpD;CACF;CAIA,4BAAmD;EACjD,KAAK,4BAA4B,kBAAkB;CACrD;CAIA,iBAAwC;EACtC,KAAK,4BAA4B,OAAO;CAC1C;CASA,gBAAuC;EACrC,KAAK,4BAA4B,MAAM;CACzC;CAEA,4BAAoC,QAAsB;EACxD,IAAI;GACF,MAAM,UAAUA,0DAAAA,aAAa,KAAK,cAAc;GAChD,MAAM,SAAS,KAAK,yBAAyB;GAC7C,IAAI,CAAC,WAAW,QAAQ;IACtB,KAAK,MAAM,MAAM,sBAAsB,OAAO,sBAAsB;IACpE,KAAK,uBAAuB,EAAE,QAAQ,aAAa,CAAC;IACpD;GACF;GACA,IAAI,WAAW,CAAC,QAAQ;IACtB,KAAK,MAAM,MAAM,mBAAmB,OAAO,oBAAoB;IAC/D,KAAK,yBAAyB,EAAE,QAAQ,aAAa,CAAC;IACtD;GACF;GACA,IAAI,WAAW,QAIb,KAAK,iCAAiC;EAE1C,SAAS,GAAG;GACV,KAAK,MAAM,KAAK,oCAAoC,CAAC;EACvD;CACF;CAEA,iCAAwD;EACtD,KAAK,8BAA8B;EACnC,IAAI;GACF,IAAI,KAAK,yBAAyB,MAChC;GAEF,KAAK,MAAM,MACT,8DACF;GACA,KAAK,uBAAuB;IAC1B,QAAQ;IACR,sBAAsB;GACxB,CAAC;EACH,SAAS,GAAG;GACV,KAAK,MAAM,KAAK,mCAAmC,CAAC;EACtD;CACF;CAEA,mCAAiD;EAC/C,KAAK,iCAAiC;EAGtC,IAAI,CAAC,KAAK,QACR;EAEF,KAAK,8BAA8B,WACjC,KAAK,0BACL,KAAK,OAAO,gDAAgD,GAC9D;CACF;CAEA,mCAAiD;EAC/C,IAAI,KAAK,gCAAgC,MAAM;GAC7C,aAAa,KAAK,2BAA2B;GAC7C,KAAK,8BAA8B;EACrC;CACF;CAEA,aAAwB,MAAW,UAAyB;EAC1D,KAAK,KAAK,QAAQ;EAClB,aAAa;GACX,MAAM,IAAI,KAAK,QAAQ,QAAQ;GAC/B,IAAI,MAAM,IACR,KAAK,OAAO,GAAG,CAAC;EAEpB;CACF;CAEA,eACE,MACA,MACA,KACM;EACN,KAAK,MAAM,YAAY,MACrB,IAAI;GACF,SAAS,GAAG;EACd,SAAS,OAAO;GACd,KAAK,MAAM,KACT,sCAAsC,KAAK,YAC3C,KACF;EACF;CAEJ;CAEA,cAA+B;EAC7B,IAAI,CAAC,KAAK,QACR,OAAO;EAET,MAAM,SAAS,KAAK,SAAS,QAC3BR,qDAAAA,gCACA,KAAK,UAAU,KAAK,MAAM,CAC5B;EACA,IAAI,QACF,KAAK,kBAAkB;EAEzB,OAAO;CACT;CAEA,+BAAuC,SAAuB;EAC5D,IAAI,EAAE,WAAW,KAAK,uBACpB;EAEF,MAAM,SAAS,EAAE,GAAG,KAAK,qBAAqB;EAC9C,OAAO,OAAO;EACd,KAAK,uBAAuB;EAC5B,uDAAA,yBAAyB,KAAK,UAAU,MAAM;CAChD;CAEA,iCAAyC,SAAuB;EAC9D,IAAI,CAAC,KAAK,UAAU,EAAE,WAAW,KAAK,OAAO,wBAC3C;EAEF,MAAM,SAAS,EAAE,GAAG,KAAK,OAAO,sBAAsB;EACtD,OAAO,OAAO;EACd,KAAK,SAAS;GACZ,GAAG,KAAK;GACR,uBAAuB;EACzB;EACA,KAAK,YAAY;CACnB;AACF"}