@newrelic/browser-agent 1.319.0-rc.2 → 1.319.0-rc.3

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.319.0-rc.2";
20
+ const VERSION = exports.VERSION = "1.319.0-rc.3";
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.319.0-rc.2";
20
+ const VERSION = exports.VERSION = "1.319.0-rc.3";
21
21
 
22
22
  /**
23
23
  * Exposes the build type of the agent
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.trackMFEVitals = trackMFEVitals;
7
7
  var _runtime = require("../constants/runtime");
8
8
  var _now = require("../timing/now");
9
+ var _scriptTrackerConstants = require("./script-tracker-constants");
9
10
  /**
10
11
  * Copyright 2020-2026 New Relic, Inc. All rights reserved.
11
12
  * SPDX-License-Identifier: Apache-2.0
@@ -163,11 +164,11 @@ function trackMFEVitals(target, timings) {
163
164
  if (!target || !_runtime.isBrowserScope || !_runtime.globalScope.MutationObserver || !_runtime.globalScope.PerformanceObserver) return vitals;
164
165
  const observers = [];
165
166
 
166
- // If FCP hasn't been observed within 10 seconds, give up and shut down all observers.
167
+ // If FCP hasn't been observed within this window, give up and shut down all observers.
167
168
  // Once FCP is observed, the other vitals are left to record until their natural lifespan ends.
168
169
  setTimeout(() => {
169
170
  if (!fcpObservedAt) vitals.disconnect();
170
- }, 10000);
171
+ }, _scriptTrackerConstants.CORRELATION_STALE_THRESHOLD_MS);
171
172
  const populateVitalMinimums = () => {
172
173
  fcpObservedAt ??= (0, _now.now)();
173
174
  lcpObservedAt ??= (0, _now.now)();
@@ -24,6 +24,8 @@ class ScriptCorrelation {
24
24
  constructor(url) {
25
25
  /** @type {string} The cleaned URL of the script */
26
26
  this.url = url;
27
+ /** @type {Set<string>} MFE target ids that have already claimed this correlation via a completed `findScriptTimings()` call. Keyed per target rather than a single flag so one script that registers multiple distinct MFEs (each with its own id) doesn't have the second MFE's registration mistaken for a stale reuse of the first's. */
28
+ this.claimedBy = new Set();
27
29
  }
28
30
 
29
31
  /**
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.CORRELATION_STALE_THRESHOLD_MS = void 0;
7
+ /**
8
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
9
+ * SPDX-License-Identifier: Apache-2.0
10
+ */
11
+
12
+ /** @type {number} - Correlations are keyed only by script URL and never cleared per-registration, so a script registered more than once in a session (SPA remount, shared bundle, etc.) can reuse a correlation from a much earlier load. If the correlation's recorded start predates `registeredAt` by more than this, treat it as stale and fall back to `registeredAt`. */
13
+ const CORRELATION_STALE_THRESHOLD_MS = exports.CORRELATION_STALE_THRESHOLD_MS = 10000;
@@ -12,6 +12,7 @@ var _now = require("../timing/now");
12
12
  var _cleanUrl = require("../url/clean-url");
13
13
  var _browserStackMatchers = require("../util/browser-stack-matchers");
14
14
  var _scriptCorrelation = require("./script-correlation");
15
+ var _scriptTrackerConstants = require("./script-tracker-constants");
15
16
  var _timingFactory = require("./timing-factory");
16
17
  /**
17
18
  * Copyright 2020-2026 New Relic, Inc. All rights reserved.
@@ -20,6 +21,7 @@ var _timingFactory = require("./timing-factory");
20
21
 
21
22
  /**
22
23
  * @typedef {import('./register-api-types').RegisterAPITimings} RegisterAPITimings
24
+ * @typedef {import('../../loaders/api/register-api-types').RegisterAPITarget} RegisterAPITarget
23
25
  */
24
26
 
25
27
  /** export for testing purposes */
@@ -227,9 +229,11 @@ function subscribeToLatePerformanceEntry(timings, mfeScriptUrl) {
227
229
 
228
230
  /**
229
231
  * Uses the stack of the initiator function, returns script timing information if a script can be found with the resource timing API matching the URL found in the stack.
232
+ * @param {RegisterAPITarget} [target] - The MFE target being registered. Its id is used to scope stale-correlation detection per-MFE rather than per-script-URL, so one script registering multiple distinct MFEs doesn't misclassify a later MFE's registration as a stale reuse of an earlier one's.
230
233
  * @returns {RegisterAPITimings} Object containing script fetch start and end times, and the asset URL if found
231
234
  */
232
- function findScriptTimings() {
235
+ function findScriptTimings(target) {
236
+ const mfeId = target?.id;
233
237
  const timings = {
234
238
  registeredAt: (0, _now.now)(),
235
239
  reportedAt: undefined,
@@ -274,12 +278,24 @@ function findScriptTimings() {
274
278
  subscribeToLatePerformanceEntry(timings, mfeScriptUrl);
275
279
  }
276
280
 
277
- /*
278
- * Use getters here because the correlation data may arrive after this function returns the timing object, and we want to provide the most up-to-date timing information possible when the getters are accessed at harvest time.
279
- * The getters will fall back to fetchEnd if correlation data isn't available yet, which is our best approximation for script execution start when actual script timings can not be determined.
280
- */
281
- Object.defineProperty(timings, 'scriptStart', (0, _timingFactory.timingFactory)(() => timings.correlation?.script.start || timings.fetchEnd));
282
- Object.defineProperty(timings, 'scriptEnd', (0, _timingFactory.timingFactory)(() => timings.correlation?.script.end || timings.registeredAt));
281
+ // A correlation can be reused across multiple `register()` calls for the same script URL (e.g. an SPA
282
+ // remounting the same MFE without the script actually reloading). When that happens, its dom/performance
283
+ // timings still describe the *original* load, not this one. Detect that case so scriptStart/scriptEnd
284
+ // below can ignore the stale data instead of reporting it as if it were fresh.
285
+ const correlation = timings.correlation;
286
+ const alreadyClaimedByThisMFE = !!mfeId && !!correlation?.claimedBy.has(mfeId);
287
+ if (correlation && mfeId) correlation.claimedBy.add(mfeId);
288
+ const isCorrelationStale = () => {
289
+ const correlationStart = correlation?.script.start;
290
+ if (!alreadyClaimedByThisMFE || !correlationStart) return false;
291
+ const staleness = timings.registeredAt - correlationStart;
292
+ return staleness > _scriptTrackerConstants.CORRELATION_STALE_THRESHOLD_MS;
293
+ };
294
+
295
+ // Use getters here because the correlation data may arrive after this function returns the timing object, and we want to provide the most up-to-date timing information possible when the getters are accessed at harvest time.
296
+ // Non-stale: fall back to fetchEnd if correlation data isn't available yet (our best approximation for script execution start). Stale: fall back straight to registeredAt — fetchEnd would be derived from the same stale correlation, so it can't be trusted either.
297
+ Object.defineProperty(timings, 'scriptStart', (0, _timingFactory.timingFactory)(() => isCorrelationStale() ? timings.registeredAt : correlation?.script.start ?? timings.fetchEnd));
298
+ Object.defineProperty(timings, 'scriptEnd', (0, _timingFactory.timingFactory)(() => isCorrelationStale() ? timings.registeredAt : correlation?.script.end ?? timings.registeredAt));
283
299
  } catch (error) {
284
300
  // Don't let stack parsing errors break anything
285
301
  }
@@ -84,7 +84,7 @@ function register(agentRef, target) {
84
84
  // getter because this is asynchronously set
85
85
  type: _utils.V2_TYPES.BA
86
86
  };
87
- const timings = (0, _scriptTracker.findScriptTimings)();
87
+ const timings = (0, _scriptTracker.findScriptTimings)(target);
88
88
 
89
89
  // Track MFE vitals for this entity
90
90
  const vitals = (0, _mfeVitals.trackMFEVitals)(target, timings);
@@ -11,7 +11,7 @@
11
11
  /**
12
12
  * Exposes the version of the agent
13
13
  */
14
- export const VERSION = "1.319.0-rc.2";
14
+ export const VERSION = "1.319.0-rc.3";
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.319.0-rc.2";
14
+ export const VERSION = "1.319.0-rc.3";
15
15
 
16
16
  /**
17
17
  * Exposes the build type of the agent
@@ -5,6 +5,7 @@
5
5
 
6
6
  import { globalScope, isBrowserScope } from '../constants/runtime';
7
7
  import { now } from '../timing/now';
8
+ import { CORRELATION_STALE_THRESHOLD_MS } from './script-tracker-constants';
8
9
 
9
10
  /**
10
11
  * @typedef {import('../../loaders/api/register-api-types').RegisterAPITimings} RegisterAPITimings
@@ -158,11 +159,11 @@ export function trackMFEVitals(target, timings) {
158
159
  if (!target || !isBrowserScope || !globalScope.MutationObserver || !globalScope.PerformanceObserver) return vitals;
159
160
  const observers = [];
160
161
 
161
- // If FCP hasn't been observed within 10 seconds, give up and shut down all observers.
162
+ // If FCP hasn't been observed within this window, give up and shut down all observers.
162
163
  // Once FCP is observed, the other vitals are left to record until their natural lifespan ends.
163
164
  setTimeout(() => {
164
165
  if (!fcpObservedAt) vitals.disconnect();
165
- }, 10000);
166
+ }, CORRELATION_STALE_THRESHOLD_MS);
166
167
  const populateVitalMinimums = () => {
167
168
  fcpObservedAt ??= now();
168
169
  lcpObservedAt ??= now();
@@ -18,6 +18,8 @@ export class ScriptCorrelation {
18
18
  constructor(url) {
19
19
  /** @type {string} The cleaned URL of the script */
20
20
  this.url = url;
21
+ /** @type {Set<string>} MFE target ids that have already claimed this correlation via a completed `findScriptTimings()` call. Keyed per target rather than a single flag so one script that registers multiple distinct MFEs (each with its own id) doesn't have the second MFE's registration mistaken for a stale reuse of the first's. */
22
+ this.claimedBy = new Set();
21
23
  }
22
24
 
23
25
  /**
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /** @type {number} - Correlations are keyed only by script URL and never cleared per-registration, so a script registered more than once in a session (SPA remount, shared bundle, etc.) can reuse a correlation from a much earlier load. If the correlation's recorded start predates `registeredAt` by more than this, treat it as stale and fall back to `registeredAt`. */
7
+ export const CORRELATION_STALE_THRESHOLD_MS = 10000;
@@ -8,10 +8,12 @@ import { now } from '../timing/now';
8
8
  import { cleanURL } from '../url/clean-url';
9
9
  import { chrome, chromeEval, gecko } from '../util/browser-stack-matchers';
10
10
  import { ScriptCorrelation } from './script-correlation';
11
+ import { CORRELATION_STALE_THRESHOLD_MS } from './script-tracker-constants';
11
12
  import { timingFactory } from './timing-factory';
12
13
 
13
14
  /**
14
15
  * @typedef {import('./register-api-types').RegisterAPITimings} RegisterAPITimings
16
+ * @typedef {import('../../loaders/api/register-api-types').RegisterAPITarget} RegisterAPITarget
15
17
  */
16
18
 
17
19
  /** export for testing purposes */
@@ -219,9 +221,11 @@ function subscribeToLatePerformanceEntry(timings, mfeScriptUrl) {
219
221
 
220
222
  /**
221
223
  * Uses the stack of the initiator function, returns script timing information if a script can be found with the resource timing API matching the URL found in the stack.
224
+ * @param {RegisterAPITarget} [target] - The MFE target being registered. Its id is used to scope stale-correlation detection per-MFE rather than per-script-URL, so one script registering multiple distinct MFEs doesn't misclassify a later MFE's registration as a stale reuse of an earlier one's.
222
225
  * @returns {RegisterAPITimings} Object containing script fetch start and end times, and the asset URL if found
223
226
  */
224
- export function findScriptTimings() {
227
+ export function findScriptTimings(target) {
228
+ const mfeId = target?.id;
225
229
  const timings = {
226
230
  registeredAt: now(),
227
231
  reportedAt: undefined,
@@ -266,12 +270,24 @@ export function findScriptTimings() {
266
270
  subscribeToLatePerformanceEntry(timings, mfeScriptUrl);
267
271
  }
268
272
 
269
- /*
270
- * Use getters here because the correlation data may arrive after this function returns the timing object, and we want to provide the most up-to-date timing information possible when the getters are accessed at harvest time.
271
- * The getters will fall back to fetchEnd if correlation data isn't available yet, which is our best approximation for script execution start when actual script timings can not be determined.
272
- */
273
- Object.defineProperty(timings, 'scriptStart', timingFactory(() => timings.correlation?.script.start || timings.fetchEnd));
274
- Object.defineProperty(timings, 'scriptEnd', timingFactory(() => timings.correlation?.script.end || timings.registeredAt));
273
+ // A correlation can be reused across multiple `register()` calls for the same script URL (e.g. an SPA
274
+ // remounting the same MFE without the script actually reloading). When that happens, its dom/performance
275
+ // timings still describe the *original* load, not this one. Detect that case so scriptStart/scriptEnd
276
+ // below can ignore the stale data instead of reporting it as if it were fresh.
277
+ const correlation = timings.correlation;
278
+ const alreadyClaimedByThisMFE = !!mfeId && !!correlation?.claimedBy.has(mfeId);
279
+ if (correlation && mfeId) correlation.claimedBy.add(mfeId);
280
+ const isCorrelationStale = () => {
281
+ const correlationStart = correlation?.script.start;
282
+ if (!alreadyClaimedByThisMFE || !correlationStart) return false;
283
+ const staleness = timings.registeredAt - correlationStart;
284
+ return staleness > CORRELATION_STALE_THRESHOLD_MS;
285
+ };
286
+
287
+ // Use getters here because the correlation data may arrive after this function returns the timing object, and we want to provide the most up-to-date timing information possible when the getters are accessed at harvest time.
288
+ // Non-stale: fall back to fetchEnd if correlation data isn't available yet (our best approximation for script execution start). Stale: fall back straight to registeredAt — fetchEnd would be derived from the same stale correlation, so it can't be trusted either.
289
+ Object.defineProperty(timings, 'scriptStart', timingFactory(() => isCorrelationStale() ? timings.registeredAt : correlation?.script.start ?? timings.fetchEnd));
290
+ Object.defineProperty(timings, 'scriptEnd', timingFactory(() => isCorrelationStale() ? timings.registeredAt : correlation?.script.end ?? timings.registeredAt));
275
291
  } catch (error) {
276
292
  // Don't let stack parsing errors break anything
277
293
  }
@@ -77,7 +77,7 @@ function register(agentRef, target) {
77
77
  // getter because this is asynchronously set
78
78
  type: V2_TYPES.BA
79
79
  };
80
- const timings = findScriptTimings();
80
+ const timings = findScriptTimings(target);
81
81
 
82
82
  // Track MFE vitals for this entity
83
83
  const vitals = trackMFEVitals(target, timings);
@@ -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/events.js","../src/common/constants/iframe-constants.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/send.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/payloads/payloads.js","../src/common/serialize/bel-serializer.js","../src/common/session/constants.js","../src/common/session/session-entity.js","../src/common/session/session-key.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/add-url.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/mfe-vitals.js","../src/common/v2/script-correlation.js","../src/common/v2/script-tracker.js","../src/common/v2/timing-factory.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/interfaces/registered-iframe-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/iframe-message-handler.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/events.js","../src/common/constants/iframe-constants.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/send.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/payloads/payloads.js","../src/common/serialize/bel-serializer.js","../src/common/session/constants.js","../src/common/session/session-entity.js","../src/common/session/session-key.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/add-url.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/mfe-vitals.js","../src/common/v2/script-correlation.js","../src/common/v2/script-tracker-constants.js","../src/common/v2/script-tracker.js","../src/common/v2/timing-factory.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/interfaces/registered-iframe-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/iframe-message-handler.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 +1 @@
1
- {"version":3,"file":"mfe-vitals.d.ts","sourceRoot":"","sources":["../../../../src/common/v2/mfe-vitals.js"],"names":[],"mappings":"AAoHA;;;;;GAKG;AACH,uCAJW,iBAAiB,WACjB,kBAAkB,GAChB,iBAAiB,CAuM7B;iCAtTY,OAAO,sCAAsC,EAAE,kBAAkB;gCACjE,OAAO,sCAAsC,EAAE,iBAAiB;gCAChE,OAAO,sCAAsC,EAAE,iBAAiB"}
1
+ {"version":3,"file":"mfe-vitals.d.ts","sourceRoot":"","sources":["../../../../src/common/v2/mfe-vitals.js"],"names":[],"mappings":"AAqHA;;;;;GAKG;AACH,uCAJW,iBAAiB,WACjB,kBAAkB,GAChB,iBAAiB,CAuM7B;iCAtTY,OAAO,sCAAsC,EAAE,kBAAkB;gCACjE,OAAO,sCAAsC,EAAE,iBAAiB;gCAChE,OAAO,sCAAsC,EAAE,iBAAiB"}
@@ -17,6 +17,8 @@ export class ScriptCorrelation {
17
17
  performance: CorrelationTiming;
18
18
  /** @type {string} The cleaned URL of the script */
19
19
  url: string;
20
+ /** @type {Set<string>} MFE target ids that have already claimed this correlation via a completed `findScriptTimings()` call. Keyed per target rather than a single flag so one script that registers multiple distinct MFEs (each with its own id) doesn't have the second MFE's registration mistaken for a stale reuse of the first's. */
21
+ claimedBy: Set<string>;
20
22
  /**
21
23
  * Gets the script timing, using DOM timings if available, otherwise falling back to performance timings or registeredAt as appropriate. This is used to provide the most accurate script timing possible for registered entities.
22
24
  * @returns {{start: number, end: number}
@@ -1 +1 @@
1
- {"version":3,"file":"script-correlation.d.ts","sourceRoot":"","sources":["../../../../src/common/v2/script-correlation.js"],"names":[],"mappings":"AAAA;;;GAGG;AACH;;GAEG;AACH;IAME;;;OAGG;IACH,iBAFW,MAAM,EAKhB;IAZD,gEAAgE;IAChE,KADW,iBAAiB,CACC;IAC7B,gFAAgF;IAChF,aADW,iBAAiB,CACS;IAOnC,mDAAmD;IACnD,KADW,MAAM,CACH;IAGhB;;;OAGG;IACH,cAFa;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAC,CASxC;CACF;AAED;IACE,wEAAwE;IACxE,OADW,MAAM,CACR;IACT,wEAAwE;IACxE,KADW,MAAM,CACV;IACP,0CAA0C;IAC1C,OADW,GAAC,CACK;CAClB"}
1
+ {"version":3,"file":"script-correlation.d.ts","sourceRoot":"","sources":["../../../../src/common/v2/script-correlation.js"],"names":[],"mappings":"AAAA;;;GAGG;AACH;;GAEG;AACH;IAME;;;OAGG;IACH,iBAFW,MAAM,EAOhB;IAdD,gEAAgE;IAChE,KADW,iBAAiB,CACC;IAC7B,gFAAgF;IAChF,aADW,iBAAiB,CACS;IAOnC,mDAAmD;IACnD,KADW,MAAM,CACH;IACd,4UAA4U;IAC5U,WADW,GAAG,CAAC,MAAM,CAAC,CACI;IAG5B;;;OAGG;IACH,cAFa;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAC,CASxC;CACF;AAED;IACE,wEAAwE;IACxE,OADW,MAAM,CACR;IACT,wEAAwE;IACxE,KADW,MAAM,CACV;IACP,0CAA0C;IAC1C,OADW,GAAC,CACK;CAClB"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ /** @type {number} - Correlations are keyed only by script URL and never cleared per-registration, so a script registered more than once in a session (SPA remount, shared bundle, etc.) can reuse a correlation from a much earlier load. If the correlation's recorded start predates `registeredAt` by more than this, treat it as stale and fall back to `registeredAt`. */
6
+ export const CORRELATION_STALE_THRESHOLD_MS: number;
7
+ //# sourceMappingURL=script-tracker-constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"script-tracker-constants.d.ts","sourceRoot":"","sources":["../../../../src/common/v2/script-tracker-constants.js"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,+WAA+W;AAC/W,6CADW,MAAM,CACkC"}
@@ -11,16 +11,19 @@ export function extractUrlsFromStack(stack: string): string[];
11
11
  export function getDeepStackTrace(): Error.stack | undefined;
12
12
  /**
13
13
  * Uses the stack of the initiator function, returns script timing information if a script can be found with the resource timing API matching the URL found in the stack.
14
+ * @param {RegisterAPITarget} [target] - The MFE target being registered. Its id is used to scope stale-correlation detection per-MFE rather than per-script-URL, so one script registering multiple distinct MFEs doesn't misclassify a later MFE's registration as a stale reuse of an earlier one's.
14
15
  * @returns {RegisterAPITimings} Object containing script fetch start and end times, and the asset URL if found
15
16
  */
16
- export function findScriptTimings(): RegisterAPITimings;
17
+ export function findScriptTimings(target?: RegisterAPITarget): RegisterAPITimings;
17
18
  /**
18
19
  * @typedef {import('./register-api-types').RegisterAPITimings} RegisterAPITimings
20
+ * @typedef {import('../../loaders/api/register-api-types').RegisterAPITarget} RegisterAPITarget
19
21
  */
20
22
  /** export for testing purposes */
21
23
  export let thisFile: any;
22
24
  /** @type {Map<string, ScriptCorrelation>} - Central registry for script correlations containing both DOM and Performance data */
23
25
  export const scriptCorrelations: Map<string, ScriptCorrelation>;
24
26
  export type RegisterAPITimings = any;
27
+ export type RegisterAPITarget = import("../../loaders/api/register-api-types").RegisterAPITarget;
25
28
  import { ScriptCorrelation } from './script-correlation';
26
29
  //# sourceMappingURL=script-tracker.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"script-tracker.d.ts","sourceRoot":"","sources":["../../../../src/common/v2/script-tracker.js"],"names":[],"mappings":"AAgHA;;;;GAIG;AACH,4CAHW,MAAM,GACJ,MAAM,EAAE,CAsBpB;AAED;;;GAGG;AACH,qCAFa,KAAK,CAAC,KAAK,GAAG,SAAS,CAanC;AAiED;;;GAGG;AACH,qCAFa,kBAAkB,CAqD9B;AAtQD;;GAEG;AAEH,kCAAkC;AAClC,yBAAmB;AAUnB,iIAAiI;AACjI,iCADW,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CACE;;kCAnBT,sBAAsB"}
1
+ {"version":3,"file":"script-tracker.d.ts","sourceRoot":"","sources":["../../../../src/common/v2/script-tracker.js"],"names":[],"mappings":"AAkHA;;;;GAIG;AACH,4CAHW,MAAM,GACJ,MAAM,EAAE,CAsBpB;AAED;;;GAGG;AACH,qCAFa,KAAK,CAAC,KAAK,GAAG,SAAS,CAanC;AAiED;;;;GAIG;AACH,2CAHW,iBAAiB,GACf,kBAAkB,CA0E9B;AA7RD;;;GAGG;AAEH,kCAAkC;AAClC,yBAAmB;AAUnB,iIAAiI;AACjI,iCADW,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CACE;;gCAf9B,OAAO,sCAAsC,EAAE,iBAAiB;kCAN3C,sBAAsB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/browser-agent",
3
- "version": "1.319.0-rc.2",
3
+ "version": "1.319.0-rc.3",
4
4
  "private": false,
5
5
  "author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
6
6
  "description": "New Relic Browser Agent",
@@ -5,6 +5,7 @@
5
5
 
6
6
  import { globalScope, isBrowserScope } from '../constants/runtime'
7
7
  import { now } from '../timing/now'
8
+ import { CORRELATION_STALE_THRESHOLD_MS } from './script-tracker-constants'
8
9
 
9
10
  /**
10
11
  * @typedef {import('../../loaders/api/register-api-types').RegisterAPITimings} RegisterAPITimings
@@ -151,11 +152,11 @@ export function trackMFEVitals (target, timings) {
151
152
 
152
153
  const observers = []
153
154
 
154
- // If FCP hasn't been observed within 10 seconds, give up and shut down all observers.
155
+ // If FCP hasn't been observed within this window, give up and shut down all observers.
155
156
  // Once FCP is observed, the other vitals are left to record until their natural lifespan ends.
156
157
  setTimeout(() => {
157
158
  if (!fcpObservedAt) vitals.disconnect()
158
- }, 10000)
159
+ }, CORRELATION_STALE_THRESHOLD_MS)
159
160
 
160
161
  const populateVitalMinimums = () => {
161
162
  fcpObservedAt ??= now()
@@ -18,6 +18,8 @@ export class ScriptCorrelation {
18
18
  constructor (url) {
19
19
  /** @type {string} The cleaned URL of the script */
20
20
  this.url = url
21
+ /** @type {Set<string>} MFE target ids that have already claimed this correlation via a completed `findScriptTimings()` call. Keyed per target rather than a single flag so one script that registers multiple distinct MFEs (each with its own id) doesn't have the second MFE's registration mistaken for a stale reuse of the first's. */
22
+ this.claimedBy = new Set()
21
23
  }
22
24
 
23
25
  /**
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /** @type {number} - Correlations are keyed only by script URL and never cleared per-registration, so a script registered more than once in a session (SPA remount, shared bundle, etc.) can reuse a correlation from a much earlier load. If the correlation's recorded start predates `registeredAt` by more than this, treat it as stale and fall back to `registeredAt`. */
7
+ export const CORRELATION_STALE_THRESHOLD_MS = 10000
@@ -8,10 +8,12 @@ import { now } from '../timing/now'
8
8
  import { cleanURL } from '../url/clean-url'
9
9
  import { chrome, chromeEval, gecko } from '../util/browser-stack-matchers'
10
10
  import { ScriptCorrelation } from './script-correlation'
11
+ import { CORRELATION_STALE_THRESHOLD_MS } from './script-tracker-constants'
11
12
  import { timingFactory } from './timing-factory'
12
13
 
13
14
  /**
14
15
  * @typedef {import('./register-api-types').RegisterAPITimings} RegisterAPITimings
16
+ * @typedef {import('../../loaders/api/register-api-types').RegisterAPITarget} RegisterAPITarget
15
17
  */
16
18
 
17
19
  /** export for testing purposes */
@@ -219,9 +221,11 @@ function subscribeToLatePerformanceEntry (timings, mfeScriptUrl) {
219
221
 
220
222
  /**
221
223
  * Uses the stack of the initiator function, returns script timing information if a script can be found with the resource timing API matching the URL found in the stack.
224
+ * @param {RegisterAPITarget} [target] - The MFE target being registered. Its id is used to scope stale-correlation detection per-MFE rather than per-script-URL, so one script registering multiple distinct MFEs doesn't misclassify a later MFE's registration as a stale reuse of an earlier one's.
222
225
  * @returns {RegisterAPITimings} Object containing script fetch start and end times, and the asset URL if found
223
226
  */
224
- export function findScriptTimings () {
227
+ export function findScriptTimings (target) {
228
+ const mfeId = target?.id
225
229
  const timings = { registeredAt: now(), reportedAt: undefined, fetchStart: 0, fetchEnd: 0, scriptStart: 0, scriptEnd: 0, asset: undefined, type: 'unknown' }
226
230
  const stack = getDeepStackTrace()
227
231
  if (!stack) return timings
@@ -261,12 +265,32 @@ export function findScriptTimings () {
261
265
  subscribeToLatePerformanceEntry(timings, mfeScriptUrl)
262
266
  }
263
267
 
264
- /*
265
- * Use getters here because the correlation data may arrive after this function returns the timing object, and we want to provide the most up-to-date timing information possible when the getters are accessed at harvest time.
266
- * The getters will fall back to fetchEnd if correlation data isn't available yet, which is our best approximation for script execution start when actual script timings can not be determined.
267
- */
268
- Object.defineProperty(timings, 'scriptStart', timingFactory(() => timings.correlation?.script.start || timings.fetchEnd))
269
- Object.defineProperty(timings, 'scriptEnd', timingFactory(() => timings.correlation?.script.end || timings.registeredAt))
268
+ // A correlation can be reused across multiple `register()` calls for the same script URL (e.g. an SPA
269
+ // remounting the same MFE without the script actually reloading). When that happens, its dom/performance
270
+ // timings still describe the *original* load, not this one. Detect that case so scriptStart/scriptEnd
271
+ // below can ignore the stale data instead of reporting it as if it were fresh.
272
+ const correlation = timings.correlation
273
+ const alreadyClaimedByThisMFE = !!mfeId && !!correlation?.claimedBy.has(mfeId)
274
+ if (correlation && mfeId) correlation.claimedBy.add(mfeId)
275
+ const isCorrelationStale = () => {
276
+ const correlationStart = correlation?.script.start
277
+ if (!alreadyClaimedByThisMFE || !correlationStart) return false
278
+ const staleness = timings.registeredAt - correlationStart
279
+ return staleness > CORRELATION_STALE_THRESHOLD_MS
280
+ }
281
+
282
+ // Use getters here because the correlation data may arrive after this function returns the timing object, and we want to provide the most up-to-date timing information possible when the getters are accessed at harvest time.
283
+ // Non-stale: fall back to fetchEnd if correlation data isn't available yet (our best approximation for script execution start). Stale: fall back straight to registeredAt — fetchEnd would be derived from the same stale correlation, so it can't be trusted either.
284
+ Object.defineProperty(
285
+ timings,
286
+ 'scriptStart',
287
+ timingFactory(() => isCorrelationStale() ? timings.registeredAt : (correlation?.script.start ?? timings.fetchEnd))
288
+ )
289
+ Object.defineProperty(
290
+ timings,
291
+ 'scriptEnd',
292
+ timingFactory(() => isCorrelationStale() ? timings.registeredAt : (correlation?.script.end ?? timings.registeredAt))
293
+ )
270
294
  } catch (error) {
271
295
  // Don't let stack parsing errors break anything
272
296
  }
@@ -82,7 +82,7 @@ function register (agentRef, target) {
82
82
  type: V2_TYPES.BA
83
83
  }
84
84
 
85
- const timings = findScriptTimings()
85
+ const timings = findScriptTimings(target)
86
86
 
87
87
  // Track MFE vitals for this entity
88
88
  const vitals = trackMFEVitals(target, timings)