@newrelic/browser-agent 1.311.0 → 1.312.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 (81) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cjs/common/constants/agent-constants.js +4 -5
  3. package/dist/cjs/common/constants/env.cdn.js +2 -2
  4. package/dist/cjs/common/constants/env.npm.js +2 -2
  5. package/dist/cjs/common/util/script-tracker.js +2 -0
  6. package/dist/cjs/common/util/stringify.js +6 -21
  7. package/dist/cjs/common/util/v2.js +90 -6
  8. package/dist/cjs/common/wrap/wrap-fetch.js +10 -5
  9. package/dist/cjs/common/wrap/wrap-function.js +17 -9
  10. package/dist/cjs/common/wrap/wrap-logger.js +6 -4
  11. package/dist/cjs/common/wrap/wrap-xhr.js +3 -1
  12. package/dist/cjs/features/ajax/aggregate/index.js +24 -6
  13. package/dist/cjs/features/ajax/instrument/index.js +12 -10
  14. package/dist/cjs/features/generic_events/instrument/index.js +3 -3
  15. package/dist/cjs/features/logging/aggregate/index.js +12 -7
  16. package/dist/cjs/features/logging/instrument/index.js +6 -4
  17. package/dist/cjs/features/logging/shared/utils.js +4 -4
  18. package/dist/cjs/features/soft_navigations/aggregate/ajax-node.js +6 -3
  19. package/dist/cjs/loaders/api/register.js +24 -23
  20. package/dist/cjs/loaders/api/wrapLogger.js +2 -2
  21. package/dist/esm/common/constants/agent-constants.js +4 -5
  22. package/dist/esm/common/constants/env.cdn.js +2 -2
  23. package/dist/esm/common/constants/env.npm.js +2 -2
  24. package/dist/esm/common/util/script-tracker.js +2 -2
  25. package/dist/esm/common/util/stringify.js +6 -21
  26. package/dist/esm/common/util/v2.js +86 -6
  27. package/dist/esm/common/wrap/wrap-fetch.js +10 -5
  28. package/dist/esm/common/wrap/wrap-function.js +17 -9
  29. package/dist/esm/common/wrap/wrap-logger.js +6 -4
  30. package/dist/esm/common/wrap/wrap-xhr.js +3 -1
  31. package/dist/esm/features/ajax/aggregate/index.js +24 -6
  32. package/dist/esm/features/ajax/instrument/index.js +12 -10
  33. package/dist/esm/features/generic_events/instrument/index.js +3 -3
  34. package/dist/esm/features/logging/aggregate/index.js +13 -8
  35. package/dist/esm/features/logging/instrument/index.js +6 -4
  36. package/dist/esm/features/logging/shared/utils.js +4 -4
  37. package/dist/esm/features/soft_navigations/aggregate/ajax-node.js +6 -3
  38. package/dist/esm/loaders/api/register.js +24 -23
  39. package/dist/esm/loaders/api/wrapLogger.js +2 -2
  40. package/dist/types/common/constants/agent-constants.d.ts.map +1 -1
  41. package/dist/types/common/util/script-tracker.d.ts +11 -0
  42. package/dist/types/common/util/script-tracker.d.ts.map +1 -1
  43. package/dist/types/common/util/stringify.d.ts.map +1 -1
  44. package/dist/types/common/util/v2.d.ts +37 -0
  45. package/dist/types/common/util/v2.d.ts.map +1 -1
  46. package/dist/types/common/wrap/wrap-fetch.d.ts +1 -1
  47. package/dist/types/common/wrap/wrap-fetch.d.ts.map +1 -1
  48. package/dist/types/common/wrap/wrap-function.d.ts +1 -1
  49. package/dist/types/common/wrap/wrap-function.d.ts.map +1 -1
  50. package/dist/types/common/wrap/wrap-logger.d.ts +1 -1
  51. package/dist/types/common/wrap/wrap-logger.d.ts.map +1 -1
  52. package/dist/types/common/wrap/wrap-xhr.d.ts +1 -1
  53. package/dist/types/common/wrap/wrap-xhr.d.ts.map +1 -1
  54. package/dist/types/features/ajax/aggregate/index.d.ts +2 -1
  55. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  56. package/dist/types/features/ajax/instrument/index.d.ts.map +1 -1
  57. package/dist/types/features/logging/aggregate/index.d.ts.map +1 -1
  58. package/dist/types/features/logging/instrument/index.d.ts.map +1 -1
  59. package/dist/types/features/logging/shared/utils.d.ts +2 -2
  60. package/dist/types/features/logging/shared/utils.d.ts.map +1 -1
  61. package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts +1 -0
  62. package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts.map +1 -1
  63. package/dist/types/loaders/api/register.d.ts.map +1 -1
  64. package/package.json +2 -2
  65. package/src/common/constants/agent-constants.js +4 -5
  66. package/src/common/util/script-tracker.js +2 -2
  67. package/src/common/util/stringify.js +6 -23
  68. package/src/common/util/v2.js +84 -7
  69. package/src/common/wrap/wrap-fetch.js +12 -5
  70. package/src/common/wrap/wrap-function.js +16 -9
  71. package/src/common/wrap/wrap-logger.js +6 -4
  72. package/src/common/wrap/wrap-xhr.js +3 -1
  73. package/src/features/ajax/aggregate/index.js +22 -6
  74. package/src/features/ajax/instrument/index.js +13 -10
  75. package/src/features/generic_events/instrument/index.js +3 -3
  76. package/src/features/logging/aggregate/index.js +13 -14
  77. package/src/features/logging/instrument/index.js +6 -4
  78. package/src/features/logging/shared/utils.js +4 -4
  79. package/src/features/soft_navigations/aggregate/ajax-node.js +6 -3
  80. package/src/loaders/api/register.js +19 -12
  81. package/src/loaders/api/wrapLogger.js +2 -2
@@ -14,7 +14,7 @@ var _wrapLogger2 = require("../../../loaders/api/wrapLogger");
14
14
  var _register = require("../../../loaders/api/register");
15
15
  var _monkeyPatched = require("../../../common/util/monkey-patched");
16
16
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } /**
17
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
17
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
18
18
  * SPDX-License-Identifier: Apache-2.0
19
19
  */
20
20
  class Instrument extends _instrumentBase.InstrumentBase {
@@ -32,17 +32,19 @@ class Instrument extends _instrumentBase.InstrumentBase {
32
32
  (0, _monkeyPatched.isNative)(_runtime.globalScope.console[method]);
33
33
  (0, _wrapLogger.wrapLogger)(instanceEE, _runtime.globalScope.console, method, {
34
34
  level: method === 'log' ? 'info' : method
35
- });
35
+ }, undefined, agentRef);
36
36
  });
37
37
 
38
38
  /** emitted by wrap-logger function */
39
- this.ee.on('wrap-logger-end', function handleLog([message]) {
39
+ this.ee.on('wrap-logger-end', function handleLog([message], _, __, targets = []) {
40
40
  const {
41
41
  level,
42
42
  customAttributes,
43
43
  autoCaptured
44
44
  } = this;
45
- (0, _utils.bufferLog)(instanceEE, message, customAttributes, level, autoCaptured);
45
+ targets.forEach(target => {
46
+ (0, _utils.bufferLog)(instanceEE, message, customAttributes, level, autoCaptured, target);
47
+ });
46
48
  });
47
49
  this.importAggregator(agentRef, () => Promise.resolve().then(() => _interopRequireWildcard(require(/* webpackChunkName: "logging-aggregate" */'../aggregate'))));
48
50
  }
@@ -11,7 +11,7 @@ var _features = require("../../../loaders/features/features");
11
11
  var _constants = require("../../metrics/constants");
12
12
  var _constants2 = require("../constants");
13
13
  /**
14
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
14
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
15
15
  * SPDX-License-Identifier: Apache-2.0
16
16
  */
17
17
 
@@ -21,11 +21,11 @@ var _constants2 = require("../constants");
21
21
  * @param {{[key: string]: *}} customAttributes - The log's custom attributes if any
22
22
  * @param {enum} level - the log level enum
23
23
  * @param {boolean} [autoCaptured=true] - True if log was captured from auto wrapping. False if it was captured from the API manual usage.
24
- * @param {object=} target - the optional target provided by an api call
24
+ * @param {object=} targets - the optional targets found
25
25
  */
26
- function bufferLog(ee, message, customAttributes = {}, level = _constants2.LOG_LEVELS.INFO, autoCaptured = true, target, timestamp = (0, _now.now)()) {
26
+ function bufferLog(ee, message, customAttributes = {}, level = _constants2.LOG_LEVELS.INFO, autoCaptured = true, targets, timestamp = (0, _now.now)()) {
27
27
  (0, _handle.handle)(_constants.SUPPORTABILITY_METRIC_CHANNEL, ["API/logging/".concat(level.toLowerCase(), "/called")], undefined, _features.FEATURE_NAMES.metrics, ee);
28
- (0, _handle.handle)(_constants2.LOGGING_EVENT_EMITTER_CHANNEL, [timestamp, message, customAttributes, level, autoCaptured, target], undefined, _features.FEATURE_NAMES.logging, ee);
28
+ (0, _handle.handle)(_constants2.LOGGING_EVENT_EMITTER_CHANNEL, [timestamp, message, customAttributes, level, autoCaptured, targets], undefined, _features.FEATURE_NAMES.logging, ee);
29
29
  }
30
30
 
31
31
  /**
@@ -8,7 +8,7 @@ var _belSerializer = require("../../../common/serialize/bel-serializer");
8
8
  var _constants = require("../constants");
9
9
  var _belNode = require("./bel-node");
10
10
  /**
11
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
11
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
12
12
  * SPDX-License-Identifier: Apache-2.0
13
13
  */
14
14
 
@@ -27,6 +27,7 @@ class AjaxNode extends _belNode.BelNode {
27
27
  this.traceId = ajaxEvent.traceId;
28
28
  this.spanTimestamp = ajaxEvent.spanTimestamp;
29
29
  this.gql = ajaxEvent.gql;
30
+ this.targetAttributes = ajaxEvent.targetAttributes;
30
31
  this.start = ajaxEvent.startTime;
31
32
  this.end = ajaxEvent.endTime;
32
33
  if (ajaxContext?.latestLongtaskEnd) {
@@ -50,8 +51,10 @@ class AjaxNode extends _belNode.BelNode {
50
51
  (0, _belSerializer.numeric)(this.callbackDuration),
51
52
  // not relative
52
53
  addString(this.method), (0, _belSerializer.numeric)(this.status), addString(this.domain), addString(this.path), (0, _belSerializer.numeric)(this.txSize), (0, _belSerializer.numeric)(this.rxSize), this.requestedWith, addString(this.nodeId), (0, _belSerializer.nullable)(this.spanId, addString, true) + (0, _belSerializer.nullable)(this.traceId, addString, true) + (0, _belSerializer.nullable)(this.spanTimestamp, _belSerializer.numeric)];
53
- let allAttachedNodes = [];
54
- if (typeof this.gql === 'object') allAttachedNodes = (0, _belSerializer.addCustomAttributes)(this.gql, addString);
54
+ let allAttachedNodes = (0, _belSerializer.addCustomAttributes)({
55
+ ...(this.gql || {}),
56
+ ...(this.targetAttributes || {})
57
+ }, addString);
55
58
  this.children.forEach(node => allAttachedNodes.push(node.serialize())); // no children is expected under ajax nodes at this time
56
59
 
57
60
  fields[1] = (0, _belSerializer.numeric)(allAttachedNodes.length);
@@ -20,6 +20,7 @@ var _measure = require("./measure");
20
20
  var _recordCustomEvent = require("./recordCustomEvent");
21
21
  var _pageVisibility = require("../../common/window/page-visibility");
22
22
  var _scriptTracker = require("../../common/util/script-tracker");
23
+ var _uniqueId = require("../../common/ids/unique-id");
23
24
  /**
24
25
  * Copyright 2020-2026 New Relic, Inc. All rights reserved.
25
26
  * SPDX-License-Identifier: Apache-2.0
@@ -53,13 +54,32 @@ function setupRegisterAPI(agent) {
53
54
  function register(agentRef, target, parent) {
54
55
  (0, _console.warn)(54, 'newrelic.register');
55
56
  target ||= {};
57
+ target.instance = (0, _uniqueId.generateRandomHexString)(8);
56
58
  target.type = _v.V2_TYPES.MFE;
57
59
  target.licenseKey ||= agentRef.info.licenseKey; // will inherit the license key from the container agent if not provided for brevity. A future state may dictate that we need different license keys to do different things.
58
60
  target.blocked = false;
59
- target.parent = parent || {};
60
61
  if (typeof target.tags !== 'object' || target.tags === null || Array.isArray(target.tags)) target.tags = {};
62
+ target.parent = parent || {
63
+ get id() {
64
+ return agentRef.runtime.appMetadata.agents[0].entityGuid;
65
+ },
66
+ // getter because this is asyncronously set
67
+ type: _v.V2_TYPES.BA
68
+ };
61
69
  const timings = (0, _scriptTracker.findScriptTimings)();
62
70
  const attrs = {};
71
+ Object.defineProperty(target, 'attributes', {
72
+ get() {
73
+ return {
74
+ ...attrs,
75
+ 'source.id': target.id,
76
+ 'source.name': target.name,
77
+ 'source.type': target.type,
78
+ 'parent.type': target.parent?.type || _v.V2_TYPES.BA,
79
+ 'parent.id': target.parent?.id
80
+ };
81
+ }
82
+ });
63
83
 
64
84
  // Process tags object and add to attrs, excluding protected keys
65
85
  Object.entries(target.tags).forEach(([key, value]) => {
@@ -140,7 +160,9 @@ function register(agentRef, target, parent) {
140
160
  setUserId: value => setLocalValue('enduser.id', value),
141
161
  /** metadata */
142
162
  metadata: {
143
- customAttributes: attrs,
163
+ get customAttributes() {
164
+ return attrs;
165
+ },
144
166
  target,
145
167
  timings
146
168
  }
@@ -213,27 +235,6 @@ function register(agentRef, target, parent) {
213
235
  const timestamp = (0, _now.now)();
214
236
  (0, _handle.handle)(_constants.SUPPORTABILITY_METRIC_CHANNEL, ["API/register/".concat(methodToCall.name, "/called")], undefined, _features.FEATURE_NAMES.metrics, agentRef.ee);
215
237
  try {
216
- const shouldDuplicate = agentRef.init.api.duplicate_registered_data && methodToCall !== register;
217
- if (shouldDuplicate) {
218
- let duplicatedArgs = args;
219
- if (args[1] instanceof Object) {
220
- const childAttrs = {
221
- 'child.id': target.id,
222
- 'child.type': target.type
223
- };
224
- if ('customAttributes' in args[1]) duplicatedArgs = [args[0], {
225
- ...args[1],
226
- customAttributes: {
227
- ...args[1].customAttributes,
228
- ...childAttrs
229
- }
230
- }, ...args.slice(2)];else duplicatedArgs = [args[0], {
231
- ...args[1],
232
- ...childAttrs
233
- }, ...args.slice(2)];
234
- }
235
- methodToCall(...duplicatedArgs, undefined, timestamp);
236
- }
237
238
  return methodToCall(...args, target, timestamp); // always report to target
238
239
  } catch (err) {
239
240
  (0, _console.warn)(50, err);
@@ -9,7 +9,7 @@ var _constants = require("../../features/logging/constants");
9
9
  var _constants2 = require("./constants");
10
10
  var _sharedHandlers = require("./sharedHandlers");
11
11
  /**
12
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
12
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
13
13
  * SPDX-License-Identifier: Apache-2.0
14
14
  */
15
15
 
@@ -21,6 +21,6 @@ function setupWrapLoggerAPI(agent) {
21
21
  (0, _wrapLogger.wrapLogger)(agent.ee, parent, functionName, {
22
22
  customAttributes,
23
23
  level
24
- }, false);
24
+ }, false, agent);
25
25
  }, agent);
26
26
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { FEATURE_NAMES } from '../../loaders/features/features';
@@ -9,8 +9,7 @@ export const DEFAULT_KEY = 'NR_CONTAINER_AGENT';
9
9
  export const SESSION_ERROR = 'SESSION_ERROR';
10
10
  export const SUPPORTS_REGISTERED_ENTITIES = {
11
11
  [FEATURE_NAMES.logging]: true,
12
- // flip other features here when they are supported by DEM consumers
13
- [FEATURE_NAMES.genericEvents]: false,
14
- [FEATURE_NAMES.jserrors]: false,
15
- [FEATURE_NAMES.ajax]: false
12
+ [FEATURE_NAMES.genericEvents]: true,
13
+ [FEATURE_NAMES.jserrors]: true,
14
+ [FEATURE_NAMES.ajax]: true
16
15
  };
@@ -11,7 +11,7 @@
11
11
  /**
12
12
  * Exposes the version of the agent
13
13
  */
14
- export const VERSION = "1.311.0";
14
+ export const VERSION = "1.312.0";
15
15
 
16
16
  /**
17
17
  * Exposes the build type of the agent
@@ -25,4 +25,4 @@ export const BUILD_ENV = "CDN";
25
25
  export const DIST_METHOD = 'CDN';
26
26
  export const RRWEB_PACKAGE_NAME = '@newrelic/rrweb';
27
27
  // Babel will inline this with the rrweb (fork) version on CDN builds.
28
- export const RRWEB_VERSION = "1.0.1";
28
+ export const RRWEB_VERSION = "1.1.0";
@@ -11,7 +11,7 @@
11
11
  /**
12
12
  * Exposes the version of the agent
13
13
  */
14
- export const VERSION = "1.311.0";
14
+ export const VERSION = "1.312.0";
15
15
 
16
16
  /**
17
17
  * Exposes the build type of the agent
@@ -26,4 +26,4 @@ export const BUILD_ENV = 'NPM';
26
26
  export const DIST_METHOD = 'NPM';
27
27
  export const RRWEB_PACKAGE_NAME = '@newrelic/rrweb';
28
28
  // Babel will inline this with the rrweb (fork) version on NPM dist esm/cjs builds.
29
- export const RRWEB_VERSION = "1.0.1";
29
+ export const RRWEB_VERSION = "1.1.0";
@@ -59,7 +59,7 @@ if (globalScope.PerformanceObserver?.supportedEntryTypes.includes('resource')) {
59
59
  * @param {string} stack The error stack trace
60
60
  * @returns {string[]} Array of cleaned URLs found in the stack trace
61
61
  */
62
- function extractUrlsFromStack(stack) {
62
+ export function extractUrlsFromStack(stack) {
63
63
  if (!stack || typeof stack !== 'string') return [];
64
64
  const urls = new Set();
65
65
  const lines = stack.split('\n');
@@ -83,7 +83,7 @@ function extractUrlsFromStack(stack) {
83
83
  * Returns a deep stack trace by temporarily increasing the stack trace limit.
84
84
  * @returns {Error.stack | undefined}
85
85
  */
86
- function getDeepStackTrace() {
86
+ export function getDeepStackTrace() {
87
87
  let stack;
88
88
  try {
89
89
  const originalStackLimit = Error.stackTraceLimit;
@@ -7,32 +7,17 @@ import { ee } from '../event-emitter/contextual-ee';
7
7
 
8
8
  /**
9
9
  * Returns a function for use as a replacer parameter in JSON.stringify() to handle circular references.
10
- * Uses an array to track the current ancestor chain, allowing the same object to appear
11
- * multiple times in the structure as long as it's not a circular reference.
12
10
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value MDN - Cyclical object value}
13
- * @returns {Function} A function that filters out circular references while allowing duplicate references.
11
+ * @returns {Function} A function that filters out values it has seen before.
14
12
  */
15
13
  const getCircularReplacer = () => {
16
- const stack = [];
17
- return function (key, value) {
18
- if (stack.length > 0) {
19
- // Find where we are in the stack
20
- const thisPos = stack.indexOf(this);
21
- if (~thisPos) {
22
- // We're still in the stack, trim it
23
- stack.splice(thisPos + 1);
24
- } else {
25
- // We're not in the stack, add ourselves
26
- stack.push(this);
27
- }
28
-
29
- // Check if value is in the current ancestor chain
30
- if (~stack.indexOf(value)) {
14
+ const seen = new WeakSet();
15
+ return (key, value) => {
16
+ if (typeof value === 'object' && value !== null) {
17
+ if (seen.has(value)) {
31
18
  return;
32
19
  }
33
- } else {
34
- // First call, initialize with root
35
- stack.push(value);
20
+ seen.add(value);
36
21
  }
37
22
  return value;
38
23
  };
@@ -3,6 +3,8 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
 
6
+ import { extractUrlsFromStack, getDeepStackTrace } from './script-tracker';
7
+
6
8
  /**
7
9
  * @enum {string}
8
10
  * @readonly
@@ -14,6 +16,30 @@ export const V2_TYPES = {
14
16
  BA: 'BA'
15
17
  };
16
18
 
19
+ /**
20
+ * Returns the registered target associated with a given ID. Returns an empty array if no target is found.
21
+ * @param {string|number} id
22
+ * @param {*} agentRef the agent reference
23
+ * @returns {import("../../interfaces/registered-entity").RegisterAPIMetadataTarget[]}
24
+ */
25
+ export function getRegisteredTargetsFromId(id, agentRef) {
26
+ if (!id || !agentRef?.init.api.allow_registered_children) return [];
27
+ const registeredEntities = agentRef.runtime.registeredEntities;
28
+ return registeredEntities?.filter(entity => String(entity.metadata.target.id) === String(id)).map(entity => entity.metadata.target) || [];
29
+ }
30
+
31
+ /**
32
+ * Returns the registered target(s) associated with a given filename if found in the resource timing API during registration. Returns an empty array if no target is found.
33
+ * @param {string} filename
34
+ * @param {*} agentRef
35
+ * @returns {import("../../interfaces/registered-entity").RegisterAPIMetadataTarget[]}
36
+ */
37
+ export function getRegisteredTargetsFromFilename(filename, agentRef) {
38
+ if (!filename || !agentRef?.init.api.allow_registered_children) return [];
39
+ const registeredEntities = agentRef.runtime.registeredEntities;
40
+ return registeredEntities?.filter(entity => entity.metadata.timings?.asset?.endsWith(filename)).map(entity => entity.metadata.target) || [];
41
+ }
42
+
17
43
  /**
18
44
  * When given a valid target, returns an object with the V2 payload attributes. Returns an empty object otherwise.
19
45
  * @note Field names may change as the schema is finalized
@@ -23,7 +49,7 @@ export const V2_TYPES = {
23
49
  * @returns {Object} returns an empty object if args are not supplied or the aggregate instance is not supporting version 2
24
50
  */
25
51
  export function getVersion2Attributes(target, aggregateInstance) {
26
- if (aggregateInstance?.harvestEndpointVersion !== 2) return {};
52
+ if (!supportsV2(aggregateInstance)) return {};
27
53
  const containerAgentEntityGuid = aggregateInstance.agentRef.runtime.appMetadata.agents[0].entityGuid;
28
54
  /** if there's no target, but we are in v2 mode, this means the data belongs to the container agent */
29
55
  if (!target) {
@@ -33,11 +59,65 @@ export function getVersion2Attributes(target, aggregateInstance) {
33
59
  };
34
60
  }
35
61
  /** otherwise, the data belongs to the target (MFE) and should be attributed as such */
62
+ return target.attributes;
63
+ }
64
+
65
+ /**
66
+ * Returns the attributes used for duplicating data in version 2 of the harvest endpoint.
67
+ * If not valid for duplication, returns an empty object.
68
+ * @note BEST PRACTICE - Caller should call shouldDuplicate() before utilizing this method to determine if duplication attributes should be added to the event.
69
+ * @param {import("../../interfaces/registered-entity").RegisterAPIMetadataTarget} target
70
+ * @param {*} aggregateInstance the aggregate instance calling the method
71
+ * @returns {Object}
72
+ */
73
+ export function getVersion2DuplicationAttributes(target, aggregateInstance) {
74
+ if (!shouldDuplicate(target, aggregateInstance)) return {};
36
75
  return {
37
- 'source.id': target.id,
38
- 'source.name': target.name,
39
- 'source.type': target.type,
40
- 'parent.id': target.parent?.id || containerAgentEntityGuid,
41
- 'parent.type': target.parent?.type || V2_TYPES.BA
76
+ 'child.id': target.id,
77
+ 'child.type': target.type,
78
+ ...getVersion2Attributes(undefined, aggregateInstance)
42
79
  };
80
+ }
81
+
82
+ /**
83
+ * Determines if an event should be duplicated for a given target and aggregate instance. This is used to determine if duplication attributes should be added to an event and if the event should be sent to the soft nav feature for evaluation.
84
+ * @note This method is intended to be used in conjunction with getVersion2DuplicationAttributes and should be called before it to determine if duplication attributes should be added to an event.
85
+ * @param {import("../../interfaces/registered-entity").RegisterAPIMetadataTarget} target
86
+ * @param {*} aggregateInstance The aggregate instance calling the method. This is needed to check if duplication is enabled and if the harvest endpoint version supports it.
87
+ * @returns {boolean} returns true if the event should be duplicated for the target, false otherwise
88
+ */
89
+ export function shouldDuplicate(target, aggregateInstance) {
90
+ return !!target && !!supportsV2(aggregateInstance) && aggregateInstance.agentRef.init.api.duplicate_registered_data;
91
+ }
92
+
93
+ /**
94
+ * Finds the registered targets from the stack trace for a given agent reference.
95
+ * @param {*} agentRef The agent reference to use for finding targets.
96
+ * @returns {Array} An array of targets found from the stack trace. If no targets are found or allowed, returns an array with undefined.
97
+ */
98
+ export function findTargetsFromStackTrace(agentRef) {
99
+ if (!agentRef?.init.api.allow_registered_children) return [undefined];
100
+ const targets = [];
101
+ try {
102
+ var urls = extractUrlsFromStack(getDeepStackTrace());
103
+ let iterator = urls.length - 1;
104
+ while (urls[iterator]) {
105
+ targets.push(...getRegisteredTargetsFromFilename(urls[iterator--], agentRef));
106
+ }
107
+ } catch (err) {
108
+ // Silent catch to prevent errors from propagating
109
+ }
110
+ if (!targets.length) targets.push(undefined); // if we can't find any targets from the stack trace, return an array with undefined to signify the container agent is the target
111
+ return targets;
112
+ }
113
+
114
+ /**
115
+ * Determines if the aggregate instance supports version 2 of the harvest endpoint. Nearly all the V2 logic "depends" on
116
+ * the harvest endpoint version, so this is the main gatekeeper method for whether or not V2 logic should be executed across the
117
+ * various functions in this module.
118
+ * @param {*} aggregateInstance The aggregate instance to check.
119
+ * @returns {boolean} Returns true if the aggregate instance supports version 2, false otherwise.
120
+ */
121
+ function supportsV2(aggregateInstance) {
122
+ return aggregateInstance?.harvestEndpointVersion === 2;
43
123
  }
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import { ee as baseEE, contextId } from '../event-emitter/contextual-ee';
11
11
  import { globalScope } from '../constants/runtime';
12
+ import { findTargetsFromStackTrace } from '../util/v2';
12
13
  var prefix = 'fetch-';
13
14
  var bodyPrefix = prefix + 'body-';
14
15
  var bodyMethods = ['arrayBuffer', 'blob', 'json', 'text', 'formData'];
@@ -25,7 +26,7 @@ const wrapped = {};
25
26
  * event emitter will be based.
26
27
  * @returns {Object} Scoped event emitter with a debug ID of `fetch`.
27
28
  */
28
- export function wrapFetch(sharedEE) {
29
+ export function wrapFetch(sharedEE, agentRef) {
29
30
  const ee = scopedEE(sharedEE);
30
31
  if (!(Req && Res && globalScope.fetch)) {
31
32
  return ee;
@@ -41,8 +42,10 @@ export function wrapFetch(sharedEE) {
41
42
  wrapPromiseMethod(Res[proto], method, bodyPrefix);
42
43
  });
43
44
  wrapPromiseMethod(globalScope, 'fetch', prefix);
44
- ee.on(prefix + 'end', function (err, res) {
45
+ ee.on(prefix + 'end', function (err, res, targets) {
45
46
  var ctx = this;
47
+ // undefined target reports to container
48
+ ctx.targets = targets || [undefined];
46
49
  if (res) {
47
50
  var size = res.headers.get('content-length');
48
51
  if (size !== null) {
@@ -67,7 +70,9 @@ export function wrapFetch(sharedEE) {
67
70
  if (typeof fn === 'function') {
68
71
  target[name] = function () {
69
72
  var args = [...arguments];
70
- var ctx = {};
73
+ const ctx = {};
74
+ const targets = findTargetsFromStackTrace(agentRef);
75
+
71
76
  // we are wrapping args in an array so we can preserve the reference
72
77
  ee.emit(prefix + 'before-start', [args], ctx);
73
78
  var dtPayload;
@@ -77,10 +82,10 @@ export function wrapFetch(sharedEE) {
77
82
 
78
83
  // Note we need to cast the returned (orig) Promise from native APIs into the current global Promise, which may or may not be our WrappedPromise.
79
84
  return origPromiseFromFetch.then(function (val) {
80
- ee.emit(prefix + 'end', [null, val], origPromiseFromFetch);
85
+ ee.emit(prefix + 'end', [null, val, targets], origPromiseFromFetch);
81
86
  return val;
82
87
  }, function (err) {
83
- ee.emit(prefix + 'end', [err], origPromiseFromFetch);
88
+ ee.emit(prefix + 'end', [err, undefined, targets], origPromiseFromFetch);
84
89
  throw err;
85
90
  });
86
91
  };
@@ -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
 
@@ -9,6 +9,7 @@
9
9
 
10
10
  import { ee } from '../event-emitter/contextual-ee';
11
11
  import { bundleId } from '../ids/bundle-id';
12
+ import { findTargetsFromStackTrace } from '../util/v2';
12
13
  export const flag = "nr@original:".concat(bundleId);
13
14
  const LONG_TASK_THRESHOLD = 50;
14
15
 
@@ -33,7 +34,7 @@ export default createWrapperWithEmitter;
33
34
  * @param {boolean} always - If `true`, emit events even if already emitting an event.
34
35
  * @returns {function} The wrapped function.
35
36
  */
36
- export function createWrapperWithEmitter(emitter, always) {
37
+ export function createWrapperWithEmitter(emitter, always, agentRef) {
37
38
  emitter || (emitter = ee);
38
39
  wrapFn.inPlace = inPlace;
39
40
 
@@ -52,9 +53,10 @@ export function createWrapperWithEmitter(emitter, always) {
52
53
  * @param {function|object} getContext - The function or object that will serve as the 'this' context for handlers of events emitted by this wrapper.
53
54
  * @param {string} methodName - The name of the method being wrapped.
54
55
  * @param {boolean} bubble - If true, emitted events should also bubble up to the old emitter upon which the `emitter` in the current scope was based (if it defines one).
56
+ * @param {boolean} [evaluateStack] - If true, the wrapper will attempt to evaluate the stack of the executed wrapped function to find targets of the execution (ex. the MFE source of a console.log).
55
57
  * @returns {function} The wrapped function.
56
58
  */
57
- function wrapFn(fn, prefix, getContext, methodName, bubble) {
59
+ function wrapFn(fn, prefix, getContext, methodName, bubble, evaluateStack) {
58
60
  // Unless fn is both wrappable and unwrapped, return it unchanged.
59
61
  if (notWrappable(fn)) return fn;
60
62
  if (!prefix) prefix = '';
@@ -73,9 +75,15 @@ export function createWrapperWithEmitter(emitter, always) {
73
75
  var ctx;
74
76
  var result;
75
77
  let thrownError;
78
+ let targets;
76
79
  try {
77
80
  originalThis = this;
78
81
  args = [...arguments];
82
+
83
+ // certain wrappers can inform the function wrapper to evaluate the stack of the executed wrapped function to find targets of the execution
84
+ // (e.g. wrap-logger can inform this method to find try to find the MFE source of a console.log)
85
+ targets = evaluateStack ? findTargetsFromStackTrace(agentRef) : [undefined]; // undefined target always maps to the container agent
86
+
79
87
  if (typeof getContext === 'function') {
80
88
  ctx = getContext(args, originalThis);
81
89
  } else {
@@ -86,7 +94,7 @@ export function createWrapperWithEmitter(emitter, always) {
86
94
  }
87
95
 
88
96
  // Warning: start events may mutate args!
89
- safeEmit(prefix + 'start', [args, originalThis, methodName], ctx, bubble);
97
+ safeEmit(prefix + 'start', [args, originalThis, methodName, targets], ctx, bubble);
90
98
  const fnStartTime = performance.now();
91
99
  let fnEndTime;
92
100
  try {
@@ -95,7 +103,7 @@ export function createWrapperWithEmitter(emitter, always) {
95
103
  return result;
96
104
  } catch (err) {
97
105
  fnEndTime = performance.now();
98
- safeEmit(prefix + 'err', [args, originalThis, err], ctx, bubble);
106
+ safeEmit(prefix + 'err', [args, originalThis, err, targets], ctx, bubble);
99
107
  // rethrow error so we don't effect execution by observing.
100
108
  thrownError = err;
101
109
  throw thrownError;
@@ -112,10 +120,10 @@ export function createWrapperWithEmitter(emitter, always) {
112
120
  };
113
121
  // standalone long task message
114
122
  if (task.isLongTask) {
115
- safeEmit('long-task', [task, originalThis], ctx, bubble);
123
+ safeEmit('long-task', [task, originalThis, targets], ctx, bubble);
116
124
  }
117
125
  // -end message also includes the task execution info
118
- safeEmit(prefix + 'end', [args, originalThis, result], ctx, bubble);
126
+ safeEmit(prefix + 'end', [args, originalThis, result, targets], ctx, bubble);
119
127
  }
120
128
  }
121
129
  }
@@ -131,7 +139,7 @@ export function createWrapperWithEmitter(emitter, always) {
131
139
  * @param {boolean} [bubble=false] If `true`, emitted events should also bubble up to the old emitter upon which
132
140
  * the `emitter` in the current scope was based (if it defines one).
133
141
  */
134
- function inPlace(obj, methods, prefix, getContext, bubble) {
142
+ function inPlace(obj, methods, prefix, getContext, bubble, evaluateStack) {
135
143
  if (!prefix) prefix = '';
136
144
 
137
145
  // If prefix starts with '-' set this boolean to add the method name to the prefix before passing each one to wrap.
@@ -142,7 +150,7 @@ export function createWrapperWithEmitter(emitter, always) {
142
150
 
143
151
  // Unless fn is both wrappable and unwrapped, bail so we don't add extra properties with undefined values.
144
152
  if (notWrappable(fn)) continue;
145
- obj[method] = wrapFn(fn, prependMethodPrefix ? method + prefix : prefix, getContext, method, bubble);
153
+ obj[method] = wrapFn(fn, prependMethodPrefix ? method + prefix : prefix, getContext, method, bubble, evaluateStack);
146
154
  }
147
155
  }
148
156
 
@@ -23,10 +23,10 @@ const contextMap = new Map();
23
23
  * @returns {Object} Scoped event emitter with a debug ID of `logger`.
24
24
  */
25
25
  // eslint-disable-next-line
26
- export function wrapLogger(sharedEE, parent, loggerFn, context, autoCaptured = true) {
26
+ export function wrapLogger(sharedEE, parent, loggerFn, context, autoCaptured = true, agentRef) {
27
27
  if (!(typeof parent === 'object' && !!parent && typeof loggerFn === 'string' && !!loggerFn && typeof parent[loggerFn] === 'function')) return warn(29);
28
28
  const ee = scopedEE(sharedEE);
29
- const wrapFn = wfn(ee);
29
+ const wrapFn = wfn(ee, undefined, agentRef);
30
30
 
31
31
  /**
32
32
  * This section contains the context that will be shared across all invoked calls of the wrapped function,
@@ -39,8 +39,10 @@ export function wrapLogger(sharedEE, parent, loggerFn, context, autoCaptured = t
39
39
  const contextLookupKey = parent[loggerFn]?.[flag] || parent[loggerFn];
40
40
  contextMap.set(contextLookupKey, ctx);
41
41
 
42
- /** observe calls to <loggerFn> and emit events prefixed with `wrap-logger-` */
43
- wrapFn.inPlace(parent, [loggerFn], 'wrap-logger-', () => contextMap.get(contextLookupKey));
42
+ /** observe calls to <loggerFn> and emit events prefixed with `wrap-logger-`
43
+ * inform the inplace wrapper to evaluate the stack for targets of the log execution,
44
+ * so that logs can be attributed to a matching MFE source if being used. */
45
+ wrapFn.inPlace(parent, [loggerFn], 'wrap-logger-', () => contextMap.get(contextLookupKey), undefined, true);
44
46
  return ee;
45
47
  }
46
48
 
@@ -14,6 +14,7 @@ import { eventListenerOpts } from '../event-listener/event-listener-opts';
14
14
  import { createWrapperWithEmitter as wfn } from './wrap-function';
15
15
  import { globalScope } from '../constants/runtime';
16
16
  import { warn } from '../util/console';
17
+ import { findTargetsFromStackTrace } from '../util/v2';
17
18
  const wrapped = {};
18
19
  const XHR_PROPS = ['open', 'send']; // these are the specific funcs being wrapped on all XMLHttpRequests(.prototype)
19
20
 
@@ -24,7 +25,7 @@ const XHR_PROPS = ['open', 'send']; // these are the specific funcs being wrappe
24
25
  * @returns {Object} Scoped event emitter with a debug ID of `xhr`.
25
26
  */
26
27
  // eslint-disable-next-line
27
- export function wrapXhr(sharedEE) {
28
+ export function wrapXhr(sharedEE, agentRef) {
28
29
  var baseEE = sharedEE || contextualEE;
29
30
  const ee = scopedEE(baseEE);
30
31
  if (typeof globalScope.XMLHttpRequest === 'undefined') return ee;
@@ -47,6 +48,7 @@ export function wrapXhr(sharedEE) {
47
48
  function newXHR(opts) {
48
49
  const xhr = new OrigXHR(opts);
49
50
  const context = ee.context(xhr);
51
+ context.targets = findTargetsFromStackTrace(agentRef);
50
52
  try {
51
53
  ee.emit('new-xhr', [xhr], context);
52
54
  xhr.addEventListener(READY_STATE_CHANGE, wrapXHR(context), eventListenerOpts(false));