@newrelic/browser-agent 1.321.0-rc.0 → 1.321.0-rc.10

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 (80) hide show
  1. package/dist/cjs/common/constants/env.cdn.js +1 -1
  2. package/dist/cjs/common/constants/env.npm.js +1 -1
  3. package/dist/cjs/common/util/console.js +3 -0
  4. package/dist/cjs/common/v2/manifest.js +142 -0
  5. package/dist/cjs/common/v2/script-tracker-types.js +27 -0
  6. package/dist/cjs/common/v2/script-tracker.js +223 -25
  7. package/dist/cjs/common/v2/utils.js +47 -1
  8. package/dist/cjs/common/vitals/cumulative-layout-shift.js +6 -4
  9. package/dist/cjs/common/vitals/first-contentful-paint.js +5 -3
  10. package/dist/cjs/common/vitals/interaction-to-next-paint.js +10 -4
  11. package/dist/cjs/common/vitals/largest-contentful-paint.js +5 -3
  12. package/dist/cjs/common/vitals/register-vital.js +25 -0
  13. package/dist/cjs/common/vitals/time-to-first-byte.js +4 -2
  14. package/dist/cjs/common/vitals/vital-metric.js +3 -2
  15. package/dist/cjs/features/generic_events/aggregate/index.js +28 -1
  16. package/dist/cjs/interfaces/registered-iframe-entity.js +9 -3
  17. package/dist/cjs/loaders/api/register-api-types.js +13 -4
  18. package/dist/cjs/loaders/api/register.js +34 -1
  19. package/dist/esm/common/constants/env.cdn.js +1 -1
  20. package/dist/esm/common/constants/env.npm.js +1 -1
  21. package/dist/esm/common/util/console.js +3 -0
  22. package/dist/esm/common/v2/manifest.js +136 -0
  23. package/dist/esm/common/v2/script-tracker-types.js +24 -0
  24. package/dist/esm/common/v2/script-tracker.js +222 -26
  25. package/dist/esm/common/v2/utils.js +46 -1
  26. package/dist/esm/common/vitals/cumulative-layout-shift.js +6 -4
  27. package/dist/esm/common/vitals/first-contentful-paint.js +5 -3
  28. package/dist/esm/common/vitals/interaction-to-next-paint.js +10 -4
  29. package/dist/esm/common/vitals/largest-contentful-paint.js +5 -3
  30. package/dist/esm/common/vitals/register-vital.js +19 -0
  31. package/dist/esm/common/vitals/time-to-first-byte.js +4 -2
  32. package/dist/esm/common/vitals/vital-metric.js +3 -2
  33. package/dist/esm/features/generic_events/aggregate/index.js +29 -2
  34. package/dist/esm/interfaces/registered-iframe-entity.js +9 -3
  35. package/dist/esm/loaders/api/register-api-types.js +14 -4
  36. package/dist/esm/loaders/api/register.js +34 -2
  37. package/dist/tsconfig.tsbuildinfo +1 -1
  38. package/dist/types/common/config/init-types.d.ts +1 -1
  39. package/dist/types/common/util/console.d.ts +3 -0
  40. package/dist/types/common/util/console.d.ts.map +1 -1
  41. package/dist/types/common/v2/manifest.d.ts +66 -0
  42. package/dist/types/common/v2/manifest.d.ts.map +1 -0
  43. package/dist/types/common/v2/script-tracker-types.d.ts +25 -0
  44. package/dist/types/common/v2/script-tracker-types.d.ts.map +1 -0
  45. package/dist/types/common/v2/script-tracker.d.ts +25 -4
  46. package/dist/types/common/v2/script-tracker.d.ts.map +1 -1
  47. package/dist/types/common/v2/utils.d.ts +12 -0
  48. package/dist/types/common/v2/utils.d.ts.map +1 -1
  49. package/dist/types/common/vitals/cumulative-layout-shift.d.ts.map +1 -1
  50. package/dist/types/common/vitals/first-contentful-paint.d.ts.map +1 -1
  51. package/dist/types/common/vitals/interaction-to-next-paint.d.ts.map +1 -1
  52. package/dist/types/common/vitals/largest-contentful-paint.d.ts.map +1 -1
  53. package/dist/types/common/vitals/register-vital.d.ts +13 -0
  54. package/dist/types/common/vitals/register-vital.d.ts.map +1 -0
  55. package/dist/types/common/vitals/time-to-first-byte.d.ts.map +1 -1
  56. package/dist/types/common/vitals/vital-metric.d.ts +1 -1
  57. package/dist/types/common/vitals/vital-metric.d.ts.map +1 -1
  58. package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
  59. package/dist/types/interfaces/registered-iframe-entity.d.ts.map +1 -1
  60. package/dist/types/loaders/api/register-api-types.d.ts +31 -4
  61. package/dist/types/loaders/api/register-api-types.d.ts.map +1 -1
  62. package/dist/types/loaders/api/register.d.ts +3 -0
  63. package/dist/types/loaders/api/register.d.ts.map +1 -1
  64. package/package.json +2 -2
  65. package/src/common/util/console.js +3 -0
  66. package/src/common/v2/manifest.js +126 -0
  67. package/src/common/v2/script-tracker-types.js +24 -0
  68. package/src/common/v2/script-tracker.js +218 -26
  69. package/src/common/v2/utils.js +42 -1
  70. package/src/common/vitals/cumulative-layout-shift.js +5 -3
  71. package/src/common/vitals/first-contentful-paint.js +5 -3
  72. package/src/common/vitals/interaction-to-next-paint.js +8 -3
  73. package/src/common/vitals/largest-contentful-paint.js +5 -3
  74. package/src/common/vitals/register-vital.js +19 -0
  75. package/src/common/vitals/time-to-first-byte.js +4 -2
  76. package/src/common/vitals/vital-metric.js +3 -2
  77. package/src/features/generic_events/aggregate/index.js +22 -2
  78. package/src/interfaces/registered-iframe-entity.js +7 -2
  79. package/src/loaders/api/register-api-types.js +14 -4
  80. package/src/loaders/api/register.js +29 -2
@@ -7,20 +7,25 @@ exports.interactionToNextPaint = void 0;
7
7
  var _attribution = require("web-vitals/attribution");
8
8
  var _vitalMetric = require("./vital-metric");
9
9
  var _constants = require("./constants");
10
+ var _registerVital = require("./register-vital");
10
11
  var _runtime = require("../constants/runtime");
11
12
  /**
12
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
13
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
13
14
  * SPDX-License-Identifier: Apache-2.0
14
15
  */
15
16
 
16
17
  const interactionToNextPaint = exports.interactionToNextPaint = new _vitalMetric.VitalMetric(_constants.VITAL_NAMES.INTERACTION_TO_NEXT_PAINT);
17
18
  if (_runtime.isBrowserScope) {
18
19
  /* Interaction-to-Next-Paint */
19
- (0, _attribution.onINP)(({
20
+ const handleINP = ({
20
21
  value,
21
22
  attribution,
22
- id
23
+ id,
24
+ entries
23
25
  }) => {
26
+ /* web-vitals v6 reports a synthetic INP (value 8, no entries, no interaction attribution) after bfcache restores when
27
+ every interaction stayed below the duration threshold; skip those so only measured interactions are reported, as in v4 */
28
+ if (!entries?.length) return;
24
29
  const attrs = {
25
30
  metricId: id,
26
31
  eventTarget: attribution.interactionTarget,
@@ -40,5 +45,6 @@ if (_runtime.isBrowserScope) {
40
45
  value,
41
46
  attrs
42
47
  });
43
- });
48
+ };
49
+ (0, _registerVital.registerVital)(() => (0, _attribution.onINP)(handleINP));
44
50
  }
@@ -7,6 +7,7 @@ exports.largestContentfulPaint = void 0;
7
7
  var _attribution = require("web-vitals/attribution");
8
8
  var _vitalMetric = require("./vital-metric");
9
9
  var _constants = require("./constants");
10
+ var _registerVital = require("./register-vital");
10
11
  var _runtime = require("../constants/runtime");
11
12
  var _cleanUrl = require("../url/clean-url");
12
13
  /**
@@ -16,7 +17,7 @@ var _cleanUrl = require("../url/clean-url");
16
17
 
17
18
  const largestContentfulPaint = exports.largestContentfulPaint = new _vitalMetric.VitalMetric(_constants.VITAL_NAMES.LARGEST_CONTENTFUL_PAINT);
18
19
  if (_runtime.isBrowserScope) {
19
- (0, _attribution.onLCP)(({
20
+ const handleLCP = ({
20
21
  value,
21
22
  attribution
22
23
  }) => {
@@ -36,11 +37,12 @@ if (_runtime.isBrowserScope) {
36
37
  attrs.eid = lcpEntry.id;
37
38
  if (lcpEntry.element?.tagName) attrs.elTag = lcpEntry.element.tagName;
38
39
  }
39
- if (attribution.element) attrs.element = attribution.element;
40
+ if (attribution.target) attrs.element = attribution.target; // renamed from `element` in web-vitals v4->v5; NR attr name kept for backwards compatibility
40
41
  if (attribution.url) attrs.elUrl = (0, _cleanUrl.cleanURL)(attribution.url);
41
42
  largestContentfulPaint.update({
42
43
  value,
43
44
  attrs
44
45
  });
45
- });
46
+ };
47
+ (0, _registerVital.registerVital)(() => (0, _attribution.onLCP)(handleLCP));
46
48
  }
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.registerVital = registerVital;
7
+ /**
8
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
9
+ * SPDX-License-Identifier: Apache-2.0
10
+ */
11
+
12
+ /**
13
+ * Invokes a web-vitals registration (e.g. `onLCP(callback)`), tolerating environments where the registration itself
14
+ * throws. web-vitals >= 5 reads Performance Timeline APIs such as `performance.getEntriesByType` unguarded when a
15
+ * metric is registered; where those are missing the metric is simply never reported, instead of the throw failing
16
+ * the feature that imported it.
17
+ * @param {() => void} register
18
+ */
19
+ function registerVital(register) {
20
+ try {
21
+ register();
22
+ } catch (e) {
23
+ // metric unavailable in this environment
24
+ }
25
+ }
@@ -7,6 +7,7 @@ exports.timeToFirstByte = void 0;
7
7
  var _runtime = require("../constants/runtime");
8
8
  var _constants = require("./constants");
9
9
  var _vitalMetric = require("./vital-metric");
10
+ var _registerVital = require("./register-vital");
10
11
  var _attribution = require("web-vitals/attribution");
11
12
  /**
12
13
  * Copyright 2020-2026 New Relic, Inc. All rights reserved.
@@ -24,7 +25,7 @@ const timeToFirstByte = exports.timeToFirstByte = new _vitalMetric.VitalMetric(_
24
25
  * - onTTFB relies on a truthy `responseStart` value, should ensure that exists before relying on it (seen to be falsy in certain Electron.js cases for instance)
25
26
  */
26
27
  if (_runtime.isBrowserScope && (0, _runtime.getNavigationEntry)() && !_runtime.isiOS && window === window.parent) {
27
- (0, _attribution.onTTFB)(({
28
+ const handleTTFB = ({
28
29
  value,
29
30
  attribution
30
31
  }) => {
@@ -35,7 +36,8 @@ if (_runtime.isBrowserScope && (0, _runtime.getNavigationEntry)() && !_runtime.i
35
36
  navigationEntry: attribution.navigationEntry
36
37
  }
37
38
  });
38
- });
39
+ };
40
+ (0, _registerVital.registerVital)(() => (0, _attribution.onTTFB)(handleTTFB));
39
41
  } else {
40
42
  if (!timeToFirstByte.isValid) {
41
43
  const entry = {};
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.VitalMetric = void 0;
7
7
  /**
8
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
8
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
9
9
  * SPDX-License-Identifier: Apache-2.0
10
10
  */
11
11
  class VitalMetric {
@@ -24,7 +24,8 @@ class VitalMetric {
24
24
  const state = {
25
25
  value: this.roundingMethod(value),
26
26
  name: this.name,
27
- attrs
27
+ // optional web-vitals attribution fields are omitted rather than being serialized as null attributes
28
+ attrs: Object.fromEntries(Object.entries(attrs).filter(([, attrValue]) => attrValue !== undefined))
28
29
  };
29
30
  this.history.push(state);
30
31
  this.#subscribers.forEach(cb => {
@@ -18,6 +18,7 @@ var _iframe = require("../../../common/dom/iframe");
18
18
  var _typeCheck = require("../../../common/util/type-check");
19
19
  var _events = require("../../../common/constants/events");
20
20
  var _utils = require("../../../common/v2/utils");
21
+ var _scriptTracker = require("../../../common/v2/script-tracker");
21
22
  /**
22
23
  * Copyright 2020-2026 New Relic, Inc. All rights reserved.
23
24
  * SPDX-License-Identifier: Apache-2.0
@@ -239,7 +240,33 @@ class Aggregate extends _aggregateBase.AggregateBase {
239
240
  entryDuration: duration,
240
241
  firstParty
241
242
  };
242
- this.addEvent(event);
243
+ const targets = (0, _utils.getRegisteredTargetsFromResourceUrl)(name, this.agentRef);
244
+ if (targets.length) {
245
+ targets.forEach(target => this.addEvent({
246
+ ...event
247
+ }, target));
248
+ return;
249
+ }
250
+ this.addEvent({
251
+ ...event
252
+ }, undefined);
253
+
254
+ // This resource entry can resolve before a self-registering script has actually executed and called
255
+ // register() on itself (responseEnd fires before script parse/execution). If this URL belongs to a
256
+ // <script> we're tracking that hasn't finished loading yet, retry resolution once it has -- reporting
257
+ // a second (deliberately duplicate) copy under the real target if one shows up by then. One-shot,
258
+ // self-cleans via `once: true`, so there's nothing to leak if the script never registers.
259
+ const correlation = (0, _scriptTracker.findCorrelation)((0, _cleanUrl.cleanURL)(name));
260
+ if (correlation?.dom.value && !correlation.dom.end) {
261
+ const retryAttribution = () => {
262
+ (0, _utils.getRegisteredTargetsFromResourceUrl)(name, this.agentRef).forEach(target => this.addEvent({
263
+ ...event
264
+ }, target));
265
+ };
266
+ ['load', 'error'].forEach(evtType => correlation.dom.value.addEventListener(evtType, retryAttribution, {
267
+ once: true
268
+ }));
269
+ }
243
270
  } catch (err) {
244
271
  this.ee.emit('internal-error', [err, 'GenericEvents-Resource']);
245
272
  }
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.RegisteredIframeEntity = void 0;
7
7
  var _webVitals = require("web-vitals");
8
8
  var _runtime = require("../common/constants/runtime");
9
+ var _registerVital = require("../common/vitals/register-vital");
9
10
  var _iframe = require("../common/dom/iframe");
10
11
  var _now = require("../common/timing/now");
11
12
  var _console = require("../common/util/console");
@@ -191,9 +192,14 @@ class RegisteredIframeEntity {
191
192
  */
192
193
  #setupVitalsListeners() {
193
194
  VITALS.forEach(([vitalFn, property]) => {
194
- vitalFn(({
195
- value
195
+ (0, _registerVital.registerVital)(() => vitalFn(({
196
+ value,
197
+ entries: vitalEntries
196
198
  }) => {
199
+ /* web-vitals v6 reports a synthetic INP (value 8, no entries) after bfcache restores when
200
+ every interaction stayed below the duration threshold; skip those so only measured
201
+ interactions are reported, matching the guard in src/common/vitals/interaction-to-next-paint.js */
202
+ if (property === 'inp' && !vitalEntries?.length) return;
197
203
  this.metadata.vitals[property].value = value;
198
204
  this.#postMessageToParent(_iframeConstants.IFRAME_VITALS_UPDATE, {
199
205
  entries: [{
@@ -203,7 +209,7 @@ class RegisteredIframeEntity {
203
209
  });
204
210
  }, {
205
211
  reportAllChanges: property === 'cls' || property === 'inp'
206
- });
212
+ }));
207
213
  });
208
214
  }
209
215
 
@@ -21,6 +21,9 @@ exports.default = void 0;
21
21
  * @property {(value: string | null, resetSession?: boolean) => void} setUserId - Add an enduser.id attribute to all outgoing API data for the registered entity. Note: a registered entity will not be able to initiate a session reset. It must be done from the main agent.
22
22
  * @property {RegisterAPIMetadata} metadata - The metadata object containing the custom attributes and target information for the registered entity.
23
23
  */
24
+ /**
25
+ * @typedef {import('../../common/v2/manifest').AssetFile} AssetFile
26
+ */
24
27
  /**
25
28
  * @typedef {Object} RegisterAPIConstructor
26
29
  * @property {string} id - The unique id for the registered entity. This will be assigned to any synthesized entities.
@@ -28,6 +31,8 @@ exports.default = void 0;
28
31
  * @property {{[key: string]: any}} [tags] - The tags for the registered entity as key-value pairs. This will be assigned to any synthesized entities. Tags are converted to source.* attributes (e.g., {environment: 'production'} becomes source.environment: 'production').
29
32
  * @property {RegisterAPITarget} [parent] - The parent target for the registered entity. If none was supplied, it will assume the entity guid from the main agent.
30
33
  * @property {string} [parentId] - The parentId for the registered entity. If none was supplied, it will assume the entity guid from the main agent.
34
+ * @property {{assets?: Array<AssetFile>}} [manifest] - An optional manifest describing the MFE's known assets (scripts, stylesheets, images, fonts, etc.) as `AssetFile` entries, used to improve the accuracy of event attribution (errors, logs, ajax, websockets), attribute `BrowserPerformance` resource events (any asset type, not just scripts) and, depending on `timingMethod`, MicroFrontEndTiming values. Each `AssetFile`'s script-capability is inferred from a `.js` suffix on a string `matcher` (a RegExp `matcher` is never inferred as a script), unless overridden via `type: 'script'` or explicitly disabled via `type: 'asset'`. An `AssetFile` with an invalid `matcher` or an unrecognized `type` is discarded/ignored (with a console warning) rather than applied. If omitted, the agent falls back to its existing behavior of only evaluating the script that called `register()` (resource attribution still applies to that script).
35
+ * @property {'entry'|'scripts'|'all'} [timingMethod] - Controls which manifest assets are used to calculate MicroFrontEndTiming values: 'entry' (the default) leaves timing based entirely on the script that called `register()`, unaffected by the manifest; 'scripts' widens the timing window across all script assets; 'all' widens it across every asset.
31
36
  */
32
37
  /**
33
38
  * @typedef {Object} RegisterAPIMetadata
@@ -41,17 +46,21 @@ exports.default = void 0;
41
46
  * @property {string} name - The name returned for the registered entity.
42
47
  * @property {{[key: string]: any}} [tags] - The tags for the registered entity as key-value pairs.
43
48
  * @property {string} [parentId] - The parentId for the registered entity. If none was supplied, it will assume the entity guid from the main agent.
49
+ * @property {import('../../common/v2/manifest').ParsedManifest} [manifest] - The parsed manifest for the registered entity, if one was supplied.
50
+ * @property {'entry'|'scripts'|'all'} [timingMethod] - The timing method supplied for the registered entity, if any.
44
51
  */
45
52
  /**
46
53
  * @typedef {Object} RegisterAPITimings
47
54
  * @property {number} registeredAt - The timestamp when the registered entity was created.
48
55
  * @property {number} [reportedAt] - The timestamp when the registered entity was deregistered.
49
- * @property {number} fetchStart - The timestamp when the registered entity began fetching (performance.start).
50
- * @property {number} fetchEnd - The timestamp when the registered entity finished fetching (performance.end).
51
- * @property {number} scriptStart - The timestamp when script initialization began (max of dom.start or performance.end, or performance.end if no dom.start).
52
- * @property {number} scriptEnd - The timestamp when script loading completed (dom.end or registeredAt if no dom.end).
56
+ * @property {number} fetchStart - The timestamp when the registered entity began fetching (performance.start). When a manifest with timingMethod 'scripts'|'all' is supplied, this widens to the earliest fetchStart across all matched manifest assets.
57
+ * @property {number} fetchEnd - The timestamp when the registered entity finished fetching (performance.end). When a manifest with timingMethod 'scripts'|'all' is supplied, this widens to the latest fetchEnd across all matched manifest assets.
58
+ * @property {number} scriptStart - The timestamp when script initialization began (max of dom.start or performance.end, or performance.end if no dom.start). When a manifest with timingMethod 'scripts'|'all' is supplied, this widens to the earliest scriptStart across all matched manifest script assets (non-script assets are excluded).
59
+ * @property {number} scriptEnd - The timestamp when script loading completed (dom.end or registeredAt if no dom.end). When a manifest with timingMethod 'scripts'|'all' is supplied, this widens to the latest scriptEnd across all matched manifest script assets (non-script assets are excluded).
53
60
  * @property {Object} [asset] - The asset path (if found) for the registered entity.
54
61
  * @property {string} type - The type of timing associated with the registered entity, 'script' or 'link' if found with the performance resource API, 'fetch' for dynamic imports, 'inline' if found to be associated with the root document URL, or 'unknown' if no associated resource could be found.
62
+ * @property {number} totalWeight - The sum of `transferSize` (bytes) across every asset detected for the registered entity -- the entry script, plus, when a manifest is supplied, every matched manifest asset (script or non-script). Unlike fetchStart/fetchEnd/scriptStart/scriptEnd above, this is never gated behind `timingMethod` -- manifest assets contribute their bytes even at the 'entry' default/unset. Cross-origin assets without a Timing-Allow-Origin header report 0 bytes per the Resource Timing spec.
63
+ * @property {boolean} [renderBlocking] - True if any detected asset (entry script or matched manifest asset) has a `renderBlockingStatus` of 'blocking'; false if none were 'blocking' but at least one reported 'non-blocking'; left `undefined` if no detected asset reported the attribute at all (e.g. unsupported browser). A single 'blocking' asset always wins over any number of 'non-blocking' ones, regardless of resolution order.
55
64
  */
56
65
  /**
57
66
  * @typedef {Object} RegisterAPIVitals
@@ -23,12 +23,15 @@ var _pageVisibility = require("../../common/window/page-visibility");
23
23
  var _scriptTracker = require("../../common/v2/script-tracker");
24
24
  var _mfeVitals = require("../../common/v2/mfe-vitals");
25
25
  var _uniqueId = require("../../common/ids/unique-id");
26
+ var _manifest = require("../../common/v2/manifest");
26
27
  var _cleanUrl = require("../../common/url/clean-url");
27
28
  /**
28
29
  * Copyright 2020-2026 New Relic, Inc. All rights reserved.
29
30
  * SPDX-License-Identifier: Apache-2.0
30
31
  */
31
32
 
33
+ const TIMING_METHODS = ['entry', 'scripts', 'all'];
34
+
32
35
  /**
33
36
  * @typedef {import('./register-api-types').RegisterAPI} RegisterAPI
34
37
  */
@@ -49,7 +52,10 @@ const warnings = exports.warnings = {
49
52
  experimental: (0, _invoke.single)(() => (0, _console.warn)(54, 'newrelic.register')),
50
53
  disabled: (0, _invoke.single)(() => (0, _console.warn)(55)),
51
54
  invalidTarget: (0, _invoke.single)(target => (0, _console.warn)(48, target)),
52
- deregistered: (0, _invoke.single)(() => (0, _console.warn)(68))
55
+ deregistered: (0, _invoke.single)(() => (0, _console.warn)(68)),
56
+ invalidTimingMethod: (0, _invoke.single)(value => (0, _console.warn)(80, value)),
57
+ duplicateName: (0, _invoke.single)(target => (0, _console.warn)(81, target)),
58
+ duplicateId: (0, _invoke.single)(target => (0, _console.warn)(82, target))
53
59
  };
54
60
 
55
61
  /**
@@ -85,9 +91,17 @@ function register(agentRef, target) {
85
91
  // getter because this is asynchronously set
86
92
  type: _constants.V2_TYPES.BA
87
93
  };
94
+ target.manifest = (0, _manifest.parseManifest)(target.manifest);
95
+ if (target.timingMethod !== undefined && !TIMING_METHODS.includes(target.timingMethod)) {
96
+ warnings.invalidTimingMethod(target.timingMethod);
97
+ target.timingMethod = undefined;
98
+ }
88
99
 
89
100
  // The script timings for this entity, which will be used to populate part of the MicroFrontEndTiming custom event.
90
101
  const timings = (0, _scriptTracker.findScriptTimings)(target);
102
+ // Always applied when a manifest is present -- totalWeight/renderBlocking accumulate from manifest assets
103
+ // regardless of timingMethod; actual timing widening inside stays gated to 'scripts'/'all' (see applyManifestTimings).
104
+ if (target.manifest) (0, _scriptTracker.applyManifestTimings)(timings, target);
91
105
 
92
106
  // Track MFE vitals for this entity, which will be used to populate part of the MicroFrontEndTiming custom event.
93
107
  const vitals = (0, _mfeVitals.trackMFEVitals)(target, timings);
@@ -139,6 +153,19 @@ function register(agentRef, target) {
139
153
  /** primary cases that can block the register API from working at init time */
140
154
  if (!agentRef.init.api.register.enabled) block(warnings.disabled);
141
155
  if (!hasValidValue(target.id) || !hasValidValue(target.name)) block(() => warnings.invalidTarget(target));
156
+ /** warn if we see obviously unstable things with MFE targets */
157
+ registeredEntities.forEach(entity => {
158
+ try {
159
+ const {
160
+ name,
161
+ id
162
+ } = entity.metadata.target;
163
+ if (name === target.name && id !== target.id) warnings.duplicateName(target);
164
+ if (id === target.id && name !== target.name) warnings.duplicateId(target);
165
+ } catch (e) {
166
+ // something unexpected went wrong...
167
+ }
168
+ });
142
169
 
143
170
  /** @type {RegisterAPI} */
144
171
  const api = {
@@ -248,6 +275,12 @@ function register(agentRef, target) {
248
275
  // fetch time and script time together
249
276
  timeToRegister: timings.registeredAt,
250
277
  // timestamp when register() was called
278
+ totalWeight: timings.totalWeight,
279
+ // sum of transferSize bytes across every detected asset (entry script + matched manifest assets)
280
+ // renderBlocking: true if any detected asset was 'blocking', false if only 'non-blocking' assets were seen, omitted entirely if no detected asset reported the attribute at all
281
+ ...(timings.renderBlocking !== undefined && {
282
+ renderBlocking: timings.renderBlocking
283
+ }),
251
284
  // leave room to extend these with more data keys as needed
252
285
  ...(vitals.fcp.value >= 0 && {
253
286
  'vitals.fcp.value': vitals.fcp.value
@@ -11,7 +11,7 @@
11
11
  /**
12
12
  * Exposes the version of the agent
13
13
  */
14
- export const VERSION = "1.321.0-rc.0";
14
+ export const VERSION = "1.321.0-rc.10";
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.321.0-rc.0";
14
+ export const VERSION = "1.321.0-rc.10";
15
15
 
16
16
  /**
17
17
  * Exposes the build type of the agent
@@ -95,6 +95,9 @@ import { dispatchGlobalEvent } from '../dispatch/global-event';
95
95
  * | 77 | Agent rejected post message, could not validate origin. |
96
96
  * | 78 | RegisteredIframeEntity could not determine parent origin and will not register, to avoid trusting messages from any origin. |
97
97
  * | 79 | Unable to initialize Connector and/or Harvester. |
98
+ * | 80 | An invalid manifest option was provided to register() and will be ignored. |
99
+ * | 81 | Entities were detected that share a name with different IDs - This can cause multiple entities to have the same name in New Relic. |
100
+ * | 82 | Entities were detected that share an ID with different names - This can cause your entity's name to change unexpectedly. |
98
101
  *
99
102
  * @param {number} code The warning code to emit, which will be used to link to the warning code documentation
100
103
  * @param {*} [secondary] Secondary data to include, usually an extra message, error or object
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ import { cleanURL } from '../url/clean-url';
7
+ import { warn } from '../util/console';
8
+ import { single } from '../util/invoke';
9
+
10
+ /**
11
+ * @typedef {Object} AssetFile
12
+ * @property {string|RegExp} matcher - the path/path-fragment string, or RegExp, used to match a resolved URL against this asset
13
+ * @property {'script'|'asset'} [type] - optional override for script-capability inference. When omitted, script-capability is inferred from a
14
+ * `.js` suffix on a string `matcher` (a RegExp `matcher` is never inferred as a script). Supply `type: 'script'` to explicitly flag an entry
15
+ * as a script regardless of its matcher shape/suffix (e.g. an extensionless URL, or a RegExp targeting a script path), or `type: 'asset'` to
16
+ * explicitly flag it as non-script. Any other value is invalid, logs a warning, and is ignored (falls back to inference).
17
+ */
18
+
19
+ /** Warns at most once per page for any invalid manifest entry (bad `matcher` or bad `type`) -- mirrors the
20
+ * existing `invalidTimingMethod` single()-wrapped warning pattern in register.js. */
21
+ const warnInvalidManifestEntry = single(secondary => warn(80, secondary));
22
+
23
+ /**
24
+ * @typedef {Object} ParsedManifestAsset
25
+ * @property {string|RegExp} pattern - the original matcher supplied by the customer
26
+ * @property {(url: string) => boolean} test - precompiled matcher against a resolved URL
27
+ * @property {boolean} isScript
28
+ */
29
+
30
+ /**
31
+ * @typedef {Object} ParsedManifest
32
+ * @property {ParsedManifestAsset[]} assets - all supplied assets
33
+ * @property {ParsedManifestAsset[]} scripts - the subset of assets inferred/declared as scripts (`.js`)
34
+ */
35
+
36
+ /**
37
+ * Parses a raw manifest supplied to `register()` into precompiled matcher closures. Parsing happens once, at
38
+ * registration time, so downstream event attribution (which runs on every ajax/error/log/websocket event) never has
39
+ * to re-derive matching logic from the raw customer input.
40
+ * @param {{assets?: Array<AssetFile>}} [rawManifest]
41
+ * @returns {ParsedManifest|undefined} undefined if no usable assets were supplied, so callers can cheaply skip all manifest logic
42
+ */
43
+ export function parseManifest(rawManifest) {
44
+ if (!Array.isArray(rawManifest?.assets) || !rawManifest.assets.length) return undefined;
45
+ const assets = rawManifest.assets.map(parseAsset).filter(Boolean);
46
+ if (!assets.length) return undefined;
47
+ return {
48
+ assets,
49
+ scripts: assets.filter(asset => asset.isScript)
50
+ };
51
+ }
52
+
53
+ /**
54
+ * @param {AssetFile} entry
55
+ * @returns {ParsedManifestAsset|undefined}
56
+ */
57
+ function parseAsset(entry) {
58
+ if (!entry || typeof entry !== 'object') return undefined;
59
+ const {
60
+ matcher,
61
+ type
62
+ } = entry;
63
+ const inferredIsScript = typeof matcher === 'string' && isScriptPath(matcher);
64
+ let isScript = inferredIsScript;
65
+ if (type !== undefined) {
66
+ if (isValidType(type)) {
67
+ isScript = isScriptType(type);
68
+ } else {
69
+ // Invalid `type` never overrides -- fall back to inference-only behavior rather than forcing `isScript: false`,
70
+ // so a typo'd `type` can't silently break stack-trace attribution for an otherwise-valid `.js` matcher.
71
+ warnInvalidManifestEntry(type);
72
+ }
73
+ }
74
+ if (isRegExp(matcher)) {
75
+ // Reset lastIndex before every test -- a `g`/`y` flagged matcher otherwise carries state across calls
76
+ // (this closure is reused for every event), causing intermittent, input-order-dependent match failures.
77
+ return {
78
+ pattern: matcher,
79
+ test: url => {
80
+ matcher.lastIndex = 0;
81
+ return matcher.test(cleanURL(url));
82
+ },
83
+ isScript
84
+ };
85
+ }
86
+ if (typeof matcher === 'string' && matcher.length > 0) {
87
+ return {
88
+ pattern: matcher,
89
+ test: url => cleanURL(url).includes(matcher),
90
+ isScript
91
+ };
92
+ }
93
+
94
+ // Every other matcher shape (missing, empty string, null, number, plain object, ...) is invalid -- notably an
95
+ // empty string must be rejected here rather than falling through to the string branch above, since
96
+ // ''.includes('') is always true and would otherwise silently match every URL.
97
+ warnInvalidManifestEntry(matcher);
98
+ return undefined;
99
+ }
100
+ function isScriptPath(path) {
101
+ return path.endsWith('.js');
102
+ }
103
+ function isValidType(type) {
104
+ return type === 'script' || type === 'asset';
105
+ }
106
+
107
+ /**
108
+ * Cross-realm-safe check for whether a value is a RegExp. `instanceof RegExp` only returns true when the value's
109
+ * prototype chain links to THIS realm's `RegExp.prototype` -- a regex literal constructed in a different realm
110
+ * (an iframe, a Worker via structured clone, a WebDriver sandbox such as Firefox's geckodriver executeScript
111
+ * context) is still a genuine RegExp, just not an instance of this realm's constructor, and would otherwise be
112
+ * silently dropped by parseAsset below.
113
+ * @param {*} value
114
+ * @returns {boolean}
115
+ */
116
+ function isRegExp(value) {
117
+ return Object.prototype.toString.call(value) === '[object RegExp]';
118
+ }
119
+ function isScriptType(type) {
120
+ return type === 'script';
121
+ }
122
+
123
+ /**
124
+ * Determines whether a resolved URL matches any asset in a parsed manifest.
125
+ * @param {ParsedManifest|undefined} parsed
126
+ * @param {string} url
127
+ * @param {{scriptsOnly?: boolean}} [options]
128
+ * @returns {boolean}
129
+ */
130
+ export function matchManifestAsset(parsed, url, {
131
+ scriptsOnly = false
132
+ } = {}) {
133
+ if (!parsed || !url) return false;
134
+ const candidates = scriptsOnly ? parsed.scripts : parsed.assets;
135
+ return candidates.some(asset => asset.test(url));
136
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * @typedef {(start: number, end: number) => void} RecordManifestScriptWindowFn - Widens the live scriptStart/
8
+ * scriptEnd window with one manifest script asset's DOM correlation timing. `start`/`end` are that asset's
9
+ * `correlation.script.start`/`.end`, or falsy if not yet resolved. See `findScriptTimings`, which registers the
10
+ * concrete implementation, for the exact widening semantics (never shrinks either bound).
11
+ */
12
+
13
+ /**
14
+ * @typedef {Object} TimingsInternals
15
+ * @property {Set<string>} weighedAssetUrls - Always present (seeded by `getOrCreateInternals`, see
16
+ * `script-tracker.js`). Cleaned URLs already folded into `timings.totalWeight`/`timings.renderBlocking` by
17
+ * `applyResourceWeight`, so the same underlying resource (e.g. a manifest asset that's also the .register calling
18
+ * script itself) is never counted twice.
19
+ * @property {RecordManifestScriptWindowFn} recordManifestScriptWindow - Always present (seeded by
20
+ * `getOrCreateInternals`, see `script-tracker.js`). `findScriptTimings` overrides the seeded default with one that
21
+ * folds into the live `scriptStart`/`scriptEnd` getters instead.
22
+ */
23
+
24
+ export default {};