{"version":3,"file":"SoftNavigationPerformanceInstrumentation.cjs","names":["EmbraceInstrumentationBase","isEntryTypeSupported","createPerformanceObserver","KEY_EMB_SOFT_NAVIGATION_SPAN_IDS","KEY_EMB_SOFT_NAVIGATION_SPAN_ID_TYPES","KEY_EMB_SOFT_NAVIGATION_LOG_IDS","KEY_EMB_SOFT_NAVIGATION_LOG_ID_TYPES","SOFT_NAVIGATION_SPAN_NAME","KEY_BROWSER_URL_FULL","KEY_EMB_SOFT_NAVIGATION_SOURCE","SOFT_NAVIGATION_SOURCES","KEY_EMB_SOFT_NAVIGATION_NAVIGATION_ID","KEY_EMB_SOFT_NAVIGATION_INTERACTION_ID","KEY_EMB_SOFT_NAVIGATION_START_TIME","KEY_EMB_SOFT_NAVIGATION_DURATION","KEY_EMB_SOFT_NAVIGATION_PAINT_TIME","KEY_EMB_SOFT_NAVIGATION_PRESENTATION_TIME"],"sources":["../../../../src/instrumentations/soft-navigation-performance/SoftNavigationPerformanceInstrumentation/SoftNavigationPerformanceInstrumentation.ts"],"sourcesContent":["/* eslint-disable baseline-js/use-baseline */\nimport type { NavigationHost } from '../../../common/index.ts';\nimport { KEY_BROWSER_URL_FULL } from '../../../constants/index.ts';\nimport type { SignalBuffer } from '../../../processors/utils/SignalBuffer.ts';\nimport {\n  createPerformanceObserver,\n  isEntryTypeSupported,\n} from '../../../utils/index.ts';\nimport { EmbraceInstrumentationBase } from '../../EmbraceInstrumentationBase/index.ts';\nimport {\n  KEY_EMB_SOFT_NAVIGATION_DURATION,\n  KEY_EMB_SOFT_NAVIGATION_INTERACTION_ID,\n  KEY_EMB_SOFT_NAVIGATION_LOG_ID_TYPES,\n  KEY_EMB_SOFT_NAVIGATION_LOG_IDS,\n  KEY_EMB_SOFT_NAVIGATION_NAVIGATION_ID,\n  KEY_EMB_SOFT_NAVIGATION_PAINT_TIME,\n  KEY_EMB_SOFT_NAVIGATION_PRESENTATION_TIME,\n  KEY_EMB_SOFT_NAVIGATION_SOURCE,\n  KEY_EMB_SOFT_NAVIGATION_SPAN_ID_TYPES,\n  KEY_EMB_SOFT_NAVIGATION_SPAN_IDS,\n  KEY_EMB_SOFT_NAVIGATION_START_TIME,\n  SOFT_NAVIGATION_SOURCES,\n  SOFT_NAVIGATION_SPAN_NAME,\n} from './constants.ts';\nimport type {\n  PerformanceSoftNavigationTiming,\n  SoftNavigationPerformanceInstrumentationArgs,\n} from './types.ts';\n\n/**\n * Returns the click entry whose processing window contains the given navigation\n * timestamp, or null if no match is found.\n *\n * For pushState navigations, currententrychange fires synchronously during the\n * click handler, so the timestamp falls within the click entry's\n * [startTime, startTime + duration] window.\n */\nexport function getNavigationEventTrigger(\n  navigationTimestamp: number,\n  entry: PerformanceEventTiming,\n): PerformanceEventTiming | null {\n  return entry.startTime <= navigationTimestamp &&\n    navigationTimestamp <= entry.startTime + entry.duration\n    ? entry\n    : null;\n}\n\nconst PENDING_NAVIGATION_TTL_MS = 60_000;\n\ntype PendingNavigation = { timestamp: number; url: string };\n\nexport class SoftNavigationPerformanceInstrumentation extends EmbraceInstrumentationBase {\n  private _observer: PerformanceObserver | null = null;\n  private _eventObserver: PerformanceObserver | null = null;\n  private _pendingNavigations: PendingNavigation[] = [];\n  private _usePolyfill = false;\n  private readonly _navigationHost: NavigationHost;\n  private readonly _signalBuffer: SignalBuffer | undefined;\n\n  private readonly _handleCurrentEntryChange = (event: Event): void => {\n    const url = this._navigationHost.navigation?.currentEntry?.url;\n    if (!url) {\n      this._diag.debug('currententrychange fired with no URL, skipping');\n      return;\n    }\n\n    this._pendingNavigations.push({ timestamp: event.timeStamp, url });\n  };\n\n  public constructor({\n    diag,\n    perf,\n    limitManager,\n    navigationHost = window as NavigationHost,\n    signalBuffer,\n  }: SoftNavigationPerformanceInstrumentationArgs = {}) {\n    super({\n      instrumentationName: 'SoftNavigationPerformanceInstrumentation',\n      instrumentationVersion: '1.0.0',\n      diag,\n      perf,\n      limitManager,\n      config: {},\n    });\n\n    this._navigationHost = navigationHost;\n    this._signalBuffer = signalBuffer;\n\n    if (this._config.enabled) {\n      this.enable();\n    }\n  }\n\n  public override enable(): void {\n    if (isEntryTypeSupported('soft-navigation')) {\n      this._usePolyfill = false;\n      super.enable();\n    } else if (\n      this._navigationHost.navigation &&\n      isEntryTypeSupported('event')\n    ) {\n      this._usePolyfill = true;\n      super.enable();\n    } else {\n      this._diag.debug(\n        'soft-navigation and navigation API not supported, skipping',\n      );\n    }\n  }\n\n  public override onEnable(): void {\n    if (this._usePolyfill) {\n      this._enablePolyfill();\n    } else {\n      this._enableNative();\n    }\n  }\n\n  private _enableNative(): void {\n    if (this._observer) {\n      this._observer.disconnect();\n    }\n\n    this._observer = createPerformanceObserver<PerformanceSoftNavigationTiming>(\n      'soft-navigation',\n      (entry) => this._processEntry(entry),\n      { diag: this._diag },\n    );\n\n    if (!this._observer) {\n      this._isEnabled = false;\n      this._diag.error('failed to enable');\n      return;\n    }\n  }\n\n  private _enablePolyfill(): void {\n    if (this._eventObserver) {\n      this._eventObserver.disconnect();\n    }\n\n    // biome-ignore lint/style/noNonNullAssertion: enable() is only called when navigationHost.navigation is defined\n    const nav = this._navigationHost.navigation!;\n\n    nav.addEventListener('currententrychange', this._handleCurrentEntryChange);\n\n    // A plain click listener only provides event.timeStamp (= startTime).\n    // To correlate a click with the navigation it triggered we need the full [startTime, startTime + duration] window.\n    // PerformanceEventTiming is the only API that exposes duration for event processing.\n    this._eventObserver = createPerformanceObserver<PerformanceEventTiming>(\n      'event',\n      (entry) => this._processClickEntry(entry),\n      // SPA routers commit navigations synchronously inside the click handler,\n      // so the entry duration is well under the 104ms default threshold\n      // https://developer.mozilla.org/en-US/docs/Web/API/PerformanceObserver/observe#durationthreshold\n      // 16ms (one frame) is the spec minimum and captures any real interaction.\n      { diag: this._diag, durationThreshold: 16 },\n    );\n\n    if (!this._eventObserver) {\n      nav.removeEventListener(\n        'currententrychange',\n        this._handleCurrentEntryChange,\n      );\n      this._isEnabled = false;\n      this._diag.error('failed to enable polyfill');\n      return;\n    }\n  }\n\n  public onDisable(): void {\n    if (this._observer) {\n      this._observer.disconnect();\n      this._observer = null;\n    }\n\n    if (this._eventObserver) {\n      this._eventObserver.disconnect();\n      this._eventObserver = null;\n    }\n\n    this._navigationHost.navigation?.removeEventListener(\n      'currententrychange',\n      this._handleCurrentEntryChange,\n    );\n    this._pendingNavigations.length = 0;\n  }\n\n  // A soft-navigation span is only ever built after its window has already\n  // closed, so this is the sole opportunity to back-fill it with the ids of\n  // the spans and logs that started within that window.\n  private _correlationAttributes(\n    startTimeEpochMillis: number,\n    endTimeEpochMillis: number,\n  ): Record<string, string[]> {\n    if (!this._signalBuffer) {\n      return {};\n    }\n\n    const { spanIds, spanTypes, logIds, logTypes } =\n      this._signalBuffer.collectWindow(\n        startTimeEpochMillis,\n        endTimeEpochMillis,\n      );\n\n    return {\n      [KEY_EMB_SOFT_NAVIGATION_SPAN_IDS]: spanIds,\n      [KEY_EMB_SOFT_NAVIGATION_SPAN_ID_TYPES]: spanTypes,\n      [KEY_EMB_SOFT_NAVIGATION_LOG_IDS]: logIds,\n      [KEY_EMB_SOFT_NAVIGATION_LOG_ID_TYPES]: logTypes,\n    };\n  }\n\n  private _processEntry(entry: PerformanceSoftNavigationTiming): void {\n    if (!this._isEnabled) {\n      return;\n    }\n\n    if (this.limitManager?.limitSoftNavigationEntry()) {\n      return;\n    }\n\n    const startTimeEpochMillis = this.perf.epochMillisFromOrigin(\n      entry.startTime,\n    );\n    const endTimeEpochMillis = this.perf.epochMillisFromOrigin(\n      entry.startTime + entry.duration,\n    );\n\n    const span = this.tracer.startSpan(SOFT_NAVIGATION_SPAN_NAME, {\n      startTime: startTimeEpochMillis,\n      attributes: {\n        [KEY_BROWSER_URL_FULL]: entry.name,\n        [KEY_EMB_SOFT_NAVIGATION_SOURCE]:\n          SOFT_NAVIGATION_SOURCES.performanceObserver,\n        [KEY_EMB_SOFT_NAVIGATION_NAVIGATION_ID]: entry.navigationId,\n        [KEY_EMB_SOFT_NAVIGATION_INTERACTION_ID]: entry.interactionId,\n        [KEY_EMB_SOFT_NAVIGATION_START_TIME]: this.perf.millisFromZeroTime(\n          entry.startTime,\n        ),\n        [KEY_EMB_SOFT_NAVIGATION_DURATION]: entry.duration,\n        [KEY_EMB_SOFT_NAVIGATION_PAINT_TIME]:\n          entry.paintTime != null\n            ? this.perf.millisFromZeroTime(entry.paintTime)\n            : undefined,\n        [KEY_EMB_SOFT_NAVIGATION_PRESENTATION_TIME]:\n          entry.presentationTime != null\n            ? this.perf.millisFromZeroTime(entry.presentationTime)\n            : undefined,\n        ...this._correlationAttributes(\n          startTimeEpochMillis,\n          endTimeEpochMillis,\n        ),\n      },\n    });\n    span.end(endTimeEpochMillis);\n  }\n\n  private _processClickEntry(entry: PerformanceEventTiming): void {\n    if (!this._isEnabled) {\n      return;\n    }\n\n    if (entry.name !== 'click') {\n      return;\n    }\n\n    // Remove any pending navigations that are older than the TTL. This prevents\n    // the pending navigation list from growing indefinitely.\n    this._pendingNavigations = this._pendingNavigations.filter(\n      ({ timestamp }) =>\n        entry.startTime - timestamp < PENDING_NAVIGATION_TTL_MS,\n    );\n\n    const index = this._pendingNavigations.findIndex(\n      ({ timestamp }) => getNavigationEventTrigger(timestamp, entry) !== null,\n    );\n\n    if (index === -1) {\n      return;\n    }\n\n    const [matched] = this._pendingNavigations.splice(index, 1);\n    this._emitPolyfillSpan(entry, matched.timestamp, matched.url);\n  }\n\n  private _emitPolyfillSpan(\n    clickEntry: PerformanceEventTiming,\n    navigationTimestamp: number,\n    url: string,\n  ): void {\n    if (!this._isEnabled) {\n      return;\n    }\n\n    if (this.limitManager?.limitSoftNavigationEntry()) {\n      return;\n    }\n\n    const startTimeEpochMillis = this.perf.epochMillisFromOrigin(\n      clickEntry.startTime,\n    );\n    const endTimeEpochMillis =\n      this.perf.epochMillisFromOrigin(navigationTimestamp);\n\n    const span = this.tracer.startSpan(SOFT_NAVIGATION_SPAN_NAME, {\n      startTime: startTimeEpochMillis,\n      attributes: {\n        [KEY_BROWSER_URL_FULL]: url,\n        [KEY_EMB_SOFT_NAVIGATION_SOURCE]: SOFT_NAVIGATION_SOURCES.polyfill,\n        [KEY_EMB_SOFT_NAVIGATION_START_TIME]: this.perf.millisFromZeroTime(\n          clickEntry.startTime,\n        ),\n        [KEY_EMB_SOFT_NAVIGATION_DURATION]:\n          navigationTimestamp - clickEntry.startTime,\n        [KEY_EMB_SOFT_NAVIGATION_INTERACTION_ID]:\n          clickEntry.interactionId !== 0 ? clickEntry.interactionId : undefined,\n        ...this._correlationAttributes(\n          startTimeEpochMillis,\n          endTimeEpochMillis,\n        ),\n      },\n    });\n    span.end(endTimeEpochMillis);\n  }\n}\n"],"mappings":"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"}