@newrelic/browser-agent 1.312.1 → 1.313.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 (104) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cjs/common/config/init-types.js +3 -2
  3. package/dist/cjs/common/config/init.js +10 -8
  4. package/dist/cjs/common/constants/env.cdn.js +1 -1
  5. package/dist/cjs/common/constants/env.npm.js +1 -1
  6. package/dist/cjs/common/timing/time-keeper.js +28 -1
  7. package/dist/cjs/common/util/short-circuit.js +13 -0
  8. package/dist/cjs/common/util/submit-data.js +2 -9
  9. package/dist/cjs/common/v2/script-correlation.js +50 -0
  10. package/dist/cjs/common/v2/script-tracker.js +278 -0
  11. package/dist/cjs/common/{util/v2.js → v2/utils.js} +4 -4
  12. package/dist/cjs/common/wrap/wrap-fetch.js +2 -2
  13. package/dist/cjs/common/wrap/wrap-function.js +2 -2
  14. package/dist/cjs/common/wrap/wrap-xhr.js +2 -2
  15. package/dist/cjs/features/ajax/aggregate/index.js +4 -4
  16. package/dist/cjs/features/generic_events/aggregate/index.js +21 -2
  17. package/dist/cjs/features/generic_events/instrument/index.js +24 -21
  18. package/dist/cjs/features/jserrors/aggregate/index.js +206 -40
  19. package/dist/cjs/features/logging/aggregate/index.js +4 -4
  20. package/dist/cjs/features/metrics/instrument/index.js +1 -8
  21. package/dist/cjs/features/session_trace/aggregate/index.js +3 -4
  22. package/dist/cjs/features/session_trace/aggregate/trace/storage.js +2 -2
  23. package/dist/cjs/interfaces/registered-entity.js +7 -20
  24. package/dist/cjs/loaders/api/register-api-types.js +8 -8
  25. package/dist/cjs/loaders/api/register.js +49 -43
  26. package/dist/esm/common/config/init-types.js +3 -2
  27. package/dist/esm/common/config/init.js +10 -8
  28. package/dist/esm/common/constants/env.cdn.js +1 -1
  29. package/dist/esm/common/constants/env.npm.js +1 -1
  30. package/dist/esm/common/timing/time-keeper.js +28 -1
  31. package/dist/esm/common/util/short-circuit.js +6 -0
  32. package/dist/esm/common/util/submit-data.js +2 -9
  33. package/dist/esm/common/v2/script-correlation.js +43 -0
  34. package/dist/esm/common/v2/script-tracker.js +270 -0
  35. package/dist/esm/common/{util/v2.js → v2/utils.js} +4 -4
  36. package/dist/esm/common/wrap/wrap-fetch.js +1 -1
  37. package/dist/esm/common/wrap/wrap-function.js +1 -1
  38. package/dist/esm/common/wrap/wrap-xhr.js +1 -1
  39. package/dist/esm/features/ajax/aggregate/index.js +1 -1
  40. package/dist/esm/features/generic_events/aggregate/index.js +20 -1
  41. package/dist/esm/features/generic_events/instrument/index.js +24 -21
  42. package/dist/esm/features/jserrors/aggregate/index.js +205 -39
  43. package/dist/esm/features/logging/aggregate/index.js +1 -1
  44. package/dist/esm/features/metrics/instrument/index.js +2 -9
  45. package/dist/esm/features/session_trace/aggregate/index.js +4 -5
  46. package/dist/esm/features/session_trace/aggregate/trace/storage.js +2 -2
  47. package/dist/esm/interfaces/registered-entity.js +7 -20
  48. package/dist/esm/loaders/api/register-api-types.js +8 -8
  49. package/dist/esm/loaders/api/register.js +46 -41
  50. package/dist/tsconfig.tsbuildinfo +1 -1
  51. package/dist/types/common/config/init-types.d.ts +10 -8
  52. package/dist/types/common/config/init-types.d.ts.map +1 -1
  53. package/dist/types/common/config/init.d.ts.map +1 -1
  54. package/dist/types/common/timing/time-keeper.d.ts.map +1 -1
  55. package/dist/types/common/util/short-circuit.d.ts +8 -0
  56. package/dist/types/common/util/short-circuit.d.ts.map +1 -0
  57. package/dist/types/common/util/submit-data.d.ts.map +1 -1
  58. package/dist/types/common/v2/script-correlation.d.ts +38 -0
  59. package/dist/types/common/v2/script-correlation.d.ts.map +1 -0
  60. package/dist/types/common/{util → v2}/script-tracker.d.ts +3 -0
  61. package/dist/types/common/v2/script-tracker.d.ts.map +1 -0
  62. package/dist/types/common/{util/v2.d.ts → v2/utils.d.ts} +1 -1
  63. package/dist/types/common/v2/utils.d.ts.map +1 -0
  64. package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
  65. package/dist/types/features/generic_events/instrument/index.d.ts +1 -1
  66. package/dist/types/features/generic_events/instrument/index.d.ts.map +1 -1
  67. package/dist/types/features/jserrors/aggregate/index.d.ts +25 -16
  68. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  69. package/dist/types/features/metrics/instrument/index.d.ts.map +1 -1
  70. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  71. package/dist/types/interfaces/registered-entity.d.ts +0 -10
  72. package/dist/types/interfaces/registered-entity.d.ts.map +1 -1
  73. package/dist/types/loaders/api/register-api-types.d.ts +15 -15
  74. package/dist/types/loaders/api/register-api-types.d.ts.map +1 -1
  75. package/dist/types/loaders/api/register.d.ts +6 -0
  76. package/dist/types/loaders/api/register.d.ts.map +1 -1
  77. package/package.json +1 -1
  78. package/src/common/config/init-types.js +3 -2
  79. package/src/common/config/init.js +6 -4
  80. package/src/common/timing/time-keeper.js +30 -1
  81. package/src/common/util/short-circuit.js +6 -0
  82. package/src/common/util/submit-data.js +2 -8
  83. package/src/common/v2/script-correlation.js +44 -0
  84. package/src/common/v2/script-tracker.js +260 -0
  85. package/src/common/{util/v2.js → v2/utils.js} +4 -4
  86. package/src/common/wrap/wrap-fetch.js +1 -1
  87. package/src/common/wrap/wrap-function.js +1 -1
  88. package/src/common/wrap/wrap-xhr.js +1 -1
  89. package/src/features/ajax/aggregate/index.js +1 -1
  90. package/src/features/generic_events/aggregate/index.js +20 -1
  91. package/src/features/generic_events/instrument/index.js +25 -22
  92. package/src/features/jserrors/aggregate/index.js +200 -43
  93. package/src/features/logging/aggregate/index.js +1 -1
  94. package/src/features/metrics/instrument/index.js +2 -13
  95. package/src/features/session_trace/aggregate/index.js +4 -6
  96. package/src/features/session_trace/aggregate/trace/storage.js +2 -2
  97. package/src/interfaces/registered-entity.js +8 -20
  98. package/src/loaders/api/register-api-types.js +8 -8
  99. package/src/loaders/api/register.js +42 -34
  100. package/dist/cjs/common/util/script-tracker.js +0 -204
  101. package/dist/esm/common/util/script-tracker.js +0 -196
  102. package/dist/types/common/util/script-tracker.d.ts.map +0 -1
  103. package/dist/types/common/util/v2.d.ts.map +0 -1
  104. package/src/common/util/script-tracker.js +0 -189
@@ -19,8 +19,9 @@ import { AggregateBase } from '../../utils/aggregate-base'
19
19
  import { now } from '../../../common/timing/now'
20
20
  import { applyFnToProps } from '../../../common/util/traverse'
21
21
  import { evaluateInternalError } from './internal-errors'
22
- import { getVersion2Attributes } from '../../../common/util/v2'
22
+ import { getRegisteredTargetsFromFilename, getVersion2Attributes, getVersion2DuplicationAttributes, shouldDuplicate } from '../../../common/v2/utils'
23
23
  import { buildCauseString } from './cause-string'
24
+ import { ShortCircuit } from '../../../common/util/short-circuit'
24
25
 
25
26
  /**
26
27
  * @typedef {import('./compute-stack-trace.js').StackInfo} StackInfo
@@ -38,8 +39,8 @@ export class Aggregate extends AggregateBase {
38
39
  this.pageviewReported = {}
39
40
  this.errorOnPage = false
40
41
 
41
- register('err', (...args) => this.storeError(...args), this.featureName, this.ee)
42
- register('ierr', (...args) => this.storeError(...args), this.featureName, this.ee)
42
+ register('err', this.processError.bind(this), this.featureName, this.ee)
43
+ register('ierr', this.processError.bind(this), this.featureName, this.ee)
43
44
  register('returnJserror', (jsErrorEvent, softNavAttrs) => this.#storeJserrorForHarvest(jsErrorEvent, softNavAttrs), this.featureName, this.ee)
44
45
 
45
46
  // 0 == off, 1 == on
@@ -74,43 +75,67 @@ export class Aggregate extends AggregateBase {
74
75
  }
75
76
 
76
77
  /**
77
- * Builds a standardized stack trace string from the frames in the given `stackInfo` object, with each frame separated
78
- * by a newline character. Lines take the form `<functionName>@<url>:<lineNumber>`.
78
+ * Main entry point for processing JavaScript errors. This method orchestrates the complete error processing pipeline.
79
79
  *
80
- * @param {StackInfo} stackInfo - An object specifying a stack string and individual frames.
81
- * @returns {string} A canonical stack string built from the URLs and function names in the given `stackInfo` object.
80
+ * Processing Flow:
81
+ * 1. Filter the error through the customer's onerror handler (if configured and not internal)
82
+ * 2. Compute the stack trace from the error object
83
+ * 3. Evaluate if the error should be swallowed (internal errors, known issues, etc.)
84
+ * 4. Derive target(s) for the error (MFE detection for v2 endpoints, or default target) - Note: "undefined" indicates the default target will be used
85
+ * 5. Store the error for each derived target. During storage (#storeJserrorForHarvest), duplication for MFE <-> container will be handled
86
+ *
87
+ * Important: "ShortCircuit" Pattern:
88
+ * Several steps in the pipeline can throw a ShortCircuit error to halt processing without
89
+ * treating it as a reportable error. This pattern is used when:
90
+ * - The customer's onerror handler returns a truthy value (excluding fingerprinting objects)
91
+ * - The error is identified as an internal error that shouldn't be reported
92
+ *
93
+ * When a ShortCircuit is thrown, processing stops immediately and the error is not stored.
94
+ * Any other thrown error is re-thrown as it represents an actual problem in the agent code.
95
+ *
96
+ * @param {Error|UncaughtError} err - The error instance to be processed
97
+ * @param {number} [time] - The relative ms (to origin) timestamp of occurrence. Defaults to now()
98
+ * @param {boolean} [internal=false] - If the error was "caught" and deemed "internal" before reporting to the jserrors feature
99
+ * @param {object} [customAttributes] - Any custom attributes to be included in the error payload
100
+ * @param {boolean} [hasReplay=false] - A flag indicating if the error occurred during a replay session
101
+ * @param {string} [swallowReason] - A string indicating pre-defined reason if swallowing the error. Mainly used by internal error supportability metrics
102
+ * @param {object} [target] - The target to buffer and harvest to. If undefined, the default configuration target is used
103
+ * @returns {void}
82
104
  */
83
- buildCanonicalStackString (stackInfo) {
84
- var canonicalStackString = ''
85
-
86
- for (var i = 0; i < stackInfo.frames.length; i++) {
87
- var frame = stackInfo.frames[i]
88
- var func = canonicalFunctionName(frame.func)
89
-
90
- if (canonicalStackString) canonicalStackString += '\n'
91
- if (func) canonicalStackString += func + '@'
92
- if (typeof frame.url === 'string') canonicalStackString += frame.url
93
- if (frame.line) canonicalStackString += ':' + frame.line
105
+ processError (err, time, internal, customAttributes, hasReplay, swallowReason, target) {
106
+ if (!err) return
107
+ time = time || now()
108
+ try {
109
+ const filterOutput = this.#filterError(err, internal)
110
+ const stackInfo = computeStackTrace(err)
111
+ this.#swallowError(stackInfo, internal, swallowReason)
112
+ this.#deriveTargets(stackInfo, target).forEach(target => {
113
+ this.#storeError(err, time, stackInfo, filterOutput, customAttributes, hasReplay, target)
114
+ })
115
+ } catch (e) {
116
+ if (!(e instanceof ShortCircuit)) {
117
+ throw e
118
+ }
94
119
  }
95
-
96
- return canonicalStackString
97
120
  }
98
121
 
99
122
  /**
123
+ * Filters an error through the customer's configured onerror handler.
100
124
  *
101
- * @param {Error|UncaughtError} err The error instance to be processed
102
- * @param {number} time the relative ms (to origin) timestamp of occurrence
103
- * @param {boolean=} internal if the error was "caught" and deemed "internal" before reporting to the jserrors feature
104
- * @param {object=} customAttributes any custom attributes to be included in the error payload
105
- * @param {boolean=} hasReplay a flag indicating if the error occurred during a replay session
106
- * @param {string=} swallowReason a string indicating pre-defined reason if swallowing the error. Mainly used by the internal error SMs.
107
- * @param {object=} target the target to buffer and harvest to, if undefined the default configuration target is used
108
- * @returns
125
+ * If the customer has configured a custom onerror handler and the error is not internal,
126
+ * this method invokes that handler. The handler's return value determines whether the error
127
+ * should be reported:
128
+ * - Falsey values (false, null, undefined, etc.) Report the error normally
129
+ * - Truthy non-object values Don't report (throws ShortCircuit)
130
+ * - Object with 'group' property (non-empty string) Report with fingerprinting label
131
+ * - Any other truthy value Don't report (throws ShortCircuit)
132
+ *
133
+ * @param {Error|UncaughtError} err - The error to filter
134
+ * @param {boolean} internal - Whether this is an internal error (internal errors skip filtering)
135
+ * @returns {undefined|object} The filter output. If an object with 'group' property, contains fingerprinting data
136
+ * @throws {ShortCircuit} When the error should not be reported based on the filter output
109
137
  */
110
- storeError (err, time, internal, customAttributes, hasReplay, swallowReason, target) {
111
- if (!err) return
112
- // are we in an interaction
113
- time = time || now()
138
+ #filterError (err, internal) {
114
139
  let filterOutput
115
140
 
116
141
  if (!internal && this.agentRef.runtime.onerror) {
@@ -118,20 +143,92 @@ export class Aggregate extends AggregateBase {
118
143
  if (filterOutput && !(typeof filterOutput.group === 'string' && filterOutput.group.length)) {
119
144
  // All truthy values mean don't report (store) the error, per backwards-compatible usage,
120
145
  // - EXCEPT if a fingerprinting label is returned, via an object with key of 'group' and value of non-empty string
121
- return
146
+ throw new ShortCircuit()
122
147
  }
123
148
  // Again as with previous usage, all falsey values would include the error.
124
149
  }
150
+ return filterOutput
151
+ }
125
152
 
126
- var stackInfo = computeStackTrace(err)
127
-
153
+ /**
154
+ * Evaluates whether an error should be swallowed (not reported) based on internal error criteria.
155
+ *
156
+ * This method uses the evaluateInternalError function to determine if the error matches known
157
+ * internal error patterns (e.g., errors from the agent itself, known browser issues, etc.).
158
+ * If the error should be swallowed, a supportability metric is recorded and processing is halted.
159
+ *
160
+ * @param {StackInfo} stackInfo - The computed stack trace information
161
+ * @param {boolean} internal - Whether the error was marked as internal
162
+ * @param {string} [swallowReason] - Optional pre-determined reason for swallowing
163
+ * @returns {void}
164
+ * @throws {ShortCircuit} When the error should be swallowed and not reported
165
+ */
166
+ #swallowError (stackInfo, internal, swallowReason) {
128
167
  const { shouldSwallow, reason } = evaluateInternalError(stackInfo, internal, swallowReason)
129
168
  if (shouldSwallow) {
130
169
  this.reportSupportabilityMetric('Internal/Error/' + reason)
131
- return
170
+ throw new ShortCircuit()
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Derives the appropriate targets for reporting the given stack information.
176
+ *
177
+ * Targets represent the entities that should receive the error data. This is particularly
178
+ * important for Micro Frontend (MFE) scenarios where errors may need to be reported to
179
+ * different applications.
180
+ *
181
+ * Logic:
182
+ * - If a target is explicitly provided (e.g., from the register API), use it
183
+ * - For v2 endpoints without an explicit target, scan stack frames to detect MFE sources
184
+ * - If no MFE is detected or v2 is not enabled, use undefined (default target)
185
+ *
186
+ * @param {StackInfo} stackInfo - The computed stack trace information containing frames
187
+ * @param {object} [target] - Explicitly provided target, typically from the register API
188
+ * @returns {Array<object|undefined>} Array of targets to report the error to. Always contains at least one element.
189
+ */
190
+ #deriveTargets (stackInfo, target) {
191
+ const targets = []
192
+ if (target) {
193
+ // reported by the register API directly
194
+ targets.push(target)
195
+ } else {
196
+ // we dont know if this is MFE yet, we need to figure it out.
197
+ if (this.harvestEndpointVersion === 2) {
198
+ for (const frame of stackInfo.frames) {
199
+ targets.push(...getRegisteredTargetsFromFilename(frame.url, this.agentRef))
200
+ if (targets.length) break
201
+ }
202
+ }
203
+ if (!targets.length) targets.push(undefined)
132
204
  }
205
+ return targets
206
+ }
133
207
 
134
- var canonicalStackString = this.buildCanonicalStackString(stackInfo)
208
+ /**
209
+ * Stores error data for eventual harvesting and transmission to the backend.
210
+ *
211
+ * This method processes the error through several stages:
212
+ * 1. Build canonical stack string for cross-browser error grouping
213
+ * 2. Build cause chain string if the error has a cause property
214
+ * 3. Create params object with error metadata (stack hash, class, message, etc.)
215
+ * 4. Create bucket hash for internal deduplication
216
+ * 5. Store stack trace on first occurrence of this error
217
+ * 6. Add custom attributes and send to other features (trace, replay)
218
+ * 7. Route through soft nav if enabled, or directly to harvest storage
219
+ * 8. Handle MFE duplication for v2 endpoints if needed
220
+ *
221
+ * @param {Error|UncaughtError} err - The error instance to be processed
222
+ * @param {number} time - The relative ms (to origin) timestamp of occurrence
223
+ * @param {StackInfo} stackInfo - The computed stack trace information
224
+ * @param {object} [filterOutput] - Output from the customer's onerror handler, may contain fingerprinting group
225
+ * @param {object} [customAttributes] - Any custom attributes to be included in the error payload
226
+ * @param {boolean} [hasReplay=false] - A flag indicating if the error occurred during a replay session
227
+ * @param {object} [target] - The target to buffer and harvest to. If undefined, the default configuration target is used
228
+ * @returns {void}
229
+ */
230
+ #storeError (err, time, stackInfo, filterOutput, customAttributes, hasReplay, target) {
231
+ var canonicalStackString = this.#buildCanonicalStackString(stackInfo)
135
232
 
136
233
  const causeStackString = buildCauseString(err)
137
234
 
@@ -154,7 +251,7 @@ export class Aggregate extends AggregateBase {
154
251
  * the canonical stack trace excludes items like the column number increasing the hit-rate of different errors potentially
155
252
  * bucketing and ultimately resulting in the loss of data in NR1.
156
253
  */
157
- var bucketHash = stringHashCode(`${stackInfo.name}_${stackInfo.message}_${stackInfo.stackString}_${params.hasReplay ? 1 : 0}_${target?.id || 'container'}`)
254
+ var bucketHash = stringHashCode(`${stackInfo.name}_${stackInfo.message}_${stackInfo.stackString}_${params.hasReplay ? 1 : 0}_${target?.id || 'container'}_${target?.instance || ''}`)
158
255
 
159
256
  if (!this.stackReported[bucketHash]) {
160
257
  this.stackReported[bucketHash] = true
@@ -196,23 +293,78 @@ export class Aggregate extends AggregateBase {
196
293
  if (softNavInUse) { // pass the error to soft nav for evaluation - it will return it via 'returnJserror' when interaction is resolved
197
294
  handle('jserror', [jsErrorEvent], undefined, FEATURE_NAMES.softNav, this.ee)
198
295
  } else {
199
- this.#storeJserrorForHarvest(jsErrorEvent, false)
296
+ this.#storeJserrorForHarvest(jsErrorEvent)
200
297
  }
201
298
  }
202
299
 
203
300
  // always add directly if scoped to a sub-entity, the other pathways above will be deterministic if the main agent should procede
204
- if (target) this.#storeJserrorForHarvest([...jsErrorEvent, target], false, params._softNavAttributes)
301
+ if (target) this.#storeJserrorForHarvest(jsErrorEvent, {}, target)
205
302
  }
206
303
 
207
- #storeJserrorForHarvest (errorInfoArr, softNavCustomAttrs = {}) {
208
- let [type, bucketHash, params, newMetrics, localAttrs, target] = errorInfoArr
304
+ /**
305
+ * Builds a standardized (canonical) stack trace string from the frames in the given `stackInfo` object.
306
+ *
307
+ * The canonical format is used for cross-browser error grouping in NR1, as different browsers
308
+ * format stack traces differently. Each frame is separated by a newline character and takes
309
+ * the form: `<functionName>@<url>:<lineNumber>`
310
+ *
311
+ * Note: Column numbers are intentionally excluded from the canonical format to improve
312
+ * grouping accuracy, as the same error across different minified builds might have different
313
+ * column numbers but should still be grouped together.
314
+ *
315
+ * Example output:
316
+ * ```
317
+ * handleClick@https://example.com/app.js:42
318
+ * EventEmitter.emit@https://example.com/vendor.js:1337
319
+ * ```
320
+ *
321
+ * @param {StackInfo} stackInfo - An object containing parsed stack frames from computeStackTrace
322
+ * @returns {string} A canonical stack string built from the URLs and function names in the given `stackInfo` object
323
+ */
324
+ #buildCanonicalStackString (stackInfo) {
325
+ var canonicalStackString = ''
326
+
327
+ for (var i = 0; i < stackInfo.frames.length; i++) {
328
+ var frame = stackInfo.frames[i]
329
+ var func = canonicalFunctionName(frame.func)
330
+
331
+ if (canonicalStackString) canonicalStackString += '\n'
332
+ if (func) canonicalStackString += func + '@'
333
+ if (typeof frame.url === 'string') canonicalStackString += frame.url
334
+ if (frame.line) canonicalStackString += ':' + frame.line
335
+ }
336
+
337
+ return canonicalStackString
338
+ }
339
+
340
+ /**
341
+ * Adds a processed error to the harvest buffer with all custom attributes merged.
342
+ *
343
+ * This is the final step before an error is stored to be sent to the backend. It handles:
344
+ * - Merging all custom attributes (global, soft nav, MFE, and local)
345
+ * - Creating a unique aggregate hash for deduplication
346
+ * - Adding the error to the events buffer for harvest
347
+ * - Duplicating the error for MFE scenarios when needed (v2 endpoints)
348
+ *
349
+ * Custom Attribute Precedence (lowest to highest):
350
+ * 1. Global jsAttributes from agent config
351
+ * 2. Soft navigation attributes (if from a soft nav interaction)
352
+ * 3. MFE v2 attributes (source/parent metadata)
353
+ * 4. Local custom attributes passed with the specific error
354
+ *
355
+ * @param {Array} errorInfoArr - Array containing [type, bucketHash, params, metrics, customAttributes, target]
356
+ * @param {object} [attrs={}] - Additional attributes to merge (e.g., from soft nav interactions)
357
+ * @returns {void}
358
+ */
359
+ #storeJserrorForHarvest (errorInfoArr, attrs = {}, target) {
360
+ let [type, bucketHash, params, newMetrics, localAttrs] = errorInfoArr
209
361
  const allCustomAttrs = {
210
362
  /** MFE specific attributes if in "multiple" mode (ie consumer version 2) */
211
363
  ...getVersion2Attributes(target, this)
212
364
  }
213
365
 
214
366
  Object.entries(this.agentRef.info.jsAttributes).forEach(([k, v]) => setCustom(k, v))
215
- Object.entries(softNavCustomAttrs).forEach(([k, v]) => setCustom(k, v)) // when an ixn finishes, it'll pass attrs specific to the ixn; if no associated ixn, this defaults to empty
367
+ Object.entries(attrs).forEach(([k, v]) => setCustom(k, v)) // when an ixn finishes, it'll pass attrs specific to the ixn; if no associated ixn, this defaults to empty
216
368
  if (params.browserInteractionId) bucketHash += params.browserInteractionId
217
369
  if (localAttrs) Object.entries(localAttrs).forEach(([k, v]) => setCustom(k, v)) // local custom attrs are applied in either case with the highest precedence
218
370
 
@@ -221,6 +373,11 @@ export class Aggregate extends AggregateBase {
221
373
 
222
374
  this.events.add([type, aggregateHash, params, newMetrics, allCustomAttrs])
223
375
 
376
+ if (shouldDuplicate(target, this)) {
377
+ // Clone the array with new object references to prevent deduplication in the shared aggregator
378
+ this.#storeJserrorForHarvest([type, bucketHash, { ...params }, { ...newMetrics }, localAttrs && { ...localAttrs }], getVersion2DuplicationAttributes(target, this))
379
+ }
380
+
224
381
  function setCustom (key, val) {
225
382
  allCustomAttrs[key] = (val && typeof val === 'object' ? stringify(val) : val)
226
383
  }
@@ -13,7 +13,7 @@ import { applyFnToProps } from '../../../common/util/traverse'
13
13
  import { SESSION_EVENT_TYPES, SESSION_EVENTS } from '../../../common/session/constants'
14
14
  import { ABORT_REASONS } from '../../session_replay/constants'
15
15
  import { canEnableSessionTracking } from '../../utils/feature-gates'
16
- import { getVersion2Attributes, getVersion2DuplicationAttributes, shouldDuplicate } from '../../../common/util/v2'
16
+ import { getVersion2Attributes, getVersion2DuplicationAttributes, shouldDuplicate } from '../../../common/v2/utils'
17
17
 
18
18
  const LOGGING_EVENT = 'Logging/Event/'
19
19
 
@@ -1,27 +1,16 @@
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
- import { isBrowserScope } from '../../../common/constants/runtime'
7
- import { handle } from '../../../common/event-emitter/handle'
8
6
  import { InstrumentBase } from '../../utils/instrument-base'
9
- import {
10
- FEATURE_NAME,
11
- SUPPORTABILITY_METRIC_CHANNEL
12
- } from '../constants'
7
+ import { FEATURE_NAME } from '../constants'
13
8
 
14
9
  export class Instrument extends InstrumentBase {
15
10
  static featureName = FEATURE_NAME
16
11
  constructor (agentRef) {
17
12
  super(agentRef, FEATURE_NAME)
18
13
 
19
- if (isBrowserScope) {
20
- document.addEventListener('securitypolicyviolation', (e) => {
21
- handle(SUPPORTABILITY_METRIC_CHANNEL, ['Generic/CSPViolation/Detected'], undefined, this.featureName, this.ee)
22
- })
23
- }
24
-
25
14
  this.importAggregator(agentRef, () => import(/* webpackChunkName: "metrics-aggregate" */ '../aggregate'))
26
15
  }
27
16
  }
@@ -7,7 +7,7 @@ import { FEATURE_NAME } from '../constants'
7
7
  import { AggregateBase } from '../../utils/aggregate-base'
8
8
  import { TraceStorage } from './trace/storage'
9
9
  import { obj as encodeObj } from '../../../common/url/encode'
10
- import { globalScope, getNavigationEntry } from '../../../common/constants/runtime'
10
+ import { globalScope } from '../../../common/constants/runtime'
11
11
  import { MODE, SESSION_EVENTS } from '../../../common/session/constants'
12
12
  import { applyFnToProps } from '../../../common/util/traverse'
13
13
  import { cleanURL } from '../../../common/url/clean-url'
@@ -41,6 +41,7 @@ export class Aggregate extends AggregateBase {
41
41
  this.entitled ??= stEntitled
42
42
  if (!this.entitled) this.blocked = true
43
43
  if (this.blocked) return this.deregisterDrain()
44
+ this.timeKeeper ??= this.agentRef.runtime.timeKeeper
44
45
 
45
46
  if (!this.initialized) {
46
47
  this.initialized = true
@@ -62,9 +63,8 @@ export class Aggregate extends AggregateBase {
62
63
  if (this.sessionId !== sessionState.value || (eventType === 'cross-tab' && sessionState.sessionTraceMode === MODE.OFF)) this.abort(2)
63
64
  })
64
65
 
65
- const navEntry = getNavigationEntry()
66
- if (navEntry) {
67
- this.traceStorage.storeTiming(navEntry)
66
+ if (typeof PerformanceNavigationTiming !== 'undefined' && globalScope.performance?.getEntriesByType('navigation')?.length > 0) {
67
+ this.traceStorage.storeTiming(globalScope.performance.getEntriesByType('navigation')[0])
68
68
  } else {
69
69
  this.traceStorage.storeTiming(globalScope.performance?.timing, true)
70
70
  }
@@ -79,8 +79,6 @@ export class Aggregate extends AggregateBase {
79
79
  * If it drains later (due to a mode change), data and handlers will instantly drain instead of waiting for the registry. */
80
80
  if (this.mode === MODE.OFF) return this.deregisterDrain()
81
81
 
82
- this.timeKeeper ??= this.agentRef.runtime.timeKeeper
83
-
84
82
  /** The handlers set up by the Inst file */
85
83
  registerHandler('bst', (...args) => this.traceStorage.storeEvent(...args), this.featureName, this.ee)
86
84
  registerHandler('bstResource', (...args) => this.traceStorage.storeResources(...args), this.featureName, this.ee)
@@ -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 { MODE } from '../../../../common/session/constants'
@@ -168,7 +168,7 @@ export class TraceStorage {
168
168
  if (!(typeof val === 'number' && val >= 0)) continue
169
169
 
170
170
  val = Math.round(val)
171
- if (this.parent.timeKeeper && this.parent.timeKeeper.ready && isAbsoluteTimestamp) {
171
+ if (isAbsoluteTimestamp && this.parent.timeKeeper?.ready) {
172
172
  val = this.parent.timeKeeper.convertAbsoluteTimestamp(
173
173
  Math.floor(this.parent.timeKeeper.correctAbsoluteTimestamp(val))
174
174
  )
@@ -18,18 +18,19 @@ import { warn } from '../common/util/console'
18
18
  * An interface for registering an external caller to report through the base agent to a different target than the base agent.
19
19
  */
20
20
  export class RegisteredEntity {
21
- /** @type {RegisterAPIMetadata} */
22
- metadata = {
23
- target: {},
24
- timings: {},
25
- customAttributes: {}
26
- }
27
-
28
21
  /**
29
22
  *
30
23
  * @param {RegisterAPIConstructor} opts The options for setting up the registered entity.
31
24
  */
32
25
  constructor (opts) {
26
+ // Initialize metadata as an own property to ensure it exists even when agent is missing
27
+ /** @type {RegisterAPIMetadata} */
28
+ this.metadata = {
29
+ target: /** @type {import('../loaders/api/register-api-types').RegisterAPITarget} */ ({}),
30
+ timings: /** @type {import('../loaders/api/register-api-types').RegisterAPITimings} */ ({}),
31
+ customAttributes: {}
32
+ }
33
+
33
34
  try {
34
35
  if (!window?.newrelic) return warn(51)
35
36
  Object.assign(this, window?.newrelic?.register(opts) || {})
@@ -49,19 +50,6 @@ export class RegisteredEntity {
49
50
  warn(35, 'addPageAction')
50
51
  }
51
52
 
52
- /**
53
- * @experimental
54
- * IMPORTANT: This feature is being developed for use internally and is not in a public-facing production-ready state.
55
- * It is not recommended for use in production environments and will not receive support for issues.
56
- *
57
- * Registers an external caller to report through the base agent to a different target than the base agent. Will be related to this registered entity when called through this access point.
58
- * @param {import('../loaders/api/register-api-types').RegisterAPIConstructor} target the target object to report data to
59
- @returns {import('../loaders/api/register-api-types').RegisterAPI} Returns an object that contains the available API methods and configurations to use with the external caller. See loaders/api/api.js for more information.
60
- */
61
- register (target) {
62
- warn(35, 'register')
63
- }
64
-
65
53
  /**
66
54
  * @experimental
67
55
  * IMPORTANT: This feature is being developed for use internally and is not in a public-facing production-ready state.
@@ -8,7 +8,6 @@
8
8
  * @property {(name: string, attributes?: object) => void} addPageAction - Add a page action for the registered entity.
9
9
  * @property {(message: string, options?: { customAttributes?: object, level?: 'ERROR' | 'TRACE' | 'DEBUG' | 'INFO' | 'WARN'}) => void} log - Capture a log for the registered entity.
10
10
  * @property {(error: Error | string, customAttributes?: object) => void} noticeError - Notice an error for the registered entity.
11
- * @property {(target: RegisterAPIConstructor) => RegisterAPI} register - Record a custom event for the registered entity.
12
11
  * @property {() => void} deregister - Deregister the registered entity, which blocks its use and captures end of life timings.
13
12
  * @property {(eventType: string, attributes?: Object) => void} recordCustomEvent - Record a custom event for the registered entity.
14
13
  * @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.
@@ -23,15 +22,15 @@
23
22
  * @property {string} id - The unique id for the registered entity. This will be assigned to any synthesized entities.
24
23
  * @property {string} name - The readable name for the registered entity. This will be assigned to any synthesized entities.
25
24
  * @property {{[key: string]: any}} [tags] - The tags for the registered entity as key-value pairs. This will be assigned to any synthesized entities. Tags are converted to source.* attributes (e.g., {environment: 'production'} becomes source.environment: 'production').
26
- * @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.
25
+ * @property {RegisterAPITarget} [parent] - The parent target for the registered entity. If none was supplied, it will assume the entity guid from the main agent.
27
26
  * @property {string} [parentId] - The parentId for the registered entity. If none was supplied, it will assume the entity guid from the main agent.
28
27
  */
29
28
 
30
29
  /**
31
30
  * @typedef {Object} RegisterAPIMetadata
32
31
  * @property {Object} customAttributes - The custom attributes for the registered entity.
33
- * @property {RegisterAPITimings} timings - The timing metrics for the registered entity.
34
- * @property {RegisterAPITarget} target - The options for the registered entity.
32
+ * @property {Partial<RegisterAPITimings>} timings - The timing metrics for the registered entity.
33
+ * @property {Partial<RegisterAPITarget>} target - The options for the registered entity.
35
34
  */
36
35
 
37
36
  /**
@@ -40,17 +39,18 @@
40
39
  * @property {string} name - The name returned for the registered entity.
41
40
  * @property {{[key: string]: any}} [tags] - The tags for the registered entity as key-value pairs.
42
41
  * @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
42
  */
45
43
 
46
44
  /**
47
45
  * @typedef {Object} RegisterAPITimings
48
46
  * @property {number} registeredAt - The timestamp when the registered entity was created.
49
47
  * @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.
48
+ * @property {number} fetchStart - The timestamp when the registered entity began fetching (performance.start).
49
+ * @property {number} fetchEnd - The timestamp when the registered entity finished fetching (performance.end).
50
+ * @property {number} scriptStart - The timestamp when script initialization began (max of dom.start or performance.end, or performance.end if no dom.start).
51
+ * @property {number} scriptEnd - The timestamp when script loading completed (dom.end or registeredAt if no dom.end).
52
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.
53
+ * @property {string} type - The type of timing associated with the registered entity, 'script' or 'link' if found with the performance resource API, 'fetch' for dynamic imports, 'inline' if found to be associated with the root document URL, or 'unknown' if no associated resource could be found.
54
54
  */
55
55
 
56
56
  export default {}