@newrelic/browser-agent 1.308.0 → 1.309.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 (47) hide show
  1. package/CHANGELOG.md +15 -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/drain/drain.js +2 -2
  5. package/dist/cjs/common/session/session-entity.js +1 -1
  6. package/dist/cjs/common/util/webdriver-detection.js +58 -0
  7. package/dist/cjs/common/window/nreum.js +7 -1
  8. package/dist/cjs/features/logging/constants.js +1 -1
  9. package/dist/cjs/features/page_view_event/aggregate/index.js +8 -7
  10. package/dist/cjs/features/page_view_timing/aggregate/index.js +3 -1
  11. package/dist/cjs/features/soft_navigations/aggregate/index.js +2 -0
  12. package/dist/cjs/loaders/api/register-api-types.js +2 -0
  13. package/dist/cjs/loaders/api/register.js +12 -5
  14. package/dist/esm/common/constants/env.cdn.js +1 -1
  15. package/dist/esm/common/constants/env.npm.js +1 -1
  16. package/dist/esm/common/drain/drain.js +2 -2
  17. package/dist/esm/common/session/session-entity.js +1 -1
  18. package/dist/esm/common/util/webdriver-detection.js +53 -0
  19. package/dist/esm/common/window/nreum.js +7 -1
  20. package/dist/esm/features/logging/constants.js +1 -1
  21. package/dist/esm/features/page_view_event/aggregate/index.js +8 -7
  22. package/dist/esm/features/page_view_timing/aggregate/index.js +3 -1
  23. package/dist/esm/features/soft_navigations/aggregate/index.js +2 -0
  24. package/dist/esm/loaders/api/register-api-types.js +2 -0
  25. package/dist/esm/loaders/api/register.js +12 -5
  26. package/dist/tsconfig.tsbuildinfo +1 -1
  27. package/dist/types/common/util/webdriver-detection.d.ts +18 -0
  28. package/dist/types/common/util/webdriver-detection.d.ts.map +1 -0
  29. package/dist/types/common/window/nreum.d.ts.map +1 -1
  30. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  31. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  32. package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -1
  33. package/dist/types/loaders/api/register-api-types.d.ts +9 -0
  34. package/dist/types/loaders/api/register-api-types.d.ts.map +1 -1
  35. package/dist/types/loaders/api/register.d.ts +0 -3
  36. package/dist/types/loaders/api/register.d.ts.map +1 -1
  37. package/package.json +1 -1
  38. package/src/common/drain/drain.js +3 -3
  39. package/src/common/session/session-entity.js +1 -1
  40. package/src/common/util/webdriver-detection.js +57 -0
  41. package/src/common/window/nreum.js +7 -1
  42. package/src/features/logging/constants.js +1 -1
  43. package/src/features/page_view_event/aggregate/index.js +3 -5
  44. package/src/features/page_view_timing/aggregate/index.js +3 -1
  45. package/src/features/soft_navigations/aggregate/index.js +2 -0
  46. package/src/loaders/api/register-api-types.js +2 -0
  47. package/src/loaders/api/register.js +14 -4
package/CHANGELOG.md CHANGED
@@ -3,6 +3,21 @@
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.309.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.308.0...v1.309.0) (2026-02-03)
7
+
8
+
9
+ ### Features
10
+
11
+ * Add WebDriver detection attribute to PageView & PageViewTiming ([#1679](https://github.com/newrelic/newrelic-browser-agent/issues/1679)) ([cd70213](https://github.com/newrelic/newrelic-browser-agent/commit/cd70213f6ef9898d1e40678fad850fbf03150570))
12
+ * Improve MFE tag setting ([#1683](https://github.com/newrelic/newrelic-browser-agent/issues/1683)) ([73c0f4c](https://github.com/newrelic/newrelic-browser-agent/commit/73c0f4cd13b9ae894caefcc071b84a6d2d2ad7d6))
13
+ * Warn when more than one agent is running ([#1686](https://github.com/newrelic/newrelic-browser-agent/issues/1686)) ([0deae4b](https://github.com/newrelic/newrelic-browser-agent/commit/0deae4b1743b3dd7674e0abd729fc9c9aba6582c))
14
+
15
+
16
+ ### Bug Fixes
17
+
18
+ * allow child registrations from blocked parents ([#1677](https://github.com/newrelic/newrelic-browser-agent/issues/1677)) ([f3880e4](https://github.com/newrelic/newrelic-browser-agent/commit/f3880e41a638e68200572190ad0eb1af21f99ad4))
19
+ * Micro agent duplicate handlers ([#1658](https://github.com/newrelic/newrelic-browser-agent/issues/1658)) ([203aa30](https://github.com/newrelic/newrelic-browser-agent/commit/203aa303a939d9de67e62276d457f6ba6378e931))
20
+
6
21
  ## [1.308.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.307.0...v1.308.0) (2026-01-08)
7
22
 
8
23
 
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.RRWEB_PACKAGE_NAME = exports.D
17
17
  /**
18
18
  * Exposes the version of the agent
19
19
  */
20
- const VERSION = exports.VERSION = "1.308.0";
20
+ const VERSION = exports.VERSION = "1.309.0";
21
21
 
22
22
  /**
23
23
  * Exposes the build type of the agent
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.RRWEB_PACKAGE_NAME = exports.D
17
17
  /**
18
18
  * Exposes the version of the agent
19
19
  */
20
- const VERSION = exports.VERSION = "1.308.0";
20
+ const VERSION = exports.VERSION = "1.309.0";
21
21
 
22
22
  /**
23
23
  * Exposes the build type of the agent
@@ -12,7 +12,7 @@ var _registerHandler = require("../event-emitter/register-handler");
12
12
  var _features = require("../../loaders/features/features");
13
13
  var _eventContext = require("../event-emitter/event-context");
14
14
  /**
15
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
15
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
16
16
  * SPDX-License-Identifier: Apache-2.0
17
17
  */
18
18
 
@@ -123,7 +123,7 @@ function drainGroup(agentIdentifier, group, activateGroup = true) {
123
123
  // registration *should* be an array of: [targetEE, eventHandler]
124
124
  // certain browser polyfills of .values and .entries pass the prototype methods into the callback,
125
125
  // which breaks this assumption and throws errors. So we make sure here that we only call .on() if its an actual NR EE
126
- if (registration[0]?.on && registration[0]?.context() instanceof _eventContext.EventContext) registration[0].on(eventType, registration[1]);
126
+ if (registration[0]?.on && registration[0].context() instanceof _eventContext.EventContext && !registration[0].listeners(eventType).includes(registration[1])) registration[0].on(eventType, registration[1]);
127
127
  });
128
128
  });
129
129
  }
@@ -20,7 +20,7 @@ var _features = require("../../loaders/features/features");
20
20
  var _eventListenerOpts = require("../event-listener/event-listener-opts");
21
21
  var _constants3 = require("../../features/logging/constants");
22
22
  /**
23
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
23
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
24
24
  * SPDX-License-Identifier: Apache-2.0
25
25
  */
26
26
 
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.webdriverDetected = void 0;
7
+ var _runtime = require("../constants/runtime");
8
+ /**
9
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
10
+ * SPDX-License-Identifier: Apache-2.0
11
+ */
12
+
13
+ /**
14
+ * Detects if the page is being controlled by WebDriver or automation tools derived from it.
15
+ * Checks for common indicators including:
16
+ * - navigator.webdriver property (standard WebDriver flag)
17
+ * - window.document.__webdriver_evaluate (WebDriver internal property)
18
+ * - window.document.__selenium_unwrapped (Selenium property)
19
+ * - window.document.__driver_evaluate (WebDriver internal property)
20
+ * - window.document.__webdriver_script_function (WebDriver internal property)
21
+ * - window.callPhantom (PhantomJS property)
22
+ * - window._phantom (PhantomJS property)
23
+ * - window.__nightmare (Nightmare.js property)
24
+ * - window.domAutomation (Chrome automation property)
25
+ * - window.domAutomationController (Chrome automation property)
26
+ *
27
+ * @type {boolean}
28
+ */
29
+ const webdriverDetected = exports.webdriverDetected = (() => {
30
+ try {
31
+ // Standard WebDriver flag
32
+ if (typeof navigator !== 'undefined' && navigator.webdriver === true) {
33
+ return true;
34
+ }
35
+
36
+ // Check for various automation-related properties
37
+ if (_runtime.isBrowserScope) {
38
+ // WebDriver internal properties
39
+ if (document.__webdriver_evaluate || document.__selenium_unwrapped || document.__driver_evaluate || document.__webdriver_script_function) {
40
+ return true;
41
+ }
42
+
43
+ // PhantomJS detection
44
+ if (window.callPhantom || window._phantom) {
45
+ return true;
46
+ }
47
+
48
+ // Nightmare.js detection
49
+ if (window.__nightmare) {
50
+ return true;
51
+ }
52
+ }
53
+ return false;
54
+ } catch (err) {
55
+ // If any errors occur during detection, assume not automated
56
+ return false;
57
+ }
58
+ })();
@@ -16,9 +16,10 @@ exports.gosNREUMOriginals = gosNREUMOriginals;
16
16
  exports.setNREUMInitializedAgent = setNREUMInitializedAgent;
17
17
  var _runtime = require("../constants/runtime");
18
18
  var _now = require("../timing/now");
19
+ var _console = require("../util/console");
19
20
  var _monkeyPatched = require("../util/monkey-patched");
20
21
  /**
21
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
22
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
22
23
  * SPDX-License-Identifier: Apache-2.0
23
24
  */
24
25
 
@@ -87,6 +88,11 @@ function setNREUMInitializedAgent(id, newAgentInstance) {
87
88
  date: new Date()
88
89
  };
89
90
  nr.initializedAgents[id] = newAgentInstance;
91
+
92
+ // Warn if using more than one agent, but only once per agent load
93
+ if (Object.keys(nr.initializedAgents).length === 2) {
94
+ (0, _console.warn)(69);
95
+ }
90
96
  }
91
97
 
92
98
  /**
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.LOG_LEVELS = exports.LOGGING_MODE = exports.LOGGING_EVENT_EMITTER_CHANNEL = exports.FEATURE_NAME = void 0;
7
7
  var _features = require("../../loaders/features/features");
8
8
  /**
9
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
9
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
10
10
  * SPDX-License-Identifier: Apache-2.0
11
11
  */
12
12
 
@@ -22,9 +22,10 @@ var _traverse = require("../../../common/util/traverse");
22
22
  var _harvester = require("../../../common/harvest/harvester");
23
23
  var _features = require("../../../loaders/features/features");
24
24
  var _submitData = require("../../../common/util/submit-data");
25
+ var _webdriverDetection = require("../../../common/util/webdriver-detection");
25
26
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
26
27
  /**
27
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
28
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
28
29
  * SPDX-License-Identifier: Apache-2.0
29
30
  */
30
31
 
@@ -90,12 +91,12 @@ class Aggregate extends _aggregateBase.AggregateBase {
90
91
  };
91
92
  if (this.agentRef.runtime.session) queryParameters.fsh = Number(this.agentRef.runtime.session.isNew); // "first session harvest" aka RUM request or PageView event of a session
92
93
 
93
- let body;
94
- if (typeof customAttributes === 'object' && Object.keys(customAttributes).length > 0) {
95
- body = (0, _traverse.applyFnToProps)({
96
- ja: customAttributes
97
- }, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string');
98
- }
94
+ let body = (0, _traverse.applyFnToProps)({
95
+ ja: {
96
+ ...customAttributes,
97
+ webdriverDetected: _webdriverDetection.webdriverDetected
98
+ }
99
+ }, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string');
99
100
  if (_runtime.globalScope.performance) {
100
101
  if ((0, _runtime.supportsNavTimingL2)()) {
101
102
  // Navigation Timing level 2 API that replaced PerformanceTiming & PerformanceNavigation
@@ -20,8 +20,9 @@ var _constants2 = require("../../../common/vitals/constants");
20
20
  var _runtime = require("../../../common/constants/runtime");
21
21
  var _eventOrigin = require("../../../common/util/event-origin");
22
22
  var _loadTime = require("../../../common/vitals/load-time");
23
+ var _webdriverDetection = require("../../../common/util/webdriver-detection");
23
24
  /**
24
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
25
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
25
26
  * SPDX-License-Identifier: Apache-2.0
26
27
  */
27
28
 
@@ -133,6 +134,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
133
134
  timingAttributes[key] = val;
134
135
  }
135
136
  });
137
+ timingAttributes.webdriverDetected = _webdriverDetection.webdriverDetected;
136
138
  }
137
139
  preHarvestChecks() {
138
140
  this.checkForFirstInteraction();
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.Aggregate = void 0;
7
7
  var _handle = require("../../../common/event-emitter/handle");
8
8
  var _registerHandler = require("../../../common/event-emitter/register-handler");
9
+ var _webdriverDetection = require("../../../common/util/webdriver-detection");
9
10
  var _loadTime = require("../../../common/vitals/load-time");
10
11
  var _features = require("../../../loaders/features/features");
11
12
  var _aggregateBase = require("../../utils/aggregate-base");
@@ -31,6 +32,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
31
32
  this.initialPageLoadInteraction.onDone.push(() => {
32
33
  // this ensures the .end() method also works with iPL
33
34
  if (agentRef.runtime.session?.isNew) this.initialPageLoadInteraction.customAttributes.isFirstOfSession = true; // mark the hard page load as first of its session
35
+ this.initialPageLoadInteraction.customAttributes.webdriverDetected = _webdriverDetection.webdriverDetected;
34
36
  this.initialPageLoadInteraction.forceSave = true; // unless forcibly ignored, iPL always finish by default
35
37
  const ixn = this.initialPageLoadInteraction;
36
38
  this.events.add(ixn); // add the iPL ixn to the buffer for harvest
@@ -26,6 +26,7 @@ exports.default = void 0;
26
26
  * @typedef {Object} RegisterAPIConstructor
27
27
  * @property {string|number} id - The unique id for the registered entity. This will be assigned to any synthesized entities.
28
28
  * @property {string} name - The readable name for the registered entity. This will be assigned to any synthesized entities.
29
+ * @property {{[key: string]: any}} [tags] - The tags for the registered entity as key-value pairs. This will be assigned to any synthesized entities. Tags are converted to source.* attributes (e.g., {environment: 'production'} becomes source.environment: 'production').
29
30
  * @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.
30
31
  * @property {string} [parentId] - The parentId for the registered entity. If none was supplied, it will assume the entity guid from the main agent.
31
32
  */
@@ -36,6 +37,7 @@ exports.default = void 0;
36
37
  * @property {string} [target.licenseKey] - The license key for the registered entity. If none was supplied, it will assume the license key from the main agent.
37
38
  * @property {string} target.id - The ID for the registered entity.
38
39
  * @property {string} target.name - The name returned for the registered entity.
40
+ * @property {{[key: string]: any}} [target.tags] - The tags for the registered entity as key-value pairs.
39
41
  * @property {string} [target.parentId] - The parentId for the registered entity. If none was supplied, it will assume the entity guid from the main agent.
40
42
  * @property {boolean} [target.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.
41
43
  */
@@ -27,6 +27,8 @@ var _recordCustomEvent = require("./recordCustomEvent");
27
27
  * @typedef {import('./register-api-types').RegisterAPI} RegisterAPI
28
28
  */
29
29
 
30
+ const PROTECTED_KEYS = ['name', 'id', 'type'];
31
+
30
32
  /**
31
33
  * @experimental
32
34
  * IMPORTANT: This feature is being developed for use internally and is not in a public-facing production-ready state.
@@ -53,10 +55,14 @@ function register(agentRef, target, parent) {
53
55
  target.licenseKey ||= agentRef.info.licenseKey; // will inherit the license key from the container agent if not provided for brevity. A future state may dictate that we need different license keys to do different things.
54
56
  target.blocked = false;
55
57
  target.parent = parent || {};
56
- if (!Array.isArray(target.tags)) target.tags = [];
58
+ if (typeof target.tags !== 'object' || target.tags === null || Array.isArray(target.tags)) target.tags = {};
57
59
  const attrs = {};
58
- target.tags.forEach(tag => {
59
- if (tag !== 'name' && tag !== 'id') attrs["source.".concat(tag)] = true;
60
+
61
+ // Process tags object and add to attrs, excluding protected keys
62
+ Object.entries(target.tags).forEach(([key, value]) => {
63
+ if (!PROTECTED_KEYS.includes(key)) {
64
+ attrs["source.".concat(key)] = value;
65
+ }
60
66
  });
61
67
  target.isolated ??= true;
62
68
 
@@ -167,12 +173,13 @@ function register(agentRef, target, parent) {
167
173
  * @returns
168
174
  */
169
175
  const report = (methodToCall, args, target) => {
170
- if (isBlocked()) return;
176
+ /** Even if we are blocked, if registering we should still return a child register API so nested API calls do not throw errors */
177
+ if (isBlocked() && methodToCall !== register) return;
171
178
  /** set the timestamp before the async part of waiting for the rum response for better accuracy */
172
179
  const timestamp = (0, _now.now)();
173
180
  (0, _handle.handle)(_constants.SUPPORTABILITY_METRIC_CHANNEL, ["API/register/".concat(methodToCall.name, "/called")], undefined, _features.FEATURE_NAMES.metrics, agentRef.ee);
174
181
  try {
175
- const shouldDuplicate = agentRef.init.api.duplicate_registered_data && methodToCall.name !== 'register';
182
+ const shouldDuplicate = agentRef.init.api.duplicate_registered_data && methodToCall !== register;
176
183
  if (shouldDuplicate) {
177
184
  let duplicatedArgs = args;
178
185
  if (args[1] instanceof Object) {
@@ -11,7 +11,7 @@
11
11
  /**
12
12
  * Exposes the version of the agent
13
13
  */
14
- export const VERSION = "1.308.0";
14
+ export const VERSION = "1.309.0";
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.308.0";
14
+ export const VERSION = "1.309.0";
15
15
 
16
16
  /**
17
17
  * Exposes the build type of the agent
@@ -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
 
@@ -115,7 +115,7 @@ function drainGroup(agentIdentifier, group, activateGroup = true) {
115
115
  // registration *should* be an array of: [targetEE, eventHandler]
116
116
  // certain browser polyfills of .values and .entries pass the prototype methods into the callback,
117
117
  // which breaks this assumption and throws errors. So we make sure here that we only call .on() if its an actual NR EE
118
- if (registration[0]?.on && registration[0]?.context() instanceof EventContext) registration[0].on(eventType, registration[1]);
118
+ if (registration[0]?.on && registration[0].context() instanceof EventContext && !registration[0].listeners(eventType).includes(registration[1])) registration[0].on(eventType, registration[1]);
119
119
  });
120
120
  });
121
121
  }
@@ -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 { generateRandomHexString } from '../ids/unique-id';
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ import { isBrowserScope } from '../constants/runtime';
7
+
8
+ /**
9
+ * Detects if the page is being controlled by WebDriver or automation tools derived from it.
10
+ * Checks for common indicators including:
11
+ * - navigator.webdriver property (standard WebDriver flag)
12
+ * - window.document.__webdriver_evaluate (WebDriver internal property)
13
+ * - window.document.__selenium_unwrapped (Selenium property)
14
+ * - window.document.__driver_evaluate (WebDriver internal property)
15
+ * - window.document.__webdriver_script_function (WebDriver internal property)
16
+ * - window.callPhantom (PhantomJS property)
17
+ * - window._phantom (PhantomJS property)
18
+ * - window.__nightmare (Nightmare.js property)
19
+ * - window.domAutomation (Chrome automation property)
20
+ * - window.domAutomationController (Chrome automation property)
21
+ *
22
+ * @type {boolean}
23
+ */
24
+ export const webdriverDetected = (() => {
25
+ try {
26
+ // Standard WebDriver flag
27
+ if (typeof navigator !== 'undefined' && navigator.webdriver === true) {
28
+ return true;
29
+ }
30
+
31
+ // Check for various automation-related properties
32
+ if (isBrowserScope) {
33
+ // WebDriver internal properties
34
+ if (document.__webdriver_evaluate || document.__selenium_unwrapped || document.__driver_evaluate || document.__webdriver_script_function) {
35
+ return true;
36
+ }
37
+
38
+ // PhantomJS detection
39
+ if (window.callPhantom || window._phantom) {
40
+ return true;
41
+ }
42
+
43
+ // Nightmare.js detection
44
+ if (window.__nightmare) {
45
+ return true;
46
+ }
47
+ }
48
+ return false;
49
+ } catch (err) {
50
+ // If any errors occur during detection, assume not automated
51
+ return false;
52
+ }
53
+ })();
@@ -1,9 +1,10 @@
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 { globalScope } from '../constants/runtime';
6
6
  import { now } from '../timing/now';
7
+ import { warn } from '../util/console';
7
8
  import { isNative } from '../util/monkey-patched';
8
9
  export const defaults = {
9
10
  beacon: 'bam.nr-data.net',
@@ -70,6 +71,11 @@ export function setNREUMInitializedAgent(id, newAgentInstance) {
70
71
  date: new Date()
71
72
  };
72
73
  nr.initializedAgents[id] = newAgentInstance;
74
+
75
+ // Warn if using more than one agent, but only once per agent load
76
+ if (Object.keys(nr.initializedAgents).length === 2) {
77
+ warn(69);
78
+ }
73
79
  }
74
80
 
75
81
  /**
@@ -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 { FEATURE_NAMES } from '../../loaders/features/features';
@@ -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 { globalScope, isBrowserScope, originTime, supportsNavTimingL2 } from '../../../common/constants/runtime';
@@ -20,6 +20,7 @@ import { applyFnToProps } from '../../../common/util/traverse';
20
20
  import { send } from '../../../common/harvest/harvester';
21
21
  import { FEATURE_NAMES, FEATURE_TO_ENDPOINT } from '../../../loaders/features/features';
22
22
  import { getSubmitMethod } from '../../../common/util/submit-data';
23
+ import { webdriverDetected } from '../../../common/util/webdriver-detection';
23
24
  export class Aggregate extends AggregateBase {
24
25
  static featureName = CONSTANTS.FEATURE_NAME;
25
26
  constructor(agentRef) {
@@ -82,12 +83,12 @@ export class Aggregate extends AggregateBase {
82
83
  };
83
84
  if (this.agentRef.runtime.session) queryParameters.fsh = Number(this.agentRef.runtime.session.isNew); // "first session harvest" aka RUM request or PageView event of a session
84
85
 
85
- let body;
86
- if (typeof customAttributes === 'object' && Object.keys(customAttributes).length > 0) {
87
- body = applyFnToProps({
88
- ja: customAttributes
89
- }, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string');
90
- }
86
+ let body = applyFnToProps({
87
+ ja: {
88
+ ...customAttributes,
89
+ webdriverDetected
90
+ }
91
+ }, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string');
91
92
  if (globalScope.performance) {
92
93
  if (supportsNavTimingL2()) {
93
94
  // Navigation Timing level 2 API that replaced PerformanceTiming & PerformanceNavigation
@@ -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
 
@@ -19,6 +19,7 @@ import { VITAL_NAMES } from '../../../common/vitals/constants';
19
19
  import { initiallyHidden } from '../../../common/constants/runtime';
20
20
  import { eventOrigin } from '../../../common/util/event-origin';
21
21
  import { loadTime } from '../../../common/vitals/load-time';
22
+ import { webdriverDetected } from '../../../common/util/webdriver-detection';
22
23
  export class Aggregate extends AggregateBase {
23
24
  static featureName = FEATURE_NAME;
24
25
  #handleVitalMetric = ({
@@ -127,6 +128,7 @@ export class Aggregate extends AggregateBase {
127
128
  timingAttributes[key] = val;
128
129
  }
129
130
  });
131
+ timingAttributes.webdriverDetected = webdriverDetected;
130
132
  }
131
133
  preHarvestChecks() {
132
134
  this.checkForFirstInteraction();
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { handle } from '../../../common/event-emitter/handle';
6
6
  import { registerHandler } from '../../../common/event-emitter/register-handler';
7
+ import { webdriverDetected } from '../../../common/util/webdriver-detection';
7
8
  import { loadTime } from '../../../common/vitals/load-time';
8
9
  import { FEATURE_NAMES } from '../../../loaders/features/features';
9
10
  import { AggregateBase } from '../../utils/aggregate-base';
@@ -24,6 +25,7 @@ export class Aggregate extends AggregateBase {
24
25
  this.initialPageLoadInteraction.onDone.push(() => {
25
26
  // this ensures the .end() method also works with iPL
26
27
  if (agentRef.runtime.session?.isNew) this.initialPageLoadInteraction.customAttributes.isFirstOfSession = true; // mark the hard page load as first of its session
28
+ this.initialPageLoadInteraction.customAttributes.webdriverDetected = webdriverDetected;
27
29
  this.initialPageLoadInteraction.forceSave = true; // unless forcibly ignored, iPL always finish by default
28
30
  const ixn = this.initialPageLoadInteraction;
29
31
  this.events.add(ixn); // add the iPL ixn to the buffer for harvest
@@ -22,6 +22,7 @@
22
22
  * @typedef {Object} RegisterAPIConstructor
23
23
  * @property {string|number} 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
+ * @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').
25
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.
26
27
  * @property {string} [parentId] - The parentId for the registered entity. If none was supplied, it will assume the entity guid from the main agent.
27
28
  */
@@ -33,6 +34,7 @@
33
34
  * @property {string} [target.licenseKey] - The license key for the registered entity. If none was supplied, it will assume the license key from the main agent.
34
35
  * @property {string} target.id - The ID for the registered entity.
35
36
  * @property {string} target.name - The name returned for the registered entity.
37
+ * @property {{[key: string]: any}} [target.tags] - The tags for the registered entity as key-value pairs.
36
38
  * @property {string} [target.parentId] - The parentId for the registered entity. If none was supplied, it will assume the entity guid from the main agent.
37
39
  * @property {boolean} [target.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.
38
40
  */
@@ -21,6 +21,8 @@ import { recordCustomEvent } from './recordCustomEvent';
21
21
  * @typedef {import('./register-api-types').RegisterAPI} RegisterAPI
22
22
  */
23
23
 
24
+ const PROTECTED_KEYS = ['name', 'id', 'type'];
25
+
24
26
  /**
25
27
  * @experimental
26
28
  * IMPORTANT: This feature is being developed for use internally and is not in a public-facing production-ready state.
@@ -47,10 +49,14 @@ function register(agentRef, target, parent) {
47
49
  target.licenseKey ||= agentRef.info.licenseKey; // will inherit the license key from the container agent if not provided for brevity. A future state may dictate that we need different license keys to do different things.
48
50
  target.blocked = false;
49
51
  target.parent = parent || {};
50
- if (!Array.isArray(target.tags)) target.tags = [];
52
+ if (typeof target.tags !== 'object' || target.tags === null || Array.isArray(target.tags)) target.tags = {};
51
53
  const attrs = {};
52
- target.tags.forEach(tag => {
53
- if (tag !== 'name' && tag !== 'id') attrs["source.".concat(tag)] = true;
54
+
55
+ // Process tags object and add to attrs, excluding protected keys
56
+ Object.entries(target.tags).forEach(([key, value]) => {
57
+ if (!PROTECTED_KEYS.includes(key)) {
58
+ attrs["source.".concat(key)] = value;
59
+ }
54
60
  });
55
61
  target.isolated ??= true;
56
62
 
@@ -161,12 +167,13 @@ function register(agentRef, target, parent) {
161
167
  * @returns
162
168
  */
163
169
  const report = (methodToCall, args, target) => {
164
- if (isBlocked()) return;
170
+ /** Even if we are blocked, if registering we should still return a child register API so nested API calls do not throw errors */
171
+ if (isBlocked() && methodToCall !== register) return;
165
172
  /** set the timestamp before the async part of waiting for the rum response for better accuracy */
166
173
  const timestamp = now();
167
174
  handle(SUPPORTABILITY_METRIC_CHANNEL, ["API/register/".concat(methodToCall.name, "/called")], undefined, FEATURE_NAMES.metrics, agentRef.ee);
168
175
  try {
169
- const shouldDuplicate = agentRef.init.api.duplicate_registered_data && methodToCall.name !== 'register';
176
+ const shouldDuplicate = agentRef.init.api.duplicate_registered_data && methodToCall !== register;
170
177
  if (shouldDuplicate) {
171
178
  let duplicatedArgs = args;
172
179
  if (args[1] instanceof Object) {
@@ -1 +1 @@
1
- {"root":["../src/index.js","../src/cdn/experimental.js","../src/cdn/lite.js","../src/cdn/pro.js","../src/cdn/spa.js","../src/common/aggregate/aggregator.js","../src/common/aggregate/event-aggregator.js","../src/common/config/configurable.js","../src/common/config/info.js","../src/common/config/init-types.js","../src/common/config/init.js","../src/common/config/loader-config.js","../src/common/config/runtime.js","../src/common/constants/agent-constants.js","../src/common/constants/env.cdn.js","../src/common/constants/env.js","../src/common/constants/env.npm.js","../src/common/constants/runtime.js","../src/common/constants/shared-channel.js","../src/common/deny-list/deny-list.js","../src/common/dispatch/global-event.js","../src/common/dom/iframe.js","../src/common/dom/query-selector.js","../src/common/dom/selector-path.js","../src/common/drain/drain.js","../src/common/event-emitter/contextual-ee.js","../src/common/event-emitter/event-context.js","../src/common/event-emitter/handle.js","../src/common/event-emitter/register-handler.js","../src/common/event-listener/event-listener-opts.js","../src/common/harvest/harvester.js","../src/common/harvest/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/extract-url.js","../src/common/url/location.js","../src/common/url/parse-url.js","../src/common/url/protocol.js","../src/common/util/attribute-size.js","../src/common/util/console.js","../src/common/util/data-size.js","../src/common/util/event-origin.js","../src/common/util/feature-flags.js","../src/common/util/get-or-set.js","../src/common/util/invoke.js","../src/common/util/monkey-patched.js","../src/common/util/obfuscate.js","../src/common/util/stringify.js","../src/common/util/submit-data.js","../src/common/util/text.js","../src/common/util/traverse.js","../src/common/util/type-check.js","../src/common/util/v2.js","../src/common/vitals/constants.js","../src/common/vitals/cumulative-layout-shift.js","../src/common/vitals/first-contentful-paint.js","../src/common/vitals/first-paint.js","../src/common/vitals/interaction-to-next-paint.js","../src/common/vitals/largest-contentful-paint.js","../src/common/vitals/load-time.js","../src/common/vitals/time-to-first-byte.js","../src/common/vitals/vital-metric.js","../src/common/window/load.js","../src/common/window/nreum.js","../src/common/window/page-visibility.js","../src/common/wrap/wrap-events.js","../src/common/wrap/wrap-fetch.js","../src/common/wrap/wrap-function.js","../src/common/wrap/wrap-history.js","../src/common/wrap/wrap-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/cause-string.js","../src/features/jserrors/aggregate/compute-stack-trace.js","../src/features/jserrors/aggregate/format-stack-trace.js","../src/features/jserrors/aggregate/index.js","../src/features/jserrors/aggregate/internal-errors.js","../src/features/jserrors/aggregate/string-hash-code.js","../src/features/jserrors/instrument/index.js","../src/features/jserrors/shared/cast-error.js","../src/features/jserrors/shared/uncaught-error.js","../src/features/logging/constants.js","../src/features/logging/index.js","../src/features/logging/aggregate/index.js","../src/features/logging/instrument/index.js","../src/features/logging/shared/log.js","../src/features/logging/shared/utils.js","../src/features/metrics/constants.js","../src/features/metrics/index.js","../src/features/metrics/aggregate/framework-detection.js","../src/features/metrics/aggregate/harvest-metadata.js","../src/features/metrics/aggregate/index.js","../src/features/metrics/instrument/index.js","../src/features/page_action/constants.js","../src/features/page_action/index.js","../src/features/page_action/instrument/index.js","../src/features/page_view_event/constants.js","../src/features/page_view_event/index.js","../src/features/page_view_event/aggregate/index.js","../src/features/page_view_event/aggregate/initialized-features.js","../src/features/page_view_event/instrument/index.js","../src/features/page_view_timing/constants.js","../src/features/page_view_timing/index.js","../src/features/page_view_timing/aggregate/index.js","../src/features/page_view_timing/instrument/index.js","../src/features/session_replay/constants.js","../src/features/session_replay/index.js","../src/features/session_replay/aggregate/index.js","../src/features/session_replay/instrument/index.js","../src/features/session_replay/shared/recorder-events.js","../src/features/session_replay/shared/recorder.js","../src/features/session_replay/shared/stylesheet-evaluator.js","../src/features/session_replay/shared/utils.js","../src/features/session_trace/constants.js","../src/features/session_trace/index.js","../src/features/session_trace/aggregate/index.js","../src/features/session_trace/aggregate/trace/node.js","../src/features/session_trace/aggregate/trace/storage.js","../src/features/session_trace/aggregate/trace/utils.js","../src/features/session_trace/instrument/index.js","../src/features/soft_navigations/constants.js","../src/features/soft_navigations/index.js","../src/features/soft_navigations/aggregate/ajax-node.js","../src/features/soft_navigations/aggregate/bel-node.js","../src/features/soft_navigations/aggregate/index.js","../src/features/soft_navigations/aggregate/initial-page-load-interaction.js","../src/features/soft_navigations/aggregate/interaction.js","../src/features/soft_navigations/instrument/index.js","../src/features/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/feature-base.js","../src/features/utils/feature-gates.js","../src/features/utils/instrument-base.js","../src/features/utils/nr1-debugger.js","../src/interfaces/registered-entity.js","../src/loaders/agent-base.js","../src/loaders/agent.js","../src/loaders/api-base.js","../src/loaders/browser-agent.js","../src/loaders/micro-agent-base.js","../src/loaders/micro-agent.js","../src/loaders/api/addPageAction.js","../src/loaders/api/addRelease.js","../src/loaders/api/addToTrace.js","../src/loaders/api/consent.js","../src/loaders/api/constants.js","../src/loaders/api/finished.js","../src/loaders/api/interaction-types.js","../src/loaders/api/interaction.js","../src/loaders/api/log.js","../src/loaders/api/measure.js","../src/loaders/api/noticeError.js","../src/loaders/api/pauseReplay.js","../src/loaders/api/recordCustomEvent.js","../src/loaders/api/recordReplay.js","../src/loaders/api/register-api-types.js","../src/loaders/api/register.js","../src/loaders/api/setApplicationVersion.js","../src/loaders/api/setCustomAttribute.js","../src/loaders/api/setErrorHandler.js","../src/loaders/api/setPageViewName.js","../src/loaders/api/setUserId.js","../src/loaders/api/sharedHandlers.js","../src/loaders/api/start.js","../src/loaders/api/topLevelCallers.js","../src/loaders/api/wrapLogger.js","../src/loaders/configure/configure.js","../src/loaders/configure/nonce.js","../src/loaders/configure/public-path.js","../src/loaders/features/enabled-features.js","../src/loaders/features/featureDependencies.js","../src/loaders/features/features.js"],"version":"5.9.3"}
1
+ {"root":["../src/index.js","../src/cdn/experimental.js","../src/cdn/lite.js","../src/cdn/pro.js","../src/cdn/spa.js","../src/common/aggregate/aggregator.js","../src/common/aggregate/event-aggregator.js","../src/common/config/configurable.js","../src/common/config/info.js","../src/common/config/init-types.js","../src/common/config/init.js","../src/common/config/loader-config.js","../src/common/config/runtime.js","../src/common/constants/agent-constants.js","../src/common/constants/env.cdn.js","../src/common/constants/env.js","../src/common/constants/env.npm.js","../src/common/constants/runtime.js","../src/common/constants/shared-channel.js","../src/common/deny-list/deny-list.js","../src/common/dispatch/global-event.js","../src/common/dom/iframe.js","../src/common/dom/query-selector.js","../src/common/dom/selector-path.js","../src/common/drain/drain.js","../src/common/event-emitter/contextual-ee.js","../src/common/event-emitter/event-context.js","../src/common/event-emitter/handle.js","../src/common/event-emitter/register-handler.js","../src/common/event-listener/event-listener-opts.js","../src/common/harvest/harvester.js","../src/common/harvest/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/extract-url.js","../src/common/url/location.js","../src/common/url/parse-url.js","../src/common/url/protocol.js","../src/common/util/attribute-size.js","../src/common/util/console.js","../src/common/util/data-size.js","../src/common/util/event-origin.js","../src/common/util/feature-flags.js","../src/common/util/get-or-set.js","../src/common/util/invoke.js","../src/common/util/monkey-patched.js","../src/common/util/obfuscate.js","../src/common/util/stringify.js","../src/common/util/submit-data.js","../src/common/util/text.js","../src/common/util/traverse.js","../src/common/util/type-check.js","../src/common/util/v2.js","../src/common/util/webdriver-detection.js","../src/common/vitals/constants.js","../src/common/vitals/cumulative-layout-shift.js","../src/common/vitals/first-contentful-paint.js","../src/common/vitals/first-paint.js","../src/common/vitals/interaction-to-next-paint.js","../src/common/vitals/largest-contentful-paint.js","../src/common/vitals/load-time.js","../src/common/vitals/time-to-first-byte.js","../src/common/vitals/vital-metric.js","../src/common/window/load.js","../src/common/window/nreum.js","../src/common/window/page-visibility.js","../src/common/wrap/wrap-events.js","../src/common/wrap/wrap-fetch.js","../src/common/wrap/wrap-function.js","../src/common/wrap/wrap-history.js","../src/common/wrap/wrap-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/cause-string.js","../src/features/jserrors/aggregate/compute-stack-trace.js","../src/features/jserrors/aggregate/format-stack-trace.js","../src/features/jserrors/aggregate/index.js","../src/features/jserrors/aggregate/internal-errors.js","../src/features/jserrors/aggregate/string-hash-code.js","../src/features/jserrors/instrument/index.js","../src/features/jserrors/shared/cast-error.js","../src/features/jserrors/shared/uncaught-error.js","../src/features/logging/constants.js","../src/features/logging/index.js","../src/features/logging/aggregate/index.js","../src/features/logging/instrument/index.js","../src/features/logging/shared/log.js","../src/features/logging/shared/utils.js","../src/features/metrics/constants.js","../src/features/metrics/index.js","../src/features/metrics/aggregate/framework-detection.js","../src/features/metrics/aggregate/harvest-metadata.js","../src/features/metrics/aggregate/index.js","../src/features/metrics/instrument/index.js","../src/features/page_action/constants.js","../src/features/page_action/index.js","../src/features/page_action/instrument/index.js","../src/features/page_view_event/constants.js","../src/features/page_view_event/index.js","../src/features/page_view_event/aggregate/index.js","../src/features/page_view_event/aggregate/initialized-features.js","../src/features/page_view_event/instrument/index.js","../src/features/page_view_timing/constants.js","../src/features/page_view_timing/index.js","../src/features/page_view_timing/aggregate/index.js","../src/features/page_view_timing/instrument/index.js","../src/features/session_replay/constants.js","../src/features/session_replay/index.js","../src/features/session_replay/aggregate/index.js","../src/features/session_replay/instrument/index.js","../src/features/session_replay/shared/recorder-events.js","../src/features/session_replay/shared/recorder.js","../src/features/session_replay/shared/stylesheet-evaluator.js","../src/features/session_replay/shared/utils.js","../src/features/session_trace/constants.js","../src/features/session_trace/index.js","../src/features/session_trace/aggregate/index.js","../src/features/session_trace/aggregate/trace/node.js","../src/features/session_trace/aggregate/trace/storage.js","../src/features/session_trace/aggregate/trace/utils.js","../src/features/session_trace/instrument/index.js","../src/features/soft_navigations/constants.js","../src/features/soft_navigations/index.js","../src/features/soft_navigations/aggregate/ajax-node.js","../src/features/soft_navigations/aggregate/bel-node.js","../src/features/soft_navigations/aggregate/index.js","../src/features/soft_navigations/aggregate/initial-page-load-interaction.js","../src/features/soft_navigations/aggregate/interaction.js","../src/features/soft_navigations/instrument/index.js","../src/features/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/feature-base.js","../src/features/utils/feature-gates.js","../src/features/utils/instrument-base.js","../src/features/utils/nr1-debugger.js","../src/interfaces/registered-entity.js","../src/loaders/agent-base.js","../src/loaders/agent.js","../src/loaders/api-base.js","../src/loaders/browser-agent.js","../src/loaders/micro-agent-base.js","../src/loaders/micro-agent.js","../src/loaders/api/addPageAction.js","../src/loaders/api/addRelease.js","../src/loaders/api/addToTrace.js","../src/loaders/api/consent.js","../src/loaders/api/constants.js","../src/loaders/api/finished.js","../src/loaders/api/interaction-types.js","../src/loaders/api/interaction.js","../src/loaders/api/log.js","../src/loaders/api/measure.js","../src/loaders/api/noticeError.js","../src/loaders/api/pauseReplay.js","../src/loaders/api/recordCustomEvent.js","../src/loaders/api/recordReplay.js","../src/loaders/api/register-api-types.js","../src/loaders/api/register.js","../src/loaders/api/setApplicationVersion.js","../src/loaders/api/setCustomAttribute.js","../src/loaders/api/setErrorHandler.js","../src/loaders/api/setPageViewName.js","../src/loaders/api/setUserId.js","../src/loaders/api/sharedHandlers.js","../src/loaders/api/start.js","../src/loaders/api/topLevelCallers.js","../src/loaders/api/wrapLogger.js","../src/loaders/configure/configure.js","../src/loaders/configure/nonce.js","../src/loaders/configure/public-path.js","../src/loaders/features/enabled-features.js","../src/loaders/features/featureDependencies.js","../src/loaders/features/features.js"],"version":"5.9.3"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Detects if the page is being controlled by WebDriver or automation tools derived from it.
3
+ * Checks for common indicators including:
4
+ * - navigator.webdriver property (standard WebDriver flag)
5
+ * - window.document.__webdriver_evaluate (WebDriver internal property)
6
+ * - window.document.__selenium_unwrapped (Selenium property)
7
+ * - window.document.__driver_evaluate (WebDriver internal property)
8
+ * - window.document.__webdriver_script_function (WebDriver internal property)
9
+ * - window.callPhantom (PhantomJS property)
10
+ * - window._phantom (PhantomJS property)
11
+ * - window.__nightmare (Nightmare.js property)
12
+ * - window.domAutomation (Chrome automation property)
13
+ * - window.domAutomationController (Chrome automation property)
14
+ *
15
+ * @type {boolean}
16
+ */
17
+ export const webdriverDetected: boolean;
18
+ //# sourceMappingURL=webdriver-detection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webdriver-detection.d.ts","sourceRoot":"","sources":["../../../../src/common/util/webdriver-detection.js"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;GAeG;AACH,gCAFU,OAAO,CAmCb"}
@@ -1 +1 @@
1
- {"version":3,"file":"nreum.d.ts","sourceRoot":"","sources":["../../../../src/common/window/nreum.js"],"names":[],"mappings":"AAaA,gCAMC;AAED,oCAWC;AAED,4CASC;AAED,oCASC;AAED,yCAkBC;AAED,+EAQC;AAED;;;;GAIG;AACH,uDAGC;AAED,uDAGC;AAED,yCAGC;AAED,8BAMC"}
1
+ {"version":3,"file":"nreum.d.ts","sourceRoot":"","sources":["../../../../src/common/window/nreum.js"],"names":[],"mappings":"AAcA,gCAMC;AAED,oCAWC;AAED,4CASC;AAED,oCASC;AAED,yCAkBC;AAED,+EAaC;AAED;;;;GAIG;AACH,uDAGC;AAED,uDAGC;AAED,yCAGC;AAED,8BAMC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_event/aggregate/index.js"],"names":[],"mappings":"AAuBA;IACE,2BAA2C;IAE3C,2BA4BC;IAzBC,iBAAoB;IAEpB,wBAAwB;IACxB,8BAA8B;IAC9B,8BAA8B;IAC9B,gBAAgB;IAsBlB;;;;OAIG;IACH,2BAFW,GAAC,QAoEX;IAbC;;;;;;;;;;;;;;mBAOC;IANC,iCAAyB;IAc7B,kCAEC;IAED;;;;;;aAiGC;CACF;8BA3N6B,4BAA4B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_event/aggregate/index.js"],"names":[],"mappings":"AAwBA;IACE,2BAA2C;IAE3C,2BA4BC;IAzBC,iBAAoB;IAEpB,wBAAwB;IACxB,8BAA8B;IAC9B,8BAA8B;IAC9B,gBAAgB;IAsBlB;;;;OAIG;IACH,2BAFW,GAAC,QAiEX;IAbC;;;;;;;;;;;;;;mBAOC;IANC,iCAAyB;IAc7B,kCAEC;IAED;;;;;;aAiGC;CACF;8BAzN6B,4BAA4B"}
@@ -1 +1 @@
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,qCAwBC;;CACF;8BAvJ6B,4BAA4B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_timing/aggregate/index.js"],"names":[],"mappings":"AAuBA;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,gDAWC;IAED,4BAGC;IAGD,qCAwBC;;CACF;8BAzJ6B,4BAA4B"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/index.js"],"names":[],"mappings":"AAcA;IACE,2BAAiC;IACjC;;OA+DC;IA1DC,2BAAwC;IACxC,iBAA8B;IAE9B,uDAA0E;IAc1E,yBAA+B;IAC/B,0CAAiC;IACjC,yBAA4C;IAyC9C,qCAUC;IAED,0EAmBC;IAED,2BAiBC;IAED;;;;;;;OAOG;IACH,6BAHW,mBAAmB,OAsB7B;;CAiHF;8BAzQ6B,4BAA4B;2CAGf,iCAAiC;4BAChD,eAAe"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/index.js"],"names":[],"mappings":"AAeA;IACE,2BAAiC;IACjC;;OAgEC;IA3DC,2BAAwC;IACxC,iBAA8B;IAE9B,uDAA0E;IAe1E,yBAA+B;IAC/B,0CAAiC;IACjC,yBAA4C;IAyC9C,qCAUC;IAED,0EAmBC;IAED,2BAiBC;IAED;;;;;;;OAOG;IACH,6BAHW,mBAAmB,OAsB7B;;CAiHF;8BA1Q6B,4BAA4B;2CAGf,iCAAiC;4BAChD,eAAe"}
@@ -68,6 +68,12 @@ export type RegisterAPIConstructor = {
68
68
  * - The readable name for the registered entity. This will be assigned to any synthesized entities.
69
69
  */
70
70
  name: string;
71
+ /**
72
+ * - 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').
73
+ */
74
+ tags?: {
75
+ [key: string]: any;
76
+ } | undefined;
71
77
  /**
72
78
  * - 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.
73
79
  */
@@ -89,6 +95,9 @@ export type RegisterAPIMetadata = {
89
95
  licenseKey?: string | undefined;
90
96
  id: string;
91
97
  name: string;
98
+ tags?: {
99
+ [key: string]: any;
100
+ } | undefined;
92
101
  parentId?: string | undefined;
93
102
  isolated?: boolean | undefined;
94
103
  };
@@ -1 +1 @@
1
- {"version":3,"file":"register-api-types.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/register-api-types.js"],"names":[],"mappings":";;;;;;mBAOc,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,IAAI;;;;SAC3C,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAA;KAAC,KAAK,IAAI;;;;iBACxH,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,KAAK,IAAI;;;;cAC1D,CAAC,MAAM,EAAE,sBAAsB,KAAK,WAAW;;;;gBAC/C,MAAM,IAAI;;;;uBACV,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,IAAI;;;;aAChD,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAC,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAC,CAAC;;;;2BACrL,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI;;;;wBAC9B,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI;;;;eAClF,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,YAAY,CAAC,EAAE,OAAO,KAAK,IAAI;;;;cACtD,mBAAmB;;;;;;QAKnB,MAAM,GAAC,MAAM;;;;UACb,MAAM;;;;;;;;;;;;;;sBAON,MAAM;;;;YAEjB;QAA2B,UAAU;QACX,EAAE,EAAjB,MAAM;QACS,IAAI,EAAnB,MAAM;QACU,QAAQ;QACP,QAAQ;KACtC"}
1
+ {"version":3,"file":"register-api-types.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/register-api-types.js"],"names":[],"mappings":";;;;;;mBAOc,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,IAAI;;;;SAC3C,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAA;KAAC,KAAK,IAAI;;;;iBACxH,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,KAAK,IAAI;;;;cAC1D,CAAC,MAAM,EAAE,sBAAsB,KAAK,WAAW;;;;gBAC/C,MAAM,IAAI;;;;uBACV,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,IAAI;;;;aAChD,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAC,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAC,CAAC;;;;2BACrL,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI;;;;wBAC9B,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI;;;;eAClF,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,YAAY,CAAC,EAAE,OAAO,KAAK,IAAI;;;;cACtD,mBAAmB;;;;;;QAKnB,MAAM,GAAC,MAAM;;;;UACb,MAAM;;;;;;;;;;;;;;;;;;;;sBAQN,MAAM;;;;YAEjB;QAA2B,UAAU;QACX,EAAE,EAAjB,MAAM;QACS,IAAI,EAAnB,MAAM;QACwB,IAAI;;;QAClB,QAAQ;QACP,QAAQ;KACtC"}
@@ -1,6 +1,3 @@
1
- /**
2
- * @typedef {import('./register-api-types').RegisterAPI} RegisterAPI
3
- */
4
1
  /**
5
2
  * @experimental
6
3
  * IMPORTANT: This feature is being developed for use internally and is not in a public-facing production-ready state.
@@ -1 +1 @@
1
- {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/register.js"],"names":[],"mappings":"AAmBA;;GAEG;AAEH;;;;GAIG;AACH,mDAIC;0BAZY,OAAO,sBAAsB,EAAE,WAAW"}
1
+ {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/register.js"],"names":[],"mappings":"AAyBA;;;;GAIG;AACH,mDAIC;0BAdY,OAAO,sBAAsB,EAAE,WAAW"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/browser-agent",
3
- "version": "1.308.0",
3
+ "version": "1.309.0",
4
4
  "private": false,
5
5
  "author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
6
6
  "description": "New Relic Browser Agent",
@@ -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
 
@@ -71,7 +71,7 @@ export function drain (agentIdentifier = '', featureName = 'feature', force = fa
71
71
 
72
72
  /** Checks all items in the registry to see if they have been "staged". If ALL items are staged, it will drain all registry items (drainGroup). It not, nothing will happen */
73
73
  function checkCanDrainAll (agentIdentifier) {
74
- // Only when the event-groups for all features are ready to drain (staged) do we execute the drain. This has the effect
74
+ // Only when the event-groups for all features are ready to drain (staged) do we execute the drain. This has the effect
75
75
  // that the last feature to call drain triggers drain for all features.
76
76
  const items = Array.from(registry[agentIdentifier])
77
77
  if (items.every(([key, values]) => values.staged)) {
@@ -115,7 +115,7 @@ function drainGroup (agentIdentifier, group, activateGroup = true) {
115
115
  // registration *should* be an array of: [targetEE, eventHandler]
116
116
  // certain browser polyfills of .values and .entries pass the prototype methods into the callback,
117
117
  // which breaks this assumption and throws errors. So we make sure here that we only call .on() if its an actual NR EE
118
- if (registration[0]?.on && registration[0]?.context() instanceof EventContext) registration[0].on(eventType, registration[1])
118
+ if (registration[0]?.on && registration[0].context() instanceof EventContext && !registration[0].listeners(eventType).includes(registration[1])) registration[0].on(eventType, registration[1])
119
119
  })
120
120
  })
121
121
  }
@@ -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 { generateRandomHexString } from '../ids/unique-id'
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ import { isBrowserScope } from '../constants/runtime'
7
+
8
+ /**
9
+ * Detects if the page is being controlled by WebDriver or automation tools derived from it.
10
+ * Checks for common indicators including:
11
+ * - navigator.webdriver property (standard WebDriver flag)
12
+ * - window.document.__webdriver_evaluate (WebDriver internal property)
13
+ * - window.document.__selenium_unwrapped (Selenium property)
14
+ * - window.document.__driver_evaluate (WebDriver internal property)
15
+ * - window.document.__webdriver_script_function (WebDriver internal property)
16
+ * - window.callPhantom (PhantomJS property)
17
+ * - window._phantom (PhantomJS property)
18
+ * - window.__nightmare (Nightmare.js property)
19
+ * - window.domAutomation (Chrome automation property)
20
+ * - window.domAutomationController (Chrome automation property)
21
+ *
22
+ * @type {boolean}
23
+ */
24
+ export const webdriverDetected = (() => {
25
+ try {
26
+ // Standard WebDriver flag
27
+ if (typeof navigator !== 'undefined' && navigator.webdriver === true) {
28
+ return true
29
+ }
30
+
31
+ // Check for various automation-related properties
32
+ if (isBrowserScope) {
33
+ // WebDriver internal properties
34
+ if (document.__webdriver_evaluate ||
35
+ document.__selenium_unwrapped ||
36
+ document.__driver_evaluate ||
37
+ document.__webdriver_script_function) {
38
+ return true
39
+ }
40
+
41
+ // PhantomJS detection
42
+ if (window.callPhantom || window._phantom) {
43
+ return true
44
+ }
45
+
46
+ // Nightmare.js detection
47
+ if (window.__nightmare) {
48
+ return true
49
+ }
50
+ }
51
+
52
+ return false
53
+ } catch (err) {
54
+ // If any errors occur during detection, assume not automated
55
+ return false
56
+ }
57
+ })()
@@ -1,9 +1,10 @@
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 { globalScope } from '../constants/runtime'
6
6
  import { now } from '../timing/now'
7
+ import { warn } from '../util/console'
7
8
  import { isNative } from '../util/monkey-patched'
8
9
 
9
10
  export const defaults = {
@@ -82,6 +83,11 @@ export function setNREUMInitializedAgent (id, newAgentInstance) {
82
83
  date: new Date()
83
84
  }
84
85
  nr.initializedAgents[id] = newAgentInstance
86
+
87
+ // Warn if using more than one agent, but only once per agent load
88
+ if (Object.keys(nr.initializedAgents).length === 2) {
89
+ warn(69)
90
+ }
85
91
  }
86
92
 
87
93
  /**
@@ -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 { FEATURE_NAMES } from '../../loaders/features/features'
@@ -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 { globalScope, isBrowserScope, originTime, supportsNavTimingL2 } from '../../../common/constants/runtime'
@@ -20,6 +20,7 @@ import { applyFnToProps } from '../../../common/util/traverse'
20
20
  import { send } from '../../../common/harvest/harvester'
21
21
  import { FEATURE_NAMES, FEATURE_TO_ENDPOINT } from '../../../loaders/features/features'
22
22
  import { getSubmitMethod } from '../../../common/util/submit-data'
23
+ import { webdriverDetected } from '../../../common/util/webdriver-detection'
23
24
 
24
25
  export class Aggregate extends AggregateBase {
25
26
  static featureName = CONSTANTS.FEATURE_NAME
@@ -87,10 +88,7 @@ export class Aggregate extends AggregateBase {
87
88
 
88
89
  if (this.agentRef.runtime.session) queryParameters.fsh = Number(this.agentRef.runtime.session.isNew) // "first session harvest" aka RUM request or PageView event of a session
89
90
 
90
- let body
91
- if (typeof customAttributes === 'object' && Object.keys(customAttributes).length > 0) {
92
- body = applyFnToProps({ ja: customAttributes }, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string')
93
- }
91
+ let body = applyFnToProps({ ja: { ...customAttributes, webdriverDetected } }, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string')
94
92
 
95
93
  if (globalScope.performance) {
96
94
  if (supportsNavTimingL2()) { // Navigation Timing level 2 API that replaced PerformanceTiming & PerformanceNavigation
@@ -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
 
@@ -19,6 +19,7 @@ import { VITAL_NAMES } from '../../../common/vitals/constants'
19
19
  import { initiallyHidden } from '../../../common/constants/runtime'
20
20
  import { eventOrigin } from '../../../common/util/event-origin'
21
21
  import { loadTime } from '../../../common/vitals/load-time'
22
+ import { webdriverDetected } from '../../../common/util/webdriver-detection'
22
23
 
23
24
  export class Aggregate extends AggregateBase {
24
25
  static featureName = FEATURE_NAME
@@ -126,6 +127,7 @@ export class Aggregate extends AggregateBase {
126
127
  timingAttributes[key] = val
127
128
  }
128
129
  })
130
+ timingAttributes.webdriverDetected = webdriverDetected
129
131
  }
130
132
 
131
133
  preHarvestChecks () {
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { handle } from '../../../common/event-emitter/handle'
6
6
  import { registerHandler } from '../../../common/event-emitter/register-handler'
7
+ import { webdriverDetected } from '../../../common/util/webdriver-detection'
7
8
  import { loadTime } from '../../../common/vitals/load-time'
8
9
  import { FEATURE_NAMES } from '../../../loaders/features/features'
9
10
  import { AggregateBase } from '../../utils/aggregate-base'
@@ -25,6 +26,7 @@ export class Aggregate extends AggregateBase {
25
26
  this.initialPageLoadInteraction = new InitialPageLoadInteraction(agentRef)
26
27
  this.initialPageLoadInteraction.onDone.push(() => { // this ensures the .end() method also works with iPL
27
28
  if (agentRef.runtime.session?.isNew) this.initialPageLoadInteraction.customAttributes.isFirstOfSession = true // mark the hard page load as first of its session
29
+ this.initialPageLoadInteraction.customAttributes.webdriverDetected = webdriverDetected
28
30
  this.initialPageLoadInteraction.forceSave = true // unless forcibly ignored, iPL always finish by default
29
31
  const ixn = this.initialPageLoadInteraction
30
32
  this.events.add(ixn) // add the iPL ixn to the buffer for harvest
@@ -22,6 +22,7 @@
22
22
  * @typedef {Object} RegisterAPIConstructor
23
23
  * @property {string|number} 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
+ * @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').
25
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.
26
27
  * @property {string} [parentId] - The parentId for the registered entity. If none was supplied, it will assume the entity guid from the main agent.
27
28
  */
@@ -33,6 +34,7 @@
33
34
  * @property {string} [target.licenseKey] - The license key for the registered entity. If none was supplied, it will assume the license key from the main agent.
34
35
  * @property {string} target.id - The ID for the registered entity.
35
36
  * @property {string} target.name - The name returned for the registered entity.
37
+ * @property {{[key: string]: any}} [target.tags] - The tags for the registered entity as key-value pairs.
36
38
  * @property {string} [target.parentId] - The parentId for the registered entity. If none was supplied, it will assume the entity guid from the main agent.
37
39
  * @property {boolean} [target.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.
38
40
  */
@@ -21,6 +21,8 @@ import { recordCustomEvent } from './recordCustomEvent'
21
21
  * @typedef {import('./register-api-types').RegisterAPI} RegisterAPI
22
22
  */
23
23
 
24
+ const PROTECTED_KEYS = ['name', 'id', 'type']
25
+
24
26
  /**
25
27
  * @experimental
26
28
  * IMPORTANT: This feature is being developed for use internally and is not in a public-facing production-ready state.
@@ -48,10 +50,17 @@ function register (agentRef, target, parent) {
48
50
  target.licenseKey ||= agentRef.info.licenseKey // will inherit the license key from the container agent if not provided for brevity. A future state may dictate that we need different license keys to do different things.
49
51
  target.blocked = false
50
52
  target.parent = parent || {}
51
- if (!Array.isArray(target.tags)) target.tags = []
53
+ if (typeof target.tags !== 'object' || target.tags === null || Array.isArray(target.tags)) target.tags = {}
52
54
 
53
55
  const attrs = {}
54
- target.tags.forEach(tag => { if (tag !== 'name' && tag !== 'id') attrs[`source.${tag}`] = true })
56
+
57
+ // Process tags object and add to attrs, excluding protected keys
58
+ Object.entries(target.tags).forEach(([key, value]) => {
59
+ if (!PROTECTED_KEYS.includes(key)) {
60
+ attrs[`source.${key}`] = value
61
+ }
62
+ })
63
+
55
64
  target.isolated ??= true
56
65
 
57
66
  /** @type {Function} a function that is set and reports when APIs are triggered -- warns the customer of the invalid state */
@@ -136,12 +145,13 @@ function register (agentRef, target, parent) {
136
145
  * @returns
137
146
  */
138
147
  const report = (methodToCall, args, target) => {
139
- if (isBlocked()) return
148
+ /** Even if we are blocked, if registering we should still return a child register API so nested API calls do not throw errors */
149
+ if (isBlocked() && methodToCall !== register) return
140
150
  /** set the timestamp before the async part of waiting for the rum response for better accuracy */
141
151
  const timestamp = now()
142
152
  handle(SUPPORTABILITY_METRIC_CHANNEL, [`API/register/${methodToCall.name}/called`], undefined, FEATURE_NAMES.metrics, agentRef.ee)
143
153
  try {
144
- const shouldDuplicate = agentRef.init.api.duplicate_registered_data && methodToCall.name !== 'register'
154
+ const shouldDuplicate = agentRef.init.api.duplicate_registered_data && methodToCall !== register
145
155
  if (shouldDuplicate) {
146
156
  let duplicatedArgs = args
147
157
  if (args[1] instanceof Object) {