@newrelic/browser-agent 1.309.0 → 1.310.0

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 (133) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +0 -2
  3. package/dist/cjs/common/config/init-types.js +1 -4
  4. package/dist/cjs/common/config/init.js +1 -5
  5. package/dist/cjs/common/constants/env.cdn.js +1 -1
  6. package/dist/cjs/common/constants/env.npm.js +1 -1
  7. package/dist/cjs/common/util/browser-stack-matchers.js +15 -0
  8. package/dist/cjs/common/util/script-tracker.js +184 -0
  9. package/dist/cjs/common/wrap/wrap-fetch.js +2 -2
  10. package/dist/cjs/common/wrap/wrap-history.js +2 -2
  11. package/dist/cjs/common/wrap/wrap-logger.js +2 -2
  12. package/dist/cjs/common/wrap/wrap-xhr.js +2 -2
  13. package/dist/cjs/features/jserrors/aggregate/compute-stack-trace.js +6 -10
  14. package/dist/cjs/features/page_view_event/aggregate/initialized-features.js +1 -2
  15. package/dist/cjs/index.js +3 -3
  16. package/dist/cjs/interfaces/registered-entity.js +2 -1
  17. package/dist/cjs/loaders/agent.js +1 -4
  18. package/dist/cjs/loaders/api/register-api-types.js +20 -8
  19. package/dist/cjs/loaders/api/register.js +36 -2
  20. package/dist/cjs/loaders/api-base.js +1 -1
  21. package/dist/cjs/loaders/features/features.js +6 -9
  22. package/dist/esm/common/config/init-types.js +1 -4
  23. package/dist/esm/common/config/init.js +1 -5
  24. package/dist/esm/common/constants/env.cdn.js +1 -1
  25. package/dist/esm/common/constants/env.npm.js +1 -1
  26. package/dist/esm/common/util/browser-stack-matchers.js +9 -0
  27. package/dist/esm/common/util/script-tracker.js +179 -0
  28. package/dist/esm/common/wrap/wrap-fetch.js +2 -2
  29. package/dist/esm/common/wrap/wrap-history.js +2 -2
  30. package/dist/esm/common/wrap/wrap-logger.js +2 -2
  31. package/dist/esm/common/wrap/wrap-xhr.js +2 -2
  32. package/dist/esm/features/jserrors/aggregate/compute-stack-trace.js +2 -6
  33. package/dist/esm/features/page_view_event/aggregate/initialized-features.js +1 -2
  34. package/dist/esm/index.js +2 -2
  35. package/dist/esm/interfaces/registered-entity.js +2 -1
  36. package/dist/esm/loaders/agent.js +1 -4
  37. package/dist/esm/loaders/api/register-api-types.js +22 -8
  38. package/dist/esm/loaders/api/register.js +36 -2
  39. package/dist/esm/loaders/api-base.js +1 -1
  40. package/dist/esm/loaders/features/features.js +6 -9
  41. package/dist/tsconfig.tsbuildinfo +1 -1
  42. package/dist/types/common/config/init-types.d.ts +0 -10
  43. package/dist/types/common/config/init.d.ts.map +1 -1
  44. package/dist/types/common/util/browser-stack-matchers.d.ts +10 -0
  45. package/dist/types/common/util/browser-stack-matchers.d.ts.map +1 -0
  46. package/dist/types/common/util/script-tracker.d.ts +7 -0
  47. package/dist/types/common/util/script-tracker.d.ts.map +1 -0
  48. package/dist/types/features/jserrors/aggregate/compute-stack-trace.d.ts.map +1 -1
  49. package/dist/types/features/page_view_event/aggregate/initialized-features.d.ts.map +1 -1
  50. package/dist/types/index.d.ts +1 -1
  51. package/dist/types/interfaces/registered-entity.d.ts +4 -5
  52. package/dist/types/interfaces/registered-entity.d.ts.map +1 -1
  53. package/dist/types/loaders/agent.d.ts.map +1 -1
  54. package/dist/types/loaders/api/register-api-types.d.ts +58 -14
  55. package/dist/types/loaders/api/register-api-types.d.ts.map +1 -1
  56. package/dist/types/loaders/api/register.d.ts.map +1 -1
  57. package/dist/types/loaders/api-base.d.ts +4 -5
  58. package/dist/types/loaders/api-base.d.ts.map +1 -1
  59. package/dist/types/loaders/features/features.d.ts +2 -5
  60. package/dist/types/loaders/features/features.d.ts.map +1 -1
  61. package/package.json +1 -9
  62. package/src/common/config/init-types.js +1 -4
  63. package/src/common/config/init.js +1 -2
  64. package/src/common/util/browser-stack-matchers.js +9 -0
  65. package/src/common/util/script-tracker.js +171 -0
  66. package/src/common/wrap/wrap-fetch.js +2 -2
  67. package/src/common/wrap/wrap-history.js +2 -2
  68. package/src/common/wrap/wrap-logger.js +2 -2
  69. package/src/common/wrap/wrap-xhr.js +2 -2
  70. package/src/features/jserrors/aggregate/compute-stack-trace.js +2 -7
  71. package/src/features/page_view_event/aggregate/initialized-features.js +1 -2
  72. package/src/index.js +2 -2
  73. package/src/interfaces/registered-entity.js +2 -1
  74. package/src/loaders/agent.js +0 -4
  75. package/src/loaders/api/register-api-types.js +22 -8
  76. package/src/loaders/api/register.js +31 -2
  77. package/src/loaders/api-base.js +1 -1
  78. package/src/loaders/features/features.js +6 -9
  79. package/dist/cjs/common/wrap/wrap-jsonp.js +0 -137
  80. package/dist/cjs/common/wrap/wrap-mutation.js +0 -62
  81. package/dist/cjs/common/wrap/wrap-promise.js +0 -166
  82. package/dist/cjs/common/wrap/wrap-timer.js +0 -71
  83. package/dist/cjs/features/spa/aggregate/index.js +0 -672
  84. package/dist/cjs/features/spa/aggregate/interaction-node.js +0 -83
  85. package/dist/cjs/features/spa/aggregate/interaction.js +0 -95
  86. package/dist/cjs/features/spa/aggregate/serializer.js +0 -150
  87. package/dist/cjs/features/spa/constants.js +0 -35
  88. package/dist/cjs/features/spa/index.js +0 -12
  89. package/dist/cjs/features/spa/instrument/index.js +0 -136
  90. package/dist/esm/common/wrap/wrap-jsonp.js +0 -130
  91. package/dist/esm/common/wrap/wrap-mutation.js +0 -55
  92. package/dist/esm/common/wrap/wrap-promise.js +0 -159
  93. package/dist/esm/common/wrap/wrap-timer.js +0 -64
  94. package/dist/esm/features/spa/aggregate/index.js +0 -663
  95. package/dist/esm/features/spa/aggregate/interaction-node.js +0 -77
  96. package/dist/esm/features/spa/aggregate/interaction.js +0 -88
  97. package/dist/esm/features/spa/aggregate/serializer.js +0 -142
  98. package/dist/esm/features/spa/constants.js +0 -28
  99. package/dist/esm/features/spa/index.js +0 -5
  100. package/dist/esm/features/spa/instrument/index.js +0 -129
  101. package/dist/types/common/wrap/wrap-jsonp.d.ts +0 -16
  102. package/dist/types/common/wrap/wrap-jsonp.d.ts.map +0 -1
  103. package/dist/types/common/wrap/wrap-mutation.d.ts +0 -16
  104. package/dist/types/common/wrap/wrap-mutation.d.ts.map +0 -1
  105. package/dist/types/common/wrap/wrap-promise.d.ts +0 -17
  106. package/dist/types/common/wrap/wrap-promise.d.ts.map +0 -1
  107. package/dist/types/common/wrap/wrap-timer.d.ts +0 -17
  108. package/dist/types/common/wrap/wrap-timer.d.ts.map +0 -1
  109. package/dist/types/features/spa/aggregate/index.d.ts +0 -24
  110. package/dist/types/features/spa/aggregate/index.d.ts.map +0 -1
  111. package/dist/types/features/spa/aggregate/interaction-node.d.ts +0 -15
  112. package/dist/types/features/spa/aggregate/interaction-node.d.ts.map +0 -1
  113. package/dist/types/features/spa/aggregate/interaction.d.ts +0 -19
  114. package/dist/types/features/spa/aggregate/interaction.d.ts.map +0 -1
  115. package/dist/types/features/spa/aggregate/serializer.d.ts +0 -17
  116. package/dist/types/features/spa/aggregate/serializer.d.ts.map +0 -1
  117. package/dist/types/features/spa/constants.d.ts +0 -23
  118. package/dist/types/features/spa/constants.d.ts.map +0 -1
  119. package/dist/types/features/spa/index.d.ts +0 -2
  120. package/dist/types/features/spa/index.d.ts.map +0 -1
  121. package/dist/types/features/spa/instrument/index.d.ts +0 -12
  122. package/dist/types/features/spa/instrument/index.d.ts.map +0 -1
  123. package/src/common/wrap/wrap-jsonp.js +0 -142
  124. package/src/common/wrap/wrap-mutation.js +0 -58
  125. package/src/common/wrap/wrap-promise.js +0 -176
  126. package/src/common/wrap/wrap-timer.js +0 -70
  127. package/src/features/spa/aggregate/index.js +0 -734
  128. package/src/features/spa/aggregate/interaction-node.js +0 -85
  129. package/src/features/spa/aggregate/interaction.js +0 -108
  130. package/src/features/spa/aggregate/serializer.js +0 -200
  131. package/src/features/spa/constants.js +0 -39
  132. package/src/features/spa/index.js +0 -5
  133. package/src/features/spa/instrument/index.js +0 -134
@@ -1,11 +1,11 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
 
6
6
  /**
7
7
  * @file Wraps `pushState` and `replaceState` methods of `window.history` object for instrumentation.
8
- * This module is used by: session_trace, spa.
8
+ * This module is used by: session_trace, soft_navigations.
9
9
  */
10
10
  import { ee as globalEE } from '../event-emitter/contextual-ee'
11
11
  import { createWrapperWithEmitter as wfn } from './wrap-function'
@@ -1,11 +1,11 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
 
6
6
  /**
7
7
  * @file Wraps native timeout and interval methods for instrumentation.
8
- * This module is used by: jserrors, spa.
8
+ * This module is used by: logging.
9
9
  */
10
10
 
11
11
  import { ee as baseEE, contextId } from '../event-emitter/contextual-ee'
@@ -1,11 +1,11 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
 
6
6
  /**
7
7
  * @file Wraps AJAX (XHR) related methods for instrumentation.
8
- * This module is used by: ajax, jserrors, spa.
8
+ * This module is used by: ajax, jserrors.
9
9
  */
10
10
 
11
11
  import { wrapEvents } from './wrap-events'
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
 
@@ -58,15 +58,10 @@
58
58
  // ex.name = ReferenceError
59
59
  import { formatStackTrace } from './format-stack-trace'
60
60
  import { canonicalizeUrl } from '../../../common/url/canonicalize-url'
61
+ import { chrome, chromeEval, classNameRegex, gecko, ieEval } from '../../../common/util/browser-stack-matchers'
61
62
 
62
63
  var debug = false
63
64
 
64
- var classNameRegex = /function (.+?)\s*\(/
65
- var chrome = /^\s*at (?:((?:\[object object\])?(?:[^(]*\([^)]*\))*[^()]*(?: \[as \S+\])?) )?\(?((?:file|http|https|chrome-extension):.*?)?:(\d+)(?::(\d+))?\)?\s*$/i
66
- var gecko = /^\s*(?:(\S*|global code)(?:\(.*?\))?@)?((?:file|http|https|chrome|safari-extension).*?):(\d+)(?::(\d+))?\s*$/i
67
- var chromeEval = /^\s*at .+ \(eval at \S+ \((?:(?:file|http|https):[^)]+)?\)(?:, [^:]*:\d+:\d+)?\)$/i
68
- var ieEval = /^\s*at Function code \(Function code:\d+:\d+\)\s*/i
69
-
70
65
  /**
71
66
  * Represents an error with a stack trace.
72
67
  * @typedef {Object} StackInfo
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { FEATURE_NAMES } from '../../../loaders/features/features'
@@ -27,7 +27,6 @@ export function getActivatedFeaturesFlags (agentId) {
27
27
  case FEATURE_NAMES.sessionTrace:
28
28
  flagArr.push('stn'); break
29
29
  case FEATURE_NAMES.softNav:
30
- case FEATURE_NAMES.spa:
31
30
  flagArr.push('spa'); break
32
31
  }
33
32
  })
package/src/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  export { Agent } from './loaders/agent'
@@ -16,4 +16,4 @@ export { PageViewEvent } from './features/page_view_event'
16
16
  export { PageViewTiming } from './features/page_view_timing'
17
17
  export { SessionTrace } from './features/session_trace'
18
18
  export { SessionReplay } from './features/session_replay'
19
- export { Spa } from './features/spa'
19
+ export { SoftNav } from './features/soft_navigations'
@@ -21,6 +21,7 @@ export class RegisteredEntity {
21
21
  /** @type {RegisterAPIMetadata} */
22
22
  metadata = {
23
23
  target: {},
24
+ timings: {},
24
25
  customAttributes: {}
25
26
  }
26
27
 
@@ -87,7 +88,7 @@ export class RegisteredEntity {
87
88
  * Measures a task that is recorded as a BrowserPerformance event.
88
89
  * {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/measure/}
89
90
  * @param {string} name The name of the task
90
- * @param {{start: number, end: number, duration: number, customAttributes: object}} [options] An object used to control the way the measure API operates
91
+ * @param {{start?: number|PerformanceMark, end?: number|PerformanceMark, customAttributes?: object}} [options] An object used to control the way the measure API operates
91
92
  * @returns {{start: number, end: number, duration: number, customAttributes: object}} Measurement details
92
93
  */
93
94
  measure (name, options) {
@@ -97,10 +97,6 @@ export class Agent extends AgentBase {
97
97
  featuresToStart.sort((a, b) => featurePriority[a.featureName] - featurePriority[b.featureName])
98
98
  featuresToStart.forEach(InstrumentCtor => {
99
99
  if (!enabledFeatures[InstrumentCtor.featureName] && InstrumentCtor.featureName !== FEATURE_NAMES.pageViewEvent) return // PVE is required to run even if it's marked disabled
100
- if (InstrumentCtor.featureName === FEATURE_NAMES.spa) {
101
- warn(67)
102
- return // Skip old SPA
103
- }
104
100
 
105
101
  const dependencies = getFeatureDependencyNames(InstrumentCtor.featureName)
106
102
  const missingDependencies = dependencies.filter(featName => !(featName in this.features)) // any other feature(s) this depends on should've been initialized on prior iterations by priority order
@@ -11,7 +11,7 @@
11
11
  * @property {(target: RegisterAPIConstructor) => RegisterAPI} register - Record a custom event for the registered entity.
12
12
  * @property {() => void} deregister - Deregister the registered entity, which blocks its use and captures end of life timings.
13
13
  * @property {(eventType: string, attributes?: Object) => void} recordCustomEvent - Record a custom event for the registered entity.
14
- * @property {(eventType: string, options?: {start: number, end: number, duration: number, customAttributes: object}) => ({start: number, end: number, duration: number, customAttributes: object})} measure - Measures a task that is recorded as a BrowserPerformance event.
14
+ * @property {(eventType: string, options?: {start?: number|PerformanceMark, end?: number|PerformanceMark, customAttributes?: object}) => ({start: number, end: number, duration: number, customAttributes: object})} measure - Measures a task that is recorded as a BrowserPerformance event.
15
15
  * @property {(value: string | null) => void} setApplicationVersion - Add an application.version attribute to all outgoing data for the registered entity.
16
16
  * @property {(name: string, value: string | number | boolean | null, persist?: boolean) => void} setCustomAttribute - Add a custom attribute to outgoing data for the registered entity.
17
17
  * @property {(value: string | null, resetSession?: boolean) => void} setUserId - Add an enduser.id attribute to all outgoing API data for the registered entity. Note: a registered entity will not be able to initiate a session reset. It must be done from the main agent.
@@ -30,13 +30,27 @@
30
30
  /**
31
31
  * @typedef {Object} RegisterAPIMetadata
32
32
  * @property {Object} customAttributes - The custom attributes for the registered entity.
33
- * @property {Object} target - The options for the registered entity.
34
- * @property {string} [target.licenseKey] - The license key for the registered entity. If none was supplied, it will assume the license key from the main agent.
35
- * @property {string} target.id - The ID for the registered entity.
36
- * @property {string} target.name - The name returned for the registered entity.
37
- * @property {{[key: string]: any}} [target.tags] - The tags for the registered entity as key-value pairs.
38
- * @property {string} [target.parentId] - The parentId for the registered entity. If none was supplied, it will assume the entity guid from the main agent.
39
- * @property {boolean} [target.isolated] - When true, each registration creates an isolated instance. When false, multiple registrations with the same id and isolated: false will share a single instance, including all custom attributes, ids, names, and metadata. Calling deregister on a shared instance will deregister it for all entities using the instance. Defaults to true.
33
+ * @property {RegisterAPITimings} timings - The timing metrics for the registered entity.
34
+ * @property {RegisterAPITarget} target - The options for the registered entity.
35
+ */
36
+
37
+ /**
38
+ * @typedef {Object} RegisterAPITarget
39
+ * @property {string} id - The ID for the registered entity.
40
+ * @property {string} name - The name returned for the registered entity.
41
+ * @property {{[key: string]: any}} [tags] - The tags for the registered entity as key-value pairs.
42
+ * @property {string} [parentId] - The parentId for the registered entity. If none was supplied, it will assume the entity guid from the main agent.
43
+ * @property {boolean} [isolated] - When true, each registration creates an isolated instance. When false, multiple registrations with the same id and isolated: false will share a single instance, including all custom attributes, ids, names, and metadata. Calling deregister on a shared instance will deregister it for all entities using the instance. Defaults to true.
44
+ */
45
+
46
+ /**
47
+ * @typedef {Object} RegisterAPITimings
48
+ * @property {number} registeredAt - The timestamp when the registered entity was created.
49
+ * @property {number} [reportedAt] - The timestamp when the registered entity was deregistered.
50
+ * @property {number} fetchStart - The timestamp when the registered entity began fetching.
51
+ * @property {number} fetchEnd - The timestamp when the registered entity finished fetching.
52
+ * @property {Object} [asset] - The asset path (if found) for the registered entity.
53
+ * @property {string} type - The type of timing associated with the registered entity, 'script' or 'link' if found with the performance resource API, 'inline' if found to be associated with the root document URL, or 'unknown' if no associated resource could be found.
40
54
  */
41
55
 
42
56
  export default {}
@@ -16,6 +16,8 @@ import { noticeError } from './noticeError'
16
16
  import { single } from '../../common/util/invoke'
17
17
  import { measure } from './measure'
18
18
  import { recordCustomEvent } from './recordCustomEvent'
19
+ import { subscribeToPageUnload } from '../../common/window/page-visibility'
20
+ import { findScriptTimings } from '../../common/util/script-tracker'
19
21
 
20
22
  /**
21
23
  * @typedef {import('./register-api-types').RegisterAPI} RegisterAPI
@@ -52,6 +54,8 @@ function register (agentRef, target, parent) {
52
54
  target.parent = parent || {}
53
55
  if (typeof target.tags !== 'object' || target.tags === null || Array.isArray(target.tags)) target.tags = {}
54
56
 
57
+ const timings = findScriptTimings()
58
+
55
59
  const attrs = {}
56
60
 
57
61
  // Process tags object and add to attrs, excluding protected keys
@@ -96,6 +100,7 @@ function register (agentRef, target, parent) {
96
100
  addPageAction: (name, attributes = {}) => report(addPageAction, [name, { ...attrs, ...attributes }, agentRef], target),
97
101
  deregister: () => {
98
102
  /** note: blocking this instance will disable access for all entities sharing the instance, and will invalidate it from the v2 checks */
103
+ reportTimings()
99
104
  block(single(() => warn(68)))
100
105
  },
101
106
  log: (message, options = {}) => report(log, [message, { ...options, customAttributes: { ...attrs, ...(options.customAttributes || {}) } }, agentRef], target),
@@ -109,7 +114,8 @@ function register (agentRef, target, parent) {
109
114
  /** metadata */
110
115
  metadata: {
111
116
  customAttributes: attrs,
112
- target
117
+ target,
118
+ timings
113
119
  }
114
120
  }
115
121
 
@@ -123,7 +129,30 @@ function register (agentRef, target, parent) {
123
129
  }
124
130
 
125
131
  /** only allow registered APIs to be tracked in the agent runtime */
126
- if (!isBlocked()) registeredEntities.push(api)
132
+ if (!isBlocked()) {
133
+ registeredEntities.push(api)
134
+ subscribeToPageUnload(reportTimings)
135
+ }
136
+
137
+ /**
138
+ * Reports the gathered timings for the registered entity through a custom event to the container agent. Only reports once
139
+ * by checking for the presence of the reportedAt timing.
140
+ * @returns {void}
141
+ */
142
+ function reportTimings () {
143
+ // only ever report the timings the first time this is called
144
+ if (timings.reportedAt) return
145
+ timings.reportedAt = now()
146
+ api.recordCustomEvent('MicroFrontEndTiming', {
147
+ assetUrl: timings.asset, // the url of the script that was registered, or undefined if it could not be determined (inline or no match)
148
+ assetType: timings.type, // the type of asset that was associated with the timings, one of 'script', 'link' (if preloaded and found in the resource timing buffer), 'preload' (if preloaded but not found in the resource timing buffer), or "unknown" if it could not be determined
149
+ timeToLoad: timings.registeredAt - timings.fetchStart, // fetchStart to registeredAt
150
+ timeToBeRequested: timings.fetchStart, // origin to fetchStart
151
+ timeToFetch: timings.fetchEnd - timings.fetchStart, // fetchStart to fetchEnd
152
+ timeToRegister: timings.registeredAt - timings.fetchEnd, // fetchEnd to registeredAt
153
+ timeAlive: timings.reportedAt - timings.registeredAt // registeredAt to reportedAt
154
+ })
155
+ }
127
156
 
128
157
  /**
129
158
  * Sets a value local to the registered API attrs. Will do nothing if APIs are deregistered.
@@ -220,7 +220,7 @@ export class ApiBase {
220
220
  * Measures a task that is recorded as a BrowserPerformance event.
221
221
  * {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/measure/}
222
222
  * @param {string} name The name of the task
223
- * @param {{start: number, end: number, duration: number, customAttributes: object}} [options] An object used to control the way the measure API operates
223
+ * @param {{start?: number|PerformanceMark, end?: number|PerformanceMark, customAttributes?: object}} [options] An object used to control the way the measure API operates
224
224
  * @returns {{start: number, end: number, duration: number, customAttributes: object}} Measurement details
225
225
  */
226
226
  measure (name, options) {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
 
@@ -24,8 +24,7 @@ export const FEATURE_NAMES = {
24
24
  pageViewTiming: 'page_view_timing',
25
25
  sessionReplay: 'session_replay',
26
26
  sessionTrace: 'session_trace',
27
- softNav: 'soft_navigations',
28
- spa: 'spa'
27
+ softNav: 'soft_navigations'
29
28
  }
30
29
 
31
30
  /**
@@ -37,20 +36,18 @@ export const featurePriority = {
37
36
  [FEATURE_NAMES.pageViewTiming]: 2,
38
37
  [FEATURE_NAMES.metrics]: 3,
39
38
  [FEATURE_NAMES.jserrors]: 4,
40
- [FEATURE_NAMES.spa]: 5,
39
+ [FEATURE_NAMES.softNav]: 5,
41
40
  [FEATURE_NAMES.ajax]: 6,
42
41
  [FEATURE_NAMES.sessionTrace]: 7,
43
- [FEATURE_NAMES.softNav]: 8,
44
- [FEATURE_NAMES.sessionReplay]: 9,
45
- [FEATURE_NAMES.logging]: 10,
46
- [FEATURE_NAMES.genericEvents]: 11
42
+ [FEATURE_NAMES.sessionReplay]: 8,
43
+ [FEATURE_NAMES.logging]: 9,
44
+ [FEATURE_NAMES.genericEvents]: 10
47
45
  }
48
46
 
49
47
  export const FEATURE_TO_ENDPOINT = {
50
48
  [FEATURE_NAMES.pageViewEvent]: RUM,
51
49
  [FEATURE_NAMES.pageViewTiming]: EVENTS,
52
50
  [FEATURE_NAMES.ajax]: EVENTS,
53
- [FEATURE_NAMES.spa]: EVENTS,
54
51
  [FEATURE_NAMES.softNav]: EVENTS,
55
52
  [FEATURE_NAMES.metrics]: JSERRORS,
56
53
  [FEATURE_NAMES.jserrors]: JSERRORS,
@@ -1,137 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.scopedEE = scopedEE;
7
- exports.wrapJsonP = wrapJsonP;
8
- var _eventListenerOpts = require("../event-listener/event-listener-opts");
9
- var _contextualEe = require("../event-emitter/contextual-ee");
10
- var _wrapFunction = require("./wrap-function");
11
- var _runtime = require("../constants/runtime");
12
- /**
13
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
14
- * SPDX-License-Identifier: Apache-2.0
15
- */
16
-
17
- /**
18
- * @file Wraps DOM insertion methods which in turn wrap JSONP functions that show up in the DOM.
19
- * This module is used by: spa.
20
- */
21
-
22
- const wrapped = {};
23
- const domInsertMethods = ['appendChild', 'insertBefore', 'replaceChild'];
24
-
25
- /**
26
- * Wraps DOM insertion methods to identify script elements containing JSONP callback functions and instruments those
27
- * functions with custom events in the context of a new event emitter scoped only to JSONP.
28
- * @param {Object} sharedEE - The shared event emitter on which a new scoped event emitter will be based.
29
- * @returns {Object} Scoped event emitter with a debug ID of `jsonp`.
30
- */
31
- function wrapJsonP(sharedEE) {
32
- const ee = scopedEE(sharedEE);
33
-
34
- // Notice if our wrapping never ran yet, the falsy NaN will not early return; but if it has,
35
- // then we increment the count to track # of feats using this at runtime. JSONP deals with DOM
36
- // tags so browser window env is required.
37
- if (!_runtime.isBrowserScope || wrapped[ee.debugId]) return ee;
38
- wrapped[ee.debugId] = true; // otherwise, first feature to wrap JSONP
39
-
40
- var wrapFn = (0, _wrapFunction.createWrapperWithEmitter)(ee);
41
- var CALLBACK_REGEX = /[?&](?:callback|cb)=([^&#]+)/;
42
- var PARENT_REGEX = /(.*)\.([^.]+)/;
43
- var VALUE_REGEX = /^(\w+)(\.|$)(.*)$/;
44
-
45
- // JSONP works by dynamically inserting <script> elements - wrap DOM methods for
46
- // inserting elements to detect insertion of JSONP-specific elements.
47
- wrapFn.inPlace(Node.prototype, domInsertMethods, 'dom-');
48
- ee.on('dom-start', function (args) {
49
- wrapElement(args[0]);
50
- });
51
-
52
- // subscribe to events on the JSONP <script> element and wrap the JSONP callback
53
- // in order to track start and end of the interaction node
54
- function wrapElement(el) {
55
- var isScript = el && typeof el.nodeName === 'string' && el.nodeName.toLowerCase() === 'script';
56
- if (!isScript) return;
57
- var isValidElement = typeof el.addEventListener === 'function';
58
- if (!isValidElement) return;
59
- var callbackName = extractCallbackName(el.src);
60
- if (!callbackName) return;
61
- var callback = discoverParent(callbackName);
62
- var validCallback = typeof callback.parent[callback.key] === 'function';
63
- if (!validCallback) return;
64
-
65
- // At this point we know that the element is a valid JSONP script element.
66
- // The following events are emitted during the lifetime of a JSONP call:
67
- // * immediately emit `new-jsonp` to notify start of the JSONP work
68
- // * the wrapped callback will emit `cb-start` and `cb-end` during the execution
69
- // of the callback, here we can inspect the response
70
- // * when the element emits the `load` event (script loaded and executed),
71
- // emit `jsonp-end` to notify end of the JSONP work
72
- // * if the element emits the `error` event, in response emit `jsonp-error`
73
- // (and `jsonp-end`). Note that the callback in this case will likely not get
74
- // called.
75
-
76
- var context = {};
77
- wrapFn.inPlace(callback.parent, [callback.key], 'cb-', context);
78
- el.addEventListener('load', onLoad, (0, _eventListenerOpts.eventListenerOpts)(false));
79
- el.addEventListener('error', onError, (0, _eventListenerOpts.eventListenerOpts)(false));
80
- ee.emit('new-jsonp', [el.src], context);
81
- function onLoad() {
82
- ee.emit('jsonp-end', [], context);
83
- el.removeEventListener('load', onLoad, (0, _eventListenerOpts.eventListenerOpts)(false));
84
- el.removeEventListener('error', onError, (0, _eventListenerOpts.eventListenerOpts)(false));
85
- }
86
- function onError() {
87
- ee.emit('jsonp-error', [], context);
88
- ee.emit('jsonp-end', [], context);
89
- el.removeEventListener('load', onLoad, (0, _eventListenerOpts.eventListenerOpts)(false));
90
- el.removeEventListener('error', onError, (0, _eventListenerOpts.eventListenerOpts)(false));
91
- }
92
- }
93
- function extractCallbackName(src) {
94
- var matches = src.match(CALLBACK_REGEX);
95
- return matches ? matches[1] : null;
96
- }
97
-
98
- /**
99
- * Traverse a nested object using a '.'-delimited string wherein each substring piece maps to each subsequent object property layer.
100
- * @param {string} longKey
101
- * @param {object} obj
102
- * @returns The final nested object referred to by initial longKey.
103
- */
104
- function discoverValue(longKey, obj) {
105
- if (!longKey) return obj; // end of object recursion depth when no more key levels
106
- const matches = longKey.match(VALUE_REGEX);
107
- // if 'longKey' was not undefined, that is it at least had 1 level left, then the regexp would've at least matched 1st group
108
- const key = matches[1];
109
- const remaining = matches[3];
110
- return discoverValue(remaining, obj[key]);
111
- }
112
- function discoverParent(key) {
113
- var matches = key.match(PARENT_REGEX);
114
- if (matches && matches.length >= 3) {
115
- return {
116
- key: matches[2],
117
- parent: discoverValue(matches[1], window)
118
- };
119
- }
120
- return {
121
- key,
122
- parent: window
123
- };
124
- }
125
- return ee;
126
- }
127
-
128
- /**
129
- * Returns an event emitter scoped specifically for the `jsonp` context. This scoping is a remnant from when all the
130
- * features shared the same group in the event, to isolate events between features. It will likely be revisited.
131
- * @param {Object} sharedEE - Optional event emitter on which to base the scoped emitter.
132
- * Uses `ee` on the global scope if undefined).
133
- * @returns {Object} Scoped event emitter with a debug ID of 'jsonp'.
134
- */
135
- function scopedEE(sharedEE) {
136
- return (sharedEE || _contextualEe.ee).get('jsonp');
137
- }
@@ -1,62 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.scopedEE = scopedEE;
7
- exports.wrapMutation = wrapMutation;
8
- var _contextualEe = require("../event-emitter/contextual-ee");
9
- var _wrapFunction = require("./wrap-function");
10
- var _runtime = require("../constants/runtime");
11
- /**
12
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
13
- * SPDX-License-Identifier: Apache-2.0
14
- */
15
-
16
- /**
17
- * @file Wraps the window's DOM mutation observer for instrumentation.
18
- * This module is used by: spa.
19
- */
20
-
21
- const wrapped = {};
22
-
23
- /**
24
- * In web environments only, wraps the `window.MutationObserver` function to emit events on start, end, and error, in
25
- * the context of a new event emitter scoped only to mutations.
26
- * @param {Object} sharedEE - The shared event emitter on which a new scoped event emitter will be based.
27
- * @returns {Object} Scoped event emitter with a debug ID of `mutation`.
28
- */
29
- function wrapMutation(sharedEE) {
30
- const ee = scopedEE(sharedEE);
31
-
32
- // Notice if our wrapping never ran yet, the falsy NaN will not early return; but if it has,
33
- // then we increment the count to track # of feats using this at runtime. Mutations API is only
34
- // available in browser DOM context.
35
- if (!_runtime.isBrowserScope || wrapped[ee.debugId]) return ee;
36
- wrapped[ee.debugId] = true; // otherwise, first feature to wrap mutations
37
-
38
- var wrapFn = (0, _wrapFunction.createWrapperWithEmitter)(ee);
39
- var OriginalObserver = _runtime.globalScope.MutationObserver;
40
- if (OriginalObserver) {
41
- window.MutationObserver = function WrappedMutationObserver(cb) {
42
- if (this instanceof OriginalObserver) {
43
- return new OriginalObserver(wrapFn(cb, 'fn-'));
44
- } else {
45
- return OriginalObserver.apply(this, arguments);
46
- }
47
- };
48
- MutationObserver.prototype = OriginalObserver.prototype;
49
- }
50
- return ee;
51
- }
52
-
53
- /**
54
- * Returns an event emitter scoped specifically for the `mutation` context. This scoping is a remnant from when all the
55
- * features shared the same group in the event, to isolate events between features. It will likely be revisited.
56
- * @param {Object} sharedEE - Optional event emitter on which to base the scoped emitter.
57
- * Uses `ee` on the global scope if undefined).
58
- * @returns {Object} Scoped event emitter with a debug ID of 'mutation'.
59
- */
60
- function scopedEE(sharedEE) {
61
- return (sharedEE || _contextualEe.ee).get('mutation');
62
- }
@@ -1,166 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.scopedEE = scopedEE;
7
- exports.wrapPromise = wrapPromise;
8
- var _wrapFunction = require("./wrap-function");
9
- var _contextualEe = require("../event-emitter/contextual-ee");
10
- var _runtime = require("../constants/runtime");
11
- /**
12
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
13
- * SPDX-License-Identifier: Apache-2.0
14
- */
15
-
16
- /**
17
- * @file Wraps the native Promise object for instrumentation.
18
- * This module is used by: spa.
19
- */
20
-
21
- const wrapped = {};
22
-
23
- /**
24
- * Wraps the native Promise object so that it will emit events for start, end and error in the context of a new event
25
- * emitter scoped only to promise methods. Also instruments various methods, such as `all`, `race`, `resolve`,
26
- * `reject`, `then`, and `catch`.
27
- * @param {Object} sharedEE - The shared event emitter on which a new scoped event emitter will be based.
28
- * @returns {Object} Scoped event emitter with a debug ID of `promise`.
29
- */
30
- function wrapPromise(sharedEE) {
31
- const promiseEE = scopedEE(sharedEE);
32
-
33
- // Notice if our wrapping never ran yet, the falsy NaN will not early return; but if it has,
34
- // then we increment the count to track # of feats using this at runtime.
35
- if (wrapped[promiseEE.debugId]) return promiseEE;
36
- wrapped[promiseEE.debugId] = true; // otherwise, first feature to wrap promise
37
-
38
- var getContext = promiseEE.context;
39
- var promiseWrapper = (0, _wrapFunction.createWrapperWithEmitter)(promiseEE);
40
- var prevPromiseObj = _runtime.globalScope.Promise;
41
- if (prevPromiseObj) {
42
- // ensure there's a Promise API (native or otherwise) to even wrap
43
- wrap();
44
- }
45
- function wrap() {
46
- _runtime.globalScope.Promise = WrappedPromise;
47
-
48
- // Renamed from "WrappedPromise" back to "Promise" & toString() so that we appear "native" to TP libraries...
49
- Object.defineProperty(WrappedPromise, 'name', {
50
- value: 'Promise'
51
- });
52
- WrappedPromise.toString = function () {
53
- return prevPromiseObj.toString();
54
- };
55
-
56
- /**
57
- * This constitutes the global used when calling "Promise.staticMethod" or chaining off a "new Promise()" object.
58
- * @param {Function} executor - to be executed by the original Promise constructor
59
- * @returns A new WrappedPromise object prototyped off the original.
60
- */
61
- function WrappedPromise(executor) {
62
- var ctx = promiseEE.context();
63
- var wrappedExecutor = promiseWrapper(executor, 'executor-', ctx, null, false);
64
- const newCustomPromiseInst = Reflect.construct(prevPromiseObj, [wrappedExecutor], WrappedPromise); // new Promises will use WrappedPromise.prototype as theirs prototype
65
-
66
- promiseEE.context(newCustomPromiseInst).getCtx = function () {
67
- return ctx;
68
- };
69
- return newCustomPromiseInst;
70
- }
71
-
72
- // Make WrappedPromise inherit statics from the orig Promise.
73
- Object.setPrototypeOf(WrappedPromise, prevPromiseObj);
74
- ['all', 'race'].forEach(function (method) {
75
- const prevStaticFn = prevPromiseObj[method];
76
- WrappedPromise[method] = function (subPromises) {
77
- // use our own wrapped version of "Promise.all" and ".race" static fns
78
- let finalized = false;
79
- [...(subPromises || [])].forEach(sub => {
80
- this.resolve(sub).then(setNrId(method === 'all'), setNrId(false));
81
- });
82
- const origFnCallWithThis = prevStaticFn.apply(this, arguments);
83
- return origFnCallWithThis;
84
- function setNrId(overwrite) {
85
- return function () {
86
- promiseEE.emit('propagate', [null, !finalized], origFnCallWithThis, false, false);
87
- finalized = finalized || !overwrite;
88
- };
89
- }
90
- };
91
- });
92
- ['resolve', 'reject'].forEach(function (method) {
93
- const prevStaticFn = prevPromiseObj[method];
94
- WrappedPromise[method] = function (val) {
95
- // and the same for ".resolve" and ".reject"
96
- const origFnCallWithThis = prevStaticFn.apply(this, arguments);
97
- if (val !== origFnCallWithThis) {
98
- promiseEE.emit('propagate', [val, true], origFnCallWithThis, false, false);
99
- }
100
- return origFnCallWithThis;
101
- };
102
- });
103
-
104
- /*
105
- * Ideally, we create a new WrappedPromise.prototype chained off the original Promise's so that we don't alter it.
106
- * However, there's no way to make the (native) promise returned from async functions use our WrappedPromise,
107
- * so we have to modify the original prototype. This ensures that promises returned from async functions execute
108
- * the same instance methods as promises created with "new Promise()", and also that instanceof async() is
109
- * the global Promise (see GH issue #409). This also affects the promise returned from fetch().
110
- */
111
- WrappedPromise.prototype = prevPromiseObj.prototype;
112
-
113
- // Note that this wrapping affects the same originals.PR (prototype) object.
114
- const prevPromiseOrigThen = prevPromiseObj.prototype.then;
115
- prevPromiseObj.prototype.then = function wrappedThen(...args) {
116
- var originalThis = this;
117
- var ctx = getContext(originalThis);
118
- ctx.promise = originalThis;
119
- args[0] = promiseWrapper(args[0], 'cb-', ctx, null, false);
120
- args[1] = promiseWrapper(args[1], 'cb-', ctx, null, false);
121
- const origFnCallWithThis = prevPromiseOrigThen.apply(this, args);
122
- ctx.nextPromise = origFnCallWithThis;
123
- promiseEE.emit('propagate', [originalThis, true], origFnCallWithThis, false, false);
124
- return origFnCallWithThis;
125
- };
126
- prevPromiseObj.prototype.then[_wrapFunction.flag] = prevPromiseOrigThen;
127
- promiseEE.on('executor-start', function (args) {
128
- args[0] = promiseWrapper(args[0], 'resolve-', this, null, false);
129
- args[1] = promiseWrapper(args[1], 'resolve-', this, null, false);
130
- });
131
- promiseEE.on('executor-err', function (args, originalThis, err) {
132
- args[1](err);
133
- });
134
- promiseEE.on('cb-end', function (args, originalThis, result) {
135
- promiseEE.emit('propagate', [result, true], this.nextPromise, false, false);
136
- });
137
- promiseEE.on('propagate', function (val, overwrite, trigger) {
138
- if (!this.getCtx || overwrite) {
139
- const selfStore = this;
140
- const parentStore = val instanceof Promise ? promiseEE.context(val) : null;
141
- let cachedCtx;
142
- this.getCtx = function getCtx() {
143
- if (cachedCtx) return cachedCtx;
144
- if (parentStore && parentStore !== selfStore) {
145
- cachedCtx = typeof parentStore.getCtx === 'function' ? parentStore.getCtx() : parentStore;
146
- } else {
147
- cachedCtx = selfStore;
148
- }
149
- return cachedCtx;
150
- };
151
- }
152
- });
153
- }
154
- return promiseEE;
155
- }
156
-
157
- /**
158
- * Returns an event emitter scoped specifically for the `promise` context. This scoping is a remnant from when all the
159
- * features shared the same group in the event, to isolate events between features. It will likely be revisited.
160
- * @param {Object} sharedEE - Optional event emitter on which to base the scoped emitter.
161
- * Uses `ee` on the global scope if undefined).
162
- * @returns {Object} Scoped event emitter with a debug ID of 'promise'.
163
- */
164
- function scopedEE(sharedEE) {
165
- return (sharedEE || _contextualEe.ee).get('promise');
166
- }