@newrelic/browser-agent 1.284.0 → 1.284.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 (31) 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/util/event-origin.js +36 -0
  5. package/dist/cjs/common/vitals/interaction-to-next-paint.js +1 -20
  6. package/dist/cjs/features/ajax/instrument/index.js +3 -2
  7. package/dist/cjs/features/page_view_timing/aggregate/index.js +31 -3
  8. package/dist/cjs/features/session_trace/aggregate/trace/storage.js +4 -21
  9. package/dist/esm/common/constants/env.cdn.js +1 -1
  10. package/dist/esm/common/constants/env.npm.js +1 -1
  11. package/dist/esm/common/util/event-origin.js +30 -0
  12. package/dist/esm/common/vitals/interaction-to-next-paint.js +1 -20
  13. package/dist/esm/features/ajax/instrument/index.js +3 -2
  14. package/dist/esm/features/page_view_timing/aggregate/index.js +32 -4
  15. package/dist/esm/features/session_trace/aggregate/trace/storage.js +4 -21
  16. package/dist/tsconfig.tsbuildinfo +1 -1
  17. package/dist/types/common/util/event-origin.d.ts +13 -0
  18. package/dist/types/common/util/event-origin.d.ts.map +1 -0
  19. package/dist/types/common/vitals/interaction-to-next-paint.d.ts +0 -1
  20. package/dist/types/common/vitals/interaction-to-next-paint.d.ts.map +1 -1
  21. package/dist/types/features/ajax/instrument/index.d.ts.map +1 -1
  22. package/dist/types/features/page_view_timing/aggregate/index.d.ts +12 -1
  23. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  24. package/dist/types/features/session_trace/aggregate/trace/storage.d.ts +0 -1
  25. package/dist/types/features/session_trace/aggregate/trace/storage.d.ts.map +1 -1
  26. package/package.json +1 -1
  27. package/src/common/util/event-origin.js +36 -0
  28. package/src/common/vitals/interaction-to-next-paint.js +1 -20
  29. package/src/features/ajax/instrument/index.js +3 -2
  30. package/src/features/page_view_timing/aggregate/index.js +34 -4
  31. package/src/features/session_trace/aggregate/trace/storage.js +4 -28
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.284.1](https://github.com/newrelic/newrelic-browser-agent/compare/v1.284.0...v1.284.1) (2025-03-11)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * Console error on some cross-origin requests without NR CAT header ([#1407](https://github.com/newrelic/newrelic-browser-agent/issues/1407)) ([6660c44](https://github.com/newrelic/newrelic-browser-agent/commit/6660c4455f73bdd90da9946f48ca6c6b377a866f))
12
+ * Obtain FirstInteraction directly from performance API ([#1410](https://github.com/newrelic/newrelic-browser-agent/issues/1410)) ([22ef4ff](https://github.com/newrelic/newrelic-browser-agent/commit/22ef4ffaef72729f99ee615d4851870f51a129f7))
13
+
6
14
  ## [1.284.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.283.2...v1.284.0) (2025-03-04)
7
15
 
8
16
 
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
17
17
  /**
18
18
  * Exposes the version of the agent
19
19
  */
20
- const VERSION = exports.VERSION = "1.284.0";
20
+ const VERSION = exports.VERSION = "1.284.1";
21
21
 
22
22
  /**
23
23
  * Exposes the build type of the agent
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
17
17
  /**
18
18
  * Exposes the version of the agent
19
19
  */
20
- const VERSION = exports.VERSION = "1.284.0";
20
+ const VERSION = exports.VERSION = "1.284.1";
21
21
 
22
22
  /**
23
23
  * Exposes the build type of the agent
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.eventOrigin = eventOrigin;
7
+ /**
8
+ * Copyright 2020-2025 New Relic, Inc. All rights reserved.
9
+ * SPDX-License-Identifier: Apache-2.0
10
+ */
11
+
12
+ /**
13
+ * Returns a string representing the origin of an event target. Used by SessionTrace and PageViewTiming features to assign a "better" target to events
14
+ * @param {*} t The target to derive the origin from.
15
+ * @param {*} [target] A known target to compare to. If supplied, and a derived origin could not be reached, this will be referenced.
16
+ * @param {*} [ee] An event emitter instance to use for context retrieval, which only applies to XMLHttpRequests.
17
+ * @returns {string} The derived origin of the event target.
18
+ */
19
+ function eventOrigin(t, target, ee) {
20
+ let origin = 'unknown';
21
+ if (t && t instanceof XMLHttpRequest) {
22
+ const params = ee.context(t).params;
23
+ if (!params || !params.status || !params.method || !params.host || !params.pathname) return 'xhrOriginMissing';
24
+ origin = params.status + ' ' + params.method + ': ' + params.host + params.pathname;
25
+ } else if (t && typeof t.tagName === 'string') {
26
+ origin = t.tagName.toLowerCase();
27
+ if (t.id) origin += '#' + t.id;
28
+ if (t.className) {
29
+ for (let i = 0; i < t.classList.length; i++) origin += '.' + t.classList[i];
30
+ }
31
+ }
32
+ if (origin === 'unknown') {
33
+ if (typeof target === 'string') origin = target;else if (target === document) origin = 'document';else if (target === window) origin = 'window';else if (target instanceof FileReader) origin = 'FileReader';
34
+ }
35
+ return origin;
36
+ }
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.interactionToNextPaint = exports.firstInteraction = void 0;
6
+ 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");
@@ -14,21 +14,7 @@ var _runtime = require("../constants/runtime");
14
14
  */
15
15
 
16
16
  const interactionToNextPaint = exports.interactionToNextPaint = new _vitalMetric.VitalMetric(_constants.VITAL_NAMES.INTERACTION_TO_NEXT_PAINT);
17
- // Note: First Interaction is a legacy NR timing event, not an actual CWV metric
18
- // ('fi' used to be detected via FID. It is now represented by the first INP)
19
- const firstInteraction = exports.firstInteraction = new _vitalMetric.VitalMetric(_constants.VITAL_NAMES.FIRST_INTERACTION);
20
17
  if (_runtime.isBrowserScope) {
21
- const recordFirstInteraction = attribution => {
22
- firstInteraction.update({
23
- value: attribution.interactionTime,
24
- attrs: {
25
- type: attribution.interactionType,
26
- eventTarget: attribution.interactionTarget,
27
- loadState: attribution.loadState
28
- }
29
- });
30
- };
31
-
32
18
  /* Interaction-to-Next-Paint */
33
19
  (0, _attribution.onINP)(({
34
20
  value,
@@ -54,10 +40,5 @@ if (_runtime.isBrowserScope) {
54
40
  value,
55
41
  attrs
56
42
  });
57
-
58
- // preserve the original behavior where FID is not reported if the page is hidden before the first interaction
59
- if (!firstInteraction.isValid && !_runtime.initiallyHidden) {
60
- recordFirstInteraction(attribution);
61
- }
62
43
  });
63
44
  }
@@ -30,6 +30,7 @@ var handlers = ['load', 'error', 'abort', 'timeout'];
30
30
  var handlersLen = handlers.length;
31
31
  var origRequest = (0, _nreum.gosNREUMOriginals)().o.REQ;
32
32
  var origXHR = (0, _nreum.gosNREUMOriginals)().o.XHR;
33
+ const NR_CAT_HEADER = 'X-NewRelic-App-Data';
33
34
  class Instrument extends _instrumentBase.InstrumentBase {
34
35
  static featureName = _constants.FEATURE_NAME;
35
36
  constructor(agentRef, auto = true) {
@@ -360,8 +361,8 @@ function subscribeToEvents(agentRef, ee, handler, dt) {
360
361
  ctx.params.status = xhr.status;
361
362
  var size = (0, _responseSize.responseSizeFromXhr)(xhr, ctx.lastSize);
362
363
  if (size) ctx.metrics.rxSize = size;
363
- if (ctx.sameOrigin) {
364
- var header = xhr.getResponseHeader('X-NewRelic-App-Data');
364
+ if (ctx.sameOrigin && xhr.getAllResponseHeaders().indexOf(NR_CAT_HEADER) >= 0) {
365
+ var header = xhr.getResponseHeader(NR_CAT_HEADER);
365
366
  if (header) {
366
367
  (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC, ['Ajax/CrossApplicationTracing/Header/Seen'], undefined, _features.FEATURE_NAMES.metrics, ee);
367
368
  ctx.params.cat = header.split(', ').pop();
@@ -18,6 +18,8 @@ var _largestContentfulPaint = require("../../../common/vitals/largest-contentful
18
18
  var _timeToFirstByte = require("../../../common/vitals/time-to-first-byte");
19
19
  var _pageVisibility = require("../../../common/window/page-visibility");
20
20
  var _constants2 = require("../../../common/vitals/constants");
21
+ var _runtime = require("../../../common/constants/runtime");
22
+ var _eventOrigin = require("../../../common/util/event-origin");
21
23
  /**
22
24
  * Copyright 2020-2025 New Relic, Inc. All rights reserved.
23
25
  * SPDX-License-Identifier: Apache-2.0
@@ -35,6 +37,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
35
37
  constructor(agentRef) {
36
38
  super(agentRef, _constants.FEATURE_NAME);
37
39
  this.curSessEndRecorded = false;
40
+ this.firstIxnRecorded = false;
38
41
  (0, _registerHandler.registerHandler)('docHidden', msTimestamp => this.endCurrentSession(msTimestamp), this.featureName, this.ee);
39
42
  // Add the time of _window pagehide event_ firing to the next PVT harvest == NRDB windowUnload attr:
40
43
  (0, _registerHandler.registerHandler)('winPagehide', msTimestamp => this.addTiming('unload', msTimestamp, null), this.featureName, this.ee);
@@ -42,7 +45,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
42
45
  _firstPaint.firstPaint.subscribe(this.#handleVitalMetric);
43
46
  _firstContentfulPaint.firstContentfulPaint.subscribe(this.#handleVitalMetric);
44
47
  _largestContentfulPaint.largestContentfulPaint.subscribe(this.#handleVitalMetric);
45
- _interactionToNextPaint.firstInteraction.subscribe(this.#handleVitalMetric);
46
48
  _interactionToNextPaint.interactionToNextPaint.subscribe(this.#handleVitalMetric);
47
49
  _timeToFirstByte.timeToFirstByte.subscribe(({
48
50
  attrs
@@ -92,12 +94,34 @@ class Aggregate extends _aggregateBase.AggregateBase {
92
94
  if (name !== _constants2.VITAL_NAMES.CUMULATIVE_LAYOUT_SHIFT && _cumulativeLayoutShift.cumulativeLayoutShift.current.value >= 0) {
93
95
  attrs.cls = _cumulativeLayoutShift.cumulativeLayoutShift.current.value;
94
96
  }
95
- this.events.add({
97
+ const timing = {
96
98
  name,
97
99
  value,
98
100
  attrs
99
- });
101
+ };
102
+ this.events.add(timing);
100
103
  (0, _handle.handle)('pvtAdded', [name, value, attrs], undefined, _features.FEATURE_NAMES.sessionTrace, this.ee);
104
+ this.checkForFirstInteraction();
105
+
106
+ // makes testing easier
107
+ return timing;
108
+ }
109
+
110
+ /**
111
+ * Checks the performance API to see if the agent can set a first interaction event value
112
+ * @returns {void}
113
+ */
114
+ checkForFirstInteraction() {
115
+ // preserve the original behavior where FID is not reported if the page is hidden before the first interaction
116
+ if (this.firstIxnRecorded || _runtime.initiallyHidden || !performance) return;
117
+ const firstInput = performance.getEntriesByType('first-input')[0];
118
+ if (!firstInput) return;
119
+ this.firstIxnRecorded = true;
120
+ this.addTiming('fi', firstInput.startTime, {
121
+ type: firstInput.name,
122
+ eventTarget: (0, _eventOrigin.eventOrigin)(firstInput.target),
123
+ loadState: document.readyState
124
+ });
101
125
  }
102
126
  appendGlobalCustomAttributes(timing) {
103
127
  var timingAttributes = timing.attrs || {};
@@ -108,6 +132,10 @@ class Aggregate extends _aggregateBase.AggregateBase {
108
132
  }
109
133
  });
110
134
  }
135
+ preHarvestChecks() {
136
+ this.checkForFirstInteraction();
137
+ return super.preHarvestChecks();
138
+ }
111
139
 
112
140
  // serialize array of timing data
113
141
  serializer(eventBuffer) {
@@ -8,6 +8,7 @@ var _runtime = require("../../../../common/constants/runtime");
8
8
  var _constants = require("../../../../common/session/constants");
9
9
  var _now = require("../../../../common/timing/now");
10
10
  var _parseUrl = require("../../../../common/url/parse-url");
11
+ var _eventOrigin = require("../../../../common/util/event-origin");
11
12
  var _constants2 = require("../../constants");
12
13
  var _node = require("./node");
13
14
  /**
@@ -176,14 +177,14 @@ class TraceStorage {
176
177
  try {
177
178
  // webcomponents-lite.js can trigger an exception on currentEvent.target getter because
178
179
  // it does not check currentEvent.currentTarget before calling getRootNode() on it
179
- evt.o = this.evtOrigin(currentEvent.target, target);
180
+ evt.o = (0, _eventOrigin.eventOrigin)(currentEvent.target, target, this.parent.ee);
180
181
  } catch (e) {
181
- evt.o = this.evtOrigin(null, target);
182
+ evt.o = (0, _eventOrigin.eventOrigin)(null, target, this.parent.ee);
182
183
  }
183
184
  this.storeSTN(evt);
184
185
  }
185
186
  shouldIgnoreEvent(event, target) {
186
- const origin = this.evtOrigin(event.target, target);
187
+ const origin = (0, _eventOrigin.eventOrigin)(event.target, target, this.parent.ee);
187
188
  if (event.type in ignoredEvents.global) return true;
188
189
  if (!!ignoredEvents[origin] && ignoredEvents[origin].ignoreAll) return true;
189
190
  return !!(!!ignoredEvents[origin] && event.type in ignoredEvents[origin]);
@@ -213,24 +214,6 @@ class TraceStorage {
213
214
  return type;
214
215
  }
215
216
  }
216
- evtOrigin(t, target) {
217
- let origin = 'unknown';
218
- if (t && t instanceof XMLHttpRequest) {
219
- const params = this.parent.ee.context(t).params;
220
- if (!params || !params.status || !params.method || !params.host || !params.pathname) return 'xhrOriginMissing';
221
- origin = params.status + ' ' + params.method + ': ' + params.host + params.pathname;
222
- } else if (t && typeof t.tagName === 'string') {
223
- origin = t.tagName.toLowerCase();
224
- if (t.id) origin += '#' + t.id;
225
- if (t.className) {
226
- for (let i = 0; i < t.classList.length; i++) origin += '.' + t.classList[i];
227
- }
228
- }
229
- if (origin === 'unknown') {
230
- if (typeof target === 'string') origin = target;else if (target === document) origin = 'document';else if (target === window) origin = 'window';else if (target instanceof FileReader) origin = 'FileReader';
231
- }
232
- return origin;
233
- }
234
217
 
235
218
  // Tracks when the window history API specified by wrap-history is used.
236
219
  storeHist(path, old, time) {
@@ -11,7 +11,7 @@
11
11
  /**
12
12
  * Exposes the version of the agent
13
13
  */
14
- export const VERSION = "1.284.0";
14
+ export const VERSION = "1.284.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.284.0";
14
+ export const VERSION = "1.284.1";
15
15
 
16
16
  /**
17
17
  * Exposes the build type of the agent
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Copyright 2020-2025 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * Returns a string representing the origin of an event target. Used by SessionTrace and PageViewTiming features to assign a "better" target to events
8
+ * @param {*} t The target to derive the origin from.
9
+ * @param {*} [target] A known target to compare to. If supplied, and a derived origin could not be reached, this will be referenced.
10
+ * @param {*} [ee] An event emitter instance to use for context retrieval, which only applies to XMLHttpRequests.
11
+ * @returns {string} The derived origin of the event target.
12
+ */
13
+ export function eventOrigin(t, target, ee) {
14
+ let origin = 'unknown';
15
+ if (t && t instanceof XMLHttpRequest) {
16
+ const params = ee.context(t).params;
17
+ if (!params || !params.status || !params.method || !params.host || !params.pathname) return 'xhrOriginMissing';
18
+ origin = params.status + ' ' + params.method + ': ' + params.host + params.pathname;
19
+ } else if (t && typeof t.tagName === 'string') {
20
+ origin = t.tagName.toLowerCase();
21
+ if (t.id) origin += '#' + t.id;
22
+ if (t.className) {
23
+ for (let i = 0; i < t.classList.length; i++) origin += '.' + t.classList[i];
24
+ }
25
+ }
26
+ if (origin === 'unknown') {
27
+ if (typeof target === 'string') origin = target;else if (target === document) origin = 'document';else if (target === window) origin = 'window';else if (target instanceof FileReader) origin = 'FileReader';
28
+ }
29
+ return origin;
30
+ }
@@ -5,23 +5,9 @@
5
5
  import { onINP } from 'web-vitals/attribution';
6
6
  import { VitalMetric } from './vital-metric';
7
7
  import { VITAL_NAMES } from './constants';
8
- import { initiallyHidden, isBrowserScope } from '../constants/runtime';
8
+ import { isBrowserScope } from '../constants/runtime';
9
9
  export const interactionToNextPaint = new VitalMetric(VITAL_NAMES.INTERACTION_TO_NEXT_PAINT);
10
- // Note: First Interaction is a legacy NR timing event, not an actual CWV metric
11
- // ('fi' used to be detected via FID. It is now represented by the first INP)
12
- export const firstInteraction = new VitalMetric(VITAL_NAMES.FIRST_INTERACTION);
13
10
  if (isBrowserScope) {
14
- const recordFirstInteraction = attribution => {
15
- firstInteraction.update({
16
- value: attribution.interactionTime,
17
- attrs: {
18
- type: attribution.interactionType,
19
- eventTarget: attribution.interactionTarget,
20
- loadState: attribution.loadState
21
- }
22
- });
23
- };
24
-
25
11
  /* Interaction-to-Next-Paint */
26
12
  onINP(({
27
13
  value,
@@ -47,10 +33,5 @@ if (isBrowserScope) {
47
33
  value,
48
34
  attrs
49
35
  });
50
-
51
- // preserve the original behavior where FID is not reported if the page is hidden before the first interaction
52
- if (!firstInteraction.isValid && !initiallyHidden) {
53
- recordFirstInteraction(attribution);
54
- }
55
36
  });
56
37
  }
@@ -23,6 +23,7 @@ var handlers = ['load', 'error', 'abort', 'timeout'];
23
23
  var handlersLen = handlers.length;
24
24
  var origRequest = gosNREUMOriginals().o.REQ;
25
25
  var origXHR = gosNREUMOriginals().o.XHR;
26
+ const NR_CAT_HEADER = 'X-NewRelic-App-Data';
26
27
  export class Instrument extends InstrumentBase {
27
28
  static featureName = FEATURE_NAME;
28
29
  constructor(agentRef, auto = true) {
@@ -352,8 +353,8 @@ function subscribeToEvents(agentRef, ee, handler, dt) {
352
353
  ctx.params.status = xhr.status;
353
354
  var size = responseSizeFromXhr(xhr, ctx.lastSize);
354
355
  if (size) ctx.metrics.rxSize = size;
355
- if (ctx.sameOrigin) {
356
- var header = xhr.getResponseHeader('X-NewRelic-App-Data');
356
+ if (ctx.sameOrigin && xhr.getAllResponseHeaders().indexOf(NR_CAT_HEADER) >= 0) {
357
+ var header = xhr.getResponseHeader(NR_CAT_HEADER);
357
358
  if (header) {
358
359
  handle(SUPPORTABILITY_METRIC, ['Ajax/CrossApplicationTracing/Header/Seen'], undefined, FEATURE_NAMES.metrics, ee);
359
360
  ctx.params.cat = header.split(', ').pop();
@@ -12,11 +12,13 @@ import { AggregateBase } from '../../utils/aggregate-base';
12
12
  import { cumulativeLayoutShift } from '../../../common/vitals/cumulative-layout-shift';
13
13
  import { firstContentfulPaint } from '../../../common/vitals/first-contentful-paint';
14
14
  import { firstPaint } from '../../../common/vitals/first-paint';
15
- import { firstInteraction, interactionToNextPaint } from '../../../common/vitals/interaction-to-next-paint';
15
+ import { interactionToNextPaint } from '../../../common/vitals/interaction-to-next-paint';
16
16
  import { largestContentfulPaint } from '../../../common/vitals/largest-contentful-paint';
17
17
  import { timeToFirstByte } from '../../../common/vitals/time-to-first-byte';
18
18
  import { subscribeToVisibilityChange } from '../../../common/window/page-visibility';
19
19
  import { VITAL_NAMES } from '../../../common/vitals/constants';
20
+ import { initiallyHidden } from '../../../common/constants/runtime';
21
+ import { eventOrigin } from '../../../common/util/event-origin';
20
22
  export class Aggregate extends AggregateBase {
21
23
  static featureName = FEATURE_NAME;
22
24
  #handleVitalMetric = ({
@@ -29,6 +31,7 @@ export class Aggregate extends AggregateBase {
29
31
  constructor(agentRef) {
30
32
  super(agentRef, FEATURE_NAME);
31
33
  this.curSessEndRecorded = false;
34
+ this.firstIxnRecorded = false;
32
35
  registerHandler('docHidden', msTimestamp => this.endCurrentSession(msTimestamp), this.featureName, this.ee);
33
36
  // Add the time of _window pagehide event_ firing to the next PVT harvest == NRDB windowUnload attr:
34
37
  registerHandler('winPagehide', msTimestamp => this.addTiming('unload', msTimestamp, null), this.featureName, this.ee);
@@ -36,7 +39,6 @@ export class Aggregate extends AggregateBase {
36
39
  firstPaint.subscribe(this.#handleVitalMetric);
37
40
  firstContentfulPaint.subscribe(this.#handleVitalMetric);
38
41
  largestContentfulPaint.subscribe(this.#handleVitalMetric);
39
- firstInteraction.subscribe(this.#handleVitalMetric);
40
42
  interactionToNextPaint.subscribe(this.#handleVitalMetric);
41
43
  timeToFirstByte.subscribe(({
42
44
  attrs
@@ -86,12 +88,34 @@ export class Aggregate extends AggregateBase {
86
88
  if (name !== VITAL_NAMES.CUMULATIVE_LAYOUT_SHIFT && cumulativeLayoutShift.current.value >= 0) {
87
89
  attrs.cls = cumulativeLayoutShift.current.value;
88
90
  }
89
- this.events.add({
91
+ const timing = {
90
92
  name,
91
93
  value,
92
94
  attrs
93
- });
95
+ };
96
+ this.events.add(timing);
94
97
  handle('pvtAdded', [name, value, attrs], undefined, FEATURE_NAMES.sessionTrace, this.ee);
98
+ this.checkForFirstInteraction();
99
+
100
+ // makes testing easier
101
+ return timing;
102
+ }
103
+
104
+ /**
105
+ * Checks the performance API to see if the agent can set a first interaction event value
106
+ * @returns {void}
107
+ */
108
+ checkForFirstInteraction() {
109
+ // preserve the original behavior where FID is not reported if the page is hidden before the first interaction
110
+ if (this.firstIxnRecorded || initiallyHidden || !performance) return;
111
+ const firstInput = performance.getEntriesByType('first-input')[0];
112
+ if (!firstInput) return;
113
+ this.firstIxnRecorded = true;
114
+ this.addTiming('fi', firstInput.startTime, {
115
+ type: firstInput.name,
116
+ eventTarget: eventOrigin(firstInput.target),
117
+ loadState: document.readyState
118
+ });
95
119
  }
96
120
  appendGlobalCustomAttributes(timing) {
97
121
  var timingAttributes = timing.attrs || {};
@@ -102,6 +126,10 @@ export class Aggregate extends AggregateBase {
102
126
  }
103
127
  });
104
128
  }
129
+ preHarvestChecks() {
130
+ this.checkForFirstInteraction();
131
+ return super.preHarvestChecks();
132
+ }
105
133
 
106
134
  // serialize array of timing data
107
135
  serializer(eventBuffer) {
@@ -6,6 +6,7 @@ import { globalScope } from '../../../../common/constants/runtime';
6
6
  import { MODE } from '../../../../common/session/constants';
7
7
  import { now } from '../../../../common/timing/now';
8
8
  import { parseUrl } from '../../../../common/url/parse-url';
9
+ import { eventOrigin } from '../../../../common/util/event-origin';
9
10
  import { MAX_NODES_PER_HARVEST } from '../../constants';
10
11
  import { TraceNode } from './node';
11
12
  const ERROR_MODE_SECONDS_WINDOW = 30 * 1000; // sliding window of nodes to track when simply monitoring (but not harvesting) in error mode
@@ -169,14 +170,14 @@ export class TraceStorage {
169
170
  try {
170
171
  // webcomponents-lite.js can trigger an exception on currentEvent.target getter because
171
172
  // it does not check currentEvent.currentTarget before calling getRootNode() on it
172
- evt.o = this.evtOrigin(currentEvent.target, target);
173
+ evt.o = eventOrigin(currentEvent.target, target, this.parent.ee);
173
174
  } catch (e) {
174
- evt.o = this.evtOrigin(null, target);
175
+ evt.o = eventOrigin(null, target, this.parent.ee);
175
176
  }
176
177
  this.storeSTN(evt);
177
178
  }
178
179
  shouldIgnoreEvent(event, target) {
179
- const origin = this.evtOrigin(event.target, target);
180
+ const origin = eventOrigin(event.target, target, this.parent.ee);
180
181
  if (event.type in ignoredEvents.global) return true;
181
182
  if (!!ignoredEvents[origin] && ignoredEvents[origin].ignoreAll) return true;
182
183
  return !!(!!ignoredEvents[origin] && event.type in ignoredEvents[origin]);
@@ -206,24 +207,6 @@ export class TraceStorage {
206
207
  return type;
207
208
  }
208
209
  }
209
- evtOrigin(t, target) {
210
- let origin = 'unknown';
211
- if (t && t instanceof XMLHttpRequest) {
212
- const params = this.parent.ee.context(t).params;
213
- if (!params || !params.status || !params.method || !params.host || !params.pathname) return 'xhrOriginMissing';
214
- origin = params.status + ' ' + params.method + ': ' + params.host + params.pathname;
215
- } else if (t && typeof t.tagName === 'string') {
216
- origin = t.tagName.toLowerCase();
217
- if (t.id) origin += '#' + t.id;
218
- if (t.className) {
219
- for (let i = 0; i < t.classList.length; i++) origin += '.' + t.classList[i];
220
- }
221
- }
222
- if (origin === 'unknown') {
223
- if (typeof target === 'string') origin = target;else if (target === document) origin = 'document';else if (target === window) origin = 'window';else if (target instanceof FileReader) origin = 'FileReader';
224
- }
225
- return origin;
226
- }
227
210
 
228
211
  // Tracks when the window history API specified by wrap-history is used.
229
212
  storeHist(path, old, time) {
@@ -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.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/runtime.js","../src/common/constants/shared-channel.js","../src/common/context/shared-context.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/types.js","../src/common/ids/bundle-id.js","../src/common/ids/id.js","../src/common/ids/unique-id.js","../src/common/serialize/bel-serializer.js","../src/common/session/constants.js","../src/common/session/session-entity.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/canonicalize-url.js","../src/common/url/clean-url.js","../src/common/url/encode.js","../src/common/url/location.js","../src/common/url/parse-url.js","../src/common/url/protocol.js","../src/common/util/console.js","../src/common/util/data-size.js","../src/common/util/feature-flags.js","../src/common/util/get-or-set.js","../src/common/util/invoke.js","../src/common/util/obfuscate.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/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/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-jsonp.js","../src/common/wrap/wrap-logger.js","../src/common/wrap/wrap-mutation.js","../src/common/wrap/wrap-promise.js","../src/common/wrap/wrap-timer.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/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/index.js","../src/features/metrics/aggregate/websocket-detection.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/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/spa/constants.js","../src/features/spa/index.js","../src/features/spa/aggregate/index.js","../src/features/spa/aggregate/interaction-node.js","../src/features/spa/aggregate/interaction.js","../src/features/spa/aggregate/serializer.js","../src/features/spa/instrument/index.js","../src/features/utils/agent-session.js","../src/features/utils/aggregate-base.js","../src/features/utils/event-buffer.js","../src/features/utils/event-store-manager.js","../src/features/utils/feature-base.js","../src/features/utils/feature-gates.js","../src/features/utils/instrument-base.js","../src/features/utils/lazy-feature-loader.js","../src/features/utils/nr1-debugger.js","../src/loaders/agent-base.js","../src/loaders/agent.js","../src/loaders/browser-agent.js","../src/loaders/micro-agent-base.js","../src/loaders/micro-agent.js","../src/loaders/api/api-methods.js","../src/loaders/api/api.js","../src/loaders/api/apiAsync.js","../src/loaders/api/interaction-types.js","../src/loaders/configure/configure.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.7.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.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/runtime.js","../src/common/constants/shared-channel.js","../src/common/context/shared-context.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/types.js","../src/common/ids/bundle-id.js","../src/common/ids/id.js","../src/common/ids/unique-id.js","../src/common/serialize/bel-serializer.js","../src/common/session/constants.js","../src/common/session/session-entity.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/canonicalize-url.js","../src/common/url/clean-url.js","../src/common/url/encode.js","../src/common/url/location.js","../src/common/url/parse-url.js","../src/common/url/protocol.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/obfuscate.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/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/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-jsonp.js","../src/common/wrap/wrap-logger.js","../src/common/wrap/wrap-mutation.js","../src/common/wrap/wrap-promise.js","../src/common/wrap/wrap-timer.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/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/index.js","../src/features/metrics/aggregate/websocket-detection.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/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/spa/constants.js","../src/features/spa/index.js","../src/features/spa/aggregate/index.js","../src/features/spa/aggregate/interaction-node.js","../src/features/spa/aggregate/interaction.js","../src/features/spa/aggregate/serializer.js","../src/features/spa/instrument/index.js","../src/features/utils/agent-session.js","../src/features/utils/aggregate-base.js","../src/features/utils/event-buffer.js","../src/features/utils/event-store-manager.js","../src/features/utils/feature-base.js","../src/features/utils/feature-gates.js","../src/features/utils/instrument-base.js","../src/features/utils/lazy-feature-loader.js","../src/features/utils/nr1-debugger.js","../src/loaders/agent-base.js","../src/loaders/agent.js","../src/loaders/browser-agent.js","../src/loaders/micro-agent-base.js","../src/loaders/micro-agent.js","../src/loaders/api/api-methods.js","../src/loaders/api/api.js","../src/loaders/api/apiAsync.js","../src/loaders/api/interaction-types.js","../src/loaders/configure/configure.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.7.3"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Copyright 2020-2025 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ /**
6
+ * Returns a string representing the origin of an event target. Used by SessionTrace and PageViewTiming features to assign a "better" target to events
7
+ * @param {*} t The target to derive the origin from.
8
+ * @param {*} [target] A known target to compare to. If supplied, and a derived origin could not be reached, this will be referenced.
9
+ * @param {*} [ee] An event emitter instance to use for context retrieval, which only applies to XMLHttpRequests.
10
+ * @returns {string} The derived origin of the event target.
11
+ */
12
+ export function eventOrigin(t: any, target?: any, ee?: any): string;
13
+ //# sourceMappingURL=event-origin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-origin.d.ts","sourceRoot":"","sources":["../../../../src/common/util/event-origin.js"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;GAMG;AACH,+BALW,GAAC,WACD,GAAC,OACD,GAAC,GACC,MAAM,CAyBlB"}
@@ -1,4 +1,3 @@
1
1
  export const interactionToNextPaint: VitalMetric;
2
- export const firstInteraction: VitalMetric;
3
2
  import { VitalMetric } from './vital-metric';
4
3
  //# sourceMappingURL=interaction-to-next-paint.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"interaction-to-next-paint.d.ts","sourceRoot":"","sources":["../../../../src/common/vitals/interaction-to-next-paint.js"],"names":[],"mappings":"AASA,iDAA4F;AAG5F,2CAA8E;4BAPlD,gBAAgB"}
1
+ {"version":3,"file":"interaction-to-next-paint.d.ts","sourceRoot":"","sources":["../../../../src/common/vitals/interaction-to-next-paint.js"],"names":[],"mappings":"AASA,iDAA4F;4BAJhE,gBAAgB"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/ajax/instrument/index.js"],"names":[],"mappings":"AA4BA;IACE,2BAAiC;IACjC,2CAkCC;IA/BC,OAA0C;IAE1C,8DAAkF;CA8BrF;AAkWD,qCAA8B;+BApZC,6BAA6B;mBAFzC,uBAAuB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/ajax/instrument/index.js"],"names":[],"mappings":"AA6BA;IACE,2BAAiC;IACjC,2CAkCC;IA/BC,OAA0C;IAE1C,8DAAkF;CA8BrF;AAkWD,qCAA8B;+BArZC,6BAA6B;mBAFzC,uBAAuB"}
@@ -2,13 +2,24 @@ export class Aggregate extends AggregateBase {
2
2
  static featureName: string;
3
3
  constructor(agentRef: any);
4
4
  curSessEndRecorded: boolean;
5
+ firstIxnRecorded: boolean;
5
6
  /**
6
7
  * Add the time of _document visibilitychange to hidden_ to the next PVT harvest == NRDB pageHide attr.
7
8
  * @param {number} timestamp
8
9
  */
9
10
  endCurrentSession(timestamp: number): void;
10
- addTiming(name: any, value: any, attrs: any): void;
11
+ addTiming(name: any, value: any, attrs: any): {
12
+ name: any;
13
+ value: any;
14
+ attrs: any;
15
+ };
16
+ /**
17
+ * Checks the performance API to see if the agent can set a first interaction event value
18
+ * @returns {void}
19
+ */
20
+ checkForFirstInteraction(): void;
11
21
  appendGlobalCustomAttributes(timing: any): void;
22
+ preHarvestChecks(): boolean;
12
23
  serializer(eventBuffer: any): string;
13
24
  #private;
14
25
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_timing/aggregate/index.js"],"names":[],"mappings":"AAoBA;IACE,2BAAiC;IAMjC,2BA4BC;IA1BC,4BAA+B;IA4BjC;;;OAGG;IACH,6BAFW,MAAM,QAOhB;IAED,mDAuBC;IAED,gDAUC;IAGD,qCAuBC;;CACF;8BAxH6B,4BAA4B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_timing/aggregate/index.js"],"names":[],"mappings":"AAsBA;IACE,2BAAiC;IAMjC,2BA4BC;IA1BC,4BAA+B;IAC/B,0BAA6B;IA2B/B;;;OAGG;IACH,6BAFW,MAAM,QAOhB;IAED;;;;MA6BC;IAED;;;OAGG;IACH,4BAFa,IAAI,CAahB;IAED,gDAUC;IAED,4BAGC;IAGD,qCAuBC;;CACF;8BAtJ6B,4BAA4B"}
@@ -27,7 +27,6 @@ export class TraceStorage {
27
27
  storeEvent(currentEvent: any, target: any, start: any, end: any): void;
28
28
  shouldIgnoreEvent(event: any, target: any): boolean;
29
29
  evtName(type: any): any;
30
- evtOrigin(t: any, target: any): string;
31
30
  storeHist(path: any, old: any, time: any): void;
32
31
  storeResources(resources: any): void;
33
32
  storeErrorAgg(type: any, name: any, params: any, metrics: any): void;
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../../../../src/features/session_trace/aggregate/trace/storage.js"],"names":[],"mappings":"AA6BA,+HAA+H;AAC/H;IAQE,yBAEC;IATD,kBAAa;IACb,UAAU;IACV,0BAA4B;IAC5B,wBAAmB;IACnB,2BAA4B;IAI1B,YAAoB;IAGtB,gGAAgG;IAChG,yBAcC;IAED;;;;OAIG;IACH,2BAHW,MAAM,GACJ,MAAM,CAsBlB;IAED,oEAAoE;IACpE;;;;MAgBC;IAED,mEA6BC;IAED,oDAEC;IAED,mEAuBC;IAGD,uEAcC;IAED,oDAKC;IAED,wBAwBC;IAED,uCAuBC;IAGD,gDAEC;IAID,qCAaC;IAGD,qEAGC;IAGD,mEAGC;IAID,mBAEC;IAED,aAEC;IAED;;;;;;;QAEC;IAED,cAMC;IAED,mBAEC;IAED,kBAEC;;CACF"}
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../../../../src/features/session_trace/aggregate/trace/storage.js"],"names":[],"mappings":"AA8BA,+HAA+H;AAC/H;IAQE,yBAEC;IATD,kBAAa;IACb,UAAU;IACV,0BAA4B;IAC5B,wBAAmB;IACnB,2BAA4B;IAI1B,YAAoB;IAGtB,gGAAgG;IAChG,yBAcC;IAED;;;;OAIG;IACH,2BAHW,MAAM,GACJ,MAAM,CAsBlB;IAED,oEAAoE;IACpE;;;;MAgBC;IAED,mEA6BC;IAED,oDAEC;IAED,mEAuBC;IAGD,uEAcC;IAED,oDAKC;IAED,wBAwBC;IAGD,gDAEC;IAID,qCAaC;IAGD,qEAGC;IAGD,mEAGC;IAID,mBAEC;IAED,aAEC;IAED;;;;;;;QAEC;IAED,cAMC;IAED,mBAEC;IAED,kBAEC;;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/browser-agent",
3
- "version": "1.284.0",
3
+ "version": "1.284.1",
4
4
  "private": false,
5
5
  "author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
6
6
  "description": "New Relic Browser Agent",
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Copyright 2020-2025 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * Returns a string representing the origin of an event target. Used by SessionTrace and PageViewTiming features to assign a "better" target to events
8
+ * @param {*} t The target to derive the origin from.
9
+ * @param {*} [target] A known target to compare to. If supplied, and a derived origin could not be reached, this will be referenced.
10
+ * @param {*} [ee] An event emitter instance to use for context retrieval, which only applies to XMLHttpRequests.
11
+ * @returns {string} The derived origin of the event target.
12
+ */
13
+ export function eventOrigin (t, target, ee) {
14
+ let origin = 'unknown'
15
+
16
+ if (t && t instanceof XMLHttpRequest) {
17
+ const params = ee.context(t).params
18
+ if (!params || !params.status || !params.method || !params.host || !params.pathname) return 'xhrOriginMissing'
19
+ origin = params.status + ' ' + params.method + ': ' + params.host + params.pathname
20
+ } else if (t && typeof (t.tagName) === 'string') {
21
+ origin = t.tagName.toLowerCase()
22
+ if (t.id) origin += '#' + t.id
23
+ if (t.className) {
24
+ for (let i = 0; i < t.classList.length; i++) origin += '.' + t.classList[i]
25
+ }
26
+ }
27
+
28
+ if (origin === 'unknown') {
29
+ if (typeof target === 'string') origin = target
30
+ else if (target === document) origin = 'document'
31
+ else if (target === window) origin = 'window'
32
+ else if (target instanceof FileReader) origin = 'FileReader'
33
+ }
34
+
35
+ return origin
36
+ }
@@ -5,25 +5,11 @@
5
5
  import { onINP } from 'web-vitals/attribution'
6
6
  import { VitalMetric } from './vital-metric'
7
7
  import { VITAL_NAMES } from './constants'
8
- import { initiallyHidden, isBrowserScope } from '../constants/runtime'
8
+ import { isBrowserScope } from '../constants/runtime'
9
9
 
10
10
  export const interactionToNextPaint = new VitalMetric(VITAL_NAMES.INTERACTION_TO_NEXT_PAINT)
11
- // Note: First Interaction is a legacy NR timing event, not an actual CWV metric
12
- // ('fi' used to be detected via FID. It is now represented by the first INP)
13
- export const firstInteraction = new VitalMetric(VITAL_NAMES.FIRST_INTERACTION)
14
11
 
15
12
  if (isBrowserScope) {
16
- const recordFirstInteraction = (attribution) => {
17
- firstInteraction.update({
18
- value: attribution.interactionTime,
19
- attrs: {
20
- type: attribution.interactionType,
21
- eventTarget: attribution.interactionTarget,
22
- loadState: attribution.loadState
23
- }
24
- })
25
- }
26
-
27
13
  /* Interaction-to-Next-Paint */
28
14
  onINP(({ value, attribution, id }) => {
29
15
  const attrs = {
@@ -40,10 +26,5 @@ if (isBrowserScope) {
40
26
  loadState: attribution.loadState
41
27
  }
42
28
  interactionToNextPaint.update({ value, attrs })
43
-
44
- // preserve the original behavior where FID is not reported if the page is hidden before the first interaction
45
- if (!firstInteraction.isValid && !initiallyHidden) {
46
- recordFirstInteraction(attribution)
47
- }
48
29
  })
49
30
  }
@@ -25,6 +25,7 @@ var handlersLen = handlers.length
25
25
 
26
26
  var origRequest = gosNREUMOriginals().o.REQ
27
27
  var origXHR = gosNREUMOriginals().o.XHR
28
+ const NR_CAT_HEADER = 'X-NewRelic-App-Data'
28
29
 
29
30
  export class Instrument extends InstrumentBase {
30
31
  static featureName = FEATURE_NAME
@@ -392,8 +393,8 @@ function subscribeToEvents (agentRef, ee, handler, dt) {
392
393
  var size = responseSizeFromXhr(xhr, ctx.lastSize)
393
394
  if (size) ctx.metrics.rxSize = size
394
395
 
395
- if (ctx.sameOrigin) {
396
- var header = xhr.getResponseHeader('X-NewRelic-App-Data')
396
+ if (ctx.sameOrigin && xhr.getAllResponseHeaders().indexOf(NR_CAT_HEADER) >= 0) {
397
+ var header = xhr.getResponseHeader(NR_CAT_HEADER)
397
398
  if (header) {
398
399
  handle(SUPPORTABILITY_METRIC, ['Ajax/CrossApplicationTracing/Header/Seen'], undefined, FEATURE_NAMES.metrics, ee)
399
400
  ctx.params.cat = header.split(', ').pop()
@@ -12,11 +12,13 @@ import { AggregateBase } from '../../utils/aggregate-base'
12
12
  import { cumulativeLayoutShift } from '../../../common/vitals/cumulative-layout-shift'
13
13
  import { firstContentfulPaint } from '../../../common/vitals/first-contentful-paint'
14
14
  import { firstPaint } from '../../../common/vitals/first-paint'
15
- import { firstInteraction, interactionToNextPaint } from '../../../common/vitals/interaction-to-next-paint'
15
+ import { interactionToNextPaint } from '../../../common/vitals/interaction-to-next-paint'
16
16
  import { largestContentfulPaint } from '../../../common/vitals/largest-contentful-paint'
17
17
  import { timeToFirstByte } from '../../../common/vitals/time-to-first-byte'
18
18
  import { subscribeToVisibilityChange } from '../../../common/window/page-visibility'
19
19
  import { VITAL_NAMES } from '../../../common/vitals/constants'
20
+ import { initiallyHidden } from '../../../common/constants/runtime'
21
+ import { eventOrigin } from '../../../common/util/event-origin'
20
22
 
21
23
  export class Aggregate extends AggregateBase {
22
24
  static featureName = FEATURE_NAME
@@ -28,6 +30,7 @@ export class Aggregate extends AggregateBase {
28
30
  constructor (agentRef) {
29
31
  super(agentRef, FEATURE_NAME)
30
32
  this.curSessEndRecorded = false
33
+ this.firstIxnRecorded = false
31
34
 
32
35
  registerHandler('docHidden', msTimestamp => this.endCurrentSession(msTimestamp), this.featureName, this.ee)
33
36
  // Add the time of _window pagehide event_ firing to the next PVT harvest == NRDB windowUnload attr:
@@ -37,7 +40,6 @@ export class Aggregate extends AggregateBase {
37
40
  firstPaint.subscribe(this.#handleVitalMetric)
38
41
  firstContentfulPaint.subscribe(this.#handleVitalMetric)
39
42
  largestContentfulPaint.subscribe(this.#handleVitalMetric)
40
- firstInteraction.subscribe(this.#handleVitalMetric)
41
43
  interactionToNextPaint.subscribe(this.#handleVitalMetric)
42
44
  timeToFirstByte.subscribe(({ attrs }) => {
43
45
  this.addTiming('load', Math.round(attrs.navigationEntry.loadEventEnd))
@@ -82,13 +84,36 @@ export class Aggregate extends AggregateBase {
82
84
  attrs.cls = cumulativeLayoutShift.current.value
83
85
  }
84
86
 
85
- this.events.add({
87
+ const timing = {
86
88
  name,
87
89
  value,
88
90
  attrs
89
- })
91
+ }
92
+ this.events.add(timing)
90
93
 
91
94
  handle('pvtAdded', [name, value, attrs], undefined, FEATURE_NAMES.sessionTrace, this.ee)
95
+
96
+ this.checkForFirstInteraction()
97
+
98
+ // makes testing easier
99
+ return timing
100
+ }
101
+
102
+ /**
103
+ * Checks the performance API to see if the agent can set a first interaction event value
104
+ * @returns {void}
105
+ */
106
+ checkForFirstInteraction () {
107
+ // preserve the original behavior where FID is not reported if the page is hidden before the first interaction
108
+ if (this.firstIxnRecorded || initiallyHidden || !performance) return
109
+ const firstInput = performance.getEntriesByType('first-input')[0]
110
+ if (!firstInput) return
111
+ this.firstIxnRecorded = true
112
+ this.addTiming('fi', firstInput.startTime, {
113
+ type: firstInput.name,
114
+ eventTarget: eventOrigin(firstInput.target),
115
+ loadState: document.readyState
116
+ })
92
117
  }
93
118
 
94
119
  appendGlobalCustomAttributes (timing) {
@@ -103,6 +128,11 @@ export class Aggregate extends AggregateBase {
103
128
  })
104
129
  }
105
130
 
131
+ preHarvestChecks () {
132
+ this.checkForFirstInteraction()
133
+ return super.preHarvestChecks()
134
+ }
135
+
106
136
  // serialize array of timing data
107
137
  serializer (eventBuffer) {
108
138
  var addString = getAddStringContext(this.agentIdentifier)
@@ -6,6 +6,7 @@ import { globalScope } from '../../../../common/constants/runtime'
6
6
  import { MODE } from '../../../../common/session/constants'
7
7
  import { now } from '../../../../common/timing/now'
8
8
  import { parseUrl } from '../../../../common/url/parse-url'
9
+ import { eventOrigin } from '../../../../common/util/event-origin'
9
10
  import { MAX_NODES_PER_HARVEST } from '../../constants'
10
11
  import { TraceNode } from './node'
11
12
 
@@ -173,15 +174,15 @@ export class TraceStorage {
173
174
  try {
174
175
  // webcomponents-lite.js can trigger an exception on currentEvent.target getter because
175
176
  // it does not check currentEvent.currentTarget before calling getRootNode() on it
176
- evt.o = this.evtOrigin(currentEvent.target, target)
177
+ evt.o = eventOrigin(currentEvent.target, target, this.parent.ee)
177
178
  } catch (e) {
178
- evt.o = this.evtOrigin(null, target)
179
+ evt.o = eventOrigin(null, target, this.parent.ee)
179
180
  }
180
181
  this.storeSTN(evt)
181
182
  }
182
183
 
183
184
  shouldIgnoreEvent (event, target) {
184
- const origin = this.evtOrigin(event.target, target)
185
+ const origin = eventOrigin(event.target, target, this.parent.ee)
185
186
  if (event.type in ignoredEvents.global) return true
186
187
  if (!!ignoredEvents[origin] && ignoredEvents[origin].ignoreAll) return true
187
188
  return !!(!!ignoredEvents[origin] && event.type in ignoredEvents[origin])
@@ -213,31 +214,6 @@ export class TraceStorage {
213
214
  }
214
215
  }
215
216
 
216
- evtOrigin (t, target) {
217
- let origin = 'unknown'
218
-
219
- if (t && t instanceof XMLHttpRequest) {
220
- const params = this.parent.ee.context(t).params
221
- if (!params || !params.status || !params.method || !params.host || !params.pathname) return 'xhrOriginMissing'
222
- origin = params.status + ' ' + params.method + ': ' + params.host + params.pathname
223
- } else if (t && typeof (t.tagName) === 'string') {
224
- origin = t.tagName.toLowerCase()
225
- if (t.id) origin += '#' + t.id
226
- if (t.className) {
227
- for (let i = 0; i < t.classList.length; i++) origin += '.' + t.classList[i]
228
- }
229
- }
230
-
231
- if (origin === 'unknown') {
232
- if (typeof target === 'string') origin = target
233
- else if (target === document) origin = 'document'
234
- else if (target === window) origin = 'window'
235
- else if (target instanceof FileReader) origin = 'FileReader'
236
- }
237
-
238
- return origin
239
- }
240
-
241
217
  // Tracks when the window history API specified by wrap-history is used.
242
218
  storeHist (path, old, time) {
243
219
  this.storeSTN(new TraceNode('history.pushState', time, time, path, old))