@newrelic/browser-agent 1.312.1-rc.12 → 1.312.1-rc.13

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.
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.RRWEB_PACKAGE_NAME = exports.D
17
17
  /**
18
18
  * Exposes the version of the agent
19
19
  */
20
- const VERSION = exports.VERSION = "1.312.1-rc.12";
20
+ const VERSION = exports.VERSION = "1.312.1-rc.13";
21
21
 
22
22
  /**
23
23
  * Exposes the build type of the agent
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.RRWEB_PACKAGE_NAME = exports.D
17
17
  /**
18
18
  * Exposes the version of the agent
19
19
  */
20
- const VERSION = exports.VERSION = "1.312.1-rc.12";
20
+ const VERSION = exports.VERSION = "1.312.1-rc.13";
21
21
 
22
22
  /**
23
23
  * Exposes the build type of the agent
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.ShortCircuit = void 0;
7
+ /**
8
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
9
+ * SPDX-License-Identifier: Apache-2.0
10
+ */
11
+ /** A special error used to short-circuit the error processing pipeline */
12
+ class ShortCircuit extends Error {}
13
+ exports.ShortCircuit = ShortCircuit;
@@ -20,6 +20,7 @@ var _traverse = require("../../../common/util/traverse");
20
20
  var _internalErrors = require("./internal-errors");
21
21
  var _utils = require("../../../common/v2/utils");
22
22
  var _causeString = require("./cause-string");
23
+ var _shortCircuit = require("../../../common/util/short-circuit");
23
24
  /**
24
25
  * Copyright 2020-2026 New Relic, Inc. All rights reserved.
25
26
  * SPDX-License-Identifier: Apache-2.0
@@ -40,8 +41,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
40
41
  this.observedAt = {};
41
42
  this.pageviewReported = {};
42
43
  this.errorOnPage = false;
43
- (0, _registerHandler.registerHandler)('err', (...args) => this.storeError(...args), this.featureName, this.ee);
44
- (0, _registerHandler.registerHandler)('ierr', (...args) => this.storeError(...args), this.featureName, this.ee);
44
+ (0, _registerHandler.registerHandler)('err', this.processError.bind(this), this.featureName, this.ee);
45
+ (0, _registerHandler.registerHandler)('ierr', this.processError.bind(this), this.featureName, this.ee);
45
46
  (0, _registerHandler.registerHandler)('returnJserror', (jsErrorEvent, softNavAttrs) => this.#storeJserrorForHarvest(jsErrorEvent, softNavAttrs), this.featureName, this.ee);
46
47
 
47
48
  // 0 == off, 1 == on
@@ -73,60 +74,162 @@ class Aggregate extends _aggregateBase.AggregateBase {
73
74
  }
74
75
 
75
76
  /**
76
- * Builds a standardized stack trace string from the frames in the given `stackInfo` object, with each frame separated
77
- * by a newline character. Lines take the form `<functionName>@<url>:<lineNumber>`.
77
+ * Main entry point for processing JavaScript errors. This method orchestrates the complete error processing pipeline.
78
78
  *
79
- * @param {StackInfo} stackInfo - An object specifying a stack string and individual frames.
80
- * @returns {string} A canonical stack string built from the URLs and function names in the given `stackInfo` object.
79
+ * Processing Flow:
80
+ * 1. Filter the error through the customer's onerror handler (if configured and not internal)
81
+ * 2. Compute the stack trace from the error object
82
+ * 3. Evaluate if the error should be swallowed (internal errors, known issues, etc.)
83
+ * 4. Derive target(s) for the error (MFE detection for v2 endpoints, or default target) - Note: "undefined" indicates the default target will be used
84
+ * 5. Store the error for each derived target. During storage (#storeJserrorForHarvest), duplication for MFE <-> container will be handled
85
+ *
86
+ * Important: "ShortCircuit" Pattern:
87
+ * Several steps in the pipeline can throw a ShortCircuit error to halt processing without
88
+ * treating it as a reportable error. This pattern is used when:
89
+ * - The customer's onerror handler returns a truthy value (excluding fingerprinting objects)
90
+ * - The error is identified as an internal error that shouldn't be reported
91
+ *
92
+ * When a ShortCircuit is thrown, processing stops immediately and the error is not stored.
93
+ * Any other thrown error is re-thrown as it represents an actual problem in the agent code.
94
+ *
95
+ * @param {Error|UncaughtError} err - The error instance to be processed
96
+ * @param {number} [time] - The relative ms (to origin) timestamp of occurrence. Defaults to now()
97
+ * @param {boolean} [internal=false] - If the error was "caught" and deemed "internal" before reporting to the jserrors feature
98
+ * @param {object} [customAttributes] - Any custom attributes to be included in the error payload
99
+ * @param {boolean} [hasReplay=false] - A flag indicating if the error occurred during a replay session
100
+ * @param {string} [swallowReason] - A string indicating pre-defined reason if swallowing the error. Mainly used by internal error supportability metrics
101
+ * @param {object} [target] - The target to buffer and harvest to. If undefined, the default configuration target is used
102
+ * @returns {void}
81
103
  */
82
- buildCanonicalStackString(stackInfo) {
83
- var canonicalStackString = '';
84
- for (var i = 0; i < stackInfo.frames.length; i++) {
85
- var frame = stackInfo.frames[i];
86
- var func = (0, _canonicalFunctionName.canonicalFunctionName)(frame.func);
87
- if (canonicalStackString) canonicalStackString += '\n';
88
- if (func) canonicalStackString += func + '@';
89
- if (typeof frame.url === 'string') canonicalStackString += frame.url;
90
- if (frame.line) canonicalStackString += ':' + frame.line;
104
+ processError(err, time, internal, customAttributes, hasReplay, swallowReason, target) {
105
+ if (!err) return;
106
+ time = time || (0, _now.now)();
107
+ try {
108
+ const filterOutput = this.#filterError(err, internal);
109
+ const stackInfo = (0, _computeStackTrace.computeStackTrace)(err);
110
+ this.#swallowError(stackInfo, internal, swallowReason);
111
+ this.#deriveTargets(stackInfo, target).forEach(target => {
112
+ this.#storeError(err, time, stackInfo, filterOutput, customAttributes, hasReplay, target);
113
+ });
114
+ } catch (e) {
115
+ if (!(e instanceof _shortCircuit.ShortCircuit)) {
116
+ throw e;
117
+ }
91
118
  }
92
- return canonicalStackString;
93
119
  }
94
120
 
95
121
  /**
122
+ * Filters an error through the customer's configured onerror handler.
96
123
  *
97
- * @param {Error|UncaughtError} err The error instance to be processed
98
- * @param {number} time the relative ms (to origin) timestamp of occurrence
99
- * @param {boolean=} internal if the error was "caught" and deemed "internal" before reporting to the jserrors feature
100
- * @param {object=} customAttributes any custom attributes to be included in the error payload
101
- * @param {boolean=} hasReplay a flag indicating if the error occurred during a replay session
102
- * @param {string=} swallowReason a string indicating pre-defined reason if swallowing the error. Mainly used by the internal error SMs.
103
- * @param {object=} target the target to buffer and harvest to, if undefined the default configuration target is used
104
- * @returns
124
+ * If the customer has configured a custom onerror handler and the error is not internal,
125
+ * this method invokes that handler. The handler's return value determines whether the error
126
+ * should be reported:
127
+ * - Falsey values (false, null, undefined, etc.) Report the error normally
128
+ * - Truthy non-object values Don't report (throws ShortCircuit)
129
+ * - Object with 'group' property (non-empty string) Report with fingerprinting label
130
+ * - Any other truthy value Don't report (throws ShortCircuit)
131
+ *
132
+ * @param {Error|UncaughtError} err - The error to filter
133
+ * @param {boolean} internal - Whether this is an internal error (internal errors skip filtering)
134
+ * @returns {undefined|object} The filter output. If an object with 'group' property, contains fingerprinting data
135
+ * @throws {ShortCircuit} When the error should not be reported based on the filter output
105
136
  */
106
- storeError(err, time, internal, customAttributes, hasReplay, swallowReason, target) {
107
- if (!err) return;
108
- // are we in an interaction
109
- time = time || (0, _now.now)();
137
+ #filterError(err, internal) {
110
138
  let filterOutput;
111
139
  if (!internal && this.agentRef.runtime.onerror) {
112
140
  filterOutput = this.agentRef.runtime.onerror(err);
113
141
  if (filterOutput && !(typeof filterOutput.group === 'string' && filterOutput.group.length)) {
114
142
  // All truthy values mean don't report (store) the error, per backwards-compatible usage,
115
143
  // - EXCEPT if a fingerprinting label is returned, via an object with key of 'group' and value of non-empty string
116
- return;
144
+ throw new _shortCircuit.ShortCircuit();
117
145
  }
118
146
  // Again as with previous usage, all falsey values would include the error.
119
147
  }
120
- var stackInfo = (0, _computeStackTrace.computeStackTrace)(err);
148
+ return filterOutput;
149
+ }
150
+
151
+ /**
152
+ * Evaluates whether an error should be swallowed (not reported) based on internal error criteria.
153
+ *
154
+ * This method uses the evaluateInternalError function to determine if the error matches known
155
+ * internal error patterns (e.g., errors from the agent itself, known browser issues, etc.).
156
+ * If the error should be swallowed, a supportability metric is recorded and processing is halted.
157
+ *
158
+ * @param {StackInfo} stackInfo - The computed stack trace information
159
+ * @param {boolean} internal - Whether the error was marked as internal
160
+ * @param {string} [swallowReason] - Optional pre-determined reason for swallowing
161
+ * @returns {void}
162
+ * @throws {ShortCircuit} When the error should be swallowed and not reported
163
+ */
164
+ #swallowError(stackInfo, internal, swallowReason) {
121
165
  const {
122
166
  shouldSwallow,
123
167
  reason
124
168
  } = (0, _internalErrors.evaluateInternalError)(stackInfo, internal, swallowReason);
125
169
  if (shouldSwallow) {
126
170
  this.reportSupportabilityMetric('Internal/Error/' + reason);
127
- return;
171
+ throw new _shortCircuit.ShortCircuit();
128
172
  }
129
- var canonicalStackString = this.buildCanonicalStackString(stackInfo);
173
+ }
174
+
175
+ /**
176
+ * Derives the appropriate targets for reporting the given stack information.
177
+ *
178
+ * Targets represent the entities that should receive the error data. This is particularly
179
+ * important for Micro Frontend (MFE) scenarios where errors may need to be reported to
180
+ * different applications.
181
+ *
182
+ * Logic:
183
+ * - If a target is explicitly provided (e.g., from the register API), use it
184
+ * - For v2 endpoints without an explicit target, scan stack frames to detect MFE sources
185
+ * - If no MFE is detected or v2 is not enabled, use undefined (default target)
186
+ *
187
+ * @param {StackInfo} stackInfo - The computed stack trace information containing frames
188
+ * @param {object} [target] - Explicitly provided target, typically from the register API
189
+ * @returns {Array<object|undefined>} Array of targets to report the error to. Always contains at least one element.
190
+ */
191
+ #deriveTargets(stackInfo, target) {
192
+ const targets = [];
193
+ if (target) {
194
+ // reported by the register API directly
195
+ targets.push(target);
196
+ } else {
197
+ // we dont know if this is MFE yet, we need to figure it out.
198
+ if (this.harvestEndpointVersion === 2) {
199
+ for (const frame of stackInfo.frames) {
200
+ targets.push(...(0, _utils.getRegisteredTargetsFromFilename)(frame.url, this.agentRef));
201
+ if (targets.length) break;
202
+ }
203
+ }
204
+ if (!targets.length) targets.push(undefined);
205
+ }
206
+ return targets;
207
+ }
208
+
209
+ /**
210
+ * Stores error data for eventual harvesting and transmission to the backend.
211
+ *
212
+ * This method processes the error through several stages:
213
+ * 1. Build canonical stack string for cross-browser error grouping
214
+ * 2. Build cause chain string if the error has a cause property
215
+ * 3. Create params object with error metadata (stack hash, class, message, etc.)
216
+ * 4. Create bucket hash for internal deduplication
217
+ * 5. Store stack trace on first occurrence of this error
218
+ * 6. Add custom attributes and send to other features (trace, replay)
219
+ * 7. Route through soft nav if enabled, or directly to harvest storage
220
+ * 8. Handle MFE duplication for v2 endpoints if needed
221
+ *
222
+ * @param {Error|UncaughtError} err - The error instance to be processed
223
+ * @param {number} time - The relative ms (to origin) timestamp of occurrence
224
+ * @param {StackInfo} stackInfo - The computed stack trace information
225
+ * @param {object} [filterOutput] - Output from the customer's onerror handler, may contain fingerprinting group
226
+ * @param {object} [customAttributes] - Any custom attributes to be included in the error payload
227
+ * @param {boolean} [hasReplay=false] - A flag indicating if the error occurred during a replay session
228
+ * @param {object} [target] - The target to buffer and harvest to. If undefined, the default configuration target is used
229
+ * @returns {void}
230
+ */
231
+ #storeError(err, time, stackInfo, filterOutput, customAttributes, hasReplay, target) {
232
+ var canonicalStackString = this.#buildCanonicalStackString(stackInfo);
130
233
  const causeStackString = (0, _causeString.buildCauseString)(err);
131
234
  const params = {
132
235
  stackHash: (0, _stringHashCode.stringHashCode)(canonicalStackString),
@@ -149,7 +252,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
149
252
  * the canonical stack trace excludes items like the column number increasing the hit-rate of different errors potentially
150
253
  * bucketing and ultimately resulting in the loss of data in NR1.
151
254
  */
152
- var bucketHash = (0, _stringHashCode.stringHashCode)("".concat(stackInfo.name, "_").concat(stackInfo.message, "_").concat(stackInfo.stackString, "_").concat(params.hasReplay ? 1 : 0, "_").concat(target?.id || 'container'));
255
+ var bucketHash = (0, _stringHashCode.stringHashCode)("".concat(stackInfo.name, "_").concat(stackInfo.message, "_").concat(stackInfo.stackString, "_").concat(params.hasReplay ? 1 : 0, "_").concat(target?.id || 'container', "_").concat(target?.instance || ''));
153
256
  if (!this.stackReported[bucketHash]) {
154
257
  this.stackReported[bucketHash] = true;
155
258
  params.stack_trace = (0, _formatStackTrace.truncateSize)(stackInfo.stackString);
@@ -189,27 +292,90 @@ class Aggregate extends _aggregateBase.AggregateBase {
189
292
  // pass the error to soft nav for evaluation - it will return it via 'returnJserror' when interaction is resolved
190
293
  (0, _handle.handle)('jserror', [jsErrorEvent], undefined, _features.FEATURE_NAMES.softNav, this.ee);
191
294
  } else {
192
- this.#storeJserrorForHarvest(jsErrorEvent, false);
295
+ this.#storeJserrorForHarvest(jsErrorEvent);
193
296
  }
194
297
  }
195
298
 
196
299
  // always add directly if scoped to a sub-entity, the other pathways above will be deterministic if the main agent should procede
197
- if (target) this.#storeJserrorForHarvest([...jsErrorEvent, target], false, params._softNavAttributes);
300
+ if (target) this.#storeJserrorForHarvest(jsErrorEvent, {}, target);
301
+ }
302
+
303
+ /**
304
+ * Builds a standardized (canonical) stack trace string from the frames in the given `stackInfo` object.
305
+ *
306
+ * The canonical format is used for cross-browser error grouping in NR1, as different browsers
307
+ * format stack traces differently. Each frame is separated by a newline character and takes
308
+ * the form: `<functionName>@<url>:<lineNumber>`
309
+ *
310
+ * Note: Column numbers are intentionally excluded from the canonical format to improve
311
+ * grouping accuracy, as the same error across different minified builds might have different
312
+ * column numbers but should still be grouped together.
313
+ *
314
+ * Example output:
315
+ * ```
316
+ * handleClick@https://example.com/app.js:42
317
+ * EventEmitter.emit@https://example.com/vendor.js:1337
318
+ * ```
319
+ *
320
+ * @param {StackInfo} stackInfo - An object containing parsed stack frames from computeStackTrace
321
+ * @returns {string} A canonical stack string built from the URLs and function names in the given `stackInfo` object
322
+ */
323
+ #buildCanonicalStackString(stackInfo) {
324
+ var canonicalStackString = '';
325
+ for (var i = 0; i < stackInfo.frames.length; i++) {
326
+ var frame = stackInfo.frames[i];
327
+ var func = (0, _canonicalFunctionName.canonicalFunctionName)(frame.func);
328
+ if (canonicalStackString) canonicalStackString += '\n';
329
+ if (func) canonicalStackString += func + '@';
330
+ if (typeof frame.url === 'string') canonicalStackString += frame.url;
331
+ if (frame.line) canonicalStackString += ':' + frame.line;
332
+ }
333
+ return canonicalStackString;
198
334
  }
199
- #storeJserrorForHarvest(errorInfoArr, softNavCustomAttrs = {}) {
200
- let [type, bucketHash, params, newMetrics, localAttrs, target] = errorInfoArr;
335
+
336
+ /**
337
+ * Adds a processed error to the harvest buffer with all custom attributes merged.
338
+ *
339
+ * This is the final step before an error is stored to be sent to the backend. It handles:
340
+ * - Merging all custom attributes (global, soft nav, MFE, and local)
341
+ * - Creating a unique aggregate hash for deduplication
342
+ * - Adding the error to the events buffer for harvest
343
+ * - Duplicating the error for MFE scenarios when needed (v2 endpoints)
344
+ *
345
+ * Custom Attribute Precedence (lowest to highest):
346
+ * 1. Global jsAttributes from agent config
347
+ * 2. Soft navigation attributes (if from a soft nav interaction)
348
+ * 3. MFE v2 attributes (source/parent metadata)
349
+ * 4. Local custom attributes passed with the specific error
350
+ *
351
+ * @param {Array} errorInfoArr - Array containing [type, bucketHash, params, metrics, customAttributes, target]
352
+ * @param {object} [attrs={}] - Additional attributes to merge (e.g., from soft nav interactions)
353
+ * @returns {void}
354
+ */
355
+ #storeJserrorForHarvest(errorInfoArr, attrs = {}, target) {
356
+ let [type, bucketHash, params, newMetrics, localAttrs] = errorInfoArr;
201
357
  const allCustomAttrs = {
202
358
  /** MFE specific attributes if in "multiple" mode (ie consumer version 2) */
203
359
  ...(0, _utils.getVersion2Attributes)(target, this)
204
360
  };
205
361
  Object.entries(this.agentRef.info.jsAttributes).forEach(([k, v]) => setCustom(k, v));
206
- 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
362
+ 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
207
363
  if (params.browserInteractionId) bucketHash += params.browserInteractionId;
208
364
  if (localAttrs) Object.entries(localAttrs).forEach(([k, v]) => setCustom(k, v)); // local custom attrs are applied in either case with the highest precedence
209
365
 
210
366
  const jsAttributesHash = (0, _stringHashCode.stringHashCode)((0, _stringify.stringify)(allCustomAttrs));
211
367
  const aggregateHash = bucketHash + ':' + jsAttributesHash;
212
368
  this.events.add([type, aggregateHash, params, newMetrics, allCustomAttrs]);
369
+ if ((0, _utils.shouldDuplicate)(target, this)) {
370
+ // Clone the array with new object references to prevent deduplication in the shared aggregator
371
+ this.#storeJserrorForHarvest([type, bucketHash, {
372
+ ...params
373
+ }, {
374
+ ...newMetrics
375
+ }, localAttrs && {
376
+ ...localAttrs
377
+ }], (0, _utils.getVersion2DuplicationAttributes)(target, this));
378
+ }
213
379
  function setCustom(key, val) {
214
380
  allCustomAttrs[key] = val && typeof val === 'object' ? (0, _stringify.stringify)(val) : val;
215
381
  }
@@ -11,7 +11,7 @@
11
11
  /**
12
12
  * Exposes the version of the agent
13
13
  */
14
- export const VERSION = "1.312.1-rc.12";
14
+ export const VERSION = "1.312.1-rc.13";
15
15
 
16
16
  /**
17
17
  * Exposes the build type of the agent
@@ -11,7 +11,7 @@
11
11
  /**
12
12
  * Exposes the version of the agent
13
13
  */
14
- export const VERSION = "1.312.1-rc.12";
14
+ export const VERSION = "1.312.1-rc.13";
15
15
 
16
16
  /**
17
17
  * Exposes the build type of the agent
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ /** A special error used to short-circuit the error processing pipeline */
6
+ export class ShortCircuit extends Error {}
@@ -17,8 +17,9 @@ import { AggregateBase } from '../../utils/aggregate-base';
17
17
  import { now } from '../../../common/timing/now';
18
18
  import { applyFnToProps } from '../../../common/util/traverse';
19
19
  import { evaluateInternalError } from './internal-errors';
20
- import { getVersion2Attributes } from '../../../common/v2/utils';
20
+ import { getRegisteredTargetsFromFilename, getVersion2Attributes, getVersion2DuplicationAttributes, shouldDuplicate } from '../../../common/v2/utils';
21
21
  import { buildCauseString } from './cause-string';
22
+ import { ShortCircuit } from '../../../common/util/short-circuit';
22
23
 
23
24
  /**
24
25
  * @typedef {import('./compute-stack-trace.js').StackInfo} StackInfo
@@ -35,8 +36,8 @@ export class Aggregate extends AggregateBase {
35
36
  this.observedAt = {};
36
37
  this.pageviewReported = {};
37
38
  this.errorOnPage = false;
38
- register('err', (...args) => this.storeError(...args), this.featureName, this.ee);
39
- register('ierr', (...args) => this.storeError(...args), this.featureName, this.ee);
39
+ register('err', this.processError.bind(this), this.featureName, this.ee);
40
+ register('ierr', this.processError.bind(this), this.featureName, this.ee);
40
41
  register('returnJserror', (jsErrorEvent, softNavAttrs) => this.#storeJserrorForHarvest(jsErrorEvent, softNavAttrs), this.featureName, this.ee);
41
42
 
42
43
  // 0 == off, 1 == on
@@ -68,60 +69,162 @@ export class Aggregate extends AggregateBase {
68
69
  }
69
70
 
70
71
  /**
71
- * Builds a standardized stack trace string from the frames in the given `stackInfo` object, with each frame separated
72
- * by a newline character. Lines take the form `<functionName>@<url>:<lineNumber>`.
72
+ * Main entry point for processing JavaScript errors. This method orchestrates the complete error processing pipeline.
73
73
  *
74
- * @param {StackInfo} stackInfo - An object specifying a stack string and individual frames.
75
- * @returns {string} A canonical stack string built from the URLs and function names in the given `stackInfo` object.
74
+ * Processing Flow:
75
+ * 1. Filter the error through the customer's onerror handler (if configured and not internal)
76
+ * 2. Compute the stack trace from the error object
77
+ * 3. Evaluate if the error should be swallowed (internal errors, known issues, etc.)
78
+ * 4. Derive target(s) for the error (MFE detection for v2 endpoints, or default target) - Note: "undefined" indicates the default target will be used
79
+ * 5. Store the error for each derived target. During storage (#storeJserrorForHarvest), duplication for MFE <-> container will be handled
80
+ *
81
+ * Important: "ShortCircuit" Pattern:
82
+ * Several steps in the pipeline can throw a ShortCircuit error to halt processing without
83
+ * treating it as a reportable error. This pattern is used when:
84
+ * - The customer's onerror handler returns a truthy value (excluding fingerprinting objects)
85
+ * - The error is identified as an internal error that shouldn't be reported
86
+ *
87
+ * When a ShortCircuit is thrown, processing stops immediately and the error is not stored.
88
+ * Any other thrown error is re-thrown as it represents an actual problem in the agent code.
89
+ *
90
+ * @param {Error|UncaughtError} err - The error instance to be processed
91
+ * @param {number} [time] - The relative ms (to origin) timestamp of occurrence. Defaults to now()
92
+ * @param {boolean} [internal=false] - If the error was "caught" and deemed "internal" before reporting to the jserrors feature
93
+ * @param {object} [customAttributes] - Any custom attributes to be included in the error payload
94
+ * @param {boolean} [hasReplay=false] - A flag indicating if the error occurred during a replay session
95
+ * @param {string} [swallowReason] - A string indicating pre-defined reason if swallowing the error. Mainly used by internal error supportability metrics
96
+ * @param {object} [target] - The target to buffer and harvest to. If undefined, the default configuration target is used
97
+ * @returns {void}
76
98
  */
77
- buildCanonicalStackString(stackInfo) {
78
- var canonicalStackString = '';
79
- for (var i = 0; i < stackInfo.frames.length; i++) {
80
- var frame = stackInfo.frames[i];
81
- var func = canonicalFunctionName(frame.func);
82
- if (canonicalStackString) canonicalStackString += '\n';
83
- if (func) canonicalStackString += func + '@';
84
- if (typeof frame.url === 'string') canonicalStackString += frame.url;
85
- if (frame.line) canonicalStackString += ':' + frame.line;
99
+ processError(err, time, internal, customAttributes, hasReplay, swallowReason, target) {
100
+ if (!err) return;
101
+ time = time || now();
102
+ try {
103
+ const filterOutput = this.#filterError(err, internal);
104
+ const stackInfo = computeStackTrace(err);
105
+ this.#swallowError(stackInfo, internal, swallowReason);
106
+ this.#deriveTargets(stackInfo, target).forEach(target => {
107
+ this.#storeError(err, time, stackInfo, filterOutput, customAttributes, hasReplay, target);
108
+ });
109
+ } catch (e) {
110
+ if (!(e instanceof ShortCircuit)) {
111
+ throw e;
112
+ }
86
113
  }
87
- return canonicalStackString;
88
114
  }
89
115
 
90
116
  /**
117
+ * Filters an error through the customer's configured onerror handler.
91
118
  *
92
- * @param {Error|UncaughtError} err The error instance to be processed
93
- * @param {number} time the relative ms (to origin) timestamp of occurrence
94
- * @param {boolean=} internal if the error was "caught" and deemed "internal" before reporting to the jserrors feature
95
- * @param {object=} customAttributes any custom attributes to be included in the error payload
96
- * @param {boolean=} hasReplay a flag indicating if the error occurred during a replay session
97
- * @param {string=} swallowReason a string indicating pre-defined reason if swallowing the error. Mainly used by the internal error SMs.
98
- * @param {object=} target the target to buffer and harvest to, if undefined the default configuration target is used
99
- * @returns
119
+ * If the customer has configured a custom onerror handler and the error is not internal,
120
+ * this method invokes that handler. The handler's return value determines whether the error
121
+ * should be reported:
122
+ * - Falsey values (false, null, undefined, etc.) Report the error normally
123
+ * - Truthy non-object values Don't report (throws ShortCircuit)
124
+ * - Object with 'group' property (non-empty string) Report with fingerprinting label
125
+ * - Any other truthy value Don't report (throws ShortCircuit)
126
+ *
127
+ * @param {Error|UncaughtError} err - The error to filter
128
+ * @param {boolean} internal - Whether this is an internal error (internal errors skip filtering)
129
+ * @returns {undefined|object} The filter output. If an object with 'group' property, contains fingerprinting data
130
+ * @throws {ShortCircuit} When the error should not be reported based on the filter output
100
131
  */
101
- storeError(err, time, internal, customAttributes, hasReplay, swallowReason, target) {
102
- if (!err) return;
103
- // are we in an interaction
104
- time = time || now();
132
+ #filterError(err, internal) {
105
133
  let filterOutput;
106
134
  if (!internal && this.agentRef.runtime.onerror) {
107
135
  filterOutput = this.agentRef.runtime.onerror(err);
108
136
  if (filterOutput && !(typeof filterOutput.group === 'string' && filterOutput.group.length)) {
109
137
  // All truthy values mean don't report (store) the error, per backwards-compatible usage,
110
138
  // - EXCEPT if a fingerprinting label is returned, via an object with key of 'group' and value of non-empty string
111
- return;
139
+ throw new ShortCircuit();
112
140
  }
113
141
  // Again as with previous usage, all falsey values would include the error.
114
142
  }
115
- var stackInfo = computeStackTrace(err);
143
+ return filterOutput;
144
+ }
145
+
146
+ /**
147
+ * Evaluates whether an error should be swallowed (not reported) based on internal error criteria.
148
+ *
149
+ * This method uses the evaluateInternalError function to determine if the error matches known
150
+ * internal error patterns (e.g., errors from the agent itself, known browser issues, etc.).
151
+ * If the error should be swallowed, a supportability metric is recorded and processing is halted.
152
+ *
153
+ * @param {StackInfo} stackInfo - The computed stack trace information
154
+ * @param {boolean} internal - Whether the error was marked as internal
155
+ * @param {string} [swallowReason] - Optional pre-determined reason for swallowing
156
+ * @returns {void}
157
+ * @throws {ShortCircuit} When the error should be swallowed and not reported
158
+ */
159
+ #swallowError(stackInfo, internal, swallowReason) {
116
160
  const {
117
161
  shouldSwallow,
118
162
  reason
119
163
  } = evaluateInternalError(stackInfo, internal, swallowReason);
120
164
  if (shouldSwallow) {
121
165
  this.reportSupportabilityMetric('Internal/Error/' + reason);
122
- return;
166
+ throw new ShortCircuit();
123
167
  }
124
- var canonicalStackString = this.buildCanonicalStackString(stackInfo);
168
+ }
169
+
170
+ /**
171
+ * Derives the appropriate targets for reporting the given stack information.
172
+ *
173
+ * Targets represent the entities that should receive the error data. This is particularly
174
+ * important for Micro Frontend (MFE) scenarios where errors may need to be reported to
175
+ * different applications.
176
+ *
177
+ * Logic:
178
+ * - If a target is explicitly provided (e.g., from the register API), use it
179
+ * - For v2 endpoints without an explicit target, scan stack frames to detect MFE sources
180
+ * - If no MFE is detected or v2 is not enabled, use undefined (default target)
181
+ *
182
+ * @param {StackInfo} stackInfo - The computed stack trace information containing frames
183
+ * @param {object} [target] - Explicitly provided target, typically from the register API
184
+ * @returns {Array<object|undefined>} Array of targets to report the error to. Always contains at least one element.
185
+ */
186
+ #deriveTargets(stackInfo, target) {
187
+ const targets = [];
188
+ if (target) {
189
+ // reported by the register API directly
190
+ targets.push(target);
191
+ } else {
192
+ // we dont know if this is MFE yet, we need to figure it out.
193
+ if (this.harvestEndpointVersion === 2) {
194
+ for (const frame of stackInfo.frames) {
195
+ targets.push(...getRegisteredTargetsFromFilename(frame.url, this.agentRef));
196
+ if (targets.length) break;
197
+ }
198
+ }
199
+ if (!targets.length) targets.push(undefined);
200
+ }
201
+ return targets;
202
+ }
203
+
204
+ /**
205
+ * Stores error data for eventual harvesting and transmission to the backend.
206
+ *
207
+ * This method processes the error through several stages:
208
+ * 1. Build canonical stack string for cross-browser error grouping
209
+ * 2. Build cause chain string if the error has a cause property
210
+ * 3. Create params object with error metadata (stack hash, class, message, etc.)
211
+ * 4. Create bucket hash for internal deduplication
212
+ * 5. Store stack trace on first occurrence of this error
213
+ * 6. Add custom attributes and send to other features (trace, replay)
214
+ * 7. Route through soft nav if enabled, or directly to harvest storage
215
+ * 8. Handle MFE duplication for v2 endpoints if needed
216
+ *
217
+ * @param {Error|UncaughtError} err - The error instance to be processed
218
+ * @param {number} time - The relative ms (to origin) timestamp of occurrence
219
+ * @param {StackInfo} stackInfo - The computed stack trace information
220
+ * @param {object} [filterOutput] - Output from the customer's onerror handler, may contain fingerprinting group
221
+ * @param {object} [customAttributes] - Any custom attributes to be included in the error payload
222
+ * @param {boolean} [hasReplay=false] - A flag indicating if the error occurred during a replay session
223
+ * @param {object} [target] - The target to buffer and harvest to. If undefined, the default configuration target is used
224
+ * @returns {void}
225
+ */
226
+ #storeError(err, time, stackInfo, filterOutput, customAttributes, hasReplay, target) {
227
+ var canonicalStackString = this.#buildCanonicalStackString(stackInfo);
125
228
  const causeStackString = buildCauseString(err);
126
229
  const params = {
127
230
  stackHash: stringHashCode(canonicalStackString),
@@ -144,7 +247,7 @@ export class Aggregate extends AggregateBase {
144
247
  * the canonical stack trace excludes items like the column number increasing the hit-rate of different errors potentially
145
248
  * bucketing and ultimately resulting in the loss of data in NR1.
146
249
  */
147
- var bucketHash = stringHashCode("".concat(stackInfo.name, "_").concat(stackInfo.message, "_").concat(stackInfo.stackString, "_").concat(params.hasReplay ? 1 : 0, "_").concat(target?.id || 'container'));
250
+ var bucketHash = stringHashCode("".concat(stackInfo.name, "_").concat(stackInfo.message, "_").concat(stackInfo.stackString, "_").concat(params.hasReplay ? 1 : 0, "_").concat(target?.id || 'container', "_").concat(target?.instance || ''));
148
251
  if (!this.stackReported[bucketHash]) {
149
252
  this.stackReported[bucketHash] = true;
150
253
  params.stack_trace = truncateSize(stackInfo.stackString);
@@ -184,27 +287,90 @@ export class Aggregate extends AggregateBase {
184
287
  // pass the error to soft nav for evaluation - it will return it via 'returnJserror' when interaction is resolved
185
288
  handle('jserror', [jsErrorEvent], undefined, FEATURE_NAMES.softNav, this.ee);
186
289
  } else {
187
- this.#storeJserrorForHarvest(jsErrorEvent, false);
290
+ this.#storeJserrorForHarvest(jsErrorEvent);
188
291
  }
189
292
  }
190
293
 
191
294
  // always add directly if scoped to a sub-entity, the other pathways above will be deterministic if the main agent should procede
192
- if (target) this.#storeJserrorForHarvest([...jsErrorEvent, target], false, params._softNavAttributes);
295
+ if (target) this.#storeJserrorForHarvest(jsErrorEvent, {}, target);
296
+ }
297
+
298
+ /**
299
+ * Builds a standardized (canonical) stack trace string from the frames in the given `stackInfo` object.
300
+ *
301
+ * The canonical format is used for cross-browser error grouping in NR1, as different browsers
302
+ * format stack traces differently. Each frame is separated by a newline character and takes
303
+ * the form: `<functionName>@<url>:<lineNumber>`
304
+ *
305
+ * Note: Column numbers are intentionally excluded from the canonical format to improve
306
+ * grouping accuracy, as the same error across different minified builds might have different
307
+ * column numbers but should still be grouped together.
308
+ *
309
+ * Example output:
310
+ * ```
311
+ * handleClick@https://example.com/app.js:42
312
+ * EventEmitter.emit@https://example.com/vendor.js:1337
313
+ * ```
314
+ *
315
+ * @param {StackInfo} stackInfo - An object containing parsed stack frames from computeStackTrace
316
+ * @returns {string} A canonical stack string built from the URLs and function names in the given `stackInfo` object
317
+ */
318
+ #buildCanonicalStackString(stackInfo) {
319
+ var canonicalStackString = '';
320
+ for (var i = 0; i < stackInfo.frames.length; i++) {
321
+ var frame = stackInfo.frames[i];
322
+ var func = canonicalFunctionName(frame.func);
323
+ if (canonicalStackString) canonicalStackString += '\n';
324
+ if (func) canonicalStackString += func + '@';
325
+ if (typeof frame.url === 'string') canonicalStackString += frame.url;
326
+ if (frame.line) canonicalStackString += ':' + frame.line;
327
+ }
328
+ return canonicalStackString;
193
329
  }
194
- #storeJserrorForHarvest(errorInfoArr, softNavCustomAttrs = {}) {
195
- let [type, bucketHash, params, newMetrics, localAttrs, target] = errorInfoArr;
330
+
331
+ /**
332
+ * Adds a processed error to the harvest buffer with all custom attributes merged.
333
+ *
334
+ * This is the final step before an error is stored to be sent to the backend. It handles:
335
+ * - Merging all custom attributes (global, soft nav, MFE, and local)
336
+ * - Creating a unique aggregate hash for deduplication
337
+ * - Adding the error to the events buffer for harvest
338
+ * - Duplicating the error for MFE scenarios when needed (v2 endpoints)
339
+ *
340
+ * Custom Attribute Precedence (lowest to highest):
341
+ * 1. Global jsAttributes from agent config
342
+ * 2. Soft navigation attributes (if from a soft nav interaction)
343
+ * 3. MFE v2 attributes (source/parent metadata)
344
+ * 4. Local custom attributes passed with the specific error
345
+ *
346
+ * @param {Array} errorInfoArr - Array containing [type, bucketHash, params, metrics, customAttributes, target]
347
+ * @param {object} [attrs={}] - Additional attributes to merge (e.g., from soft nav interactions)
348
+ * @returns {void}
349
+ */
350
+ #storeJserrorForHarvest(errorInfoArr, attrs = {}, target) {
351
+ let [type, bucketHash, params, newMetrics, localAttrs] = errorInfoArr;
196
352
  const allCustomAttrs = {
197
353
  /** MFE specific attributes if in "multiple" mode (ie consumer version 2) */
198
354
  ...getVersion2Attributes(target, this)
199
355
  };
200
356
  Object.entries(this.agentRef.info.jsAttributes).forEach(([k, v]) => setCustom(k, v));
201
- 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
357
+ 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
202
358
  if (params.browserInteractionId) bucketHash += params.browserInteractionId;
203
359
  if (localAttrs) Object.entries(localAttrs).forEach(([k, v]) => setCustom(k, v)); // local custom attrs are applied in either case with the highest precedence
204
360
 
205
361
  const jsAttributesHash = stringHashCode(stringify(allCustomAttrs));
206
362
  const aggregateHash = bucketHash + ':' + jsAttributesHash;
207
363
  this.events.add([type, aggregateHash, params, newMetrics, allCustomAttrs]);
364
+ if (shouldDuplicate(target, this)) {
365
+ // Clone the array with new object references to prevent deduplication in the shared aggregator
366
+ this.#storeJserrorForHarvest([type, bucketHash, {
367
+ ...params
368
+ }, {
369
+ ...newMetrics
370
+ }, localAttrs && {
371
+ ...localAttrs
372
+ }], getVersion2DuplicationAttributes(target, this));
373
+ }
208
374
  function setCustom(key, val) {
209
375
  allCustomAttrs[key] = val && typeof val === 'object' ? stringify(val) : val;
210
376
  }
@@ -1 +1 @@
1
- {"root":["../src/index.js","../src/cdn/experimental.js","../src/cdn/lite.js","../src/cdn/pro.js","../src/cdn/spa.js","../src/common/aggregate/aggregator.js","../src/common/aggregate/event-aggregator.js","../src/common/config/configurable.js","../src/common/config/info.js","../src/common/config/init-types.js","../src/common/config/init.js","../src/common/config/loader-config.js","../src/common/config/runtime.js","../src/common/constants/agent-constants.js","../src/common/constants/env.cdn.js","../src/common/constants/env.js","../src/common/constants/env.npm.js","../src/common/constants/runtime.js","../src/common/constants/shared-channel.js","../src/common/deny-list/deny-list.js","../src/common/dispatch/global-event.js","../src/common/dom/iframe.js","../src/common/dom/query-selector.js","../src/common/dom/selector-path.js","../src/common/drain/drain.js","../src/common/event-emitter/contextual-ee.js","../src/common/event-emitter/event-context.js","../src/common/event-emitter/handle.js","../src/common/event-emitter/register-handler.js","../src/common/event-listener/event-listener-opts.js","../src/common/harvest/harvester.js","../src/common/harvest/types.js","../src/common/ids/bundle-id.js","../src/common/ids/id.js","../src/common/ids/unique-id.js","../src/common/serialize/bel-serializer.js","../src/common/session/constants.js","../src/common/session/session-entity.js","../src/common/storage/local-storage.js","../src/common/timer/interaction-timer.js","../src/common/timer/timer.js","../src/common/timing/nav-timing.js","../src/common/timing/now.js","../src/common/timing/time-keeper.js","../src/common/unload/eol.js","../src/common/url/canonicalize-url.js","../src/common/url/clean-url.js","../src/common/url/encode.js","../src/common/url/extract-url.js","../src/common/url/location.js","../src/common/url/parse-url.js","../src/common/url/protocol.js","../src/common/util/attribute-size.js","../src/common/util/browser-stack-matchers.js","../src/common/util/console.js","../src/common/util/data-size.js","../src/common/util/event-origin.js","../src/common/util/feature-flags.js","../src/common/util/get-or-set.js","../src/common/util/invoke.js","../src/common/util/monkey-patched.js","../src/common/util/obfuscate.js","../src/common/util/stringify.js","../src/common/util/submit-data.js","../src/common/util/text.js","../src/common/util/traverse.js","../src/common/util/type-check.js","../src/common/util/webdriver-detection.js","../src/common/v2/script-correlation.js","../src/common/v2/script-tracker.js","../src/common/v2/utils.js","../src/common/vitals/constants.js","../src/common/vitals/cumulative-layout-shift.js","../src/common/vitals/first-contentful-paint.js","../src/common/vitals/first-paint.js","../src/common/vitals/interaction-to-next-paint.js","../src/common/vitals/largest-contentful-paint.js","../src/common/vitals/load-time.js","../src/common/vitals/time-to-first-byte.js","../src/common/vitals/vital-metric.js","../src/common/window/load.js","../src/common/window/nreum.js","../src/common/window/page-visibility.js","../src/common/wrap/wrap-events.js","../src/common/wrap/wrap-fetch.js","../src/common/wrap/wrap-function.js","../src/common/wrap/wrap-history.js","../src/common/wrap/wrap-logger.js","../src/common/wrap/wrap-websocket.js","../src/common/wrap/wrap-xhr.js","../src/features/ajax/constants.js","../src/features/ajax/index.js","../src/features/ajax/aggregate/gql.js","../src/features/ajax/aggregate/index.js","../src/features/ajax/instrument/distributed-tracing.js","../src/features/ajax/instrument/index.js","../src/features/ajax/instrument/response-size.js","../src/features/generic_events/constants.js","../src/features/generic_events/index.js","../src/features/generic_events/aggregate/index.js","../src/features/generic_events/aggregate/user-actions/aggregated-user-action.js","../src/features/generic_events/aggregate/user-actions/user-actions-aggregator.js","../src/features/generic_events/instrument/index.js","../src/features/jserrors/constants.js","../src/features/jserrors/index.js","../src/features/jserrors/aggregate/canonical-function-name.js","../src/features/jserrors/aggregate/cause-string.js","../src/features/jserrors/aggregate/compute-stack-trace.js","../src/features/jserrors/aggregate/format-stack-trace.js","../src/features/jserrors/aggregate/index.js","../src/features/jserrors/aggregate/internal-errors.js","../src/features/jserrors/aggregate/string-hash-code.js","../src/features/jserrors/instrument/index.js","../src/features/jserrors/shared/cast-error.js","../src/features/jserrors/shared/uncaught-error.js","../src/features/logging/constants.js","../src/features/logging/index.js","../src/features/logging/aggregate/index.js","../src/features/logging/instrument/index.js","../src/features/logging/shared/log.js","../src/features/logging/shared/utils.js","../src/features/metrics/constants.js","../src/features/metrics/index.js","../src/features/metrics/aggregate/framework-detection.js","../src/features/metrics/aggregate/harvest-metadata.js","../src/features/metrics/aggregate/index.js","../src/features/metrics/instrument/index.js","../src/features/page_action/constants.js","../src/features/page_action/index.js","../src/features/page_action/instrument/index.js","../src/features/page_view_event/constants.js","../src/features/page_view_event/index.js","../src/features/page_view_event/aggregate/index.js","../src/features/page_view_event/aggregate/initialized-features.js","../src/features/page_view_event/instrument/index.js","../src/features/page_view_timing/constants.js","../src/features/page_view_timing/index.js","../src/features/page_view_timing/aggregate/index.js","../src/features/page_view_timing/instrument/index.js","../src/features/session_replay/constants.js","../src/features/session_replay/index.js","../src/features/session_replay/aggregate/index.js","../src/features/session_replay/instrument/index.js","../src/features/session_replay/shared/recorder-events.js","../src/features/session_replay/shared/recorder.js","../src/features/session_replay/shared/stylesheet-evaluator.js","../src/features/session_replay/shared/utils.js","../src/features/session_trace/constants.js","../src/features/session_trace/index.js","../src/features/session_trace/aggregate/index.js","../src/features/session_trace/aggregate/trace/node.js","../src/features/session_trace/aggregate/trace/storage.js","../src/features/session_trace/aggregate/trace/utils.js","../src/features/session_trace/instrument/index.js","../src/features/soft_navigations/constants.js","../src/features/soft_navigations/index.js","../src/features/soft_navigations/aggregate/ajax-node.js","../src/features/soft_navigations/aggregate/bel-node.js","../src/features/soft_navigations/aggregate/index.js","../src/features/soft_navigations/aggregate/initial-page-load-interaction.js","../src/features/soft_navigations/aggregate/interaction.js","../src/features/soft_navigations/instrument/index.js","../src/features/utils/agent-session.js","../src/features/utils/aggregate-base.js","../src/features/utils/event-buffer.js","../src/features/utils/feature-base.js","../src/features/utils/feature-gates.js","../src/features/utils/instrument-base.js","../src/interfaces/registered-entity.js","../src/loaders/agent-base.js","../src/loaders/agent.js","../src/loaders/api-base.js","../src/loaders/browser-agent.js","../src/loaders/micro-agent-base.js","../src/loaders/micro-agent.js","../src/loaders/api/addPageAction.js","../src/loaders/api/addRelease.js","../src/loaders/api/addToTrace.js","../src/loaders/api/consent.js","../src/loaders/api/constants.js","../src/loaders/api/finished.js","../src/loaders/api/interaction-types.js","../src/loaders/api/interaction.js","../src/loaders/api/log.js","../src/loaders/api/measure.js","../src/loaders/api/noticeError.js","../src/loaders/api/pauseReplay.js","../src/loaders/api/recordCustomEvent.js","../src/loaders/api/recordReplay.js","../src/loaders/api/register-api-types.js","../src/loaders/api/register.js","../src/loaders/api/setApplicationVersion.js","../src/loaders/api/setCustomAttribute.js","../src/loaders/api/setErrorHandler.js","../src/loaders/api/setPageViewName.js","../src/loaders/api/setUserId.js","../src/loaders/api/sharedHandlers.js","../src/loaders/api/start.js","../src/loaders/api/topLevelCallers.js","../src/loaders/api/wrapLogger.js","../src/loaders/configure/configure.js","../src/loaders/configure/nonce.js","../src/loaders/configure/public-path.js","../src/loaders/features/enabled-features.js","../src/loaders/features/featureDependencies.js","../src/loaders/features/features.js"],"version":"5.9.3"}
1
+ {"root":["../src/index.js","../src/cdn/experimental.js","../src/cdn/lite.js","../src/cdn/pro.js","../src/cdn/spa.js","../src/common/aggregate/aggregator.js","../src/common/aggregate/event-aggregator.js","../src/common/config/configurable.js","../src/common/config/info.js","../src/common/config/init-types.js","../src/common/config/init.js","../src/common/config/loader-config.js","../src/common/config/runtime.js","../src/common/constants/agent-constants.js","../src/common/constants/env.cdn.js","../src/common/constants/env.js","../src/common/constants/env.npm.js","../src/common/constants/runtime.js","../src/common/constants/shared-channel.js","../src/common/deny-list/deny-list.js","../src/common/dispatch/global-event.js","../src/common/dom/iframe.js","../src/common/dom/query-selector.js","../src/common/dom/selector-path.js","../src/common/drain/drain.js","../src/common/event-emitter/contextual-ee.js","../src/common/event-emitter/event-context.js","../src/common/event-emitter/handle.js","../src/common/event-emitter/register-handler.js","../src/common/event-listener/event-listener-opts.js","../src/common/harvest/harvester.js","../src/common/harvest/types.js","../src/common/ids/bundle-id.js","../src/common/ids/id.js","../src/common/ids/unique-id.js","../src/common/serialize/bel-serializer.js","../src/common/session/constants.js","../src/common/session/session-entity.js","../src/common/storage/local-storage.js","../src/common/timer/interaction-timer.js","../src/common/timer/timer.js","../src/common/timing/nav-timing.js","../src/common/timing/now.js","../src/common/timing/time-keeper.js","../src/common/unload/eol.js","../src/common/url/canonicalize-url.js","../src/common/url/clean-url.js","../src/common/url/encode.js","../src/common/url/extract-url.js","../src/common/url/location.js","../src/common/url/parse-url.js","../src/common/url/protocol.js","../src/common/util/attribute-size.js","../src/common/util/browser-stack-matchers.js","../src/common/util/console.js","../src/common/util/data-size.js","../src/common/util/event-origin.js","../src/common/util/feature-flags.js","../src/common/util/get-or-set.js","../src/common/util/invoke.js","../src/common/util/monkey-patched.js","../src/common/util/obfuscate.js","../src/common/util/short-circuit.js","../src/common/util/stringify.js","../src/common/util/submit-data.js","../src/common/util/text.js","../src/common/util/traverse.js","../src/common/util/type-check.js","../src/common/util/webdriver-detection.js","../src/common/v2/script-correlation.js","../src/common/v2/script-tracker.js","../src/common/v2/utils.js","../src/common/vitals/constants.js","../src/common/vitals/cumulative-layout-shift.js","../src/common/vitals/first-contentful-paint.js","../src/common/vitals/first-paint.js","../src/common/vitals/interaction-to-next-paint.js","../src/common/vitals/largest-contentful-paint.js","../src/common/vitals/load-time.js","../src/common/vitals/time-to-first-byte.js","../src/common/vitals/vital-metric.js","../src/common/window/load.js","../src/common/window/nreum.js","../src/common/window/page-visibility.js","../src/common/wrap/wrap-events.js","../src/common/wrap/wrap-fetch.js","../src/common/wrap/wrap-function.js","../src/common/wrap/wrap-history.js","../src/common/wrap/wrap-logger.js","../src/common/wrap/wrap-websocket.js","../src/common/wrap/wrap-xhr.js","../src/features/ajax/constants.js","../src/features/ajax/index.js","../src/features/ajax/aggregate/gql.js","../src/features/ajax/aggregate/index.js","../src/features/ajax/instrument/distributed-tracing.js","../src/features/ajax/instrument/index.js","../src/features/ajax/instrument/response-size.js","../src/features/generic_events/constants.js","../src/features/generic_events/index.js","../src/features/generic_events/aggregate/index.js","../src/features/generic_events/aggregate/user-actions/aggregated-user-action.js","../src/features/generic_events/aggregate/user-actions/user-actions-aggregator.js","../src/features/generic_events/instrument/index.js","../src/features/jserrors/constants.js","../src/features/jserrors/index.js","../src/features/jserrors/aggregate/canonical-function-name.js","../src/features/jserrors/aggregate/cause-string.js","../src/features/jserrors/aggregate/compute-stack-trace.js","../src/features/jserrors/aggregate/format-stack-trace.js","../src/features/jserrors/aggregate/index.js","../src/features/jserrors/aggregate/internal-errors.js","../src/features/jserrors/aggregate/string-hash-code.js","../src/features/jserrors/instrument/index.js","../src/features/jserrors/shared/cast-error.js","../src/features/jserrors/shared/uncaught-error.js","../src/features/logging/constants.js","../src/features/logging/index.js","../src/features/logging/aggregate/index.js","../src/features/logging/instrument/index.js","../src/features/logging/shared/log.js","../src/features/logging/shared/utils.js","../src/features/metrics/constants.js","../src/features/metrics/index.js","../src/features/metrics/aggregate/framework-detection.js","../src/features/metrics/aggregate/harvest-metadata.js","../src/features/metrics/aggregate/index.js","../src/features/metrics/instrument/index.js","../src/features/page_action/constants.js","../src/features/page_action/index.js","../src/features/page_action/instrument/index.js","../src/features/page_view_event/constants.js","../src/features/page_view_event/index.js","../src/features/page_view_event/aggregate/index.js","../src/features/page_view_event/aggregate/initialized-features.js","../src/features/page_view_event/instrument/index.js","../src/features/page_view_timing/constants.js","../src/features/page_view_timing/index.js","../src/features/page_view_timing/aggregate/index.js","../src/features/page_view_timing/instrument/index.js","../src/features/session_replay/constants.js","../src/features/session_replay/index.js","../src/features/session_replay/aggregate/index.js","../src/features/session_replay/instrument/index.js","../src/features/session_replay/shared/recorder-events.js","../src/features/session_replay/shared/recorder.js","../src/features/session_replay/shared/stylesheet-evaluator.js","../src/features/session_replay/shared/utils.js","../src/features/session_trace/constants.js","../src/features/session_trace/index.js","../src/features/session_trace/aggregate/index.js","../src/features/session_trace/aggregate/trace/node.js","../src/features/session_trace/aggregate/trace/storage.js","../src/features/session_trace/aggregate/trace/utils.js","../src/features/session_trace/instrument/index.js","../src/features/soft_navigations/constants.js","../src/features/soft_navigations/index.js","../src/features/soft_navigations/aggregate/ajax-node.js","../src/features/soft_navigations/aggregate/bel-node.js","../src/features/soft_navigations/aggregate/index.js","../src/features/soft_navigations/aggregate/initial-page-load-interaction.js","../src/features/soft_navigations/aggregate/interaction.js","../src/features/soft_navigations/instrument/index.js","../src/features/utils/agent-session.js","../src/features/utils/aggregate-base.js","../src/features/utils/event-buffer.js","../src/features/utils/feature-base.js","../src/features/utils/feature-gates.js","../src/features/utils/instrument-base.js","../src/interfaces/registered-entity.js","../src/loaders/agent-base.js","../src/loaders/agent.js","../src/loaders/api-base.js","../src/loaders/browser-agent.js","../src/loaders/micro-agent-base.js","../src/loaders/micro-agent.js","../src/loaders/api/addPageAction.js","../src/loaders/api/addRelease.js","../src/loaders/api/addToTrace.js","../src/loaders/api/consent.js","../src/loaders/api/constants.js","../src/loaders/api/finished.js","../src/loaders/api/interaction-types.js","../src/loaders/api/interaction.js","../src/loaders/api/log.js","../src/loaders/api/measure.js","../src/loaders/api/noticeError.js","../src/loaders/api/pauseReplay.js","../src/loaders/api/recordCustomEvent.js","../src/loaders/api/recordReplay.js","../src/loaders/api/register-api-types.js","../src/loaders/api/register.js","../src/loaders/api/setApplicationVersion.js","../src/loaders/api/setCustomAttribute.js","../src/loaders/api/setErrorHandler.js","../src/loaders/api/setPageViewName.js","../src/loaders/api/setUserId.js","../src/loaders/api/sharedHandlers.js","../src/loaders/api/start.js","../src/loaders/api/topLevelCallers.js","../src/loaders/api/wrapLogger.js","../src/loaders/configure/configure.js","../src/loaders/configure/nonce.js","../src/loaders/configure/public-path.js","../src/loaders/features/enabled-features.js","../src/loaders/features/featureDependencies.js","../src/loaders/features/features.js"],"version":"5.9.3"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ /** A special error used to short-circuit the error processing pipeline */
6
+ export class ShortCircuit extends Error {
7
+ }
8
+ //# sourceMappingURL=short-circuit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"short-circuit.d.ts","sourceRoot":"","sources":["../../../../src/common/util/short-circuit.js"],"names":[],"mappings":"AAAA;;;GAGG;AACH,0EAA0E;AAC1E;CAA0C"}
@@ -14,25 +14,34 @@ export class Aggregate extends AggregateBase {
14
14
  pve: string;
15
15
  };
16
16
  /**
17
- * Builds a standardized stack trace string from the frames in the given `stackInfo` object, with each frame separated
18
- * by a newline character. Lines take the form `<functionName>@<url>:<lineNumber>`.
17
+ * Main entry point for processing JavaScript errors. This method orchestrates the complete error processing pipeline.
19
18
  *
20
- * @param {StackInfo} stackInfo - An object specifying a stack string and individual frames.
21
- * @returns {string} A canonical stack string built from the URLs and function names in the given `stackInfo` object.
22
- */
23
- buildCanonicalStackString(stackInfo: StackInfo): string;
24
- /**
19
+ * Processing Flow:
20
+ * 1. Filter the error through the customer's onerror handler (if configured and not internal)
21
+ * 2. Compute the stack trace from the error object
22
+ * 3. Evaluate if the error should be swallowed (internal errors, known issues, etc.)
23
+ * 4. Derive target(s) for the error (MFE detection for v2 endpoints, or default target) - Note: "undefined" indicates the default target will be used
24
+ * 5. Store the error for each derived target. During storage (#storeJserrorForHarvest), duplication for MFE <-> container will be handled
25
+ *
26
+ * Important: "ShortCircuit" Pattern:
27
+ * Several steps in the pipeline can throw a ShortCircuit error to halt processing without
28
+ * treating it as a reportable error. This pattern is used when:
29
+ * - The customer's onerror handler returns a truthy value (excluding fingerprinting objects)
30
+ * - The error is identified as an internal error that shouldn't be reported
31
+ *
32
+ * When a ShortCircuit is thrown, processing stops immediately and the error is not stored.
33
+ * Any other thrown error is re-thrown as it represents an actual problem in the agent code.
25
34
  *
26
- * @param {Error|UncaughtError} err The error instance to be processed
27
- * @param {number} time the relative ms (to origin) timestamp of occurrence
28
- * @param {boolean=} internal if the error was "caught" and deemed "internal" before reporting to the jserrors feature
29
- * @param {object=} customAttributes any custom attributes to be included in the error payload
30
- * @param {boolean=} hasReplay a flag indicating if the error occurred during a replay session
31
- * @param {string=} swallowReason a string indicating pre-defined reason if swallowing the error. Mainly used by the internal error SMs.
32
- * @param {object=} target the target to buffer and harvest to, if undefined the default configuration target is used
33
- * @returns
35
+ * @param {Error|UncaughtError} err - The error instance to be processed
36
+ * @param {number} [time] - The relative ms (to origin) timestamp of occurrence. Defaults to now()
37
+ * @param {boolean} [internal=false] - If the error was "caught" and deemed "internal" before reporting to the jserrors feature
38
+ * @param {object} [customAttributes] - Any custom attributes to be included in the error payload
39
+ * @param {boolean} [hasReplay=false] - A flag indicating if the error occurred during a replay session
40
+ * @param {string} [swallowReason] - A string indicating pre-defined reason if swallowing the error. Mainly used by internal error supportability metrics
41
+ * @param {object} [target] - The target to buffer and harvest to. If undefined, the default configuration target is used
42
+ * @returns {void}
34
43
  */
35
- storeError(err: Error | UncaughtError, time: number, internal?: boolean | undefined, customAttributes?: object | undefined, hasReplay?: boolean | undefined, swallowReason?: string | undefined, target?: object | undefined): void;
44
+ processError(err: Error | UncaughtError, time?: number, internal?: boolean, customAttributes?: object, hasReplay?: boolean, swallowReason?: string, target?: object): void;
36
45
  #private;
37
46
  }
38
47
  export type StackInfo = import("./compute-stack-trace.js").StackInfo;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/aggregate/index.js"],"names":[],"mappings":"AAwBA;;GAEG;AAEH;IACE,2BAAiC;IACjC,2BAuBC;IAlBC,kBAAuB;IACvB,eAAoB;IACpB,qBAA0B;IAC1B,qBAAwB;IAiB1B,oDAEC;IAED;;;MAcC;IAED;;;;;;OAMG;IACH,qCAHW,SAAS,GACP,MAAM,CAgBlB;IAED;;;;;;;;;;OAUG;IACH,gBATW,KAAK,GAAC,aAAa,QACnB,MAAM,aACN,OAAO,YAAC,qBACR,MAAM,YAAC,cACP,OAAO,YAAC,kBACR,MAAM,YAAC,WACP,MAAM,YAAC,QAkGjB;;CAuBF;wBA1MY,OAAO,0BAA0B,EAAE,SAAS;8BAR3B,4BAA4B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/aggregate/index.js"],"names":[],"mappings":"AAyBA;;GAEG;AAEH;IACE,2BAAiC;IACjC,2BAuBC;IAlBC,kBAAuB;IACvB,eAAoB;IACpB,qBAA0B;IAC1B,qBAAwB;IAiB1B,oDAEC;IAED;;;MAcC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,kBATW,KAAK,GAAC,aAAa,SACnB,MAAM,aACN,OAAO,qBACP,MAAM,cACN,OAAO,kBACP,MAAM,WACN,MAAM,GACJ,IAAI,CAiBhB;;CAyQF;wBAtWY,OAAO,0BAA0B,EAAE,SAAS;8BAT3B,4BAA4B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/browser-agent",
3
- "version": "1.312.1-rc.12",
3
+ "version": "1.312.1-rc.13",
4
4
  "private": false,
5
5
  "author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
6
6
  "description": "New Relic Browser Agent",
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ /** A special error used to short-circuit the error processing pipeline */
6
+ export class ShortCircuit extends Error {}
@@ -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/v2/utils'
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
  }