@newrelic/browser-agent 1.304.0 → 1.305.0-rc.1

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 (72) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/cjs/common/constants/env.cdn.js +1 -1
  3. package/dist/cjs/common/constants/env.npm.js +1 -1
  4. package/dist/cjs/common/constants/runtime.js +4 -2
  5. package/dist/cjs/common/timing/nav-timing.js +7 -2
  6. package/dist/cjs/common/util/mfe.js +4 -4
  7. package/dist/cjs/common/vitals/constants.js +1 -0
  8. package/dist/cjs/common/vitals/load-time.js +27 -0
  9. package/dist/cjs/common/vitals/time-to-first-byte.js +1 -1
  10. package/dist/cjs/common/window/load.js +19 -1
  11. package/dist/cjs/features/generic_events/aggregate/index.js +1 -1
  12. package/dist/cjs/features/generic_events/aggregate/user-actions/user-actions-aggregator.js +5 -9
  13. package/dist/cjs/features/generic_events/instrument/index.js +28 -33
  14. package/dist/cjs/features/page_view_event/aggregate/index.js +7 -8
  15. package/dist/cjs/features/page_view_timing/aggregate/index.js +5 -4
  16. package/dist/cjs/features/session_trace/aggregate/index.js +1 -1
  17. package/dist/cjs/features/soft_navigations/aggregate/index.js +3 -5
  18. package/dist/cjs/features/spa/instrument/index.js +8 -0
  19. package/dist/cjs/loaders/api/register.js +1 -1
  20. package/dist/esm/common/constants/env.cdn.js +1 -1
  21. package/dist/esm/common/constants/env.npm.js +1 -1
  22. package/dist/esm/common/constants/runtime.js +2 -1
  23. package/dist/esm/common/timing/nav-timing.js +7 -2
  24. package/dist/esm/common/util/mfe.js +4 -4
  25. package/dist/esm/common/vitals/constants.js +1 -0
  26. package/dist/esm/common/vitals/load-time.js +20 -0
  27. package/dist/esm/common/vitals/time-to-first-byte.js +2 -2
  28. package/dist/esm/common/window/load.js +19 -1
  29. package/dist/esm/features/generic_events/aggregate/index.js +1 -1
  30. package/dist/esm/features/generic_events/aggregate/user-actions/user-actions-aggregator.js +5 -9
  31. package/dist/esm/features/generic_events/instrument/index.js +28 -33
  32. package/dist/esm/features/page_view_event/aggregate/index.js +8 -9
  33. package/dist/esm/features/page_view_timing/aggregate/index.js +5 -4
  34. package/dist/esm/features/session_trace/aggregate/index.js +2 -2
  35. package/dist/esm/features/soft_navigations/aggregate/index.js +3 -5
  36. package/dist/esm/features/spa/instrument/index.js +8 -0
  37. package/dist/esm/loaders/api/register.js +1 -1
  38. package/dist/tsconfig.tsbuildinfo +1 -1
  39. package/dist/types/common/constants/runtime.d.ts +1 -0
  40. package/dist/types/common/constants/runtime.d.ts.map +1 -1
  41. package/dist/types/common/timing/nav-timing.d.ts.map +1 -1
  42. package/dist/types/common/util/mfe.d.ts +2 -5
  43. package/dist/types/common/util/mfe.d.ts.map +1 -1
  44. package/dist/types/common/vitals/constants.d.ts +1 -0
  45. package/dist/types/common/vitals/load-time.d.ts +3 -0
  46. package/dist/types/common/vitals/load-time.d.ts.map +1 -0
  47. package/dist/types/common/window/load.d.ts +10 -1
  48. package/dist/types/common/window/load.d.ts.map +1 -1
  49. package/dist/types/features/generic_events/aggregate/user-actions/user-actions-aggregator.d.ts +0 -1
  50. package/dist/types/features/generic_events/aggregate/user-actions/user-actions-aggregator.d.ts.map +1 -1
  51. package/dist/types/features/generic_events/instrument/index.d.ts.map +1 -1
  52. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  53. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  54. package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -1
  55. package/dist/types/features/spa/instrument/index.d.ts.map +1 -1
  56. package/package.json +2 -2
  57. package/src/common/constants/runtime.js +2 -0
  58. package/src/common/timing/nav-timing.js +7 -2
  59. package/src/common/util/mfe.js +4 -4
  60. package/src/common/vitals/constants.js +1 -0
  61. package/src/common/vitals/load-time.js +23 -0
  62. package/src/common/vitals/time-to-first-byte.js +2 -2
  63. package/src/common/window/load.js +18 -1
  64. package/src/features/generic_events/aggregate/index.js +1 -1
  65. package/src/features/generic_events/aggregate/user-actions/user-actions-aggregator.js +5 -9
  66. package/src/features/generic_events/instrument/index.js +35 -37
  67. package/src/features/page_view_event/aggregate/index.js +8 -9
  68. package/src/features/page_view_timing/aggregate/index.js +2 -4
  69. package/src/features/session_trace/aggregate/index.js +2 -2
  70. package/src/features/soft_navigations/aggregate/index.js +3 -4
  71. package/src/features/spa/instrument/index.js +6 -0
  72. package/src/loaders/api/register.js +1 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,14 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [1.305.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.304.0...v1.305.0) (2025-12-10)
7
+
8
+
9
+ ### Features
10
+
11
+ * Enable user frustrations for Pro/SPA agents ([#1641](https://github.com/newrelic/newrelic-browser-agent/issues/1641)) ([5f7faf3](https://github.com/newrelic/newrelic-browser-agent/commit/5f7faf3112823d242cc7d05069e0080ca8e1f403))
12
+ * Improve agent compatibility with ChatGPT connector apps ([#1640](https://github.com/newrelic/newrelic-browser-agent/issues/1640)) ([da2ef2f](https://github.com/newrelic/newrelic-browser-agent/commit/da2ef2f50af05085dc5806d422d411deec2d2ba5))
13
+
6
14
  ## [1.304.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.303.0...v1.304.0) (2025-12-03)
7
15
 
8
16
 
@@ -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.304.0";
20
+ const VERSION = exports.VERSION = "1.305.0-rc.1";
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.304.0";
20
+ const VERSION = exports.VERSION = "1.305.0-rc.1";
21
21
 
22
22
  /**
23
23
  * Exposes the build type of the agent
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.originTime = exports.loadedAsDeferredBrowserScript = exports.isiOS = exports.isWorkerScope = exports.isBrowserScope = exports.initiallyHidden = exports.initialLocation = exports.iOSBelow16 = exports.globalScope = exports.ffVersion = void 0;
6
+ exports.supportsNavTimingL2 = exports.originTime = exports.loadedAsDeferredBrowserScript = exports.isiOS = exports.isWorkerScope = exports.isBrowserScope = exports.initiallyHidden = exports.initialLocation = exports.iOSBelow16 = exports.globalScope = exports.ffVersion = void 0;
7
7
  var _now = require("../timing/now");
8
8
  /**
9
9
  * Copyright 2020-2025 New Relic, Inc. All rights reserved.
@@ -53,4 +53,6 @@ const ffVersion = exports.ffVersion = (() => {
53
53
  * according to the browser's local clock.
54
54
  * @type {number}
55
55
  */
56
- const originTime = exports.originTime = Date.now() - (0, _now.now)();
56
+ const originTime = exports.originTime = Date.now() - (0, _now.now)();
57
+ const supportsNavTimingL2 = () => typeof PerformanceNavigationTiming !== 'undefined' && globalScope?.performance?.getEntriesByType('navigation')?.length > 0;
58
+ exports.supportsNavTimingL2 = supportsNavTimingL2;
@@ -72,8 +72,13 @@ function addPT(offset, pt, v = {}, isL1Api = false) {
72
72
 
73
73
  // Add Performance Navigation values to the given object
74
74
  function addPN(pn, v) {
75
- handleValue(getPntType(pn.type), v, 'ty');
76
- handleValue(pn.redirectCount, v, 'rc');
75
+ try {
76
+ handleValue(getPntType(pn.type), v, 'ty');
77
+ handleValue(pn.redirectCount, v, 'rc');
78
+ } catch (e) {
79
+ v.ty = 0;
80
+ v.rc = 0;
81
+ }
77
82
  return v;
78
83
  }
79
84
 
@@ -28,7 +28,7 @@ function hasValidValue(val) {
28
28
  *
29
29
  * @param {Object} [target] the registered target
30
30
  * @param {AggregateInstance} [aggregateInstance] the aggregate instance calling the method
31
- * @returns {{'mfe.id': *, 'mfe.name': String}|{}} returns an empty object if args are not supplied or the aggregate instance is not supporting version 2
31
+ * @returns {Object} returns an empty object if args are not supplied or the aggregate instance is not supporting version 2
32
32
  */
33
33
  function getVersion2Attributes(target, aggregateInstance) {
34
34
  if (aggregateInstance?.harvestEndpointVersion !== 2) return {};
@@ -40,9 +40,9 @@ function getVersion2Attributes(target, aggregateInstance) {
40
40
  };
41
41
  }
42
42
  return {
43
- 'mfe.id': target.id,
44
- 'mfe.name': target.name,
45
- eventSource: target.eventSource,
43
+ 'source.id': target.id,
44
+ 'source.name': target.name,
45
+ 'source.type': target.type,
46
46
  'parent.id': target.parent?.id || containerAgentEntityGuid
47
47
  };
48
48
  }
@@ -13,6 +13,7 @@ const VITAL_NAMES = exports.VITAL_NAMES = {
13
13
  FIRST_CONTENTFUL_PAINT: 'fcp',
14
14
  FIRST_INTERACTION: 'fi',
15
15
  LARGEST_CONTENTFUL_PAINT: 'lcp',
16
+ LOAD_TIME: 'load',
16
17
  CUMULATIVE_LAYOUT_SHIFT: 'cls',
17
18
  INTERACTION_TO_NEXT_PAINT: 'inp',
18
19
  TIME_TO_FIRST_BYTE: 'ttfb'
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.loadTime = void 0;
7
+ var _runtime = require("../constants/runtime");
8
+ var _load = require("../window/load");
9
+ var _constants = require("./constants");
10
+ var _vitalMetric = require("./vital-metric");
11
+ /**
12
+ * Copyright 2020-2025 New Relic, Inc. All rights reserved.
13
+ * SPDX-License-Identifier: Apache-2.0
14
+ */
15
+
16
+ const loadTime = exports.loadTime = new _vitalMetric.VitalMetric(_constants.VITAL_NAMES.LOAD_TIME);
17
+ if (_runtime.isBrowserScope) {
18
+ const perf = _runtime.globalScope.performance;
19
+ const handler = () => {
20
+ if (!loadTime.isValid && perf) {
21
+ loadTime.update({
22
+ value: (0, _runtime.supportsNavTimingL2)() ? perf.getEntriesByType('navigation')?.[0]?.loadEventEnd : perf.timing?.loadEventEnd - _runtime.originTime
23
+ });
24
+ }
25
+ };
26
+ (0, _load.onWindowLoad)(handler, true);
27
+ }
@@ -22,7 +22,7 @@ const timeToFirstByte = exports.timeToFirstByte = new _vitalMetric.VitalMetric(_
22
22
  * - in an iOS browser
23
23
  * - cross-origin iframes specifically in firefox and safari
24
24
  */
25
- if (_runtime.isBrowserScope && typeof PerformanceNavigationTiming !== 'undefined' && !_runtime.isiOS && window === window.parent) {
25
+ if (_runtime.isBrowserScope && (0, _runtime.supportsNavTimingL2)() && !_runtime.isiOS && window === window.parent) {
26
26
  (0, _attribution.onTTFB)(({
27
27
  value,
28
28
  attribution
@@ -8,6 +8,7 @@ exports.onDOMContentLoaded = onDOMContentLoaded;
8
8
  exports.onPopstateChange = onPopstateChange;
9
9
  exports.onWindowLoad = onWindowLoad;
10
10
  var _eventListenerOpts = require("../event-listener/event-listener-opts");
11
+ var _invoke = require("../util/invoke");
11
12
  /**
12
13
  * Copyright 2020-2025 New Relic, Inc. All rights reserved.
13
14
  * SPDX-License-Identifier: Apache-2.0
@@ -16,9 +17,26 @@ var _eventListenerOpts = require("../event-listener/event-listener-opts");
16
17
  function checkState() {
17
18
  return typeof document === 'undefined' || document.readyState === 'complete';
18
19
  }
20
+
21
+ /**
22
+ * Executes a callback when the window 'load' event fires, or immediately if the load event has already occurred.
23
+ * Sets up a backup polling mechanism in the rare case that the browser does not fire the load event when the page has loaded,
24
+ * such as in certain iframe scenarios like ChatGPT connector frames. Cannot use document.readystatechange event here because
25
+ * it blocks back/forward cache in Safari browsers.
26
+ * @param {Function} cb
27
+ * @param {boolean} [useCapture]
28
+ * @returns {void}
29
+ */
19
30
  function onWindowLoad(cb, useCapture) {
20
31
  if (checkState()) return cb();
21
- (0, _eventListenerOpts.windowAddEventListener)('load', cb, useCapture);
32
+ const singleCb = (0, _invoke.single)(cb);
33
+ const poll = setInterval(() => {
34
+ if (checkState()) {
35
+ clearInterval(poll);
36
+ singleCb();
37
+ }
38
+ }, 500);
39
+ (0, _eventListenerOpts.windowAddEventListener)('load', singleCb, useCapture);
22
40
  }
23
41
  function onDOMContentLoaded(cb) {
24
42
  if (checkState()) return cb();
@@ -61,7 +61,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
61
61
  }
62
62
  let addUserAction = () => {/** no-op */};
63
63
  if (_runtime.isBrowserScope && agentRef.init.user_actions.enabled) {
64
- this.#userActionAggregator = new _userActionsAggregator.UserActionsAggregator(agentRef.init.feature_flags.includes('user_frustrations'));
64
+ this.#userActionAggregator = new _userActionsAggregator.UserActionsAggregator();
65
65
  this.harvestOpts.beforeUnload = () => addUserAction?.(this.#userActionAggregator.aggregationEvent);
66
66
  addUserAction = aggregatedUserAction => {
67
67
  try {
@@ -18,14 +18,12 @@ class UserActionsAggregator {
18
18
  /** @type {AggregatedUserAction=} */
19
19
  #aggregationEvent = undefined;
20
20
  #aggregationKey = '';
21
- #ufEnabled = false;
22
21
  #deadClickTimer = undefined;
23
22
  #domObserver = undefined;
24
23
  #errorClickTimer = undefined;
25
- constructor(userFrustrationsEnabled) {
26
- if (userFrustrationsEnabled && (0, _nreum.gosNREUMOriginals)().o.MO) {
24
+ constructor() {
25
+ if ((0, _nreum.gosNREUMOriginals)().o.MO) {
27
26
  this.#domObserver = new MutationObserver(this.isLiveClick.bind(this));
28
- this.#ufEnabled = true;
29
27
  }
30
28
  }
31
29
  get aggregationEvent() {
@@ -56,15 +54,13 @@ class UserActionsAggregator {
56
54
  } else {
57
55
  // return the prev existing one (if there is one)
58
56
  const finishedEvent = this.#aggregationEvent;
59
- if (this.#ufEnabled) {
60
- this.#deadClickCleanup();
61
- this.#errorClickCleanup();
62
- }
57
+ this.#deadClickCleanup();
58
+ this.#errorClickCleanup();
63
59
 
64
60
  // then start new event aggregation
65
61
  this.#aggregationKey = aggregationKey;
66
62
  this.#aggregationEvent = new _aggregatedUserAction.AggregatedUserAction(evt, selectorInfo);
67
- if (this.#ufEnabled && evt.type === 'click' && (selectorInfo.hasButton || selectorInfo.hasLink)) {
63
+ if (evt.type === 'click' && (selectorInfo.hasButton || selectorInfo.hasLink)) {
68
64
  this.#deadClickSetup(this.#aggregationEvent);
69
65
  this.#errorClickSetup();
70
66
  }
@@ -31,7 +31,6 @@ class Instrument extends _instrumentBase.InstrumentBase {
31
31
  constructor(agentRef) {
32
32
  super(agentRef, _constants.FEATURE_NAME);
33
33
  const websocketsEnabled = agentRef.init.feature_flags.includes('websockets');
34
- const ufEnabled = agentRef.init.feature_flags.includes('user_frustrations');
35
34
 
36
35
  /** config values that gate whether the generic events aggregator should be imported at all */
37
36
  const genericEventSourceConfigs = [agentRef.init.page_action.enabled, agentRef.init.performance.capture_marks, agentRef.init.performance.capture_measures, agentRef.init.performance.resources.enabled, agentRef.init.user_actions.enabled, websocketsEnabled];
@@ -43,13 +42,11 @@ class Instrument extends _instrumentBase.InstrumentBase {
43
42
  (0, _register.setupRegisterAPI)(agentRef);
44
43
  (0, _measure.setupMeasureAPI)(agentRef);
45
44
  let historyEE, websocketsEE;
46
- if (_runtime.isBrowserScope && ufEnabled) {
45
+ if (websocketsEnabled) websocketsEE = (0, _wrapWebsocket.wrapWebSocket)(this.ee);
46
+ if (_runtime.isBrowserScope) {
47
47
  (0, _wrapFetch.wrapFetch)(this.ee);
48
48
  (0, _wrapXhr.wrapXhr)(this.ee);
49
49
  historyEE = (0, _wrapHistory.wrapHistory)(this.ee);
50
- }
51
- if (websocketsEnabled) websocketsEE = (0, _wrapWebsocket.wrapWebSocket)(this.ee);
52
- if (_runtime.isBrowserScope) {
53
50
  if (agentRef.init.user_actions.enabled) {
54
51
  _constants.OBSERVED_EVENTS.forEach(eventType => (0, _eventListenerOpts.windowAddEventListener)(eventType, evt => (0, _handle.handle)('ua', [evt], undefined, this.featureName, this.ee), true));
55
52
  _constants.OBSERVED_WINDOW_EVENTS.forEach(eventType => {
@@ -62,36 +59,34 @@ class Instrument extends _instrumentBase.InstrumentBase {
62
59
  }
63
60
  // Capture is not used here so that we don't get element focus/blur events, only the window's as they do not bubble. They are also not cancellable, so no worries about being front of line.
64
61
  );
65
- if (ufEnabled) {
66
- _runtime.globalScope.addEventListener('error', () => {
67
- (0, _handle.handle)('uaErr', [], undefined, _features.FEATURE_NAMES.genericEvents, this.ee);
68
- }, (0, _eventListenerOpts.eventListenerOpts)(false, this.removeOnAbort?.signal));
69
- this.ee.on('open-xhr-start', (args, xhr) => {
70
- if (!isInternalTraffic(args[1])) {
71
- xhr.addEventListener('readystatechange', () => {
72
- if (xhr.readyState === 2) {
73
- // HEADERS_RECEIVED
74
- (0, _handle.handle)('uaXhr', [], undefined, _features.FEATURE_NAMES.genericEvents, this.ee);
75
- }
76
- });
77
- }
78
- });
79
- this.ee.on('fetch-start', fetchArguments => {
80
- if (fetchArguments.length >= 1 && !isInternalTraffic((0, _extractUrl.extractUrl)(fetchArguments[0]))) {
81
- (0, _handle.handle)('uaXhr', [], undefined, _features.FEATURE_NAMES.genericEvents, this.ee);
82
- }
83
- });
84
- function isInternalTraffic(url) {
85
- const parsedUrl = (0, _parseUrl.parseUrl)(url);
86
- return agentRef.beacons.includes(parsedUrl.hostname + ':' + parsedUrl.port);
62
+ _runtime.globalScope.addEventListener('error', () => {
63
+ (0, _handle.handle)('uaErr', [], undefined, _features.FEATURE_NAMES.genericEvents, this.ee);
64
+ }, (0, _eventListenerOpts.eventListenerOpts)(false, this.removeOnAbort?.signal));
65
+ this.ee.on('open-xhr-start', (args, xhr) => {
66
+ if (!isInternalTraffic(args[1])) {
67
+ xhr.addEventListener('readystatechange', () => {
68
+ if (xhr.readyState === 2) {
69
+ // HEADERS_RECEIVED
70
+ (0, _handle.handle)('uaXhr', [], undefined, _features.FEATURE_NAMES.genericEvents, this.ee);
71
+ }
72
+ });
87
73
  }
88
- historyEE.on('pushState-end', navigationChange);
89
- historyEE.on('replaceState-end', navigationChange);
90
- window.addEventListener('hashchange', navigationChange, (0, _eventListenerOpts.eventListenerOpts)(true, this.removeOnAbort?.signal));
91
- window.addEventListener('popstate', navigationChange, (0, _eventListenerOpts.eventListenerOpts)(true, this.removeOnAbort?.signal));
92
- function navigationChange() {
93
- historyEE.emit('navChange');
74
+ });
75
+ this.ee.on('fetch-start', fetchArguments => {
76
+ if (fetchArguments.length >= 1 && !isInternalTraffic((0, _extractUrl.extractUrl)(fetchArguments[0]))) {
77
+ (0, _handle.handle)('uaXhr', [], undefined, _features.FEATURE_NAMES.genericEvents, this.ee);
94
78
  }
79
+ });
80
+ function isInternalTraffic(url) {
81
+ const parsedUrl = (0, _parseUrl.parseUrl)(url);
82
+ return agentRef.beacons.includes(parsedUrl.hostname + ':' + parsedUrl.port);
83
+ }
84
+ historyEE.on('pushState-end', navigationChange);
85
+ historyEE.on('replaceState-end', navigationChange);
86
+ window.addEventListener('hashchange', navigationChange, (0, _eventListenerOpts.eventListenerOpts)(true, this.removeOnAbort?.signal));
87
+ window.addEventListener('popstate', navigationChange, (0, _eventListenerOpts.eventListenerOpts)(true, this.removeOnAbort?.signal));
88
+ function navigationChange() {
89
+ historyEE.emit('navChange');
95
90
  }
96
91
  }
97
92
  if (agentRef.init.performance.resources.enabled && _runtime.globalScope.PerformanceObserver?.supportedEntryTypes.includes('resource')) {
@@ -52,9 +52,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
52
52
  this.timeToFirstByte = Math.max(value, this.timeToFirstByte);
53
53
  this.firstByteToWindowLoad = Math.max(Math.round(navEntry.loadEventEnd - this.timeToFirstByte), this.firstByteToWindowLoad); // our "frontend" duration
54
54
  this.firstByteToDomContent = Math.max(Math.round(navEntry.domContentLoadedEventEnd - this.timeToFirstByte), this.firstByteToDomContent); // our "dom processing" duration
55
-
56
- this.sendRum();
57
55
  });
56
+ setTimeout(this.sendRum.bind(this), 0); // we want to sendRum after ttfb has reported something, but we dont want to wait forever incase TTFB fails to report in niche environments.
58
57
  } else {
59
58
  // worker agent build does not get TTFB values, use default 0 values
60
59
  this.sendRum();
@@ -98,7 +97,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
98
97
  }, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string');
99
98
  }
100
99
  if (_runtime.globalScope.performance) {
101
- if (typeof PerformanceNavigationTiming !== 'undefined') {
100
+ if ((0, _runtime.supportsNavTimingL2)()) {
102
101
  // Navigation Timing level 2 API that replaced PerformanceTiming & PerformanceNavigation
103
102
  const navTimingEntry = _runtime.globalScope?.performance?.getEntriesByType('navigation')?.[0];
104
103
  const perf = {
@@ -107,7 +106,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
107
106
  };
108
107
  queryParameters.perf = (0, _stringify.stringify)(perf);
109
108
  } else if (typeof PerformanceTiming !== 'undefined') {
110
- // Safari pre-15 did not support level 2 timing
109
+ // Modern Safari iFrames and Safari pre-15 do not support level 2 timing.
111
110
  const perf = {
112
111
  timing: (0, _navTiming.addPT)(_runtime.originTime, _runtime.globalScope.performance.timing, {}, true),
113
112
  navigation: (0, _navTiming.addPN)(_runtime.globalScope.performance.navigation, {})
@@ -175,19 +174,19 @@ class Aggregate extends _aggregateBase.AggregateBase {
175
174
  const encoded = textEncoder.encode(value);
176
175
  return acc + encoded.byteLength;
177
176
  }, 0);
178
-
177
+ const BCSError = 'BCS/Error/';
179
178
  // Send SMs about failed RUM request
180
179
  const body = {
181
180
  sm: [{
182
181
  params: {
183
- name: "Browser/Supportability/BCS/Error/".concat(status)
182
+ name: BCSError + status
184
183
  },
185
184
  stats: {
186
185
  c: 1
187
186
  }
188
187
  }, {
189
188
  params: {
190
- name: 'Browser/Supportability/BCS/Error/Dropped/Bytes'
189
+ name: BCSError + 'Dropped/Bytes'
191
190
  },
192
191
  stats: {
193
192
  c: 1,
@@ -195,7 +194,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
195
194
  }
196
195
  }, {
197
196
  params: {
198
- name: 'Browser/Supportability/BCS/Error/Duration/Ms'
197
+ name: BCSError + 'Duration/Ms'
199
198
  },
200
199
  stats: {
201
200
  c: 1,
@@ -15,11 +15,11 @@ var _firstContentfulPaint = require("../../../common/vitals/first-contentful-pai
15
15
  var _firstPaint = require("../../../common/vitals/first-paint");
16
16
  var _interactionToNextPaint = require("../../../common/vitals/interaction-to-next-paint");
17
17
  var _largestContentfulPaint = require("../../../common/vitals/largest-contentful-paint");
18
- var _timeToFirstByte = require("../../../common/vitals/time-to-first-byte");
19
18
  var _pageVisibility = require("../../../common/window/page-visibility");
20
19
  var _constants2 = require("../../../common/vitals/constants");
21
20
  var _runtime = require("../../../common/constants/runtime");
22
21
  var _eventOrigin = require("../../../common/util/event-origin");
22
+ var _loadTime = require("../../../common/vitals/load-time");
23
23
  /**
24
24
  * Copyright 2020-2025 New Relic, Inc. All rights reserved.
25
25
  * SPDX-License-Identifier: Apache-2.0
@@ -47,10 +47,11 @@ class Aggregate extends _aggregateBase.AggregateBase {
47
47
  _firstContentfulPaint.firstContentfulPaint.subscribe(this.#handleVitalMetric);
48
48
  _largestContentfulPaint.largestContentfulPaint.subscribe(this.#handleVitalMetric);
49
49
  _interactionToNextPaint.interactionToNextPaint.subscribe(this.#handleVitalMetric);
50
- _timeToFirstByte.timeToFirstByte.subscribe(({
51
- attrs
50
+ _loadTime.loadTime.subscribe(({
51
+ name,
52
+ value
52
53
  }) => {
53
- this.addTiming('load', Math.round(attrs.navigationEntry.loadEventEnd));
54
+ this.addTiming(name, Math.round(value));
54
55
  });
55
56
  (0, _pageVisibility.subscribeToVisibilityChange)(() => {
56
57
  /* Downstream, the event consumer interprets all timing node value as ms-unit and converts it to seconds via division by 1000. CLS is unitless so this normally is a problem.
@@ -64,7 +64,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
64
64
  // if another page's session entity has expired, or another page has transitioned to off and this one hasn't... we can just abort straight away here
65
65
  if (this.sessionId !== sessionState.value || eventType === 'cross-tab' && sessionState.sessionTraceMode === _constants2.MODE.OFF) this.abort(2);
66
66
  });
67
- if (typeof PerformanceNavigationTiming !== 'undefined') {
67
+ if ((0, _runtime.supportsNavTimingL2)()) {
68
68
  this.traceStorage.storeTiming(_runtime.globalScope.performance?.getEntriesByType?.('navigation')[0]);
69
69
  } else {
70
70
  this.traceStorage.storeTiming(_runtime.globalScope.performance?.timing, true);
@@ -7,7 +7,7 @@ exports.Aggregate = void 0;
7
7
  var _handle = require("../../../common/event-emitter/handle");
8
8
  var _registerHandler = require("../../../common/event-emitter/register-handler");
9
9
  var _invoke = require("../../../common/util/invoke");
10
- var _timeToFirstByte = require("../../../common/vitals/time-to-first-byte");
10
+ var _loadTime = require("../../../common/vitals/load-time");
11
11
  var _features = require("../../../loaders/features/features");
12
12
  var _aggregateBase = require("../../utils/aggregate-base");
13
13
  var _constants = require("../constants");
@@ -37,12 +37,10 @@ class Aggregate extends _aggregateBase.AggregateBase {
37
37
  this.events.add(ixn); // add the iPL ixn to the buffer for harvest
38
38
  this.initialPageLoadInteraction = null;
39
39
  });
40
- _timeToFirstByte.timeToFirstByte.subscribe(({
41
- attrs
40
+ _loadTime.loadTime.subscribe(({
41
+ value: loadEventTime
42
42
  }) => {
43
- const loadEventTime = attrs.navigationEntry.loadEventEnd;
44
43
  this.initialPageLoadInteraction.done(loadEventTime);
45
- // Report metric on the initial page load time
46
44
  this.reportSupportabilityMetric('SoftNav/Interaction/InitialPageLoad/Duration/Ms', Math.round(loadEventTime));
47
45
  });
48
46
  this.latestRouteSetByApi = null;
@@ -18,6 +18,7 @@ var _wrapFetch = require("../../../common/wrap/wrap-fetch");
18
18
  var _wrapHistory = require("../../../common/wrap/wrap-history");
19
19
  var _wrapMutation = require("../../../common/wrap/wrap-mutation");
20
20
  var _interaction = require("../../../loaders/api/interaction");
21
+ var _load = require("../../../common/window/load");
21
22
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } /**
22
23
  * Copyright 2020-2025 New Relic, Inc. All rights reserved.
23
24
  * SPDX-License-Identifier: Apache-2.0
@@ -87,6 +88,13 @@ class Instrument extends _instrumentBase.InstrumentBase {
87
88
  timestamp(jsonpEE, 'cb-start');
88
89
  historyEE.on('pushState-end', trackURLChange);
89
90
  historyEE.on('replaceState-end', trackURLChange);
91
+
92
+ /** niche cases like GPT apps cause no window.load event to fire - which breaks IPLs - so manually force one through the pipe */
93
+ (0, _load.onWindowLoad)(() => {
94
+ eventsEE.emit(FN_START, [[{
95
+ type: 'load'
96
+ }], window], undefined, true);
97
+ });
90
98
  window.addEventListener('hashchange', trackURLChange, (0, _eventListenerOpts.eventListenerOpts)(true, this.removeOnAbort?.signal));
91
99
  window.addEventListener('load', trackURLChange, (0, _eventListenerOpts.eventListenerOpts)(true, this.removeOnAbort?.signal));
92
100
  window.addEventListener('popstate', function () {
@@ -50,7 +50,7 @@ function register(agentRef, target, parent) {
50
50
  const attrs = {};
51
51
  (0, _console.warn)(54, 'newrelic.register');
52
52
  target ||= {};
53
- target.eventSource = 'MicroFrontendBrowserAgent';
53
+ target.type = 'MFE';
54
54
  target.licenseKey ||= agentRef.info.licenseKey; // will inherit the license key from the container agent if not provided for brevity. A future state may dictate that we need different license keys to do different things.
55
55
  target.blocked = false;
56
56
  target.parent = parent || {};
@@ -11,7 +11,7 @@
11
11
  /**
12
12
  * Exposes the version of the agent
13
13
  */
14
- export const VERSION = "1.304.0";
14
+ export const VERSION = "1.305.0-rc.1";
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.304.0";
14
+ export const VERSION = "1.305.0-rc.1";
15
15
 
16
16
  /**
17
17
  * Exposes the build type of the agent
@@ -48,4 +48,5 @@ export const ffVersion = (() => {
48
48
  * according to the browser's local clock.
49
49
  * @type {number}
50
50
  */
51
- export const originTime = Date.now() - now();
51
+ export const originTime = Date.now() - now();
52
+ export const supportsNavTimingL2 = () => typeof PerformanceNavigationTiming !== 'undefined' && globalScope?.performance?.getEntriesByType('navigation')?.length > 0;
@@ -64,8 +64,13 @@ export function addPT(offset, pt, v = {}, isL1Api = false) {
64
64
 
65
65
  // Add Performance Navigation values to the given object
66
66
  export function addPN(pn, v) {
67
- handleValue(getPntType(pn.type), v, 'ty');
68
- handleValue(pn.redirectCount, v, 'rc');
67
+ try {
68
+ handleValue(getPntType(pn.type), v, 'ty');
69
+ handleValue(pn.redirectCount, v, 'rc');
70
+ } catch (e) {
71
+ v.ty = 0;
72
+ v.rc = 0;
73
+ }
69
74
  return v;
70
75
  }
71
76
 
@@ -20,7 +20,7 @@ export function hasValidValue(val) {
20
20
  *
21
21
  * @param {Object} [target] the registered target
22
22
  * @param {AggregateInstance} [aggregateInstance] the aggregate instance calling the method
23
- * @returns {{'mfe.id': *, 'mfe.name': String}|{}} returns an empty object if args are not supplied or the aggregate instance is not supporting version 2
23
+ * @returns {Object} returns an empty object if args are not supplied or the aggregate instance is not supporting version 2
24
24
  */
25
25
  export function getVersion2Attributes(target, aggregateInstance) {
26
26
  if (aggregateInstance?.harvestEndpointVersion !== 2) return {};
@@ -32,9 +32,9 @@ export function getVersion2Attributes(target, aggregateInstance) {
32
32
  };
33
33
  }
34
34
  return {
35
- 'mfe.id': target.id,
36
- 'mfe.name': target.name,
37
- eventSource: target.eventSource,
35
+ 'source.id': target.id,
36
+ 'source.name': target.name,
37
+ 'source.type': target.type,
38
38
  'parent.id': target.parent?.id || containerAgentEntityGuid
39
39
  };
40
40
  }
@@ -7,6 +7,7 @@ export const VITAL_NAMES = {
7
7
  FIRST_CONTENTFUL_PAINT: 'fcp',
8
8
  FIRST_INTERACTION: 'fi',
9
9
  LARGEST_CONTENTFUL_PAINT: 'lcp',
10
+ LOAD_TIME: 'load',
10
11
  CUMULATIVE_LAYOUT_SHIFT: 'cls',
11
12
  INTERACTION_TO_NEXT_PAINT: 'inp',
12
13
  TIME_TO_FIRST_BYTE: 'ttfb'
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Copyright 2020-2025 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import { globalScope, isBrowserScope, originTime, supportsNavTimingL2 } from '../constants/runtime';
6
+ import { onWindowLoad } from '../window/load';
7
+ import { VITAL_NAMES } from './constants';
8
+ import { VitalMetric } from './vital-metric';
9
+ export const loadTime = new VitalMetric(VITAL_NAMES.LOAD_TIME);
10
+ if (isBrowserScope) {
11
+ const perf = globalScope.performance;
12
+ const handler = () => {
13
+ if (!loadTime.isValid && perf) {
14
+ loadTime.update({
15
+ value: supportsNavTimingL2() ? perf.getEntriesByType('navigation')?.[0]?.loadEventEnd : perf.timing?.loadEventEnd - originTime
16
+ });
17
+ }
18
+ };
19
+ onWindowLoad(handler, true);
20
+ }
@@ -2,7 +2,7 @@
2
2
  * Copyright 2020-2025 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
- import { globalScope, isBrowserScope, isiOS, originTime } from '../constants/runtime';
5
+ import { globalScope, isBrowserScope, isiOS, originTime, supportsNavTimingL2 } from '../constants/runtime';
6
6
  import { VITAL_NAMES } from './constants';
7
7
  import { VitalMetric } from './vital-metric';
8
8
  import { onTTFB } from 'web-vitals/attribution';
@@ -15,7 +15,7 @@ export const timeToFirstByte = new VitalMetric(VITAL_NAMES.TIME_TO_FIRST_BYTE);
15
15
  * - in an iOS browser
16
16
  * - cross-origin iframes specifically in firefox and safari
17
17
  */
18
- if (isBrowserScope && typeof PerformanceNavigationTiming !== 'undefined' && !isiOS && window === window.parent) {
18
+ if (isBrowserScope && supportsNavTimingL2() && !isiOS && window === window.parent) {
19
19
  onTTFB(({
20
20
  value,
21
21
  attribution
@@ -3,12 +3,30 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { windowAddEventListener, documentAddEventListener } from '../event-listener/event-listener-opts';
6
+ import { single } from '../util/invoke';
6
7
  export function checkState() {
7
8
  return typeof document === 'undefined' || document.readyState === 'complete';
8
9
  }
10
+
11
+ /**
12
+ * Executes a callback when the window 'load' event fires, or immediately if the load event has already occurred.
13
+ * Sets up a backup polling mechanism in the rare case that the browser does not fire the load event when the page has loaded,
14
+ * such as in certain iframe scenarios like ChatGPT connector frames. Cannot use document.readystatechange event here because
15
+ * it blocks back/forward cache in Safari browsers.
16
+ * @param {Function} cb
17
+ * @param {boolean} [useCapture]
18
+ * @returns {void}
19
+ */
9
20
  export function onWindowLoad(cb, useCapture) {
10
21
  if (checkState()) return cb();
11
- windowAddEventListener('load', cb, useCapture);
22
+ const singleCb = single(cb);
23
+ const poll = setInterval(() => {
24
+ if (checkState()) {
25
+ clearInterval(poll);
26
+ singleCb();
27
+ }
28
+ }, 500);
29
+ windowAddEventListener('load', singleCb, useCapture);
12
30
  }
13
31
  export function onDOMContentLoaded(cb) {
14
32
  if (checkState()) return cb();
@@ -54,7 +54,7 @@ export class Aggregate extends AggregateBase {
54
54
  }
55
55
  let addUserAction = () => {/** no-op */};
56
56
  if (isBrowserScope && agentRef.init.user_actions.enabled) {
57
- this.#userActionAggregator = new UserActionsAggregator(agentRef.init.feature_flags.includes('user_frustrations'));
57
+ this.#userActionAggregator = new UserActionsAggregator();
58
58
  this.harvestOpts.beforeUnload = () => addUserAction?.(this.#userActionAggregator.aggregationEvent);
59
59
  addUserAction = aggregatedUserAction => {
60
60
  try {