@opentelemetry/browser-instrumentation 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +93 -0
  2. package/dist/console/instrumentation.d.ts +1 -2
  3. package/dist/console/instrumentation.js.map +1 -1
  4. package/dist/console/types.d.ts +2 -3
  5. package/dist/errors/instrumentation.d.ts +1 -2
  6. package/dist/errors/instrumentation.js.map +1 -1
  7. package/dist/errors/types.d.ts +2 -3
  8. package/dist/fetch/index.d.ts +3 -0
  9. package/dist/fetch/index.js +2 -0
  10. package/dist/fetch/instrumentation.d.ts +35 -0
  11. package/dist/fetch/instrumentation.js +223 -0
  12. package/dist/fetch/instrumentation.js.map +1 -0
  13. package/dist/fetch/semconv.js +13 -0
  14. package/dist/fetch/semconv.js.map +1 -0
  15. package/dist/fetch/types.d.ts +51 -0
  16. package/dist/navigation/instrumentation.d.ts +1 -2
  17. package/dist/navigation/instrumentation.js.map +1 -1
  18. package/dist/navigation/types.d.ts +4 -5
  19. package/dist/navigation-timing/instrumentation.d.ts +1 -2
  20. package/dist/navigation-timing/instrumentation.js.map +1 -1
  21. package/dist/navigation-timing/types.d.ts +1 -2
  22. package/dist/package.js +1 -1
  23. package/dist/resource-timing/instrumentation.d.ts +1 -2
  24. package/dist/resource-timing/instrumentation.js +12 -11
  25. package/dist/resource-timing/instrumentation.js.map +1 -1
  26. package/dist/resource-timing/types.d.ts +1 -2
  27. package/dist/user-action/instrumentation.d.ts +1 -2
  28. package/dist/user-action/instrumentation.js.map +1 -1
  29. package/dist/user-action/types.d.ts +3 -4
  30. package/dist/utils/ContextRegistry.js +77 -0
  31. package/dist/utils/ContextRegistry.js.map +1 -0
  32. package/dist/utils/NetworkContextRegistry.js +23 -0
  33. package/dist/utils/NetworkContextRegistry.js.map +1 -0
  34. package/dist/utils/getElementCSSSelector.js.map +1 -1
  35. package/dist/utils/request.js +131 -0
  36. package/dist/utils/request.js.map +1 -0
  37. package/dist/utils/url.js +36 -0
  38. package/dist/utils/url.js.map +1 -0
  39. package/dist/web-vitals/instrumentation.d.ts +1 -2
  40. package/dist/web-vitals/instrumentation.js.map +1 -1
  41. package/dist/web-vitals/types.d.ts +1 -2
  42. package/dist/xhr/index.d.ts +3 -0
  43. package/dist/xhr/index.js +2 -0
  44. package/dist/xhr/instrumentation.d.ts +38 -0
  45. package/dist/xhr/instrumentation.js +187 -0
  46. package/dist/xhr/instrumentation.js.map +1 -0
  47. package/dist/xhr/semconv.js +13 -0
  48. package/dist/xhr/semconv.js.map +1 -0
  49. package/dist/xhr/types.d.ts +22 -0
  50. package/package.json +21 -8
@@ -1 +1 @@
1
- {"version":3,"file":"instrumentation.js","names":[],"sources":["../../src/navigation/instrumentation.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { LogRecord } from '@opentelemetry/api-logs';\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport {\n InstrumentationBase,\n safeExecuteInTheMiddle,\n} from '@opentelemetry/instrumentation';\nimport { version } from '../../package.json' with { type: 'json' };\nimport {\n ATTR_BROWSER_NAVIGATION_HASH_CHANGE,\n ATTR_BROWSER_NAVIGATION_SAME_DOCUMENT,\n ATTR_BROWSER_NAVIGATION_TYPE,\n ATTR_URL_FULL,\n BROWSER_NAVIGATION_EVENT_NAME,\n} from './semconv.ts';\nimport type {\n NavigationInstrumentationConfig,\n NavigationType,\n} from './types.ts';\nimport { isHashChange } from './utils.ts';\n\ntype ChangeState =\n | 'pushState'\n | 'replaceState'\n | 'popstate'\n | 'currententrychange';\n\ninterface NavigationApiEntry {\n url?: string;\n}\n\ninterface NavigationApiTarget {\n currentEntry?: NavigationApiEntry;\n}\n\ninterface NavigationApiEvent extends Event {\n navigationType?: NavigationType;\n target: NavigationApiTarget & EventTarget;\n}\n\ninterface NavigationApi {\n addEventListener(\n type: 'currententrychange',\n listener: (event: NavigationApiEvent) => void,\n ): void;\n removeEventListener(\n type: 'currententrychange',\n listener: (event: NavigationApiEvent) => void,\n ): void;\n}\n\nexport class NavigationInstrumentation extends InstrumentationBase<NavigationInstrumentationConfig> {\n // Use `declare` to prevent JS class field initializers from running after\n // super(), which would reset values set by the enable() call that\n // InstrumentationBase makes during its constructor.\n private declare _isEnabled: boolean;\n private declare _isHistoryPatched: boolean;\n private declare _hasProcessedInitialLoad: boolean;\n private declare _lastUrl: string;\n private declare _onDOMContentLoaded?: () => void;\n private declare _onPopState?: (event: PopStateEvent) => void;\n private declare _onCurrentEntryChange?: (event: NavigationApiEvent) => void;\n\n constructor(config: NavigationInstrumentationConfig = {}) {\n super('@opentelemetry/browser-instrumentation/navigation', version, config);\n this._lastUrl = location.href;\n }\n\n protected override init() {\n return [];\n }\n\n override enable(): void {\n if (this._isEnabled) {\n return;\n }\n this._isEnabled = true;\n\n const navigationApi = this._getNavigationApi();\n\n // Only patch history API if Navigation API is not being used.\n if (!navigationApi && !this._isHistoryPatched) {\n this._patchHistoryApi();\n this._isHistoryPatched = true;\n }\n\n this._waitForPageLoad();\n\n if (navigationApi) {\n this._onCurrentEntryChange = (event) => {\n this._onSoftNavigation('currententrychange', event);\n };\n navigationApi.addEventListener(\n 'currententrychange',\n this._onCurrentEntryChange,\n );\n } else {\n this._onPopState = () => {\n this._onSoftNavigation('popstate');\n };\n window.addEventListener('popstate', this._onPopState);\n }\n }\n\n override disable(): void {\n if (!this._isEnabled) {\n return;\n }\n this._isEnabled = false;\n\n if (this._onDOMContentLoaded) {\n document.removeEventListener(\n 'DOMContentLoaded',\n this._onDOMContentLoaded,\n );\n this._onDOMContentLoaded = undefined;\n }\n if (this._onPopState) {\n window.removeEventListener('popstate', this._onPopState);\n this._onPopState = undefined;\n }\n if (this._onCurrentEntryChange) {\n const navigationApi = this._getNavigationApi();\n navigationApi?.removeEventListener(\n 'currententrychange',\n this._onCurrentEntryChange,\n );\n this._onCurrentEntryChange = undefined;\n }\n // Reset the initial-load flag so it can be processed again if re-enabled.\n this._hasProcessedInitialLoad = false;\n }\n\n private _getNavigationApi(): NavigationApi | undefined {\n const cfg = this.getConfig();\n if (!cfg.useNavigationApiIfAvailable) {\n return undefined;\n }\n return (window as unknown as { navigation?: NavigationApi }).navigation;\n }\n\n private _onHardNavigation(): void {\n const cfg = this.getConfig();\n const logRecord: LogRecord = {\n eventName: BROWSER_NAVIGATION_EVENT_NAME,\n severityNumber: SeverityNumber.INFO,\n attributes: {\n [ATTR_URL_FULL]: cfg.sanitizeUrl\n ? cfg.sanitizeUrl(document.documentURI)\n : document.documentURI,\n [ATTR_BROWSER_NAVIGATION_SAME_DOCUMENT]: false,\n [ATTR_BROWSER_NAVIGATION_HASH_CHANGE]: false,\n },\n };\n this._applyCustomLogRecordData(logRecord);\n this.logger.emit(logRecord);\n }\n\n private _onSoftNavigation(\n changeState: ChangeState,\n navigationEvent?: NavigationApiEvent,\n ): void {\n const referrerUrl = this._lastUrl;\n const currentUrl =\n changeState === 'currententrychange' &&\n navigationEvent?.target?.currentEntry?.url\n ? navigationEvent.target.currentEntry.url\n : location.href;\n\n if (referrerUrl === currentUrl) {\n return;\n }\n\n const navType = this._mapChangeStateToType(changeState, navigationEvent);\n const sameDocument = this._determineSameDocument(referrerUrl, currentUrl);\n const hashChange = isHashChange(referrerUrl, currentUrl);\n const cfg = this.getConfig();\n\n const logRecord: LogRecord = {\n eventName: BROWSER_NAVIGATION_EVENT_NAME,\n severityNumber: SeverityNumber.INFO,\n attributes: {\n [ATTR_URL_FULL]: cfg.sanitizeUrl\n ? cfg.sanitizeUrl(currentUrl)\n : currentUrl,\n [ATTR_BROWSER_NAVIGATION_SAME_DOCUMENT]: sameDocument,\n [ATTR_BROWSER_NAVIGATION_HASH_CHANGE]: hashChange,\n ...(navType ? { [ATTR_BROWSER_NAVIGATION_TYPE]: navType } : {}),\n },\n };\n this._applyCustomLogRecordData(logRecord);\n this.logger.emit(logRecord);\n\n this._lastUrl = currentUrl;\n }\n\n private _waitForPageLoad(): void {\n if (document.readyState === 'complete' && !this._hasProcessedInitialLoad) {\n this._hasProcessedInitialLoad = true;\n this._onHardNavigation();\n return;\n }\n\n this._onDOMContentLoaded = () => {\n if (!this._hasProcessedInitialLoad) {\n this._hasProcessedInitialLoad = true;\n this._onHardNavigation();\n }\n };\n document.addEventListener('DOMContentLoaded', this._onDOMContentLoaded);\n }\n\n private _patchHistoryApi(): void {\n this._wrap(\n history,\n 'replaceState',\n this._patchHistoryMethod('replaceState'),\n );\n this._wrap(history, 'pushState', this._patchHistoryMethod('pushState'));\n }\n\n private _patchHistoryMethod(changeState: 'pushState' | 'replaceState') {\n const plugin = this;\n return (original: History['pushState' | 'replaceState']) => {\n return function patchedHistoryMethod(\n this: History,\n ...args: Parameters<History['pushState' | 'replaceState']>\n ) {\n if (!plugin._isEnabled) {\n return original.apply(this, args);\n }\n const result = original.apply(this, args);\n if (location.href !== plugin._lastUrl) {\n plugin._onSoftNavigation(changeState);\n }\n return result;\n };\n };\n }\n\n private _applyCustomLogRecordData(logRecord: LogRecord): void {\n const cfg = this.getConfig();\n const hook = cfg.applyCustomLogRecordData;\n if (!hook) {\n return;\n }\n safeExecuteInTheMiddle(\n () => hook(logRecord),\n (error) => {\n if (error) {\n this._diag.error('applyCustomLogRecordData hook failed', error);\n }\n },\n true,\n );\n }\n\n private _determineSameDocument(fromUrl: string, toUrl: string): boolean {\n try {\n const fromURL = new URL(fromUrl);\n const toURL = new URL(toUrl);\n return fromURL.origin === toURL.origin;\n } catch {\n // Fallback: assume same document for relative URLs or parsing errors.\n // In SPAs, route changes via pushState/replaceState are same-document.\n return true;\n }\n }\n\n private _mapChangeStateToType(\n changeState: ChangeState,\n navigationEvent?: NavigationApiEvent,\n ): NavigationType | undefined {\n if (changeState === 'currententrychange') {\n const navType = navigationEvent?.navigationType;\n switch (navType) {\n case 'traverse':\n return 'traverse';\n case 'replace':\n return 'replace';\n case 'reload':\n return 'reload';\n default:\n // Default to 'push' for programmatic navigations (history.pushState,\n // link clicks) when no explicit type info is available.\n return 'push';\n }\n }\n\n switch (changeState) {\n case 'pushState':\n return 'push';\n case 'replaceState':\n return 'replace';\n case 'popstate':\n return 'traverse';\n default:\n return undefined;\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"instrumentation.js","names":[],"sources":["../../src/navigation/instrumentation.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { LogRecord } from '@opentelemetry/api-logs';\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport {\n InstrumentationBase,\n safeExecuteInTheMiddle,\n} from '@opentelemetry/instrumentation';\nimport { version } from '../../package.json' with { type: 'json' };\nimport {\n ATTR_BROWSER_NAVIGATION_HASH_CHANGE,\n ATTR_BROWSER_NAVIGATION_SAME_DOCUMENT,\n ATTR_BROWSER_NAVIGATION_TYPE,\n ATTR_URL_FULL,\n BROWSER_NAVIGATION_EVENT_NAME,\n} from './semconv.ts';\nimport type {\n NavigationInstrumentationConfig,\n NavigationType,\n} from './types.ts';\nimport { isHashChange } from './utils.ts';\n\ntype ChangeState =\n | 'pushState'\n | 'replaceState'\n | 'popstate'\n | 'currententrychange';\n\ninterface NavigationApiEntry {\n url?: string;\n}\n\ninterface NavigationApiTarget {\n currentEntry?: NavigationApiEntry;\n}\n\ninterface NavigationApiEvent extends Event {\n navigationType?: NavigationType;\n target: NavigationApiTarget & EventTarget;\n}\n\ninterface NavigationApi {\n addEventListener(\n type: 'currententrychange',\n listener: (event: NavigationApiEvent) => void,\n ): void;\n removeEventListener(\n type: 'currententrychange',\n listener: (event: NavigationApiEvent) => void,\n ): void;\n}\n\nexport class NavigationInstrumentation extends InstrumentationBase<NavigationInstrumentationConfig> {\n // Use `declare` to prevent JS class field initializers from running after\n // super(), which would reset values set by the enable() call that\n // InstrumentationBase makes during its constructor.\n declare private _isEnabled: boolean;\n declare private _isHistoryPatched: boolean;\n declare private _hasProcessedInitialLoad: boolean;\n declare private _lastUrl: string;\n declare private _onDOMContentLoaded?: () => void;\n declare private _onPopState?: (event: PopStateEvent) => void;\n declare private _onCurrentEntryChange?: (event: NavigationApiEvent) => void;\n\n constructor(config: NavigationInstrumentationConfig = {}) {\n super('@opentelemetry/browser-instrumentation/navigation', version, config);\n this._lastUrl = location.href;\n }\n\n protected override init() {\n return [];\n }\n\n override enable(): void {\n if (this._isEnabled) {\n return;\n }\n this._isEnabled = true;\n\n const navigationApi = this._getNavigationApi();\n\n // Only patch history API if Navigation API is not being used.\n if (!navigationApi && !this._isHistoryPatched) {\n this._patchHistoryApi();\n this._isHistoryPatched = true;\n }\n\n this._waitForPageLoad();\n\n if (navigationApi) {\n this._onCurrentEntryChange = (event) => {\n this._onSoftNavigation('currententrychange', event);\n };\n navigationApi.addEventListener(\n 'currententrychange',\n this._onCurrentEntryChange,\n );\n } else {\n this._onPopState = () => {\n this._onSoftNavigation('popstate');\n };\n window.addEventListener('popstate', this._onPopState);\n }\n }\n\n override disable(): void {\n if (!this._isEnabled) {\n return;\n }\n this._isEnabled = false;\n\n if (this._onDOMContentLoaded) {\n document.removeEventListener(\n 'DOMContentLoaded',\n this._onDOMContentLoaded,\n );\n this._onDOMContentLoaded = undefined;\n }\n if (this._onPopState) {\n window.removeEventListener('popstate', this._onPopState);\n this._onPopState = undefined;\n }\n if (this._onCurrentEntryChange) {\n const navigationApi = this._getNavigationApi();\n navigationApi?.removeEventListener(\n 'currententrychange',\n this._onCurrentEntryChange,\n );\n this._onCurrentEntryChange = undefined;\n }\n // Reset the initial-load flag so it can be processed again if re-enabled.\n this._hasProcessedInitialLoad = false;\n }\n\n private _getNavigationApi(): NavigationApi | undefined {\n const cfg = this.getConfig();\n if (!cfg.useNavigationApiIfAvailable) {\n return undefined;\n }\n return (window as unknown as { navigation?: NavigationApi }).navigation;\n }\n\n private _onHardNavigation(): void {\n const cfg = this.getConfig();\n const logRecord: LogRecord = {\n eventName: BROWSER_NAVIGATION_EVENT_NAME,\n severityNumber: SeverityNumber.INFO,\n attributes: {\n [ATTR_URL_FULL]: cfg.sanitizeUrl\n ? cfg.sanitizeUrl(document.documentURI)\n : document.documentURI,\n [ATTR_BROWSER_NAVIGATION_SAME_DOCUMENT]: false,\n [ATTR_BROWSER_NAVIGATION_HASH_CHANGE]: false,\n },\n };\n this._applyCustomLogRecordData(logRecord);\n this.logger.emit(logRecord);\n }\n\n private _onSoftNavigation(\n changeState: ChangeState,\n navigationEvent?: NavigationApiEvent,\n ): void {\n const referrerUrl = this._lastUrl;\n const currentUrl =\n changeState === 'currententrychange' &&\n navigationEvent?.target?.currentEntry?.url\n ? navigationEvent.target.currentEntry.url\n : location.href;\n\n if (referrerUrl === currentUrl) {\n return;\n }\n\n const navType = this._mapChangeStateToType(changeState, navigationEvent);\n const sameDocument = this._determineSameDocument(referrerUrl, currentUrl);\n const hashChange = isHashChange(referrerUrl, currentUrl);\n const cfg = this.getConfig();\n\n const logRecord: LogRecord = {\n eventName: BROWSER_NAVIGATION_EVENT_NAME,\n severityNumber: SeverityNumber.INFO,\n attributes: {\n [ATTR_URL_FULL]: cfg.sanitizeUrl\n ? cfg.sanitizeUrl(currentUrl)\n : currentUrl,\n [ATTR_BROWSER_NAVIGATION_SAME_DOCUMENT]: sameDocument,\n [ATTR_BROWSER_NAVIGATION_HASH_CHANGE]: hashChange,\n ...(navType ? { [ATTR_BROWSER_NAVIGATION_TYPE]: navType } : {}),\n },\n };\n this._applyCustomLogRecordData(logRecord);\n this.logger.emit(logRecord);\n\n this._lastUrl = currentUrl;\n }\n\n private _waitForPageLoad(): void {\n if (document.readyState === 'complete' && !this._hasProcessedInitialLoad) {\n this._hasProcessedInitialLoad = true;\n this._onHardNavigation();\n return;\n }\n\n this._onDOMContentLoaded = () => {\n if (!this._hasProcessedInitialLoad) {\n this._hasProcessedInitialLoad = true;\n this._onHardNavigation();\n }\n };\n document.addEventListener('DOMContentLoaded', this._onDOMContentLoaded);\n }\n\n private _patchHistoryApi(): void {\n this._wrap(\n history,\n 'replaceState',\n this._patchHistoryMethod('replaceState'),\n );\n this._wrap(history, 'pushState', this._patchHistoryMethod('pushState'));\n }\n\n private _patchHistoryMethod(changeState: 'pushState' | 'replaceState') {\n const plugin = this;\n return (original: History['pushState' | 'replaceState']) => {\n return function patchedHistoryMethod(\n this: History,\n ...args: Parameters<History['pushState' | 'replaceState']>\n ) {\n if (!plugin._isEnabled) {\n return original.apply(this, args);\n }\n const result = original.apply(this, args);\n if (location.href !== plugin._lastUrl) {\n plugin._onSoftNavigation(changeState);\n }\n return result;\n };\n };\n }\n\n private _applyCustomLogRecordData(logRecord: LogRecord): void {\n const cfg = this.getConfig();\n const hook = cfg.applyCustomLogRecordData;\n if (!hook) {\n return;\n }\n safeExecuteInTheMiddle(\n () => hook(logRecord),\n (error) => {\n if (error) {\n this._diag.error('applyCustomLogRecordData hook failed', error);\n }\n },\n true,\n );\n }\n\n private _determineSameDocument(fromUrl: string, toUrl: string): boolean {\n try {\n const fromURL = new URL(fromUrl);\n const toURL = new URL(toUrl);\n return fromURL.origin === toURL.origin;\n } catch {\n // Fallback: assume same document for relative URLs or parsing errors.\n // In SPAs, route changes via pushState/replaceState are same-document.\n return true;\n }\n }\n\n private _mapChangeStateToType(\n changeState: ChangeState,\n navigationEvent?: NavigationApiEvent,\n ): NavigationType | undefined {\n if (changeState === 'currententrychange') {\n const navType = navigationEvent?.navigationType;\n switch (navType) {\n case 'traverse':\n return 'traverse';\n case 'replace':\n return 'replace';\n case 'reload':\n return 'reload';\n default:\n // Default to 'push' for programmatic navigations (history.pushState,\n // link clicks) when no explicit type info is available.\n return 'push';\n }\n }\n\n switch (changeState) {\n case 'pushState':\n return 'push';\n case 'replaceState':\n return 'replace';\n case 'popstate':\n return 'traverse';\n default:\n return undefined;\n }\n }\n}\n"],"mappings":"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"}
@@ -1,13 +1,13 @@
1
1
  import { LogRecord } from "@opentelemetry/api-logs";
2
2
  import { InstrumentationConfig } from "@opentelemetry/instrumentation";
3
3
  //#region src/navigation/types.d.ts
4
- type ApplyCustomLogRecordDataFunction = (logRecord: LogRecord) => void;
5
- type SanitizeUrlFunction = (url: string) => string;
6
- type NavigationType = 'push' | 'replace' | 'reload' | 'traverse';
4
+ export type ApplyCustomLogRecordDataFunction = (logRecord: LogRecord) => void;
5
+ export type SanitizeUrlFunction = (url: string) => string;
6
+ export type NavigationType = 'push' | 'replace' | 'reload' | 'traverse';
7
7
  /**
8
8
  * NavigationInstrumentation Configuration
9
9
  */
10
- interface NavigationInstrumentationConfig extends InstrumentationConfig {
10
+ export interface NavigationInstrumentationConfig extends InstrumentationConfig {
11
11
  /** Hook to modify log records before they are emitted. */
12
12
  applyCustomLogRecordData?: ApplyCustomLogRecordDataFunction;
13
13
  /** Use the Navigation API `currententrychange` event if available (experimental). Defaults to false. */
@@ -16,5 +16,4 @@ interface NavigationInstrumentationConfig extends InstrumentationConfig {
16
16
  sanitizeUrl?: SanitizeUrlFunction;
17
17
  }
18
18
  //#endregion
19
- export { ApplyCustomLogRecordDataFunction, NavigationInstrumentationConfig, NavigationType, SanitizeUrlFunction };
20
19
  //# sourceMappingURL=types.d.ts.map
@@ -4,7 +4,7 @@ import { InstrumentationBase } from "@opentelemetry/instrumentation";
4
4
  /**
5
5
  * This class automatically instruments navigation timing within the browser.
6
6
  */
7
- declare class NavigationTimingInstrumentation extends InstrumentationBase<NavigationTimingInstrumentationConfig> {
7
+ export declare class NavigationTimingInstrumentation extends InstrumentationBase<NavigationTimingInstrumentationConfig> {
8
8
  private _lastEntry?;
9
9
  private _completeDelayTimeoutId?;
10
10
  private _retryCount;
@@ -36,5 +36,4 @@ declare class NavigationTimingInstrumentation extends InstrumentationBase<Naviga
36
36
  private _unsubscribeAll;
37
37
  }
38
38
  //#endregion
39
- export { NavigationTimingInstrumentation };
40
39
  //# sourceMappingURL=instrumentation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"instrumentation.js","names":[],"sources":["../../src/navigation-timing/instrumentation.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport { InstrumentationBase } from '@opentelemetry/instrumentation';\nimport { version } from '../../package.json' with { type: 'json' };\nimport {\n ATTR_NAVIGATION_CONNECT_END,\n ATTR_NAVIGATION_CONNECT_START,\n ATTR_NAVIGATION_DECODED_BODY_SIZE,\n ATTR_NAVIGATION_DOM_COMPLETE,\n ATTR_NAVIGATION_DOM_CONTENT_LOADED_EVENT_END,\n ATTR_NAVIGATION_DOM_CONTENT_LOADED_EVENT_START,\n ATTR_NAVIGATION_DOM_INTERACTIVE,\n ATTR_NAVIGATION_DOMAIN_LOOKUP_END,\n ATTR_NAVIGATION_DOMAIN_LOOKUP_START,\n ATTR_NAVIGATION_DURATION,\n ATTR_NAVIGATION_ENCODED_BODY_SIZE,\n ATTR_NAVIGATION_FETCH_START,\n ATTR_NAVIGATION_LOAD_EVENT_END,\n ATTR_NAVIGATION_LOAD_EVENT_START,\n ATTR_NAVIGATION_REDIRECT_COUNT,\n ATTR_NAVIGATION_REQUEST_START,\n ATTR_NAVIGATION_RESPONSE_END,\n ATTR_NAVIGATION_RESPONSE_START,\n ATTR_NAVIGATION_SECURE_CONNECTION_START,\n ATTR_NAVIGATION_TRANSFER_SIZE,\n ATTR_NAVIGATION_TYPE,\n ATTR_NAVIGATION_UNLOAD_EVENT_END,\n ATTR_NAVIGATION_UNLOAD_EVENT_START,\n ATTR_NAVIGATION_URL,\n NAVIGATION_TIMING_EVENT_NAME,\n} from './semconv.ts';\nimport type { NavigationTimingInstrumentationConfig } from './types.ts';\n\nconst BASE_DELAY_MS = 50;\nconst MAX_RETRIES = 5;\n\n/**\n * This class automatically instruments navigation timing within the browser.\n */\nexport class NavigationTimingInstrumentation extends InstrumentationBase<NavigationTimingInstrumentationConfig> {\n private _lastEntry?: PerformanceNavigationTiming;\n private _completeDelayTimeoutId?: number;\n private _retryCount = 0;\n private _didEmit = false;\n\n // Use `declare` to prevent JS class field initializers from running after\n // super(), which would reset values set by the enable() call that\n // InstrumentationBase makes during its constructor.\n private declare _isEnabled: boolean;\n private declare _onLoad: () => void;\n private declare _onPageHide: () => void;\n\n constructor(config: NavigationTimingInstrumentationConfig = {}) {\n super(\n '@opentelemetry/browser-instrumentation/navigation-timing',\n version,\n config,\n );\n }\n\n protected override init() {\n return [];\n }\n\n override enable(): void {\n if (this._isEnabled) {\n return;\n }\n this._isEnabled = true;\n this._onLoad = () => this._tryEmitOrSchedule();\n this._onPageHide = () => this._handleUnload();\n\n // Try emitting immediately (e.g. when enabled after load),\n // otherwise schedule for `load` or fall back to unload.\n this._tryEmitOrSchedule();\n if (this._didEmit) {\n return;\n }\n\n window.addEventListener('pagehide', this._onPageHide);\n }\n\n override disable(): void {\n this._isEnabled = false;\n this._unsubscribeAll();\n this._lastEntry = undefined;\n this._didEmit = false;\n this._retryCount = 0;\n }\n\n private _getLatestNavigationEntry(): PerformanceNavigationTiming | undefined {\n const entries = performance?.getEntriesByType?.('navigation') as\n | PerformanceNavigationTiming[]\n | undefined;\n if (!entries || entries.length === 0) {\n return;\n }\n\n return entries[entries.length - 1];\n }\n\n private _calculateBackoffDelay(): number {\n return this._retryCount * BASE_DELAY_MS;\n }\n\n /**\n * Attempts to emit the navigation timing event.\n *\n * - Emits immediately if a complete `PerformanceNavigationTiming` entry is available.\n * - If the page is still loading, waits for `window.load` and retries.\n * - If the page is already loaded but the entry is not finalized yet, schedules one\n * deferred re-check (to allow the browser to populate the timing fields).\n *\n * This method can be called multiple times (from `enable()`, the load handler, or the\n * deferred timeout), so it must be safe to re-enter.\n */\n private _tryEmitOrSchedule(): void {\n if (this._didEmit) {\n return;\n }\n\n const entry = this._getLatestNavigationEntry();\n if (entry) {\n this._lastEntry = entry;\n }\n\n // Prefer emitting a \"complete\" navigation entry.\n if (entry && entry.loadEventEnd > 0) {\n this._emitAndCleanup(entry);\n return;\n }\n\n // If the document is still loading, wait for `load` and try again.\n if (document.readyState !== 'complete') {\n window.addEventListener('load', this._onLoad, { once: true });\n return;\n }\n\n // If the document is already complete but navigation timings are not finalized yet,\n // schedule a deferred re-check with linear backoff to allow the browser to finish\n // populating the entry.\n if (this._completeDelayTimeoutId !== undefined) {\n return;\n }\n if (this._retryCount > MAX_RETRIES) {\n if (this._lastEntry) {\n this._diag.warn(\n 'Navigation timing: retries exhausted, emitting incomplete entry',\n );\n this._emitAndCleanup(this._lastEntry);\n } else {\n this._diag.warn(\n 'Navigation timing: retries exhausted with no entry available',\n );\n this._unsubscribeAll();\n }\n return;\n }\n\n const delay = this._calculateBackoffDelay();\n this._retryCount++;\n\n this._completeDelayTimeoutId = window.setTimeout(() => {\n this._completeDelayTimeoutId = undefined;\n this._tryEmitOrSchedule();\n }, delay);\n }\n\n private _handleUnload(): void {\n if (this._didEmit) {\n return;\n }\n\n const entry = this._getLatestNavigationEntry() ?? this._lastEntry;\n if (entry) {\n this._emitAndCleanup(entry);\n } else {\n this._diag.warn('Navigation timing: no entry available at unload');\n this._unsubscribeAll();\n }\n }\n\n private _emitAndCleanup(entry: PerformanceNavigationTiming): void {\n if (this._didEmit) {\n return;\n }\n\n this._didEmit = true;\n\n this._emitNavigationTiming(entry);\n this._lastEntry = undefined;\n this._unsubscribeAll();\n }\n\n private _emitNavigationTiming(entry: PerformanceNavigationTiming) {\n if (!entry) {\n return;\n }\n\n this.logger.emit({\n eventName: NAVIGATION_TIMING_EVENT_NAME,\n severityNumber: SeverityNumber.INFO,\n attributes: {\n [ATTR_NAVIGATION_TYPE]: entry.type,\n [ATTR_NAVIGATION_URL]: entry.name,\n [ATTR_NAVIGATION_DURATION]: entry.duration,\n [ATTR_NAVIGATION_DOM_COMPLETE]: entry.domComplete,\n [ATTR_NAVIGATION_DOM_CONTENT_LOADED_EVENT_END]:\n entry.domContentLoadedEventEnd,\n [ATTR_NAVIGATION_DOM_CONTENT_LOADED_EVENT_START]:\n entry.domContentLoadedEventStart,\n [ATTR_NAVIGATION_DOM_INTERACTIVE]: entry.domInteractive,\n [ATTR_NAVIGATION_LOAD_EVENT_END]: entry.loadEventEnd,\n [ATTR_NAVIGATION_LOAD_EVENT_START]: entry.loadEventStart,\n [ATTR_NAVIGATION_REDIRECT_COUNT]: entry.redirectCount,\n [ATTR_NAVIGATION_UNLOAD_EVENT_END]: entry.unloadEventEnd,\n [ATTR_NAVIGATION_UNLOAD_EVENT_START]: entry.unloadEventStart,\n [ATTR_NAVIGATION_FETCH_START]: entry.fetchStart,\n [ATTR_NAVIGATION_DOMAIN_LOOKUP_START]: entry.domainLookupStart,\n [ATTR_NAVIGATION_DOMAIN_LOOKUP_END]: entry.domainLookupEnd,\n [ATTR_NAVIGATION_CONNECT_START]: entry.connectStart,\n [ATTR_NAVIGATION_CONNECT_END]: entry.connectEnd,\n [ATTR_NAVIGATION_SECURE_CONNECTION_START]: entry.secureConnectionStart,\n [ATTR_NAVIGATION_REQUEST_START]: entry.requestStart,\n [ATTR_NAVIGATION_RESPONSE_START]: entry.responseStart,\n [ATTR_NAVIGATION_RESPONSE_END]: entry.responseEnd,\n [ATTR_NAVIGATION_TRANSFER_SIZE]: entry.transferSize,\n [ATTR_NAVIGATION_ENCODED_BODY_SIZE]: entry.encodedBodySize,\n [ATTR_NAVIGATION_DECODED_BODY_SIZE]: entry.decodedBodySize,\n },\n });\n }\n\n private _unsubscribeAll(): void {\n if (this._completeDelayTimeoutId) {\n clearTimeout(this._completeDelayTimeoutId);\n this._completeDelayTimeoutId = undefined;\n }\n\n if (this._onLoad) {\n window.removeEventListener('load', this._onLoad);\n }\n if (this._onPageHide) {\n window.removeEventListener('pagehide', this._onPageHide);\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"instrumentation.js","names":[],"sources":["../../src/navigation-timing/instrumentation.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport { InstrumentationBase } from '@opentelemetry/instrumentation';\nimport { version } from '../../package.json' with { type: 'json' };\nimport {\n ATTR_NAVIGATION_CONNECT_END,\n ATTR_NAVIGATION_CONNECT_START,\n ATTR_NAVIGATION_DECODED_BODY_SIZE,\n ATTR_NAVIGATION_DOM_COMPLETE,\n ATTR_NAVIGATION_DOM_CONTENT_LOADED_EVENT_END,\n ATTR_NAVIGATION_DOM_CONTENT_LOADED_EVENT_START,\n ATTR_NAVIGATION_DOM_INTERACTIVE,\n ATTR_NAVIGATION_DOMAIN_LOOKUP_END,\n ATTR_NAVIGATION_DOMAIN_LOOKUP_START,\n ATTR_NAVIGATION_DURATION,\n ATTR_NAVIGATION_ENCODED_BODY_SIZE,\n ATTR_NAVIGATION_FETCH_START,\n ATTR_NAVIGATION_LOAD_EVENT_END,\n ATTR_NAVIGATION_LOAD_EVENT_START,\n ATTR_NAVIGATION_REDIRECT_COUNT,\n ATTR_NAVIGATION_REQUEST_START,\n ATTR_NAVIGATION_RESPONSE_END,\n ATTR_NAVIGATION_RESPONSE_START,\n ATTR_NAVIGATION_SECURE_CONNECTION_START,\n ATTR_NAVIGATION_TRANSFER_SIZE,\n ATTR_NAVIGATION_TYPE,\n ATTR_NAVIGATION_UNLOAD_EVENT_END,\n ATTR_NAVIGATION_UNLOAD_EVENT_START,\n ATTR_NAVIGATION_URL,\n NAVIGATION_TIMING_EVENT_NAME,\n} from './semconv.ts';\nimport type { NavigationTimingInstrumentationConfig } from './types.ts';\n\nconst BASE_DELAY_MS = 50;\nconst MAX_RETRIES = 5;\n\n/**\n * This class automatically instruments navigation timing within the browser.\n */\nexport class NavigationTimingInstrumentation extends InstrumentationBase<NavigationTimingInstrumentationConfig> {\n private _lastEntry?: PerformanceNavigationTiming;\n private _completeDelayTimeoutId?: number;\n private _retryCount = 0;\n private _didEmit = false;\n\n // Use `declare` to prevent JS class field initializers from running after\n // super(), which would reset values set by the enable() call that\n // InstrumentationBase makes during its constructor.\n declare private _isEnabled: boolean;\n declare private _onLoad: () => void;\n declare private _onPageHide: () => void;\n\n constructor(config: NavigationTimingInstrumentationConfig = {}) {\n super(\n '@opentelemetry/browser-instrumentation/navigation-timing',\n version,\n config,\n );\n }\n\n protected override init() {\n return [];\n }\n\n override enable(): void {\n if (this._isEnabled) {\n return;\n }\n this._isEnabled = true;\n this._onLoad = () => this._tryEmitOrSchedule();\n this._onPageHide = () => this._handleUnload();\n\n // Try emitting immediately (e.g. when enabled after load),\n // otherwise schedule for `load` or fall back to unload.\n this._tryEmitOrSchedule();\n if (this._didEmit) {\n return;\n }\n\n window.addEventListener('pagehide', this._onPageHide);\n }\n\n override disable(): void {\n this._isEnabled = false;\n this._unsubscribeAll();\n this._lastEntry = undefined;\n this._didEmit = false;\n this._retryCount = 0;\n }\n\n private _getLatestNavigationEntry(): PerformanceNavigationTiming | undefined {\n const entries = performance?.getEntriesByType?.('navigation') as\n | PerformanceNavigationTiming[]\n | undefined;\n if (!entries || entries.length === 0) {\n return;\n }\n\n return entries[entries.length - 1];\n }\n\n private _calculateBackoffDelay(): number {\n return this._retryCount * BASE_DELAY_MS;\n }\n\n /**\n * Attempts to emit the navigation timing event.\n *\n * - Emits immediately if a complete `PerformanceNavigationTiming` entry is available.\n * - If the page is still loading, waits for `window.load` and retries.\n * - If the page is already loaded but the entry is not finalized yet, schedules one\n * deferred re-check (to allow the browser to populate the timing fields).\n *\n * This method can be called multiple times (from `enable()`, the load handler, or the\n * deferred timeout), so it must be safe to re-enter.\n */\n private _tryEmitOrSchedule(): void {\n if (this._didEmit) {\n return;\n }\n\n const entry = this._getLatestNavigationEntry();\n if (entry) {\n this._lastEntry = entry;\n }\n\n // Prefer emitting a \"complete\" navigation entry.\n if (entry && entry.loadEventEnd > 0) {\n this._emitAndCleanup(entry);\n return;\n }\n\n // If the document is still loading, wait for `load` and try again.\n if (document.readyState !== 'complete') {\n window.addEventListener('load', this._onLoad, { once: true });\n return;\n }\n\n // If the document is already complete but navigation timings are not finalized yet,\n // schedule a deferred re-check with linear backoff to allow the browser to finish\n // populating the entry.\n if (this._completeDelayTimeoutId !== undefined) {\n return;\n }\n if (this._retryCount > MAX_RETRIES) {\n if (this._lastEntry) {\n this._diag.warn(\n 'Navigation timing: retries exhausted, emitting incomplete entry',\n );\n this._emitAndCleanup(this._lastEntry);\n } else {\n this._diag.warn(\n 'Navigation timing: retries exhausted with no entry available',\n );\n this._unsubscribeAll();\n }\n return;\n }\n\n const delay = this._calculateBackoffDelay();\n this._retryCount++;\n\n this._completeDelayTimeoutId = window.setTimeout(() => {\n this._completeDelayTimeoutId = undefined;\n this._tryEmitOrSchedule();\n }, delay);\n }\n\n private _handleUnload(): void {\n if (this._didEmit) {\n return;\n }\n\n const entry = this._getLatestNavigationEntry() ?? this._lastEntry;\n if (entry) {\n this._emitAndCleanup(entry);\n } else {\n this._diag.warn('Navigation timing: no entry available at unload');\n this._unsubscribeAll();\n }\n }\n\n private _emitAndCleanup(entry: PerformanceNavigationTiming): void {\n if (this._didEmit) {\n return;\n }\n\n this._didEmit = true;\n\n this._emitNavigationTiming(entry);\n this._lastEntry = undefined;\n this._unsubscribeAll();\n }\n\n private _emitNavigationTiming(entry: PerformanceNavigationTiming) {\n if (!entry) {\n return;\n }\n\n this.logger.emit({\n eventName: NAVIGATION_TIMING_EVENT_NAME,\n severityNumber: SeverityNumber.INFO,\n attributes: {\n [ATTR_NAVIGATION_TYPE]: entry.type,\n [ATTR_NAVIGATION_URL]: entry.name,\n [ATTR_NAVIGATION_DURATION]: entry.duration,\n [ATTR_NAVIGATION_DOM_COMPLETE]: entry.domComplete,\n [ATTR_NAVIGATION_DOM_CONTENT_LOADED_EVENT_END]:\n entry.domContentLoadedEventEnd,\n [ATTR_NAVIGATION_DOM_CONTENT_LOADED_EVENT_START]:\n entry.domContentLoadedEventStart,\n [ATTR_NAVIGATION_DOM_INTERACTIVE]: entry.domInteractive,\n [ATTR_NAVIGATION_LOAD_EVENT_END]: entry.loadEventEnd,\n [ATTR_NAVIGATION_LOAD_EVENT_START]: entry.loadEventStart,\n [ATTR_NAVIGATION_REDIRECT_COUNT]: entry.redirectCount,\n [ATTR_NAVIGATION_UNLOAD_EVENT_END]: entry.unloadEventEnd,\n [ATTR_NAVIGATION_UNLOAD_EVENT_START]: entry.unloadEventStart,\n [ATTR_NAVIGATION_FETCH_START]: entry.fetchStart,\n [ATTR_NAVIGATION_DOMAIN_LOOKUP_START]: entry.domainLookupStart,\n [ATTR_NAVIGATION_DOMAIN_LOOKUP_END]: entry.domainLookupEnd,\n [ATTR_NAVIGATION_CONNECT_START]: entry.connectStart,\n [ATTR_NAVIGATION_CONNECT_END]: entry.connectEnd,\n [ATTR_NAVIGATION_SECURE_CONNECTION_START]: entry.secureConnectionStart,\n [ATTR_NAVIGATION_REQUEST_START]: entry.requestStart,\n [ATTR_NAVIGATION_RESPONSE_START]: entry.responseStart,\n [ATTR_NAVIGATION_RESPONSE_END]: entry.responseEnd,\n [ATTR_NAVIGATION_TRANSFER_SIZE]: entry.transferSize,\n [ATTR_NAVIGATION_ENCODED_BODY_SIZE]: entry.encodedBodySize,\n [ATTR_NAVIGATION_DECODED_BODY_SIZE]: entry.decodedBodySize,\n },\n });\n }\n\n private _unsubscribeAll(): void {\n if (this._completeDelayTimeoutId) {\n clearTimeout(this._completeDelayTimeoutId);\n this._completeDelayTimeoutId = undefined;\n }\n\n if (this._onLoad) {\n window.removeEventListener('load', this._onLoad);\n }\n if (this._onPageHide) {\n window.removeEventListener('pagehide', this._onPageHide);\n }\n }\n}\n"],"mappings":"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"}
@@ -3,7 +3,6 @@ import { InstrumentationConfig } from "@opentelemetry/instrumentation";
3
3
  /**
4
4
  * NavigationTimingInstrumentation Configuration
5
5
  */
6
- interface NavigationTimingInstrumentationConfig extends InstrumentationConfig {}
6
+ export interface NavigationTimingInstrumentationConfig extends InstrumentationConfig {}
7
7
  //#endregion
8
- export { NavigationTimingInstrumentationConfig };
9
8
  //# sourceMappingURL=types.d.ts.map
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.7.0";
2
+ var version = "0.8.1";
3
3
  //#endregion
4
4
  export { version };
5
5
 
@@ -9,7 +9,7 @@ import { InstrumentationBase } from "@opentelemetry/instrumentation";
9
9
  * requestIdleCallback when available (with fallback for Safari) to ensure
10
10
  * processing happens during idle periods.
11
11
  */
12
- declare class ResourceTimingInstrumentation extends InstrumentationBase<ResourceTimingInstrumentationConfig> {
12
+ export declare class ResourceTimingInstrumentation extends InstrumentationBase<ResourceTimingInstrumentationConfig> {
13
13
  private _observer?;
14
14
  private _pendingEntries;
15
15
  private _idleHandle?;
@@ -28,5 +28,4 @@ declare class ResourceTimingInstrumentation extends InstrumentationBase<Resource
28
28
  private _cancelScheduledProcessing;
29
29
  }
30
30
  //#endregion
31
- export { ResourceTimingInstrumentation };
32
31
  //# sourceMappingURL=instrumentation.d.ts.map
@@ -1,9 +1,10 @@
1
1
  import { version } from "../package.js";
2
+ import { getNetworkContextRegistry } from "../utils/NetworkContextRegistry.js";
3
+ import { matchesUrl } from "../utils/url.js";
2
4
  import { cancelIdleCallbackShim, requestIdleCallbackShim } from "./idle-callback-shim.js";
3
5
  import { ATTR_RESOURCE_CONNECT_END, ATTR_RESOURCE_CONNECT_START, ATTR_RESOURCE_DECODED_BODY_SIZE, ATTR_RESOURCE_DOMAIN_LOOKUP_END, ATTR_RESOURCE_DOMAIN_LOOKUP_START, ATTR_RESOURCE_DURATION, ATTR_RESOURCE_ENCODED_BODY_SIZE, ATTR_RESOURCE_FETCH_START, ATTR_RESOURCE_INITIATOR_TYPE, ATTR_RESOURCE_NEXT_HOP_PROTOCOL, ATTR_RESOURCE_REDIRECT_END, ATTR_RESOURCE_REDIRECT_START, ATTR_RESOURCE_RENDER_BLOCKING_STATUS, ATTR_RESOURCE_REQUEST_START, ATTR_RESOURCE_RESPONSE_END, ATTR_RESOURCE_RESPONSE_START, ATTR_RESOURCE_SECURE_CONNECTION_START, ATTR_RESOURCE_TRANSFER_SIZE, ATTR_RESOURCE_URL, ATTR_RESOURCE_WORKER_START, RESOURCE_TIMING_EVENT_NAME } from "./semconv.js";
4
6
  import { SeverityNumber } from "@opentelemetry/api-logs";
5
7
  import { InstrumentationBase } from "@opentelemetry/instrumentation";
6
- import { isUrlIgnored } from "@opentelemetry/core";
7
8
  //#region src/resource-timing/instrumentation.ts
8
9
  const DEFAULT_BATCH_SIZE = 50;
9
10
  const DEFAULT_FORCE_PROCESSING_AFTER = 1e3;
@@ -71,14 +72,7 @@ var ResourceTimingInstrumentation = class extends InstrumentationBase {
71
72
  const initiatorTypes = this._config.initiatorTypes;
72
73
  for (const entry of entries) {
73
74
  if (initiatorTypes !== void 0 && !initiatorTypes.includes(entry.initiatorType)) continue;
74
- let ignored;
75
- try {
76
- ignored = isUrlIgnored(entry.name, this._config.ignoreUrls);
77
- } catch (e) {
78
- this._diag.error("Failed to check ignoreUrls for resource entry", e);
79
- continue;
80
- }
81
- if (ignored) continue;
75
+ if (matchesUrl(entry.name, this._config.ignoreUrls)) continue;
82
76
  if (this._pendingEntries.length >= maxQueueSize) this._flush();
83
77
  this._pendingEntries.push(entry);
84
78
  }
@@ -122,7 +116,7 @@ var ResourceTimingInstrumentation = class extends InstrumentationBase {
122
116
  }
123
117
  _emitResource(entry) {
124
118
  try {
125
- this.logger.emit({
119
+ const record = {
126
120
  eventName: RESOURCE_TIMING_EVENT_NAME,
127
121
  severityNumber: SeverityNumber.INFO,
128
122
  attributes: {
@@ -147,7 +141,14 @@ var ResourceTimingInstrumentation = class extends InstrumentationBase {
147
141
  [ATTR_RESOURCE_NEXT_HOP_PROTOCOL]: entry.nextHopProtocol,
148
142
  [ATTR_RESOURCE_RENDER_BLOCKING_STATUS]: entry.renderBlockingStatus
149
143
  }
150
- });
144
+ };
145
+ const registry = getNetworkContextRegistry();
146
+ const ctx = registry.getContext(entry);
147
+ if (ctx) {
148
+ record.context = ctx;
149
+ registry.unregister(entry);
150
+ }
151
+ this.logger.emit(record);
151
152
  } catch (error) {
152
153
  this._diag.error(`Failed to emit resource timing entry for "${entry.name}"`, error);
153
154
  }
@@ -1 +1 @@
1
- {"version":3,"file":"instrumentation.js","names":[],"sources":["../../src/resource-timing/instrumentation.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport { isUrlIgnored } from '@opentelemetry/core';\nimport { InstrumentationBase } from '@opentelemetry/instrumentation';\nimport { version } from '../../package.json' with { type: 'json' };\nimport type { IdleCallbackHandle } from './idle-callback-shim.ts';\nimport {\n cancelIdleCallbackShim,\n requestIdleCallbackShim,\n} from './idle-callback-shim.ts';\nimport {\n ATTR_RESOURCE_CONNECT_END,\n ATTR_RESOURCE_CONNECT_START,\n ATTR_RESOURCE_DECODED_BODY_SIZE,\n ATTR_RESOURCE_DOMAIN_LOOKUP_END,\n ATTR_RESOURCE_DOMAIN_LOOKUP_START,\n ATTR_RESOURCE_DURATION,\n ATTR_RESOURCE_ENCODED_BODY_SIZE,\n ATTR_RESOURCE_FETCH_START,\n ATTR_RESOURCE_INITIATOR_TYPE,\n ATTR_RESOURCE_NEXT_HOP_PROTOCOL,\n ATTR_RESOURCE_REDIRECT_END,\n ATTR_RESOURCE_REDIRECT_START,\n ATTR_RESOURCE_RENDER_BLOCKING_STATUS,\n ATTR_RESOURCE_REQUEST_START,\n ATTR_RESOURCE_RESPONSE_END,\n ATTR_RESOURCE_RESPONSE_START,\n ATTR_RESOURCE_SECURE_CONNECTION_START,\n ATTR_RESOURCE_TRANSFER_SIZE,\n ATTR_RESOURCE_URL,\n ATTR_RESOURCE_WORKER_START,\n RESOURCE_TIMING_EVENT_NAME,\n} from './semconv.ts';\nimport type { ResourceTimingInstrumentationConfig } from './types.ts';\n\nconst DEFAULT_BATCH_SIZE = 50;\nconst DEFAULT_FORCE_PROCESSING_AFTER = 1000;\nconst DEFAULT_MAX_PROCESSING_TIME = 50;\nconst DEFAULT_MAX_QUEUE_SIZE = 1000;\n\nconst MIN_BATCH_SIZE = 1;\nconst MIN_FORCE_PROCESSING_AFTER = 0;\nconst MIN_PROCESSING_TIME = 0;\nconst MIN_QUEUE_SIZE = 1;\n\n/**\n * OpenTelemetry instrumentation for resource timing for browser applications.\n *\n * This instrumentation captures resource timing data using PerformanceObserver\n * and batches emissions to avoid overwhelming the main thread. It uses\n * requestIdleCallback when available (with fallback for Safari) to ensure\n * processing happens during idle periods.\n */\nexport class ResourceTimingInstrumentation extends InstrumentationBase<ResourceTimingInstrumentationConfig> {\n private _observer?: PerformanceObserver;\n private _pendingEntries: PerformanceResourceTiming[] = [];\n private _idleHandle?: IdleCallbackHandle;\n\n // Use `declare` to prevent JS class field initializers from running after\n // super(), which would reset values set by the enable() call that\n // InstrumentationBase makes during its constructor.\n private declare _isEnabled: boolean;\n private declare _loadHandler: (() => void) | undefined;\n private declare _visibilityChangeHandler: (() => void) | undefined;\n\n constructor(config: ResourceTimingInstrumentationConfig = {}) {\n super(\n '@opentelemetry/browser-instrumentation/resource-timing',\n version,\n config,\n );\n }\n\n protected override init() {\n return [];\n }\n\n override enable(): void {\n if (this._isEnabled) {\n return;\n }\n\n if (!('PerformanceObserver' in window)) {\n this._diag.debug(\n 'PerformanceObserver is not supported, resource timings will not be collected',\n );\n return;\n }\n\n this._isEnabled = true;\n\n if (document.readyState === 'complete') {\n this._setupObserver();\n } else {\n this._loadHandler = () => this._setupObserver();\n window.addEventListener('load', this._loadHandler, { once: true });\n }\n\n this._visibilityChangeHandler = () => {\n if (document.hidden) {\n this._flush();\n }\n };\n document.addEventListener(\n 'visibilitychange',\n this._visibilityChangeHandler,\n );\n }\n\n override disable(): void {\n this._isEnabled = false;\n this._flush();\n this._observer?.disconnect();\n this._observer = undefined;\n if (this._loadHandler) {\n window.removeEventListener('load', this._loadHandler);\n this._loadHandler = undefined;\n }\n if (this._visibilityChangeHandler) {\n document.removeEventListener(\n 'visibilitychange',\n this._visibilityChangeHandler,\n );\n this._visibilityChangeHandler = undefined;\n }\n }\n\n private _setupObserver(): void {\n if (!this._isEnabled) {\n return;\n }\n\n const observer = new PerformanceObserver((list) => {\n if (!this._isEnabled) {\n return;\n }\n\n const maxQueueSize = Math.max(\n this._config.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE,\n MIN_QUEUE_SIZE,\n );\n const entries = list.getEntries() as PerformanceResourceTiming[];\n const initiatorTypes = this._config.initiatorTypes;\n\n for (const entry of entries) {\n if (\n initiatorTypes !== undefined &&\n !initiatorTypes.includes(entry.initiatorType)\n ) {\n continue;\n }\n\n let ignored: boolean;\n try {\n ignored = isUrlIgnored(entry.name, this._config.ignoreUrls);\n } catch (e) {\n this._diag.error('Failed to check ignoreUrls for resource entry', e);\n continue;\n }\n if (ignored) {\n continue;\n }\n\n if (this._pendingEntries.length >= maxQueueSize) {\n this._flush();\n }\n this._pendingEntries.push(entry);\n }\n\n if (this._pendingEntries.length > 0) {\n this._scheduleProcessing();\n }\n });\n\n this._observer = observer;\n\n try {\n observer.observe({ type: 'resource', buffered: true });\n } catch (e) {\n this._diag.error('Failed to start resource PerformanceObserver', e);\n this._observer = undefined;\n }\n }\n\n private _scheduleProcessing(): void {\n if (this._idleHandle !== undefined) {\n return;\n }\n\n const timeout = Math.max(\n this._config.forceProcessingAfter ?? DEFAULT_FORCE_PROCESSING_AFTER,\n MIN_FORCE_PROCESSING_AFTER,\n );\n this._idleHandle = requestIdleCallbackShim(\n (deadline) => this._processChunk(deadline),\n { timeout },\n );\n }\n\n private _processChunk(deadline: IdleDeadline): void {\n this._idleHandle = undefined;\n if (!this._isEnabled || this._pendingEntries.length === 0) {\n return;\n }\n\n const maxTime = Math.max(\n this._config.maxProcessingTime ?? DEFAULT_MAX_PROCESSING_TIME,\n MIN_PROCESSING_TIME,\n );\n const batchSize = Math.max(\n this._config.batchSize ?? DEFAULT_BATCH_SIZE,\n MIN_BATCH_SIZE,\n );\n const startTime = performance.now();\n\n let cursor = 0;\n try {\n for (let i = 0; i < batchSize; i++) {\n const entry = this._pendingEntries[cursor];\n if (entry === undefined) {\n break;\n }\n\n const elapsed = performance.now() - startTime;\n // timeRemaining() is not Baseline widely available (no Safari support),\n // compatibility is handled by idle-callback-shim.ts.\n // eslint-disable-next-line baseline-js/use-baseline\n if (elapsed >= maxTime || deadline.timeRemaining() < 1) {\n break;\n }\n\n cursor++;\n this._emitResource(entry);\n }\n } finally {\n this._pendingEntries.splice(0, cursor);\n if (this._pendingEntries.length > 0) {\n this._scheduleProcessing();\n }\n }\n }\n\n private _emitResource(entry: PerformanceResourceTiming): void {\n try {\n this.logger.emit({\n eventName: RESOURCE_TIMING_EVENT_NAME,\n severityNumber: SeverityNumber.INFO,\n attributes: {\n [ATTR_RESOURCE_URL]: entry.name,\n [ATTR_RESOURCE_INITIATOR_TYPE]: entry.initiatorType,\n [ATTR_RESOURCE_DURATION]: entry.duration,\n [ATTR_RESOURCE_FETCH_START]: entry.fetchStart,\n [ATTR_RESOURCE_DOMAIN_LOOKUP_START]: entry.domainLookupStart,\n [ATTR_RESOURCE_DOMAIN_LOOKUP_END]: entry.domainLookupEnd,\n [ATTR_RESOURCE_CONNECT_START]: entry.connectStart,\n [ATTR_RESOURCE_CONNECT_END]: entry.connectEnd,\n [ATTR_RESOURCE_SECURE_CONNECTION_START]: entry.secureConnectionStart,\n [ATTR_RESOURCE_REQUEST_START]: entry.requestStart,\n [ATTR_RESOURCE_RESPONSE_START]: entry.responseStart,\n [ATTR_RESOURCE_RESPONSE_END]: entry.responseEnd,\n [ATTR_RESOURCE_TRANSFER_SIZE]: entry.transferSize,\n [ATTR_RESOURCE_ENCODED_BODY_SIZE]: entry.encodedBodySize,\n [ATTR_RESOURCE_DECODED_BODY_SIZE]: entry.decodedBodySize,\n [ATTR_RESOURCE_REDIRECT_START]: entry.redirectStart,\n [ATTR_RESOURCE_REDIRECT_END]: entry.redirectEnd,\n [ATTR_RESOURCE_WORKER_START]: entry.workerStart,\n [ATTR_RESOURCE_NEXT_HOP_PROTOCOL]: entry.nextHopProtocol,\n // @ts-expect-error renderBlockingStatus is only available in Chromium as of March 2026\n [ATTR_RESOURCE_RENDER_BLOCKING_STATUS]: entry.renderBlockingStatus,\n },\n });\n } catch (error) {\n this._diag.error(\n `Failed to emit resource timing entry for \"${entry.name}\"`,\n error,\n );\n }\n }\n\n private _flush(): void {\n this._cancelScheduledProcessing();\n\n for (const entry of this._pendingEntries) {\n this._emitResource(entry);\n }\n this._pendingEntries = [];\n }\n\n private _cancelScheduledProcessing(): void {\n if (this._idleHandle !== undefined) {\n cancelIdleCallbackShim(this._idleHandle);\n this._idleHandle = undefined;\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"instrumentation.js","names":[],"sources":["../../src/resource-timing/instrumentation.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { LogRecord } from '@opentelemetry/api-logs';\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport { InstrumentationBase } from '@opentelemetry/instrumentation';\nimport { getNetworkContextRegistry } from '#utils';\nimport { version } from '../../package.json' with { type: 'json' };\nimport { matchesUrl } from '../utils/url.ts';\nimport type { IdleCallbackHandle } from './idle-callback-shim.ts';\nimport {\n cancelIdleCallbackShim,\n requestIdleCallbackShim,\n} from './idle-callback-shim.ts';\nimport {\n ATTR_RESOURCE_CONNECT_END,\n ATTR_RESOURCE_CONNECT_START,\n ATTR_RESOURCE_DECODED_BODY_SIZE,\n ATTR_RESOURCE_DOMAIN_LOOKUP_END,\n ATTR_RESOURCE_DOMAIN_LOOKUP_START,\n ATTR_RESOURCE_DURATION,\n ATTR_RESOURCE_ENCODED_BODY_SIZE,\n ATTR_RESOURCE_FETCH_START,\n ATTR_RESOURCE_INITIATOR_TYPE,\n ATTR_RESOURCE_NEXT_HOP_PROTOCOL,\n ATTR_RESOURCE_REDIRECT_END,\n ATTR_RESOURCE_REDIRECT_START,\n ATTR_RESOURCE_RENDER_BLOCKING_STATUS,\n ATTR_RESOURCE_REQUEST_START,\n ATTR_RESOURCE_RESPONSE_END,\n ATTR_RESOURCE_RESPONSE_START,\n ATTR_RESOURCE_SECURE_CONNECTION_START,\n ATTR_RESOURCE_TRANSFER_SIZE,\n ATTR_RESOURCE_URL,\n ATTR_RESOURCE_WORKER_START,\n RESOURCE_TIMING_EVENT_NAME,\n} from './semconv.ts';\nimport type { ResourceTimingInstrumentationConfig } from './types.ts';\n\nconst DEFAULT_BATCH_SIZE = 50;\nconst DEFAULT_FORCE_PROCESSING_AFTER = 1000;\nconst DEFAULT_MAX_PROCESSING_TIME = 50;\nconst DEFAULT_MAX_QUEUE_SIZE = 1000;\n\nconst MIN_BATCH_SIZE = 1;\nconst MIN_FORCE_PROCESSING_AFTER = 0;\nconst MIN_PROCESSING_TIME = 0;\nconst MIN_QUEUE_SIZE = 1;\n\n/**\n * OpenTelemetry instrumentation for resource timing for browser applications.\n *\n * This instrumentation captures resource timing data using PerformanceObserver\n * and batches emissions to avoid overwhelming the main thread. It uses\n * requestIdleCallback when available (with fallback for Safari) to ensure\n * processing happens during idle periods.\n */\nexport class ResourceTimingInstrumentation extends InstrumentationBase<ResourceTimingInstrumentationConfig> {\n private _observer?: PerformanceObserver;\n private _pendingEntries: PerformanceResourceTiming[] = [];\n private _idleHandle?: IdleCallbackHandle;\n\n // Use `declare` to prevent JS class field initializers from running after\n // super(), which would reset values set by the enable() call that\n // InstrumentationBase makes during its constructor.\n declare private _isEnabled: boolean;\n declare private _loadHandler: (() => void) | undefined;\n declare private _visibilityChangeHandler: (() => void) | undefined;\n\n constructor(config: ResourceTimingInstrumentationConfig = {}) {\n super(\n '@opentelemetry/browser-instrumentation/resource-timing',\n version,\n config,\n );\n }\n\n protected override init() {\n return [];\n }\n\n override enable(): void {\n if (this._isEnabled) {\n return;\n }\n\n if (!('PerformanceObserver' in window)) {\n this._diag.debug(\n 'PerformanceObserver is not supported, resource timings will not be collected',\n );\n return;\n }\n\n this._isEnabled = true;\n\n if (document.readyState === 'complete') {\n this._setupObserver();\n } else {\n this._loadHandler = () => this._setupObserver();\n window.addEventListener('load', this._loadHandler, { once: true });\n }\n\n this._visibilityChangeHandler = () => {\n if (document.hidden) {\n this._flush();\n }\n };\n document.addEventListener(\n 'visibilitychange',\n this._visibilityChangeHandler,\n );\n }\n\n override disable(): void {\n this._isEnabled = false;\n this._flush();\n this._observer?.disconnect();\n this._observer = undefined;\n if (this._loadHandler) {\n window.removeEventListener('load', this._loadHandler);\n this._loadHandler = undefined;\n }\n if (this._visibilityChangeHandler) {\n document.removeEventListener(\n 'visibilitychange',\n this._visibilityChangeHandler,\n );\n this._visibilityChangeHandler = undefined;\n }\n }\n\n private _setupObserver(): void {\n if (!this._isEnabled) {\n return;\n }\n\n const observer = new PerformanceObserver((list) => {\n if (!this._isEnabled) {\n return;\n }\n\n const maxQueueSize = Math.max(\n this._config.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE,\n MIN_QUEUE_SIZE,\n );\n const entries = list.getEntries() as PerformanceResourceTiming[];\n const initiatorTypes = this._config.initiatorTypes;\n\n for (const entry of entries) {\n if (\n initiatorTypes !== undefined &&\n !initiatorTypes.includes(entry.initiatorType)\n ) {\n continue;\n }\n\n const shouldIgnoreUrl = matchesUrl(entry.name, this._config.ignoreUrls);\n if (shouldIgnoreUrl) {\n continue;\n }\n\n if (this._pendingEntries.length >= maxQueueSize) {\n this._flush();\n }\n this._pendingEntries.push(entry);\n }\n\n if (this._pendingEntries.length > 0) {\n this._scheduleProcessing();\n }\n });\n\n this._observer = observer;\n\n try {\n observer.observe({ type: 'resource', buffered: true });\n } catch (e) {\n this._diag.error('Failed to start resource PerformanceObserver', e);\n this._observer = undefined;\n }\n }\n\n private _scheduleProcessing(): void {\n if (this._idleHandle !== undefined) {\n return;\n }\n\n const timeout = Math.max(\n this._config.forceProcessingAfter ?? DEFAULT_FORCE_PROCESSING_AFTER,\n MIN_FORCE_PROCESSING_AFTER,\n );\n this._idleHandle = requestIdleCallbackShim(\n (deadline) => this._processChunk(deadline),\n { timeout },\n );\n }\n\n private _processChunk(deadline: IdleDeadline): void {\n this._idleHandle = undefined;\n if (!this._isEnabled || this._pendingEntries.length === 0) {\n return;\n }\n\n const maxTime = Math.max(\n this._config.maxProcessingTime ?? DEFAULT_MAX_PROCESSING_TIME,\n MIN_PROCESSING_TIME,\n );\n const batchSize = Math.max(\n this._config.batchSize ?? DEFAULT_BATCH_SIZE,\n MIN_BATCH_SIZE,\n );\n const startTime = performance.now();\n\n let cursor = 0;\n try {\n for (let i = 0; i < batchSize; i++) {\n const entry = this._pendingEntries[cursor];\n if (entry === undefined) {\n break;\n }\n\n const elapsed = performance.now() - startTime;\n // timeRemaining() is not Baseline widely available (no Safari support),\n // compatibility is handled by idle-callback-shim.ts.\n // eslint-disable-next-line baseline-js/use-baseline\n if (elapsed >= maxTime || deadline.timeRemaining() < 1) {\n break;\n }\n\n cursor++;\n this._emitResource(entry);\n }\n } finally {\n this._pendingEntries.splice(0, cursor);\n if (this._pendingEntries.length > 0) {\n this._scheduleProcessing();\n }\n }\n }\n\n private _emitResource(entry: PerformanceResourceTiming): void {\n try {\n const record: LogRecord = {\n eventName: RESOURCE_TIMING_EVENT_NAME,\n severityNumber: SeverityNumber.INFO,\n attributes: {\n [ATTR_RESOURCE_URL]: entry.name,\n [ATTR_RESOURCE_INITIATOR_TYPE]: entry.initiatorType,\n [ATTR_RESOURCE_DURATION]: entry.duration,\n [ATTR_RESOURCE_FETCH_START]: entry.fetchStart,\n [ATTR_RESOURCE_DOMAIN_LOOKUP_START]: entry.domainLookupStart,\n [ATTR_RESOURCE_DOMAIN_LOOKUP_END]: entry.domainLookupEnd,\n [ATTR_RESOURCE_CONNECT_START]: entry.connectStart,\n [ATTR_RESOURCE_CONNECT_END]: entry.connectEnd,\n [ATTR_RESOURCE_SECURE_CONNECTION_START]: entry.secureConnectionStart,\n [ATTR_RESOURCE_REQUEST_START]: entry.requestStart,\n [ATTR_RESOURCE_RESPONSE_START]: entry.responseStart,\n [ATTR_RESOURCE_RESPONSE_END]: entry.responseEnd,\n [ATTR_RESOURCE_TRANSFER_SIZE]: entry.transferSize,\n [ATTR_RESOURCE_ENCODED_BODY_SIZE]: entry.encodedBodySize,\n [ATTR_RESOURCE_DECODED_BODY_SIZE]: entry.decodedBodySize,\n [ATTR_RESOURCE_REDIRECT_START]: entry.redirectStart,\n [ATTR_RESOURCE_REDIRECT_END]: entry.redirectEnd,\n [ATTR_RESOURCE_WORKER_START]: entry.workerStart,\n [ATTR_RESOURCE_NEXT_HOP_PROTOCOL]: entry.nextHopProtocol,\n // @ts-expect-error renderBlockingStatus is only available in Chromium as of March 2026\n [ATTR_RESOURCE_RENDER_BLOCKING_STATUS]: entry.renderBlockingStatus,\n },\n };\n const registry = getNetworkContextRegistry();\n const ctx = registry.getContext(entry);\n // Attach the context to the LogRecord and remove it from the registry\n if (ctx) {\n record.context = ctx;\n registry.unregister(entry);\n }\n this.logger.emit(record);\n } catch (error) {\n this._diag.error(\n `Failed to emit resource timing entry for \"${entry.name}\"`,\n error,\n );\n }\n }\n\n private _flush(): void {\n this._cancelScheduledProcessing();\n\n for (const entry of this._pendingEntries) {\n this._emitResource(entry);\n }\n this._pendingEntries = [];\n }\n\n private _cancelScheduledProcessing(): void {\n if (this._idleHandle !== undefined) {\n cancelIdleCallbackShim(this._idleHandle);\n this._idleHandle = undefined;\n }\n }\n}\n"],"mappings":"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"}
@@ -3,7 +3,7 @@ import { InstrumentationConfig } from "@opentelemetry/instrumentation";
3
3
  /**
4
4
  * ResourceTimingInstrumentation Configuration
5
5
  */
6
- interface ResourceTimingInstrumentationConfig extends InstrumentationConfig {
6
+ export interface ResourceTimingInstrumentationConfig extends InstrumentationConfig {
7
7
  /**
8
8
  * Number of resources to process per batch.
9
9
  * Default: 50
@@ -43,5 +43,4 @@ interface ResourceTimingInstrumentationConfig extends InstrumentationConfig {
43
43
  ignoreUrls?: (string | RegExp)[];
44
44
  }
45
45
  //#endregion
46
- export { ResourceTimingInstrumentationConfig };
47
46
  //# sourceMappingURL=types.d.ts.map
@@ -4,7 +4,7 @@ import { InstrumentationBase } from "@opentelemetry/instrumentation";
4
4
  /**
5
5
  * This class automatically instruments different User Actions within the browser.
6
6
  */
7
- declare class UserActionInstrumentation extends InstrumentationBase<UserActionInstrumentationConfig> {
7
+ export declare class UserActionInstrumentation extends InstrumentationBase<UserActionInstrumentationConfig> {
8
8
  private _onClickHandler?;
9
9
  constructor(config?: UserActionInstrumentationConfig);
10
10
  protected init(): never[];
@@ -15,5 +15,4 @@ declare class UserActionInstrumentation extends InstrumentationBase<UserActionIn
15
15
  disable(): void;
16
16
  }
17
17
  //#endregion
18
- export { UserActionInstrumentation };
19
18
  //# sourceMappingURL=instrumentation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"instrumentation.js","names":[],"sources":["../../src/user-action/instrumentation.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\nimport type { LogRecord } from '@opentelemetry/api-logs';\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport {\n InstrumentationBase,\n safeExecuteInTheMiddle,\n} from '@opentelemetry/instrumentation';\nimport { getElementCSSSelector } from '#utils';\nimport { version } from '../../package.json' with { type: 'json' };\nimport {\n ATTR_CSS_SELECTOR,\n ATTR_MOUSE_EVENT_BUTTON,\n ATTR_PAGE_X,\n ATTR_PAGE_Y,\n ATTR_TAG_NAME,\n ATTR_TAGS,\n CLICK_EVENT_NAME,\n} from './semconv.ts';\nimport type {\n AutoCapturedUserAction,\n MouseButton,\n UserActionInstrumentationConfig,\n} from './types.ts';\n\nconst DEFAULT_AUTO_CAPTURED_ACTIONS: AutoCapturedUserAction[] = ['click'];\nconst OTEL_ELEMENT_ATTRIBUTE_PREFIX = 'data-otel-';\n\n/**\n * This class automatically instruments different User Actions within the browser.\n */\nexport class UserActionInstrumentation extends InstrumentationBase<UserActionInstrumentationConfig> {\n private declare _onClickHandler?: (event: MouseEvent) => void;\n\n constructor(config: UserActionInstrumentationConfig = {}) {\n super(\n '@opentelemetry/browser-instrumentation/user-action',\n version,\n config,\n );\n }\n\n protected override init() {\n return [];\n }\n\n private _getMouseButtonFromMouseEvent(event: MouseEvent): MouseButton {\n switch (event.button) {\n case 0:\n return 'left';\n case 1:\n return 'middle';\n case 2:\n return 'right';\n default:\n return 'left';\n }\n }\n\n private onClick(event: MouseEvent) {\n const element = event.target;\n\n if (!(element instanceof HTMLElement)) {\n return;\n }\n\n if (element.hasAttribute('disabled')) {\n return;\n }\n\n const cssSelector = getElementCSSSelector(element, {\n useIdForTargetElement: true,\n useIdForAncestors: true,\n });\n const otelPrefixedAttributes: Record<string, string> = {};\n\n // Grab all the attributes in the element that start with data-otel-*\n for (const attr of element.attributes) {\n if (attr.name.startsWith(OTEL_ELEMENT_ATTRIBUTE_PREFIX)) {\n otelPrefixedAttributes[\n attr.name.slice(OTEL_ELEMENT_ATTRIBUTE_PREFIX.length)\n ] = attr.value;\n }\n }\n\n const logRecord: LogRecord = {\n severityNumber: SeverityNumber.INFO,\n eventName: CLICK_EVENT_NAME,\n attributes: {\n [ATTR_PAGE_X]: event.pageX,\n [ATTR_PAGE_Y]: event.pageY,\n [ATTR_TAG_NAME]: element.tagName,\n [ATTR_TAGS]: otelPrefixedAttributes,\n [ATTR_MOUSE_EVENT_BUTTON]: this._getMouseButtonFromMouseEvent(event),\n [ATTR_CSS_SELECTOR]: cssSelector,\n },\n };\n\n this._applyCustomLogRecordData(logRecord);\n\n this.logger.emit(logRecord);\n }\n\n private _applyCustomLogRecordData(logRecord: LogRecord) {\n const applyCustomLogRecordData = this.getConfig().applyCustomLogRecordData;\n if (applyCustomLogRecordData) {\n safeExecuteInTheMiddle(\n () => applyCustomLogRecordData(logRecord),\n (error) => {\n if (error) {\n this._diag.error('applyCustomLogRecordData hook failed', error);\n }\n },\n true,\n );\n }\n }\n\n override enable(): void {\n const autoCapturedActions =\n this._config.autoCapturedActions ?? DEFAULT_AUTO_CAPTURED_ACTIONS;\n if (!this._onClickHandler) {\n this._onClickHandler = this.onClick.bind(this);\n }\n\n if (autoCapturedActions.includes('click')) {\n document.addEventListener('click', this._onClickHandler, true);\n }\n }\n\n override disable(): void {\n if (this._onClickHandler) {\n document.removeEventListener('click', this._onClickHandler, true);\n }\n }\n}\n"],"mappings":";;;;;;AA2BA,MAAM,gCAA0D,CAAC,OAAO;AACxE,MAAM,gCAAgC;;;;AAKtC,IAAa,4BAAb,cAA+C,oBAAqD;CAGlG,YAAY,SAA0C,CAAC,GAAG;EACxD,MACE,sDACA,SACA,MACF;CACF;CAEA,OAA0B;EACxB,OAAO,CAAC;CACV;CAEA,8BAAsC,OAAgC;EACpE,QAAQ,MAAM,QAAd;GACE,KAAK,GACH,OAAO;GACT,KAAK,GACH,OAAO;GACT,KAAK,GACH,OAAO;GACT,SACE,OAAO;EACX;CACF;CAEA,QAAgB,OAAmB;EACjC,MAAM,UAAU,MAAM;EAEtB,IAAI,EAAE,mBAAmB,cACvB;EAGF,IAAI,QAAQ,aAAa,UAAU,GACjC;EAGF,MAAM,cAAc,sBAAsB,SAAS;GACjD,uBAAuB;GACvB,mBAAmB;EACrB,CAAC;EACD,MAAM,yBAAiD,CAAC;EAGxD,KAAK,MAAM,QAAQ,QAAQ,YACzB,IAAI,KAAK,KAAK,WAAW,6BAA6B,GACpD,uBACE,KAAK,KAAK,MAAM,EAAoC,KAClD,KAAK;EAIb,MAAM,YAAuB;GAC3B,gBAAgB,eAAe;GAC/B,WAAW;GACX,YAAY;KACT,cAAc,MAAM;KACpB,cAAc,MAAM;KACpB,gBAAgB,QAAQ;KACxB,YAAY;KACZ,0BAA0B,KAAK,8BAA8B,KAAK;KAClE,oBAAoB;GACvB;EACF;EAEA,KAAK,0BAA0B,SAAS;EAExC,KAAK,OAAO,KAAK,SAAS;CAC5B;CAEA,0BAAkC,WAAsB;EACtD,MAAM,2BAA2B,KAAK,UAAU,CAAC,CAAC;EAClD,IAAI,0BACF,6BACQ,yBAAyB,SAAS,IACvC,UAAU;GACT,IAAI,OACF,KAAK,MAAM,MAAM,wCAAwC,KAAK;EAElE,GACA,IACF;CAEJ;CAEA,SAAwB;EACtB,MAAM,sBACJ,KAAK,QAAQ,uBAAuB;EACtC,IAAI,CAAC,KAAK,iBACR,KAAK,kBAAkB,KAAK,QAAQ,KAAK,IAAI;EAG/C,IAAI,oBAAoB,SAAS,OAAO,GACtC,SAAS,iBAAiB,SAAS,KAAK,iBAAiB,IAAI;CAEjE;CAEA,UAAyB;EACvB,IAAI,KAAK,iBACP,SAAS,oBAAoB,SAAS,KAAK,iBAAiB,IAAI;CAEpE;AACF"}
1
+ {"version":3,"file":"instrumentation.js","names":[],"sources":["../../src/user-action/instrumentation.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\nimport type { LogRecord } from '@opentelemetry/api-logs';\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport {\n InstrumentationBase,\n safeExecuteInTheMiddle,\n} from '@opentelemetry/instrumentation';\nimport { getElementCSSSelector } from '#utils';\nimport { version } from '../../package.json' with { type: 'json' };\nimport {\n ATTR_CSS_SELECTOR,\n ATTR_MOUSE_EVENT_BUTTON,\n ATTR_PAGE_X,\n ATTR_PAGE_Y,\n ATTR_TAG_NAME,\n ATTR_TAGS,\n CLICK_EVENT_NAME,\n} from './semconv.ts';\nimport type {\n AutoCapturedUserAction,\n MouseButton,\n UserActionInstrumentationConfig,\n} from './types.ts';\n\nconst DEFAULT_AUTO_CAPTURED_ACTIONS: AutoCapturedUserAction[] = ['click'];\nconst OTEL_ELEMENT_ATTRIBUTE_PREFIX = 'data-otel-';\n\n/**\n * This class automatically instruments different User Actions within the browser.\n */\nexport class UserActionInstrumentation extends InstrumentationBase<UserActionInstrumentationConfig> {\n declare private _onClickHandler?: (event: MouseEvent) => void;\n\n constructor(config: UserActionInstrumentationConfig = {}) {\n super(\n '@opentelemetry/browser-instrumentation/user-action',\n version,\n config,\n );\n }\n\n protected override init() {\n return [];\n }\n\n private _getMouseButtonFromMouseEvent(event: MouseEvent): MouseButton {\n switch (event.button) {\n case 0:\n return 'left';\n case 1:\n return 'middle';\n case 2:\n return 'right';\n default:\n return 'left';\n }\n }\n\n private onClick(event: MouseEvent) {\n const element = event.target;\n\n if (!(element instanceof HTMLElement)) {\n return;\n }\n\n if (element.hasAttribute('disabled')) {\n return;\n }\n\n const cssSelector = getElementCSSSelector(element, {\n useIdForTargetElement: true,\n useIdForAncestors: true,\n });\n const otelPrefixedAttributes: Record<string, string> = {};\n\n // Grab all the attributes in the element that start with data-otel-*\n for (const attr of element.attributes) {\n if (attr.name.startsWith(OTEL_ELEMENT_ATTRIBUTE_PREFIX)) {\n otelPrefixedAttributes[\n attr.name.slice(OTEL_ELEMENT_ATTRIBUTE_PREFIX.length)\n ] = attr.value;\n }\n }\n\n const logRecord: LogRecord = {\n severityNumber: SeverityNumber.INFO,\n eventName: CLICK_EVENT_NAME,\n attributes: {\n [ATTR_PAGE_X]: event.pageX,\n [ATTR_PAGE_Y]: event.pageY,\n [ATTR_TAG_NAME]: element.tagName,\n [ATTR_TAGS]: otelPrefixedAttributes,\n [ATTR_MOUSE_EVENT_BUTTON]: this._getMouseButtonFromMouseEvent(event),\n [ATTR_CSS_SELECTOR]: cssSelector,\n },\n };\n\n this._applyCustomLogRecordData(logRecord);\n\n this.logger.emit(logRecord);\n }\n\n private _applyCustomLogRecordData(logRecord: LogRecord) {\n const applyCustomLogRecordData = this.getConfig().applyCustomLogRecordData;\n if (applyCustomLogRecordData) {\n safeExecuteInTheMiddle(\n () => applyCustomLogRecordData(logRecord),\n (error) => {\n if (error) {\n this._diag.error('applyCustomLogRecordData hook failed', error);\n }\n },\n true,\n );\n }\n }\n\n override enable(): void {\n const autoCapturedActions =\n this._config.autoCapturedActions ?? DEFAULT_AUTO_CAPTURED_ACTIONS;\n if (!this._onClickHandler) {\n this._onClickHandler = this.onClick.bind(this);\n }\n\n if (autoCapturedActions.includes('click')) {\n document.addEventListener('click', this._onClickHandler, true);\n }\n }\n\n override disable(): void {\n if (this._onClickHandler) {\n document.removeEventListener('click', this._onClickHandler, true);\n }\n }\n}\n"],"mappings":";;;;;;AA2BA,MAAM,gCAA0D,CAAC,OAAO;AACxE,MAAM,gCAAgC;;;;AAKtC,IAAa,4BAAb,cAA+C,oBAAqD;CAGlG,YAAY,SAA0C,CAAC,GAAG;EACxD,MACE,sDACA,SACA,MACF;CACF;CAEA,OAA0B;EACxB,OAAO,CAAC;CACV;CAEA,8BAAsC,OAAgC;EACpE,QAAQ,MAAM,QAAd;GACE,KAAK,GACH,OAAO;GACT,KAAK,GACH,OAAO;GACT,KAAK,GACH,OAAO;GACT,SACE,OAAO;EACX;CACF;CAEA,QAAgB,OAAmB;EACjC,MAAM,UAAU,MAAM;EAEtB,IAAI,EAAE,mBAAmB,cACvB;EAGF,IAAI,QAAQ,aAAa,UAAU,GACjC;EAGF,MAAM,cAAc,sBAAsB,SAAS;GACjD,uBAAuB;GACvB,mBAAmB;EACrB,CAAC;EACD,MAAM,yBAAiD,CAAC;EAGxD,KAAK,MAAM,QAAQ,QAAQ,YACzB,IAAI,KAAK,KAAK,WAAW,6BAA6B,GACpD,uBACE,KAAK,KAAK,MAAM,EAAoC,KAClD,KAAK;EAIb,MAAM,YAAuB;GAC3B,gBAAgB,eAAe;GAC/B,WAAW;GACX,YAAY;KACT,cAAc,MAAM;KACpB,cAAc,MAAM;KACpB,gBAAgB,QAAQ;KACxB,YAAY;KACZ,0BAA0B,KAAK,8BAA8B,KAAK;KAClE,oBAAoB;GACvB;EACF;EAEA,KAAK,0BAA0B,SAAS;EAExC,KAAK,OAAO,KAAK,SAAS;CAC5B;CAEA,0BAAkC,WAAsB;EACtD,MAAM,2BAA2B,KAAK,UAAU,CAAC,CAAC;EAClD,IAAI,0BACF,6BACQ,yBAAyB,SAAS,IACvC,UAAU;GACT,IAAI,OACF,KAAK,MAAM,MAAM,wCAAwC,KAAK;EAElE,GACA,IACF;CAEJ;CAEA,SAAwB;EACtB,MAAM,sBACJ,KAAK,QAAQ,uBAAuB;EACtC,IAAI,CAAC,KAAK,iBACR,KAAK,kBAAkB,KAAK,QAAQ,KAAK,IAAI;EAG/C,IAAI,oBAAoB,SAAS,OAAO,GACtC,SAAS,iBAAiB,SAAS,KAAK,iBAAiB,IAAI;CAEjE;CAEA,UAAyB;EACvB,IAAI,KAAK,iBACP,SAAS,oBAAoB,SAAS,KAAK,iBAAiB,IAAI;CAEpE;AACF"}
@@ -1,16 +1,15 @@
1
1
  import { LogRecord } from "@opentelemetry/api-logs";
2
2
  import { InstrumentationConfig } from "@opentelemetry/instrumentation";
3
3
  //#region src/user-action/types.d.ts
4
- type AutoCapturedUserAction = 'click';
5
- type ApplyCustomLogRecordDataFunction = (logRecord: LogRecord) => void;
4
+ export type AutoCapturedUserAction = 'click';
5
+ export type ApplyCustomLogRecordDataFunction = (logRecord: LogRecord) => void;
6
6
  /**
7
7
  * UserActionInstrumentation Configuration
8
8
  */
9
- interface UserActionInstrumentationConfig extends InstrumentationConfig {
9
+ export interface UserActionInstrumentationConfig extends InstrumentationConfig {
10
10
  autoCapturedActions?: AutoCapturedUserAction[];
11
11
  /** Hook to modify log records before they are emitted. */
12
12
  applyCustomLogRecordData?: ApplyCustomLogRecordDataFunction;
13
13
  }
14
14
  //#endregion
15
- export { ApplyCustomLogRecordDataFunction, AutoCapturedUserAction, UserActionInstrumentationConfig };
16
15
  //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,77 @@
1
+ import { ROOT_CONTEXT, trace } from "@opentelemetry/api";
2
+ //#region src/utils/ContextRegistry.ts
3
+ const MAX_CAPACITY = 1e3;
4
+ /**
5
+ * Base class for sharing OTel span context between two instrumentations that
6
+ * observe the same events from different angles.
7
+ *
8
+ * The producing instrumentation calls `register()` when a span ends; the
9
+ * consuming instrumentation calls `getContext()` to retrieve the span context
10
+ * associated with the event it is processing.
11
+ *
12
+ * @typeParam TData - Registration payload. Must include enough info to derive a `key` string
13
+ * which will be used as the primary index, plus any implementation-specific fields needed to
14
+ * disambiguate concurrent operations (e.g. timing windows for network spans).
15
+ * @typeParam TLookup - The object the consuming instrumentation passes to
16
+ * `getContext` and `unregister` (e.g. a `PerformanceResourceTiming` entry).
17
+ */
18
+ var ContextRegistry = class {
19
+ _recordsByKey = /* @__PURE__ */ new Map();
20
+ _recordsList = [];
21
+ /** Store the span context for the given data, key is resolved by getDataKey(). */
22
+ register(span, data) {
23
+ const ctx = trace.setSpan(ROOT_CONTEXT, span);
24
+ const key = this.getDataKey(data);
25
+ const list = this._recordsByKey.get(key) ?? [];
26
+ const record = {
27
+ ...data,
28
+ ctx
29
+ };
30
+ list.push(record);
31
+ this._recordsByKey.set(key, list);
32
+ this._recordsList.push(record);
33
+ if (this._recordsList.length > MAX_CAPACITY) {
34
+ const oldestRecord = this._recordsList.shift();
35
+ if (oldestRecord) {
36
+ const oldestKey = this.getDataKey(oldestRecord);
37
+ const oldestCtx = oldestRecord.ctx;
38
+ this.removeByKey(oldestKey, oldestCtx);
39
+ }
40
+ }
41
+ }
42
+ /**
43
+ * Remove the record that matches `lookup`. If no matching record exists this
44
+ * is a no-op. Deletes the key entirely once all records under it are removed.
45
+ */
46
+ unregister(lookup) {
47
+ const key = this.getLookupKey(lookup);
48
+ const ctx = this.getContext(lookup);
49
+ if (ctx === void 0) return;
50
+ this.removeByKey(key, ctx);
51
+ const index = this._recordsList.findIndex((r) => {
52
+ const k = this.getDataKey(r);
53
+ return key === k && ctx === r.ctx;
54
+ });
55
+ if (index > -1) this._recordsList.splice(index, 1);
56
+ }
57
+ /**
58
+ * Remove a record from the records sub-list of the given key
59
+ */
60
+ removeByKey(key, ctx) {
61
+ const list = this._recordsByKey.get(key);
62
+ if (!list) return;
63
+ const filtered = list.filter((r) => r.ctx !== ctx);
64
+ if (filtered.length === 0) this._recordsByKey.delete(key);
65
+ else this._recordsByKey.set(key, filtered);
66
+ }
67
+ /**
68
+ * Return the size number of records stored in the registry
69
+ */
70
+ size() {
71
+ return this._recordsList.length;
72
+ }
73
+ };
74
+ //#endregion
75
+ export { ContextRegistry };
76
+
77
+ //# sourceMappingURL=ContextRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ContextRegistry.js","names":[],"sources":["../../src/utils/ContextRegistry.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { Context, Span } from '@opentelemetry/api';\nimport { ROOT_CONTEXT, trace } from '@opentelemetry/api';\n\ntype StoredRecord<TData> = TData & { ctx: Context };\n\nconst MAX_CAPACITY = 1000;\n\n/**\n * Base class for sharing OTel span context between two instrumentations that\n * observe the same events from different angles.\n *\n * The producing instrumentation calls `register()` when a span ends; the\n * consuming instrumentation calls `getContext()` to retrieve the span context\n * associated with the event it is processing.\n *\n * @typeParam TData - Registration payload. Must include enough info to derive a `key` string\n * which will be used as the primary index, plus any implementation-specific fields needed to\n * disambiguate concurrent operations (e.g. timing windows for network spans).\n * @typeParam TLookup - The object the consuming instrumentation passes to\n * `getContext` and `unregister` (e.g. a `PerformanceResourceTiming` entry).\n */\nexport abstract class ContextRegistry<TData, TLookup> {\n protected _recordsByKey = new Map<string, StoredRecord<TData>[]>();\n private _recordsList: StoredRecord<TData>[] = [];\n\n /** Store the span context for the given data, key is resolved by getDataKey(). */\n register(span: Span, data: TData): void {\n const ctx = trace.setSpan(ROOT_CONTEXT, span);\n const key = this.getDataKey(data);\n const list = this._recordsByKey.get(key) ?? [];\n const record = { ...data, ctx };\n\n // Set record into the map for faster lookup and in the\n // list to track the size\n list.push(record);\n this._recordsByKey.set(key, list);\n this._recordsList.push(record);\n\n // If capacity is exceeded remove the oldest (1st in the array)\n if (this._recordsList.length > MAX_CAPACITY) {\n const oldestRecord = this._recordsList.shift();\n if (oldestRecord) {\n const oldestKey = this.getDataKey(oldestRecord);\n const oldestCtx = oldestRecord.ctx;\n this.removeByKey(oldestKey, oldestCtx);\n }\n }\n }\n\n /**\n * Remove the record that matches `lookup`. If no matching record exists this\n * is a no-op. Deletes the key entirely once all records under it are removed.\n */\n unregister(lookup: TLookup): void {\n const key = this.getLookupKey(lookup);\n const ctx = this.getContext(lookup);\n if (ctx === undefined) {\n return;\n }\n\n // Remove from the lookup map and from the global list\n // to keep size up to date\n this.removeByKey(key, ctx);\n const index = this._recordsList.findIndex((r) => {\n const k = this.getDataKey(r);\n return key === k && ctx === r.ctx;\n });\n if (index > -1) {\n this._recordsList.splice(index, 1);\n }\n }\n\n /**\n * Remove a record from the records sub-list of the given key\n */\n private removeByKey(key: string, ctx: Context): void {\n const list = this._recordsByKey.get(key);\n if (!list) {\n return;\n }\n\n const filtered = list.filter((r) => r.ctx !== ctx);\n if (filtered.length === 0) {\n this._recordsByKey.delete(key);\n } else {\n this._recordsByKey.set(key, filtered);\n }\n }\n\n /**\n * Return the size number of records stored in the registry\n */\n size(): number {\n return this._recordsList.length;\n }\n\n /** Return the index key for the given data object. */\n abstract getDataKey(data: TData): string;\n\n /** Return the index key for the given lookup object. */\n abstract getLookupKey(lookup: TLookup): string;\n\n /**\n * Return the OTel context for the record that matches `lookup`, or\n * `undefined` if no match is found.\n */\n abstract getContext(lookup: TLookup): Context | undefined;\n}\n"],"mappings":";;AAUA,MAAM,eAAe;;;;;;;;;;;;;;;AAgBrB,IAAsB,kBAAtB,MAAsD;CACpD,gCAA0B,IAAI,IAAmC;CACjE,eAA8C,CAAC;;CAG/C,SAAS,MAAY,MAAmB;EACtC,MAAM,MAAM,MAAM,QAAQ,cAAc,IAAI;EAC5C,MAAM,MAAM,KAAK,WAAW,IAAI;EAChC,MAAM,OAAO,KAAK,cAAc,IAAI,GAAG,KAAK,CAAC;EAC7C,MAAM,SAAS;GAAE,GAAG;GAAM;EAAI;EAI9B,KAAK,KAAK,MAAM;EAChB,KAAK,cAAc,IAAI,KAAK,IAAI;EAChC,KAAK,aAAa,KAAK,MAAM;EAG7B,IAAI,KAAK,aAAa,SAAS,cAAc;GAC3C,MAAM,eAAe,KAAK,aAAa,MAAM;GAC7C,IAAI,cAAc;IAChB,MAAM,YAAY,KAAK,WAAW,YAAY;IAC9C,MAAM,YAAY,aAAa;IAC/B,KAAK,YAAY,WAAW,SAAS;GACvC;EACF;CACF;;;;;CAMA,WAAW,QAAuB;EAChC,MAAM,MAAM,KAAK,aAAa,MAAM;EACpC,MAAM,MAAM,KAAK,WAAW,MAAM;EAClC,IAAI,QAAQ,KAAA,GACV;EAKF,KAAK,YAAY,KAAK,GAAG;EACzB,MAAM,QAAQ,KAAK,aAAa,WAAW,MAAM;GAC/C,MAAM,IAAI,KAAK,WAAW,CAAC;GAC3B,OAAO,QAAQ,KAAK,QAAQ,EAAE;EAChC,CAAC;EACD,IAAI,QAAQ,IACV,KAAK,aAAa,OAAO,OAAO,CAAC;CAErC;;;;CAKA,YAAoB,KAAa,KAAoB;EACnD,MAAM,OAAO,KAAK,cAAc,IAAI,GAAG;EACvC,IAAI,CAAC,MACH;EAGF,MAAM,WAAW,KAAK,QAAQ,MAAM,EAAE,QAAQ,GAAG;EACjD,IAAI,SAAS,WAAW,GACtB,KAAK,cAAc,OAAO,GAAG;OAE7B,KAAK,cAAc,IAAI,KAAK,QAAQ;CAExC;;;;CAKA,OAAe;EACb,OAAO,KAAK,aAAa;CAC3B;AAaF"}
@@ -0,0 +1,23 @@
1
+ import { ContextRegistry } from "./ContextRegistry.js";
2
+ //#region src/utils/NetworkContextRegistry.ts
3
+ var NetworkContextRegistry = class extends ContextRegistry {
4
+ getDataKey(data) {
5
+ return data.key;
6
+ }
7
+ getLookupKey(entry) {
8
+ return entry.name;
9
+ }
10
+ getContext(entry) {
11
+ const key = this.getLookupKey(entry);
12
+ return this._recordsByKey.get(key)?.find((r) => entry.fetchStart >= r.startPerfNow && entry.responseEnd <= r.endPerfNow)?.ctx;
13
+ }
14
+ };
15
+ let _instance;
16
+ function getNetworkContextRegistry() {
17
+ if (!_instance) _instance = new NetworkContextRegistry();
18
+ return _instance;
19
+ }
20
+ //#endregion
21
+ export { NetworkContextRegistry, getNetworkContextRegistry };
22
+
23
+ //# sourceMappingURL=NetworkContextRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NetworkContextRegistry.js","names":[],"sources":["../../src/utils/NetworkContextRegistry.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { Context } from '@opentelemetry/api';\nimport { ContextRegistry } from './ContextRegistry.ts';\n\nexport interface NetworkSpanData {\n key: string;\n startPerfNow: number;\n endPerfNow: number;\n}\n\nexport class NetworkContextRegistry extends ContextRegistry<\n NetworkSpanData,\n PerformanceResourceTiming\n> {\n getDataKey(data: NetworkSpanData): string {\n return data.key;\n }\n\n getLookupKey(entry: PerformanceResourceTiming): string {\n return entry.name;\n }\n\n getContext(entry: PerformanceResourceTiming): Context | undefined {\n const key = this.getLookupKey(entry);\n const list = this._recordsByKey.get(key);\n return list?.find(\n (r) =>\n entry.fetchStart >= r.startPerfNow && entry.responseEnd <= r.endPerfNow,\n )?.ctx;\n }\n}\n\nlet _instance: NetworkContextRegistry | undefined;\n\nexport function getNetworkContextRegistry(): NetworkContextRegistry {\n if (!_instance) {\n _instance = new NetworkContextRegistry();\n }\n\n return _instance;\n}\n"],"mappings":";;AAcA,IAAa,yBAAb,cAA4C,gBAG1C;CACA,WAAW,MAA+B;EACxC,OAAO,KAAK;CACd;CAEA,aAAa,OAA0C;EACrD,OAAO,MAAM;CACf;CAEA,WAAW,OAAuD;EAChE,MAAM,MAAM,KAAK,aAAa,KAAK;EAEnC,OADa,KAAK,cAAc,IAAI,GAC1B,CAAC,EAAE,MACV,MACC,MAAM,cAAc,EAAE,gBAAgB,MAAM,eAAe,EAAE,UACjE,CAAC,EAAE;CACL;AACF;AAEA,IAAI;AAEJ,SAAgB,4BAAoD;CAClE,IAAI,CAAC,WACH,YAAY,IAAI,uBAAuB;CAGzC,OAAO;AACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"getElementCSSSelector.js","names":[],"sources":["../../src/utils/getElementCSSSelector.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\ntype GetElementCSSSelectorOptions = {\n /**\n * If true, the function will attempt to use element ID to create condensed CSS selector.\n */\n useIdForTargetElement?: boolean;\n /**\n * If true, the function will attempt to use element ID for all ancestor elements to create condensed CSS selector.\n */\n useIdForAncestors?: boolean;\n};\n\n/**\n * Generates the CSS selector of a given element in the DOM tree.\n *\n * @example #main > div:nth-child(2) > button.submit\n * @example #unique-id\n */\nexport const getElementCSSSelector = (\n element: Node,\n {\n useIdForTargetElement = false,\n useIdForAncestors = false,\n }: GetElementCSSSelectorOptions = {},\n): string => {\n // Handle document node\n if (element.nodeType === Node.DOCUMENT_NODE) {\n return '';\n }\n\n const htmlElement = element as HTMLElement;\n const nodeValue = getNodeSelector(\n htmlElement,\n useIdForTargetElement || useIdForAncestors,\n );\n\n // If optimized and found an ID selector, stop recursion early\n if (nodeValue.startsWith('#')) {\n return nodeValue;\n }\n\n const parent = htmlElement.parentElement;\n const parentSelector = parent\n ? getElementCSSSelector(parent, {\n useIdForAncestors,\n useIdForTargetElement: false,\n })\n : '';\n\n return parentSelector ? `${parentSelector} > ${nodeValue}` : nodeValue;\n};\n\nconst getNodeSelector = (element: Node, useElementId = false): string => {\n if (element.nodeType !== Node.ELEMENT_NODE) {\n return '';\n }\n\n const htmlElement = element as HTMLElement;\n const id = htmlElement.getAttribute('id');\n\n // Use ID if requested and it's unique\n if (useElementId && id) {\n // Check if ID is unique in the document\n const elementsWithSameId = htmlElement.ownerDocument.querySelectorAll(\n `#${CSS.escape(id)}`,\n );\n\n if (elementsWithSameId.length === 1) {\n return `#${CSS.escape(id)}`;\n }\n }\n\n let selector = getFullClassSelector(htmlElement);\n // Add nth-child if there are siblings with the same tag and classes\n const index = getNthChild(htmlElement);\n\n if (index > 0) {\n selector += `:nth-child(${index})`;\n }\n\n return selector;\n};\n\nconst getNthChild = (element: HTMLElement): number => {\n // parentElement is needed to access children\n if (!element.parentElement) {\n return 0;\n }\n\n const selector = getFullClassSelector(element);\n\n // Get all siblings that match the same selector\n const siblings = Array.from(element.parentElement.children).filter(\n (sibling) => getFullClassSelector(sibling) === selector,\n );\n\n // Only add nth-child if there are multiple matching siblings\n if (siblings.length > 1) {\n return siblings.indexOf(element) + 1;\n }\n\n return 0;\n};\n\nconst getFullClassSelector = (element: Element): string =>\n element.localName +\n (element.classList.length > 0\n ? Array.from(element.classList)\n .map((cls) => `.${CSS.escape(cls)}`)\n .join('')\n : '');\n"],"mappings":";;;;;;;AAsBA,MAAa,yBACX,SACA,EACE,wBAAwB,OACxB,oBAAoB,UACY,CAAC,MACxB;CAEX,IAAI,QAAQ,aAAa,KAAK,eAC5B,OAAO;CAGT,MAAM,cAAc;CACpB,MAAM,YAAY,gBAChB,aACA,yBAAyB,iBAC3B;CAGA,IAAI,UAAU,WAAW,GAAG,GAC1B,OAAO;CAGT,MAAM,SAAS,YAAY;CAC3B,MAAM,iBAAiB,SACnB,sBAAsB,QAAQ;EAC5B;EACA,uBAAuB;CACzB,CAAC,IACD;CAEJ,OAAO,iBAAiB,GAAG,eAAe,KAAK,cAAc;AAC/D;AAEA,MAAM,mBAAmB,SAAe,eAAe,UAAkB;CACvE,IAAI,QAAQ,aAAa,KAAK,cAC5B,OAAO;CAGT,MAAM,cAAc;CACpB,MAAM,KAAK,YAAY,aAAa,IAAI;CAGxC,IAAI,gBAAgB;MAES,YAAY,cAAc,iBACnD,IAAI,IAAI,OAAO,EAAE,GAGE,CAAC,CAAC,WAAW,GAChC,OAAO,IAAI,IAAI,OAAO,EAAE;CAAA;CAI5B,IAAI,WAAW,qBAAqB,WAAW;CAE/C,MAAM,QAAQ,YAAY,WAAW;CAErC,IAAI,QAAQ,GACV,YAAY,cAAc,MAAM;CAGlC,OAAO;AACT;AAEA,MAAM,eAAe,YAAiC;CAEpD,IAAI,CAAC,QAAQ,eACX,OAAO;CAGT,MAAM,WAAW,qBAAqB,OAAO;CAG7C,MAAM,WAAW,MAAM,KAAK,QAAQ,cAAc,QAAQ,CAAC,CAAC,QACzD,YAAY,qBAAqB,OAAO,MAAM,QACjD;CAGA,IAAI,SAAS,SAAS,GACpB,OAAO,SAAS,QAAQ,OAAO,IAAI;CAGrC,OAAO;AACT;AAEA,MAAM,wBAAwB,YAC5B,QAAQ,aACP,QAAQ,UAAU,SAAS,IACxB,MAAM,KAAK,QAAQ,SAAS,CAAC,CAC1B,KAAK,QAAQ,IAAI,IAAI,OAAO,GAAG,GAAG,CAAC,CACnC,KAAK,EAAE,IACV"}
1
+ {"version":3,"file":"getElementCSSSelector.js","names":[],"sources":["../../src/utils/getElementCSSSelector.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\ntype GetElementCSSSelectorOptions = {\n /**\n * If true, the function will attempt to use element ID to create condensed CSS selector.\n */\n useIdForTargetElement?: boolean;\n /**\n * If true, the function will attempt to use element ID for all ancestor elements to create condensed CSS selector.\n */\n useIdForAncestors?: boolean;\n};\n\n/**\n * Generates the CSS selector of a given element in the DOM tree.\n *\n * @example #main > div:nth-child(2) > button.submit\n * @example #unique-id\n */\nexport const getElementCSSSelector = (\n element: Node,\n {\n useIdForTargetElement = false,\n useIdForAncestors = false,\n }: GetElementCSSSelectorOptions = {},\n): string => {\n // Handle document node\n if (element.nodeType === Node.DOCUMENT_NODE) {\n return '';\n }\n\n const htmlElement = element as HTMLElement;\n const nodeValue = getNodeSelector(\n htmlElement,\n useIdForTargetElement || useIdForAncestors,\n );\n\n // If optimized and found an ID selector, stop recursion early\n if (nodeValue.startsWith('#')) {\n return nodeValue;\n }\n\n const parent = htmlElement.parentElement;\n const parentSelector = parent\n ? getElementCSSSelector(parent, {\n useIdForAncestors,\n useIdForTargetElement: false,\n })\n : '';\n\n return parentSelector ? `${parentSelector} > ${nodeValue}` : nodeValue;\n};\n\nconst getNodeSelector = (element: Node, useElementId = false): string => {\n if (element.nodeType !== Node.ELEMENT_NODE) {\n return '';\n }\n\n const htmlElement = element as HTMLElement;\n const id = htmlElement.getAttribute('id');\n\n // Use ID if requested and it's unique\n if (useElementId && id) {\n // Check if ID is unique in the document\n const elementsWithSameId = htmlElement.ownerDocument.querySelectorAll(\n `#${CSS.escape(id)}`,\n );\n\n if (elementsWithSameId.length === 1) {\n return `#${CSS.escape(id)}`;\n }\n }\n\n let selector = getFullClassSelector(htmlElement);\n // Add nth-child if there are siblings with the same tag and classes\n const index = getNthChild(htmlElement);\n\n if (index > 0) {\n selector += `:nth-child(${index})`;\n }\n\n return selector;\n};\n\nconst getNthChild = (element: HTMLElement): number => {\n // parentElement is needed to access children\n if (!element.parentElement) {\n return 0;\n }\n\n const selector = getFullClassSelector(element);\n\n // Get all siblings that match the same selector\n const siblings = Array.from(element.parentElement.children).filter(\n (sibling) => getFullClassSelector(sibling) === selector,\n );\n\n // Only add nth-child if there are multiple matching siblings\n if (siblings.length > 1) {\n return siblings.indexOf(element) + 1;\n }\n\n return 0;\n};\n\nconst getFullClassSelector = (element: Element): string =>\n element.localName +\n (element.classList.length > 0\n ? Array.from(element.classList)\n .map((cls) => `.${CSS.escape(cls)}`)\n .join('')\n : '');\n"],"mappings":";;;;;;;AAsBA,MAAa,yBACX,SACA,EACE,wBAAwB,OACxB,oBAAoB,UACY,CAAC,MACxB;CAEX,IAAI,QAAQ,aAAa,KAAK,eAC5B,OAAO;CAGT,MAAM,cAAc;CACpB,MAAM,YAAY,gBAChB,aACA,yBAAyB,iBAC3B;CAGA,IAAI,UAAU,WAAW,GAAG,GAC1B,OAAO;CAGT,MAAM,SAAS,YAAY;CAC3B,MAAM,iBAAiB,SACnB,sBAAsB,QAAQ;EAC5B;EACA,uBAAuB;CACzB,CAAC,IACD;CAEJ,OAAO,iBAAiB,GAAG,eAAe,KAAK,cAAc;AAC/D;AAEA,MAAM,mBAAmB,SAAe,eAAe,UAAkB;CACvE,IAAI,QAAQ,aAAa,KAAK,cAC5B,OAAO;CAGT,MAAM,cAAc;CACpB,MAAM,KAAK,YAAY,aAAa,IAAI;CAGxC,IAAI,gBAAgB,IAES;MAAA,YAAY,cAAc,iBACnD,IAAI,IAAI,OAAO,EAAE,GAGE,CAAC,CAAC,WAAW,GAChC,OAAO,IAAI,IAAI,OAAO,EAAE;CAAA;CAI5B,IAAI,WAAW,qBAAqB,WAAW;CAE/C,MAAM,QAAQ,YAAY,WAAW;CAErC,IAAI,QAAQ,GACV,YAAY,cAAc,MAAM;CAGlC,OAAO;AACT;AAEA,MAAM,eAAe,YAAiC;CAEpD,IAAI,CAAC,QAAQ,eACX,OAAO;CAGT,MAAM,WAAW,qBAAqB,OAAO;CAG7C,MAAM,WAAW,MAAM,KAAK,QAAQ,cAAc,QAAQ,CAAC,CAAC,QACzD,YAAY,qBAAqB,OAAO,MAAM,QACjD;CAGA,IAAI,SAAS,SAAS,GACpB,OAAO,SAAS,QAAQ,OAAO,IAAI;CAGrC,OAAO;AACT;AAEA,MAAM,wBAAwB,YAC5B,QAAQ,aACP,QAAQ,UAAU,SAAS,IACxB,MAAM,KAAK,QAAQ,SAAS,CAAC,CAC1B,KAAK,QAAQ,IAAI,IAAI,OAAO,GAAG,GAAG,CAAC,CACnC,KAAK,EAAE,IACV"}