@newrelic/browser-agent 1.319.0 → 1.320.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 (84) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/cjs/common/config/init.js +17 -2
  3. package/dist/cjs/common/config/runtime.js +2 -1
  4. package/dist/cjs/common/constants/env.cdn.js +1 -1
  5. package/dist/cjs/common/constants/env.npm.js +1 -1
  6. package/dist/cjs/common/constants/iframe-constants.js +16 -0
  7. package/dist/cjs/common/url/add-url.js +23 -0
  8. package/dist/cjs/common/util/console.js +40 -33
  9. package/dist/cjs/common/v2/mfe-vitals.js +9 -2
  10. package/dist/cjs/common/v2/script-correlation.js +2 -0
  11. package/dist/cjs/common/v2/script-tracker-constants.js +13 -0
  12. package/dist/cjs/common/v2/script-tracker.js +54 -29
  13. package/dist/cjs/common/v2/timing-factory.js +18 -0
  14. package/dist/cjs/common/v2/utils.js +100 -6
  15. package/dist/cjs/features/ajax/instrument/index.js +4 -14
  16. package/dist/cjs/features/utils/instrument-base.js +13 -0
  17. package/dist/cjs/interfaces/registered-iframe-entity.js +527 -0
  18. package/dist/cjs/loaders/api/register.js +10 -6
  19. package/dist/cjs/loaders/configure/configure.js +14 -0
  20. package/dist/cjs/loaders/configure/iframe-message-handler.js +270 -0
  21. package/dist/esm/common/config/init.js +17 -2
  22. package/dist/esm/common/config/runtime.js +2 -1
  23. package/dist/esm/common/constants/env.cdn.js +1 -1
  24. package/dist/esm/common/constants/env.npm.js +1 -1
  25. package/dist/esm/common/constants/iframe-constants.js +10 -0
  26. package/dist/esm/common/url/add-url.js +16 -0
  27. package/dist/esm/common/util/console.js +40 -33
  28. package/dist/esm/common/v2/mfe-vitals.js +9 -2
  29. package/dist/esm/common/v2/script-correlation.js +2 -0
  30. package/dist/esm/common/v2/script-tracker-constants.js +7 -0
  31. package/dist/esm/common/v2/script-tracker.js +54 -29
  32. package/dist/esm/common/v2/timing-factory.js +12 -0
  33. package/dist/esm/common/v2/utils.js +97 -6
  34. package/dist/esm/features/ajax/instrument/index.js +1 -11
  35. package/dist/esm/features/utils/instrument-base.js +13 -0
  36. package/dist/esm/interfaces/registered-iframe-entity.js +518 -0
  37. package/dist/esm/loaders/api/register.js +10 -6
  38. package/dist/esm/loaders/configure/configure.js +15 -1
  39. package/dist/esm/loaders/configure/iframe-message-handler.js +263 -0
  40. package/dist/tsconfig.tsbuildinfo +1 -1
  41. package/dist/types/common/config/init.d.ts.map +1 -1
  42. package/dist/types/common/config/runtime.d.ts.map +1 -1
  43. package/dist/types/common/constants/iframe-constants.d.ts +11 -0
  44. package/dist/types/common/constants/iframe-constants.d.ts.map +1 -0
  45. package/dist/types/common/url/add-url.d.ts +2 -0
  46. package/dist/types/common/url/add-url.d.ts.map +1 -0
  47. package/dist/types/common/util/console.d.ts +40 -33
  48. package/dist/types/common/util/console.d.ts.map +1 -1
  49. package/dist/types/common/v2/mfe-vitals.d.ts.map +1 -1
  50. package/dist/types/common/v2/script-correlation.d.ts +2 -0
  51. package/dist/types/common/v2/script-correlation.d.ts.map +1 -1
  52. package/dist/types/common/v2/script-tracker-constants.d.ts +7 -0
  53. package/dist/types/common/v2/script-tracker-constants.d.ts.map +1 -0
  54. package/dist/types/common/v2/script-tracker.d.ts +4 -1
  55. package/dist/types/common/v2/script-tracker.d.ts.map +1 -1
  56. package/dist/types/common/v2/timing-factory.d.ts +9 -0
  57. package/dist/types/common/v2/timing-factory.d.ts.map +1 -0
  58. package/dist/types/common/v2/utils.d.ts +37 -0
  59. package/dist/types/common/v2/utils.d.ts.map +1 -1
  60. package/dist/types/features/ajax/instrument/index.d.ts.map +1 -1
  61. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  62. package/dist/types/interfaces/registered-iframe-entity.d.ts +116 -0
  63. package/dist/types/interfaces/registered-iframe-entity.d.ts.map +1 -0
  64. package/dist/types/loaders/configure/configure.d.ts.map +1 -1
  65. package/dist/types/loaders/configure/iframe-message-handler.d.ts +17 -0
  66. package/dist/types/loaders/configure/iframe-message-handler.d.ts.map +1 -0
  67. package/package.json +9 -1
  68. package/src/common/config/init.js +13 -2
  69. package/src/common/config/runtime.js +2 -1
  70. package/src/common/constants/iframe-constants.js +11 -0
  71. package/src/common/url/add-url.js +18 -0
  72. package/src/common/util/console.js +40 -33
  73. package/src/common/v2/mfe-vitals.js +7 -4
  74. package/src/common/v2/script-correlation.js +2 -0
  75. package/src/common/v2/script-tracker-constants.js +7 -0
  76. package/src/common/v2/script-tracker.js +65 -26
  77. package/src/common/v2/timing-factory.js +10 -0
  78. package/src/common/v2/utils.js +102 -6
  79. package/src/features/ajax/instrument/index.js +1 -13
  80. package/src/features/utils/instrument-base.js +12 -0
  81. package/src/interfaces/registered-iframe-entity.js +482 -0
  82. package/src/loaders/api/register.js +9 -5
  83. package/src/loaders/configure/configure.js +15 -1
  84. package/src/loaders/configure/iframe-message-handler.js +244 -0
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ export function timingFactory(getLazyValue = () => {}, override) {
6
+ return {
7
+ get: () => override !== undefined ? override : getLazyValue(),
8
+ set: newValue => {
9
+ override = newValue;
10
+ }
11
+ };
12
+ }
@@ -16,6 +16,18 @@ export const V2_TYPES = {
16
16
  BA: 'BA'
17
17
  };
18
18
 
19
+ /**
20
+ * Returns a single registered entity associated with a given iframe interface ID. Returns undefined if no entity is found.
21
+ * @param {string} iframeInterfaceId
22
+ * @param {*} agentRef the agent reference
23
+ * @returns {import("../../loaders/api/register-api-types").RegisterAPI|undefined}
24
+ */
25
+ export function getRegisteredEntityByIframeInterfaceId(iframeInterfaceId, agentRef) {
26
+ if (!isValid(iframeInterfaceId, agentRef)) return undefined;
27
+ const registeredEntities = agentRef.runtime.registeredEntities;
28
+ return registeredEntities?.find(entity => entity.metadata.target.iframeInterfaceId === iframeInterfaceId);
29
+ }
30
+
19
31
  /**
20
32
  * Returns the registered target associated with a given ID. Returns an empty array if no target is found.
21
33
  * @param {string|number} id
@@ -23,21 +35,69 @@ export const V2_TYPES = {
23
35
  * @returns {import("../../interfaces/registered-entity").RegisterAPIMetadataTarget[]}
24
36
  */
25
37
  export function getRegisteredTargetsFromId(id, agentRef) {
26
- if (!id || !agentRef?.init.api.register.enabled) return [];
38
+ if (!isValid(id, agentRef)) return [];
27
39
  const registeredEntities = agentRef.runtime.registeredEntities;
28
40
  return registeredEntities?.filter(entity => String(entity.metadata.target.id) === String(id)).map(entity => entity.metadata.target) || [];
29
41
  }
30
42
 
31
43
  /**
32
44
  * 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.
45
+ * Multiple registrations that resolve to the same underlying script asset AND represent the same logical MFE (i.e.
46
+ * share the same customer-supplied `target.id`) are collapsed to a single target via
47
+ * {@link dedupeRegisteredEntitiesByAsset}, since auto-instrumented events (AJAX, JS errors, logs, WebSockets) should
48
+ * only be reported once per real occurrence when the same MFE was registered many times over -- not once per
49
+ * duplicate registration. Distinct MFEs (different `target.id`) that happen to share a script (e.g. two different
50
+ * MFEs both registered from the same inline `<script>` block) are intentionally NOT collapsed, since each is a
51
+ * genuinely different entity that should still receive its own copy of the matched event.
33
52
  * @param {string} filename
34
53
  * @param {*} agentRef
35
54
  * @returns {import("../../interfaces/registered-entity").RegisterAPIMetadataTarget[]}
36
55
  */
37
56
  export function getRegisteredTargetsFromFilename(filename, agentRef) {
38
- if (!filename || !agentRef?.init.api.register.enabled) return [];
57
+ if (!isValid(filename, agentRef)) return [];
39
58
  const registeredEntities = agentRef.runtime.registeredEntities;
40
- return registeredEntities?.filter(entity => entity.metadata.timings?.asset?.endsWith(filename)).map(entity => entity.metadata.target) || [];
59
+ const matches = registeredEntities?.filter(entity => entity.metadata.timings?.asset?.endsWith(filename));
60
+ return dedupeRegisteredEntitiesByAsset(matches).map(entity => entity.metadata.target);
61
+ }
62
+
63
+ /**
64
+ * Collapses a list of registered entities down to one canonical entity per unique, defined `metadata.timings.asset` +
65
+ * `target.id` combination. Entities whose asset could not be resolved (undefined -- e.g. inline scripts or scripts
66
+ * not found in the resource timing buffer) are never deduped against each other or against resolved entities, since
67
+ * there is no signal that they represent the same underlying script. Entities that share an asset but have different
68
+ * `target.id`s are never deduped against each other either, since a differing id means the customer registered
69
+ * genuinely distinct MFEs (not duplicate registrations of the same one).
70
+ *
71
+ * Canonical selection per asset+id: prefer an entity whose target has not been deregistered (`target.blocked ===
72
+ * false`) over one that has; otherwise the first-encountered entity wins, for determinism.
73
+ * @param {Array} entities registered entities (each with `metadata.timings.asset` and `metadata.target`)
74
+ * @returns {Array} deduped list of entities, preserving relative order of first occurrence
75
+ */
76
+ export function dedupeRegisteredEntitiesByAsset(entities) {
77
+ if (!entities?.length) return entities || [];
78
+ const byKey = new Map(); // `${asset}::${id}` -> canonical entity
79
+ const result = [];
80
+ for (const entity of entities) {
81
+ const asset = entity.metadata?.timings?.asset;
82
+ if (!asset) {
83
+ // can't safely dedupe unresolved-asset entities -- always keep as-is
84
+ result.push(entity);
85
+ continue;
86
+ }
87
+ const key = "".concat(asset, "::").concat(entity.metadata?.target?.id);
88
+ const existing = byKey.get(key);
89
+ if (!existing) {
90
+ byKey.set(key, entity);
91
+ result.push(entity);
92
+ } else if (existing.metadata.target?.blocked && !entity.metadata.target?.blocked) {
93
+ // swap in a non-deregistered target as the canonical one for this asset+id
94
+ const idx = result.indexOf(existing);
95
+ if (idx !== -1) result[idx] = entity;
96
+ byKey.set(key, entity);
97
+ }
98
+ // else: existing canonical entity wins (already non-blocked, or both blocked -- first wins); drop this duplicate
99
+ }
100
+ return result;
41
101
  }
42
102
 
43
103
  /**
@@ -96,7 +156,7 @@ export function shouldDuplicate(target, aggregateInstance) {
96
156
  * @returns {Array} An array of targets found from the stack trace. If no targets are found or allowed, returns an array with undefined.
97
157
  */
98
158
  export function findTargetsFromStackTrace(agentRef) {
99
- if (!agentRef?.init.api.register.enabled || !agentRef?.runtime?.registeredEntities?.length) return [undefined];
159
+ if (!isValid(true, agentRef) || !agentRef?.runtime?.registeredEntities?.length) return [undefined];
100
160
  const targets = [];
101
161
  try {
102
162
  var urls = extractUrlsFromStack(getDeepStackTrace());
@@ -107,8 +167,29 @@ export function findTargetsFromStackTrace(agentRef) {
107
167
  } catch (err) {
108
168
  // Silent catch to prevent errors from propagating
109
169
  }
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;
170
+ const deduped = dedupeTargetsByInstance(targets);
171
+ if (!deduped.length) deduped.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
172
+ return deduped;
173
+ }
174
+
175
+ /**
176
+ * Removes duplicate targets from an array, keyed by `target.instance`. This guards against the same canonical
177
+ * target re-entering the array via multiple matched stack-frame URLs (e.g. a recursive call whose stack contains the
178
+ * same file at multiple depths). Entries with no `instance` (i.e. `undefined`, meaning "the container agent")
179
+ * naturally collapse to a single entry too, which is the desired behavior.
180
+ * @param {Array} targets
181
+ * @returns {Array} deduped list of targets, preserving relative order of first occurrence
182
+ */
183
+ export function dedupeTargetsByInstance(targets) {
184
+ const seen = new Set();
185
+ const result = [];
186
+ for (const target of targets) {
187
+ const key = target?.instance;
188
+ if (seen.has(key)) continue;
189
+ seen.add(key);
190
+ result.push(target);
191
+ }
192
+ return result;
112
193
  }
113
194
 
114
195
  /**
@@ -120,4 +201,14 @@ export function findTargetsFromStackTrace(agentRef) {
120
201
  */
121
202
  function supportsV2(aggregateInstance) {
122
203
  return aggregateInstance?.harvestEndpointVersion === 2;
204
+ }
205
+
206
+ /**
207
+ * Determines if the given identifier and agent reference are valid for use for entity lookups and other operations in the utils methods. This is a common check that is used across multiple methods in this module to ensure that the necessary data is present and that the register API is enabled before attempting to perform operations that depend on those things.
208
+ * @param {*} identifier The identifier to check.
209
+ * @param {*} agentRef The agent reference to check.
210
+ * @returns {boolean} Returns true if the identifier and agent reference are valid, false otherwise.
211
+ */
212
+ function isValid(identifier, agentRef) {
213
+ return !!identifier && !!agentRef?.init.api.register.enabled;
123
214
  }
@@ -20,6 +20,7 @@ import { SUPPORTABILITY_METRIC } from '../../metrics/constants';
20
20
  import { now } from '../../../common/timing/now';
21
21
  import { hasUndefinedHostname } from '../../../common/deny-list/deny-list';
22
22
  import { extractUrl } from '../../../common/url/extract-url';
23
+ import { addUrl } from '../../../common/url/add-url';
23
24
  var handlers = ['load', 'error', 'abort', 'timeout'];
24
25
  var handlersLen = handlers.length;
25
26
  var origRequest = gosNREUMOriginals().o.REQ;
@@ -434,17 +435,6 @@ function subscribeToEvents(agentRef, ee, handler, dt) {
434
435
  ctx.loadCaptureCalled = true;
435
436
  }
436
437
  }
437
- function addUrl(ctx, url) {
438
- var parsed = parseUrl(url);
439
- var params = ctx.params || ctx;
440
- params.hostname = parsed.hostname;
441
- params.port = parsed.port;
442
- params.protocol = parsed.protocol;
443
- params.host = parsed.hostname + ':' + parsed.port;
444
- params.pathname = parsed.pathname;
445
- ctx.parsedOrigin = parsed;
446
- ctx.sameOrigin = parsed.sameOrigin;
447
- }
448
438
  function parseResponseHeaders(headerStr) {
449
439
  const headers = {};
450
440
  if (!headerStr) return headers;
@@ -113,6 +113,19 @@ export class InstrumentBase extends FeatureBase {
113
113
  this.ee.emit('internal-error', [e]);
114
114
  handle(SESSION_ERROR, [e], undefined, this.featureName, this.ee);
115
115
  }
116
+ if (agentRef.init.api.register.allow_iframe_bridge) {
117
+ try {
118
+ // This chunk doesn't exist in the lite build (see webpack IgnorePlugin config) since none
119
+ // of lite's features wire up agent.register -- guard against that rather than letting an
120
+ // unhandled rejection surface if this flag is ever set on a lite page.
121
+ const {
122
+ setupIframeMFEMessageListener
123
+ } = await import(/* webpackChunkName: "iframe-message-handler" */'../../loaders/configure/iframe-message-handler');
124
+ setupIframeMFEMessageListener(agentRef);
125
+ } catch (e) {
126
+ warn(23, e);
127
+ }
128
+ }
116
129
 
117
130
  /**
118
131
  * Note this try-catch differs from the one in Agent.run() in that it's placed later in a page's lifecycle and