@newrelic/browser-agent 1.310.1 → 1.311.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cjs/common/config/configurable.js +2 -2
  3. package/dist/cjs/common/config/runtime.js +8 -2
  4. package/dist/cjs/common/constants/env.cdn.js +1 -1
  5. package/dist/cjs/common/constants/env.npm.js +1 -1
  6. package/dist/cjs/common/constants/runtime.js +17 -4
  7. package/dist/cjs/common/deny-list/deny-list.js +2 -2
  8. package/dist/cjs/common/drain/drain.js +27 -37
  9. package/dist/cjs/common/harvest/harvester.js +1 -3
  10. package/dist/cjs/common/session/session-entity.js +7 -8
  11. package/dist/cjs/common/util/console.js +1 -2
  12. package/dist/cjs/common/util/feature-flags.js +5 -16
  13. package/dist/cjs/common/util/script-tracker.js +22 -4
  14. package/dist/cjs/common/util/stringify.js +22 -7
  15. package/dist/cjs/common/vitals/largest-contentful-paint.js +0 -1
  16. package/dist/cjs/common/vitals/load-time.js +3 -2
  17. package/dist/cjs/common/vitals/time-to-first-byte.js +2 -2
  18. package/dist/cjs/features/jserrors/aggregate/index.js +0 -4
  19. package/dist/cjs/features/page_view_event/aggregate/index.js +3 -3
  20. package/dist/cjs/features/page_view_event/aggregate/initialized-features.js +3 -5
  21. package/dist/cjs/features/page_view_event/instrument/index.js +3 -5
  22. package/dist/cjs/features/page_view_timing/aggregate/index.js +2 -0
  23. package/dist/cjs/features/session_trace/aggregate/index.js +4 -3
  24. package/dist/cjs/features/utils/agent-session.js +3 -4
  25. package/dist/cjs/features/utils/aggregate-base.js +4 -6
  26. package/dist/cjs/features/utils/feature-base.js +6 -7
  27. package/dist/cjs/features/utils/instrument-base.js +7 -8
  28. package/dist/cjs/loaders/api/register-api-types.js +1 -1
  29. package/dist/cjs/loaders/api/register.js +1 -1
  30. package/dist/cjs/loaders/api/sharedHandlers.js +2 -4
  31. package/dist/cjs/loaders/configure/configure.js +23 -21
  32. package/dist/esm/common/config/configurable.js +2 -2
  33. package/dist/esm/common/config/runtime.js +8 -2
  34. package/dist/esm/common/constants/env.cdn.js +1 -1
  35. package/dist/esm/common/constants/env.npm.js +1 -1
  36. package/dist/esm/common/constants/runtime.js +15 -2
  37. package/dist/esm/common/deny-list/deny-list.js +2 -2
  38. package/dist/esm/common/drain/drain.js +27 -36
  39. package/dist/esm/common/harvest/harvester.js +1 -3
  40. package/dist/esm/common/session/session-entity.js +7 -8
  41. package/dist/esm/common/util/console.js +1 -2
  42. package/dist/esm/common/util/feature-flags.js +5 -14
  43. package/dist/esm/common/util/script-tracker.js +22 -5
  44. package/dist/esm/common/util/stringify.js +22 -7
  45. package/dist/esm/common/vitals/largest-contentful-paint.js +0 -1
  46. package/dist/esm/common/vitals/load-time.js +4 -3
  47. package/dist/esm/common/vitals/time-to-first-byte.js +3 -3
  48. package/dist/esm/features/jserrors/aggregate/index.js +0 -4
  49. package/dist/esm/features/page_view_event/aggregate/index.js +4 -4
  50. package/dist/esm/features/page_view_event/aggregate/initialized-features.js +3 -5
  51. package/dist/esm/features/page_view_event/instrument/index.js +3 -5
  52. package/dist/esm/features/page_view_timing/aggregate/index.js +3 -1
  53. package/dist/esm/features/session_trace/aggregate/index.js +5 -4
  54. package/dist/esm/features/utils/agent-session.js +3 -4
  55. package/dist/esm/features/utils/aggregate-base.js +4 -6
  56. package/dist/esm/features/utils/feature-base.js +6 -7
  57. package/dist/esm/features/utils/instrument-base.js +7 -8
  58. package/dist/esm/loaders/api/register-api-types.js +1 -1
  59. package/dist/esm/loaders/api/register.js +1 -1
  60. package/dist/esm/loaders/api/sharedHandlers.js +2 -4
  61. package/dist/esm/loaders/configure/configure.js +24 -21
  62. package/dist/tsconfig.tsbuildinfo +1 -1
  63. package/dist/types/common/config/configurable.d.ts.map +1 -1
  64. package/dist/types/common/config/runtime.d.ts.map +1 -1
  65. package/dist/types/common/constants/runtime.d.ts +1 -1
  66. package/dist/types/common/constants/runtime.d.ts.map +1 -1
  67. package/dist/types/common/drain/drain.d.ts +6 -6
  68. package/dist/types/common/drain/drain.d.ts.map +1 -1
  69. package/dist/types/common/harvest/harvester.d.ts.map +1 -1
  70. package/dist/types/common/session/session-entity.d.ts +2 -2
  71. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  72. package/dist/types/common/util/console.d.ts.map +1 -1
  73. package/dist/types/common/util/feature-flags.d.ts +3 -5
  74. package/dist/types/common/util/feature-flags.d.ts.map +1 -1
  75. package/dist/types/common/util/script-tracker.d.ts +5 -0
  76. package/dist/types/common/util/script-tracker.d.ts.map +1 -1
  77. package/dist/types/common/util/stringify.d.ts.map +1 -1
  78. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  79. package/dist/types/features/page_view_event/aggregate/initialized-features.d.ts +2 -2
  80. package/dist/types/features/page_view_event/aggregate/initialized-features.d.ts.map +1 -1
  81. package/dist/types/features/page_view_event/instrument/index.d.ts +1 -1
  82. package/dist/types/features/page_view_event/instrument/index.d.ts.map +1 -1
  83. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  84. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  85. package/dist/types/features/utils/agent-session.d.ts.map +1 -1
  86. package/dist/types/features/utils/aggregate-base.d.ts +0 -1
  87. package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
  88. package/dist/types/features/utils/feature-base.d.ts +3 -3
  89. package/dist/types/features/utils/feature-base.d.ts.map +1 -1
  90. package/dist/types/features/utils/instrument-base.d.ts +2 -3
  91. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  92. package/dist/types/loaders/api/register-api-types.d.ts +1 -1
  93. package/dist/types/loaders/api/register-api-types.d.ts.map +1 -1
  94. package/dist/types/loaders/api/sharedHandlers.d.ts.map +1 -1
  95. package/dist/types/loaders/configure/configure.d.ts.map +1 -1
  96. package/package.json +1 -1
  97. package/src/common/config/configurable.js +2 -1
  98. package/src/common/config/runtime.js +8 -2
  99. package/src/common/constants/runtime.js +16 -2
  100. package/src/common/deny-list/deny-list.js +2 -2
  101. package/src/common/drain/__mocks__/drain.js +2 -1
  102. package/src/common/drain/drain.js +27 -37
  103. package/src/common/harvest/harvester.js +1 -3
  104. package/src/common/session/session-entity.js +7 -8
  105. package/src/common/util/console.js +1 -2
  106. package/src/common/util/feature-flags.js +5 -17
  107. package/src/common/util/script-tracker.js +23 -5
  108. package/src/common/util/stringify.js +24 -7
  109. package/src/common/vitals/largest-contentful-paint.js +0 -1
  110. package/src/common/vitals/load-time.js +4 -3
  111. package/src/common/vitals/time-to-first-byte.js +3 -3
  112. package/src/features/jserrors/aggregate/index.js +0 -4
  113. package/src/features/page_view_event/aggregate/index.js +4 -4
  114. package/src/features/page_view_event/aggregate/initialized-features.js +3 -5
  115. package/src/features/page_view_event/instrument/index.js +3 -5
  116. package/src/features/page_view_timing/aggregate/index.js +4 -1
  117. package/src/features/session_trace/aggregate/index.js +5 -4
  118. package/src/features/utils/__mocks__/feature-base.js +3 -3
  119. package/src/features/utils/agent-session.js +3 -4
  120. package/src/features/utils/aggregate-base.js +4 -6
  121. package/src/features/utils/feature-base.js +6 -7
  122. package/src/features/utils/instrument-base.js +7 -9
  123. package/src/loaders/api/register-api-types.js +1 -1
  124. package/src/loaders/api/register.js +1 -1
  125. package/src/loaders/api/sharedHandlers.js +2 -4
  126. package/src/loaders/configure/configure.js +30 -26
  127. package/dist/cjs/features/utils/nr1-debugger.js +0 -30
  128. package/dist/esm/features/utils/nr1-debugger.js +0 -23
  129. package/dist/types/features/utils/nr1-debugger.d.ts +0 -2
  130. package/dist/types/features/utils/nr1-debugger.d.ts.map +0 -1
  131. package/src/features/utils/nr1-debugger.js +0 -26
@@ -5,7 +5,6 @@
5
5
  import { generateRandomHexString } from '../ids/unique-id';
6
6
  import { warn } from '../util/console';
7
7
  import { stringify } from '../util/stringify';
8
- import { ee } from '../event-emitter/contextual-ee';
9
8
  import { Timer } from '../timer/timer';
10
9
  import { isBrowserScope } from '../constants/runtime';
11
10
  import { DEFAULT_EXPIRES_MS, DEFAULT_INACTIVE_MS, MODE, PREFIX, SESSION_EVENTS, SESSION_EVENT_TYPES } from './constants';
@@ -39,27 +38,27 @@ const model = {
39
38
  };
40
39
  export class SessionEntity {
41
40
  /**
42
- * Create a self-managing Session Entity. This entity is scoped to the agent identifier which triggered it, allowing for multiple simultaneous session objects to exist.
41
+ * Create a self-managing Session Entity. This entity is scoped to the agent which triggered it, allowing for multiple simultaneous session objects to exist.
43
42
  * There is one "namespace" an agent can store data in LS -- NRBA_{key}. If there are two agents on one page, and they both use the same key, they could overwrite each other since they would both use the same namespace in LS by default.
44
43
  * The value can be overridden in the constructor, but will default to a unique 16 character hex string
45
44
  * expiresMs and inactiveMs are used to "expire" the session, but can be overridden in the constructor. Pass 0 to disable expiration timers.
46
45
  */
47
46
  constructor(opts) {
48
47
  const {
49
- agentIdentifier,
48
+ agentRef,
50
49
  key,
51
50
  storage
52
51
  } = opts;
53
- if (!agentIdentifier || !key || !storage) {
54
- throw new Error("Missing required field(s):".concat(!agentIdentifier ? ' agentID' : '').concat(!key ? ' key' : '').concat(!storage ? ' storage' : ''));
52
+ if (!agentRef || !key || !storage) {
53
+ throw new Error("Missing required field(s):".concat(!agentRef ? ' agentRef' : '').concat(!key ? ' key' : '').concat(!storage ? ' storage' : ''));
55
54
  }
56
- this.agentIdentifier = agentIdentifier;
55
+ this.agentRef = agentRef;
57
56
  this.storage = storage;
58
57
  this.state = {};
59
58
 
60
59
  // key is intended to act as the k=v pair
61
60
  this.key = key;
62
- this.ee = ee.get(agentIdentifier);
61
+ this.ee = agentRef.ee;
63
62
  wrapEvents(this.ee);
64
63
  this.setup(opts);
65
64
 
@@ -247,7 +246,7 @@ export class SessionEntity {
247
246
  this.expiresTimer?.clear?.();
248
247
  delete this.isNew;
249
248
  this.setup({
250
- agentIdentifier: this.agentIdentifier,
249
+ agentRef: this.agentRef,
251
250
  key: this.key,
252
251
  storage: this.storage,
253
252
  expiresMs: this.expiresMs,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
 
@@ -17,7 +17,6 @@ export function warn(code, secondary) {
17
17
  if (typeof console.debug !== 'function') return;
18
18
  console.debug("New Relic Warning: https://github.com/newrelic/newrelic-browser-agent/blob/main/docs/warning-codes.md#".concat(code), secondary);
19
19
  dispatchGlobalEvent({
20
- agentIdentifier: null,
21
20
  drained: null,
22
21
  type: 'data',
23
22
  name: 'warn',
@@ -1,32 +1,23 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { dispatchGlobalEvent } from '../dispatch/global-event';
6
- const sentIds = new Set();
7
-
8
- /** A map of feature flags and their values as provided by the rum call -- scoped by agent ID */
9
- export const activatedFeatures = {};
10
6
 
11
7
  /**
12
- * Sets the activatedFeatures object, dispatches the global loaded event,
8
+ * Sets the activatedFeatures on the agentRef, dispatches the global loaded event,
13
9
  * and emits the rumresp flag to features
14
10
  * @param {{[key:string]:number}} flags key-val pair of flag names and numeric
15
- * @param {string} agentIdentifier agent instance identifier
11
+ * @param {Object} agentRef agent reference
16
12
  * @returns {void}
17
13
  */
18
14
  export function activateFeatures(flags, agentRef) {
19
- const agentIdentifier = agentRef.agentIdentifier;
20
- activatedFeatures[agentIdentifier] ??= {};
21
- if (!flags || typeof flags !== 'object') return;
22
- if (sentIds.has(agentIdentifier)) return;
15
+ if (!flags || typeof flags !== 'object' || !!agentRef.runtime.activatedFeatures) return;
23
16
  agentRef.ee.emit('rumresp', [flags]);
24
- activatedFeatures[agentIdentifier] = flags;
25
- sentIds.add(agentIdentifier);
17
+ agentRef.runtime.activatedFeatures = flags;
26
18
 
27
19
  // let any window level subscribers know that the agent is running, per install docs
28
20
  dispatchGlobalEvent({
29
- agentIdentifier,
30
21
  loaded: true,
31
22
  drained: true,
32
23
  type: 'lifecycle',
@@ -6,14 +6,22 @@
6
6
  import { globalScope } from '../constants/runtime';
7
7
  import { now } from '../timing/now';
8
8
  import { cleanURL } from '../url/clean-url';
9
- import { chrome, gecko } from './browser-stack-matchers';
9
+ import { chrome, chromeEval, gecko } from './browser-stack-matchers';
10
10
 
11
11
  /**
12
12
  * @typedef {import('./register-api-types').RegisterAPITimings} RegisterAPITimings
13
13
  */
14
14
 
15
+ /** export for testing purposes */
16
+ export let thisFile;
17
+ try {
18
+ thisFile = extractUrlsFromStack(getDeepStackTrace()).at(0);
19
+ } catch (err) {
20
+ thisFile = extractUrlsFromStack(err).at(0);
21
+ }
22
+
15
23
  /** @type {(entry: PerformanceEntry) => boolean} - A shared function to determine if a performance entry is a valid script or link resource for evaluation */
16
- const validEntryCriteria = entry => entry.initiatorType === 'script' || entry.initiatorType === 'link' && entry.name.endsWith('.js');
24
+ const validEntryCriteria = entry => entry.initiatorType === 'script' || ['link', 'fetch'].includes(entry.initiatorType) && entry.name.endsWith('.js');
17
25
 
18
26
  /** @type {Set<PerformanceResourceTiming>} - A set of resource timing objects that are "valid" -- see "validEntryCriteria" */
19
27
  const scripts = new Set();
@@ -57,9 +65,15 @@ function extractUrlsFromStack(stack) {
57
65
  const lines = stack.split('\n');
58
66
  for (const line of lines) {
59
67
  // Try gecko format first, then chrome
60
- const parts = line.match(gecko) || line.match(chrome);
68
+ const parts = line.match(gecko) || line.match(chrome) || line.match(chromeEval);
61
69
  if (parts && parts[2]) {
62
70
  urls.add(cleanURL(parts[2]));
71
+ } else {
72
+ // Fallback: match URLs using a generic .js pattern (non-greedy to handle ports and query params)
73
+ const fallbackMatch = line.match(/\(([^)]+\.js):\d+:\d+\)/) || line.match(/^\s+at\s+([^\s(]+\.js):\d+:\d+/);
74
+ if (fallbackMatch && fallbackMatch[1]) {
75
+ urls.add(cleanURL(fallbackMatch[1]));
76
+ }
63
77
  }
64
78
  }
65
79
  return [...urls];
@@ -116,9 +130,11 @@ export function findScriptTimings() {
116
130
  };
117
131
  const stack = getDeepStackTrace();
118
132
  if (!stack) return timings;
119
- const navUrl = globalScope.performance?.getEntriesByType('navigation')?.find(entry => entry.initiatorType === 'navigation')?.name || '';
133
+ const navUrl = globalScope.performance?.getEntriesByType('navigation')?.[0]?.name || '';
120
134
  try {
121
- const mfeScriptUrl = extractUrlsFromStack(stack).at(-1); // array of URLs from the stack of the register API caller, the MFE script should be at the bottom
135
+ const urls = extractUrlsFromStack(stack);
136
+ /** if there is exactly one url, this means the MFE script is running in the same file as the agent. Otherwise, lets strip away the known agent file from any other file lines */
137
+ const mfeScriptUrl = (urls.length > 1 ? urls.filter(line => !thisFile.endsWith(line) && !line.endsWith(thisFile)) : urls).at(0);
122
138
  if (!mfeScriptUrl) return timings;
123
139
  if (navUrl.includes(mfeScriptUrl)) {
124
140
  // this means the stack is indicating that the registration came from an inline script or eval, so we won't find a matching script resource - return early with just the URL
@@ -138,6 +154,7 @@ export function findScriptTimings() {
138
154
  if (wasPreloaded(mfeScriptUrl)) {
139
155
  timings.asset = mfeScriptUrl;
140
156
  timings.type = 'preload';
157
+
141
158
  // wait for a late PO callback... The timings object can be mutated after the fact since we return a pointer and not a cloned object
142
159
  poSubscribers.push({
143
160
  addedAt: now(),
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
 
@@ -7,17 +7,32 @@ import { ee } from '../event-emitter/contextual-ee';
7
7
 
8
8
  /**
9
9
  * Returns a function for use as a replacer parameter in JSON.stringify() to handle circular references.
10
+ * Uses an array to track the current ancestor chain, allowing the same object to appear
11
+ * multiple times in the structure as long as it's not a circular reference.
10
12
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value MDN - Cyclical object value}
11
- * @returns {Function} A function that filters out values it has seen before.
13
+ * @returns {Function} A function that filters out circular references while allowing duplicate references.
12
14
  */
13
15
  const getCircularReplacer = () => {
14
- const seen = new WeakSet();
15
- return (key, value) => {
16
- if (typeof value === 'object' && value !== null) {
17
- if (seen.has(value)) {
16
+ const stack = [];
17
+ return function (key, value) {
18
+ if (stack.length > 0) {
19
+ // Find where we are in the stack
20
+ const thisPos = stack.indexOf(this);
21
+ if (~thisPos) {
22
+ // We're still in the stack, trim it
23
+ stack.splice(thisPos + 1);
24
+ } else {
25
+ // We're not in the stack, add ourselves
26
+ stack.push(this);
27
+ }
28
+
29
+ // Check if value is in the current ancestor chain
30
+ if (~stack.indexOf(value)) {
18
31
  return;
19
32
  }
20
- seen.add(value);
33
+ } else {
34
+ // First call, initialize with root
35
+ stack.push(value);
21
36
  }
22
37
  return value;
23
38
  };
@@ -30,7 +30,6 @@ if (isBrowserScope) {
30
30
  if (lcpEntry.element?.tagName) attrs.elTag = lcpEntry.element.tagName;
31
31
  }
32
32
  if (attribution.element) attrs.element = attribution.element;
33
- if (attribution.navigationEntry) attrs.pageUrl = cleanURL(attribution.navigationEntry.name); // used to ensure the LCP gets the correct URL at harvest time if a soft nav has occurred before page load
34
33
  if (attribution.url) attrs.elUrl = cleanURL(attribution.url);
35
34
  largestContentfulPaint.update({
36
35
  value,
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
- import { globalScope, isBrowserScope, originTime, supportsNavTimingL2 } from '../constants/runtime';
5
+ import { globalScope, isBrowserScope, originTime, getNavigationEntry } from '../constants/runtime';
6
6
  import { onWindowLoad } from '../window/load';
7
7
  import { VITAL_NAMES } from './constants';
8
8
  import { VitalMetric } from './vital-metric';
@@ -11,8 +11,9 @@ if (isBrowserScope) {
11
11
  const perf = globalScope.performance;
12
12
  const handler = () => {
13
13
  if (!loadTime.isValid && perf) {
14
+ const navEntry = getNavigationEntry();
14
15
  loadTime.update({
15
- value: supportsNavTimingL2() ? perf.getEntriesByType('navigation')?.[0]?.loadEventEnd : perf.timing?.loadEventEnd - originTime
16
+ value: navEntry ? navEntry.loadEventEnd : perf.timing?.loadEventEnd - originTime
16
17
  });
17
18
  }
18
19
  };
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
- import { globalScope, isBrowserScope, isiOS, originTime, supportsNavTimingL2 } from '../constants/runtime';
5
+ import { globalScope, isBrowserScope, isiOS, originTime, getNavigationEntry } 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';
@@ -16,7 +16,7 @@ export const timeToFirstByte = new VitalMetric(VITAL_NAMES.TIME_TO_FIRST_BYTE);
16
16
  * - cross-origin iframes specifically in firefox and safari
17
17
  * - onTTFB relies on a truthy `responseStart` value, should ensure that exists before relying on it (seen to be falsy in certain Electron.js cases for instance)
18
18
  */
19
- if (isBrowserScope && supportsNavTimingL2() && !isiOS && window === window.parent) {
19
+ if (isBrowserScope && getNavigationEntry() && !isiOS && window === window.parent) {
20
20
  onTTFB(({
21
21
  value,
22
22
  attribution
@@ -175,10 +175,6 @@ export class Aggregate extends AggregateBase {
175
175
  if (!target) handle('trace-jserror', jsErrorEvent, undefined, FEATURE_NAMES.sessionTrace, this.ee);
176
176
  // still send EE events for other features such as above, but stop this one from aggregating internal data
177
177
  if (this.blocked) return;
178
- if (err.__newrelic?.[this.agentIdentifier]) {
179
- params._interactionId = err.__newrelic[this.agentIdentifier].interactionId;
180
- params._interactionNodeId = err.__newrelic[this.agentIdentifier].interactionNodeId;
181
- }
182
178
  if (err.__newrelic?.socketId) {
183
179
  customAttributes.socketId = err.__newrelic.socketId;
184
180
  }
@@ -2,7 +2,7 @@
2
2
  * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
- import { globalScope, isBrowserScope, originTime, supportsNavTimingL2 } from '../../../common/constants/runtime';
5
+ import { globalScope, isBrowserScope, originTime, getNavigationEntry } from '../../../common/constants/runtime';
6
6
  import { addPT, addPN } from '../../../common/timing/nav-timing';
7
7
  import { stringify } from '../../../common/util/stringify';
8
8
  import { isValid } from '../../../common/config/info';
@@ -75,7 +75,7 @@ export class Aggregate extends AggregateBase {
75
75
  us: info.user,
76
76
  ac: info.account,
77
77
  pr: info.product,
78
- af: getActivatedFeaturesFlags(this.agentIdentifier).join(','),
78
+ af: getActivatedFeaturesFlags(this.agentRef).join(','),
79
79
  ...measures,
80
80
  xx: info.extra,
81
81
  ua: info.userAttributes,
@@ -90,9 +90,9 @@ export class Aggregate extends AggregateBase {
90
90
  }
91
91
  }, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string');
92
92
  if (globalScope.performance) {
93
- if (supportsNavTimingL2()) {
93
+ const navTimingEntry = getNavigationEntry();
94
+ if (navTimingEntry) {
94
95
  // Navigation Timing level 2 API that replaced PerformanceTiming & PerformanceNavigation
95
- const navTimingEntry = globalScope?.performance?.getEntriesByType('navigation')?.[0];
96
96
  const perf = {
97
97
  timing: addPT(originTime, navTimingEntry, {}),
98
98
  navigation: addPN(navTimingEntry, {})
@@ -3,19 +3,17 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { FEATURE_NAMES } from '../../../loaders/features/features';
6
- import { gosNREUM } from '../../../common/window/nreum';
7
6
 
8
7
  /**
9
8
  * Get an array of flags required by downstream (NR UI) based on the features initialized in this agent
10
9
  * (aka what is running on the page).
11
- * @param {String} agentId - the ID of the initialized agent on the page, mapping to the one under the global 'newrelic' object
10
+ * @param {Object} agentRef - the agent reference object
12
11
  * @returns {String[]} Up to 5 short strings corresponding to ingest mapping of features.
13
12
  */
14
- export function getActivatedFeaturesFlags(agentId) {
13
+ export function getActivatedFeaturesFlags(agentRef) {
15
14
  const flagArr = [];
16
- const newrelic = gosNREUM();
17
15
  try {
18
- Object.keys(newrelic.initializedAgents[agentId].features).forEach(featName => {
16
+ Object.keys(agentRef.features || {}).forEach(featName => {
19
17
  switch (featName) {
20
18
  case FEATURE_NAMES.ajax:
21
19
  flagArr.push('xhr');
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { setupSetPageViewNameAPI } from '../../../loaders/api/setPageViewName';
@@ -14,17 +14,16 @@ export class Instrument extends InstrumentBase {
14
14
  super(agentRef, CONSTANTS.FEATURE_NAME);
15
15
 
16
16
  /** setup inspection events for window lifecycle */
17
- this.setupInspectionEvents(agentRef.agentIdentifier);
17
+ this.setupInspectionEvents();
18
18
 
19
19
  /** feature specific APIs */
20
20
  setupSetPageViewNameAPI(agentRef);
21
21
  this.importAggregator(agentRef, () => import(/* webpackChunkName: "page_view_event-aggregate" */'../aggregate'));
22
22
  }
23
- setupInspectionEvents(agentIdentifier) {
23
+ setupInspectionEvents() {
24
24
  const dispatch = (evt, name) => {
25
25
  if (!evt) return;
26
26
  dispatchGlobalEvent({
27
- agentIdentifier,
28
27
  timeStamp: evt.timeStamp,
29
28
  loaded: evt.target.readyState === 'complete',
30
29
  type: 'window',
@@ -43,7 +42,6 @@ export class Instrument extends InstrumentBase {
43
42
  });
44
43
  this.ee.on(SESSION_EVENTS.UPDATE, (_, data) => {
45
44
  dispatchGlobalEvent({
46
- agentIdentifier,
47
45
  type: 'lifecycle',
48
46
  name: 'session',
49
47
  data
@@ -16,10 +16,11 @@ import { interactionToNextPaint } from '../../../common/vitals/interaction-to-ne
16
16
  import { largestContentfulPaint } from '../../../common/vitals/largest-contentful-paint';
17
17
  import { subscribeToVisibilityChange } from '../../../common/window/page-visibility';
18
18
  import { VITAL_NAMES } from '../../../common/vitals/constants';
19
- import { initiallyHidden } from '../../../common/constants/runtime';
19
+ import { initiallyHidden, getNavigationEntry, initialLocation } from '../../../common/constants/runtime';
20
20
  import { eventOrigin } from '../../../common/util/event-origin';
21
21
  import { loadTime } from '../../../common/vitals/load-time';
22
22
  import { webdriverDetected } from '../../../common/util/webdriver-detection';
23
+ import { cleanURL } from '../../../common/url/clean-url';
23
24
  export class Aggregate extends AggregateBase {
24
25
  static featureName = FEATURE_NAME;
25
26
  #handleVitalMetric = ({
@@ -78,6 +79,7 @@ export class Aggregate extends AggregateBase {
78
79
  }
79
80
  addTiming(name, value, attrs) {
80
81
  attrs = attrs || {};
82
+ attrs.pageUrl = cleanURL(getNavigationEntry()?.name || initialLocation);
81
83
  addConnectionAttributes(attrs); // network conditions may differ from the actual for VitalMetrics when they were captured
82
84
 
83
85
  // If cls was set to another value by `onCLS`, then it's supported and is attached onto any timing but is omitted until such time.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { registerHandler } from '../../../common/event-emitter/register-handler';
@@ -7,7 +7,7 @@ import { FEATURE_NAME } from '../constants';
7
7
  import { AggregateBase } from '../../utils/aggregate-base';
8
8
  import { TraceStorage } from './trace/storage';
9
9
  import { obj as encodeObj } from '../../../common/url/encode';
10
- import { globalScope, supportsNavTimingL2 } from '../../../common/constants/runtime';
10
+ import { globalScope, getNavigationEntry } from '../../../common/constants/runtime';
11
11
  import { MODE, SESSION_EVENTS } from '../../../common/session/constants';
12
12
  import { applyFnToProps } from '../../../common/util/traverse';
13
13
  import { cleanURL } from '../../../common/url/clean-url';
@@ -58,8 +58,9 @@ export class Aggregate extends AggregateBase {
58
58
  // 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
59
59
  if (this.sessionId !== sessionState.value || eventType === 'cross-tab' && sessionState.sessionTraceMode === MODE.OFF) this.abort(2);
60
60
  });
61
- if (supportsNavTimingL2()) {
62
- this.traceStorage.storeTiming(globalScope.performance?.getEntriesByType?.('navigation')[0]);
61
+ const navEntry = getNavigationEntry();
62
+ if (navEntry) {
63
+ this.traceStorage.storeTiming(navEntry);
63
64
  } else {
64
65
  this.traceStorage.storeTiming(globalScope.performance?.timing, true);
65
66
  }
@@ -3,7 +3,6 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { drain } from '../../common/drain/drain';
6
- import { ee } from '../../common/event-emitter/contextual-ee';
7
6
  import { registerHandler } from '../../common/event-emitter/register-handler';
8
7
  import { SessionEntity } from '../../common/session/session-entity';
9
8
  import { LocalStorage } from '../../common/storage/local-storage.js';
@@ -20,7 +19,7 @@ export function setupAgentSession(agentRef) {
20
19
 
21
20
  const sessionInit = agentRef.init.session;
22
21
  agentRef.runtime.session = new SessionEntity({
23
- agentIdentifier: agentRef.agentIdentifier,
22
+ agentRef,
24
23
  key: DEFAULT_KEY,
25
24
  storage: new LocalStorage(),
26
25
  expiresMs: sessionInit?.expiresMs,
@@ -42,7 +41,7 @@ export function setupAgentSession(agentRef) {
42
41
 
43
42
  /** track changes to the jsAttributes field over time for aiding with harvest mechanics */
44
43
  agentRef.runtime.jsAttributesMetadata = trackObjectAttributeSize(agentRef.info, 'jsAttributes');
45
- const sharedEE = ee.get(agentRef.agentIdentifier);
44
+ const sharedEE = agentRef.ee;
46
45
 
47
46
  // any calls to newrelic.setCustomAttribute(<persisted>) will need to be added to:
48
47
  // local info.jsAttributes {}
@@ -67,6 +66,6 @@ export function setupAgentSession(agentRef) {
67
66
  consent: accept === undefined ? true : accept
68
67
  });
69
68
  }, 'session', sharedEE);
70
- drain(agentRef.agentIdentifier, 'session');
69
+ drain(agentRef, 'session');
71
70
  return agentRef.runtime.session;
72
71
  }
@@ -7,7 +7,6 @@ import { isValid } from '../../common/config/info';
7
7
  import { configure } from '../../loaders/configure/configure';
8
8
  import { gosCDN } from '../../common/window/nreum';
9
9
  import { drain } from '../../common/drain/drain';
10
- import { activatedFeatures } from '../../common/util/feature-flags';
11
10
  import { Obfuscator } from '../../common/util/obfuscate';
12
11
  import { FEATURE_NAMES } from '../../loaders/features/features';
13
12
  import { Harvester } from '../../common/harvest/harvester';
@@ -23,8 +22,7 @@ export class AggregateBase extends FeatureBase {
23
22
  * @param {string} featureName The name of the feature creating the instance.
24
23
  */
25
24
  constructor(agentRef, featureName) {
26
- super(agentRef.agentIdentifier, featureName);
27
- this.agentRef = agentRef;
25
+ super(agentRef, featureName);
28
26
  this.checkConfiguration(agentRef);
29
27
  this.doOnceForAllAggregate(agentRef);
30
28
 
@@ -108,8 +106,8 @@ export class AggregateBase extends FeatureBase {
108
106
  */
109
107
  waitForFlags(flagNames = []) {
110
108
  const flagsPromise = new Promise((resolve, reject) => {
111
- if (activatedFeatures[this.agentIdentifier]) {
112
- resolve(buildOutput(activatedFeatures[this.agentIdentifier]));
109
+ if (this.agentRef.runtime?.activatedFeatures) {
110
+ resolve(buildOutput(this.agentRef.runtime.activatedFeatures));
113
111
  } else {
114
112
  this.ee.on('rumresp', (resp = {}) => {
115
113
  resolve(buildOutput(resp));
@@ -133,7 +131,7 @@ export class AggregateBase extends FeatureBase {
133
131
  * Stages the feature to be drained
134
132
  */
135
133
  drain() {
136
- drain(this.agentIdentifier, this.featureName);
134
+ drain(this.agentRef, this.featureName);
137
135
  }
138
136
  preHarvestChecks(opts) {
139
137
  return !this.blocked && !this.ee.aborted;
@@ -1,15 +1,14 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
- import { ee } from '../../common/event-emitter/contextual-ee';
6
5
  import { deregisterDrain } from '../../common/drain/drain';
7
6
  export class FeatureBase {
8
- constructor(agentIdentifier, featureName) {
9
- /** @type {string} */
10
- this.agentIdentifier = agentIdentifier;
7
+ constructor(agentRef, featureName) {
8
+ /** @type {Object} */
9
+ this.agentRef = agentRef;
11
10
  /** @type {import('../../common/event-emitter/contextual-ee').ee} */
12
- this.ee = ee.get(agentIdentifier);
11
+ this.ee = agentRef?.ee;
13
12
  /** @type {string} */
14
13
  this.featureName = featureName;
15
14
  /**
@@ -20,6 +19,6 @@ export class FeatureBase {
20
19
  this.blocked = false;
21
20
  }
22
21
  deregisterDrain() {
23
- deregisterDrain(this.agentIdentifier, this.featureName);
22
+ deregisterDrain(this.agentRef, this.featureName);
24
23
  }
25
24
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
 
@@ -28,12 +28,11 @@ import { handle } from '../../common/event-emitter/handle';
28
28
  export class InstrumentBase extends FeatureBase {
29
29
  /**
30
30
  * Instantiate InstrumentBase.
31
- * @param {string} agentIdentifier - The unique ID of the instantiated agent (relative to global scope).
31
+ * @param {Object} agentRef - The agent reference object.
32
32
  * @param {string} featureName - The name of the feature module (used to construct file path).
33
33
  */
34
34
  constructor(agentRef, featureName) {
35
- super(agentRef.agentIdentifier, featureName);
36
- this.agentRef = agentRef;
35
+ super(agentRef, featureName);
37
36
 
38
37
  /** @type {Function | undefined} This should be set by any derived Instrument class if it has things to do when feature fails or is killed. */
39
38
  this.abortHandler = undefined;
@@ -63,13 +62,13 @@ export class InstrumentBase extends FeatureBase {
63
62
  this.ee.on('manual-start-all', single(() => {
64
63
  // register the feature to drain only once the API has been called, it will drain when importAggregator finishes for all the features
65
64
  // called by the api in that cycle
66
- registerDrain(agentRef.agentIdentifier, this.featureName);
65
+ registerDrain(agentRef, this.featureName);
67
66
  resolve();
68
67
  }));
69
68
  });
70
69
  } else {
71
70
  /** if the feature requires opt-in (!auto-start), it will get registered once the api has been called */
72
- registerDrain(agentRef.agentIdentifier, featureName);
71
+ registerDrain(agentRef, featureName);
73
72
  }
74
73
  }
75
74
 
@@ -109,7 +108,7 @@ export class InstrumentBase extends FeatureBase {
109
108
  */
110
109
  try {
111
110
  if (!this.#shouldImportAgg(this.featureName, session, agentRef.init)) {
112
- drain(this.agentIdentifier, this.featureName);
111
+ drain(this.agentRef, this.featureName);
113
112
  this.loadedSuccessfully(false); // aggregate module isn't loaded at all
114
113
  return;
115
114
  }
@@ -123,7 +122,7 @@ export class InstrumentBase extends FeatureBase {
123
122
  warn(34, e);
124
123
  this.abortHandler?.(); // undo any important alterations made to the page
125
124
  // not supported yet but nice to do: "abort" this agent's EE for this feature specifically
126
- drain(this.agentIdentifier, this.featureName, true);
125
+ drain(this.agentRef, this.featureName, true);
127
126
  this.loadedSuccessfully(false);
128
127
  if (this.ee) this.ee.abort();
129
128
  }
@@ -20,7 +20,7 @@
20
20
 
21
21
  /**
22
22
  * @typedef {Object} RegisterAPIConstructor
23
- * @property {string|number} id - The unique id for the registered entity. This will be assigned to any synthesized entities.
23
+ * @property {string} id - The unique id for the registered entity. This will be assigned to any synthesized entities.
24
24
  * @property {string} name - The readable name for the registered entity. This will be assigned to any synthesized entities.
25
25
  * @property {{[key: string]: any}} [tags] - The tags for the registered entity as key-value pairs. This will be assigned to any synthesized entities. Tags are converted to source.* attributes (e.g., {environment: 'production'} becomes source.environment: 'production').
26
26
  * @property {boolean} [isolated] - When true, each registration creates an isolated instance. When false, multiple registrations with the same id and isolated: false will share a single instance, including all custom attributes, ids, names, and metadata. Calling deregister on a shared instance will deregister it for all entities using the instance. Defaults to true.
@@ -88,7 +88,7 @@ function register(agentRef, target, parent) {
88
88
  invalidApiResponse = warning;
89
89
  };
90
90
  function hasValidValue(val) {
91
- return typeof val === 'string' && !!val.trim() && val.trim().length < 501 || typeof val === 'number';
91
+ return typeof val === 'string' && !!val.trim() && val.trim().length < 501;
92
92
  }
93
93
 
94
94
  /** primary cases that can block the register API from working at init time */
@@ -1,12 +1,11 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { dispatchGlobalEvent } from '../../common/dispatch/global-event';
6
6
  import { handle } from '../../common/event-emitter/handle';
7
7
  import { now } from '../../common/timing/now';
8
8
  import { warn } from '../../common/util/console';
9
- import { activatedFeatures } from '../../common/util/feature-flags';
10
9
  import { SUPPORTABILITY_METRIC_CHANNEL } from '../../features/metrics/constants';
11
10
  import { AgentBase } from '../agent-base';
12
11
  import { FEATURE_NAMES } from '../features/features';
@@ -28,8 +27,7 @@ export function setupAPI(name, fn, agent, obj) {
28
27
  api[name] = function () {
29
28
  handle(SUPPORTABILITY_METRIC_CHANNEL, ['API/' + name + '/called'], undefined, FEATURE_NAMES.metrics, agent.ee);
30
29
  dispatchGlobalEvent({
31
- agentIdentifier: agent.agentIdentifier,
32
- drained: !!activatedFeatures?.[agent.agentIdentifier],
30
+ drained: !!agent.runtime?.activatedFeatures,
33
31
  type: 'data',
34
32
  name: 'api',
35
33
  feature: prefix + name,