@newrelic/browser-agent 1.315.0 → 1.316.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 (46) hide show
  1. package/CHANGELOG.md +7 -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/session/constants.js +3 -4
  5. package/dist/cjs/common/session/session-entity.js +2 -2
  6. package/dist/cjs/common/session/session-key.js +22 -0
  7. package/dist/cjs/features/page_view_event/aggregate/index.js +0 -5
  8. package/dist/cjs/features/session_replay/instrument/index.js +4 -2
  9. package/dist/cjs/features/utils/agent-session.js +7 -6
  10. package/dist/cjs/features/utils/aggregate-base.js +0 -34
  11. package/dist/cjs/features/utils/instrument-base.js +47 -2
  12. package/dist/cjs/loaders/api-base.js +1 -1
  13. package/dist/esm/common/constants/env.cdn.js +1 -1
  14. package/dist/esm/common/constants/env.npm.js +1 -1
  15. package/dist/esm/common/session/constants.js +2 -3
  16. package/dist/esm/common/session/session-entity.js +3 -3
  17. package/dist/esm/common/session/session-key.js +16 -0
  18. package/dist/esm/features/page_view_event/aggregate/index.js +0 -5
  19. package/dist/esm/features/session_replay/instrument/index.js +5 -3
  20. package/dist/esm/features/utils/agent-session.js +3 -2
  21. package/dist/esm/features/utils/aggregate-base.js +0 -34
  22. package/dist/esm/features/utils/instrument-base.js +46 -2
  23. package/dist/esm/loaders/api-base.js +1 -1
  24. package/dist/tsconfig.tsbuildinfo +1 -1
  25. package/dist/types/common/session/constants.d.ts +2 -3
  26. package/dist/types/common/session/constants.d.ts.map +1 -1
  27. package/dist/types/common/session/session-entity.d.ts +1 -1
  28. package/dist/types/common/session/session-key.d.ts +2 -0
  29. package/dist/types/common/session/session-key.d.ts.map +1 -0
  30. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  31. package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -1
  32. package/dist/types/features/utils/agent-session.d.ts.map +1 -1
  33. package/dist/types/features/utils/aggregate-base.d.ts +0 -5
  34. package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
  35. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  36. package/dist/types/loaders/api-base.d.ts +1 -1
  37. package/package.json +1 -1
  38. package/src/common/session/constants.js +2 -3
  39. package/src/common/session/session-entity.js +3 -3
  40. package/src/common/session/session-key.js +17 -0
  41. package/src/features/page_view_event/aggregate/index.js +0 -5
  42. package/src/features/session_replay/instrument/index.js +5 -3
  43. package/src/features/utils/agent-session.js +3 -2
  44. package/src/features/utils/aggregate-base.js +0 -32
  45. package/src/features/utils/instrument-base.js +45 -2
  46. package/src/loaders/api-base.js +1 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,13 @@
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.316.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.315.0...v1.316.0) (2026-06-01)
7
+
8
+
9
+ ### Features
10
+
11
+ * Use app-namespaced keys for session ([#1776](https://github.com/newrelic/newrelic-browser-agent/issues/1776)) ([60ec5bf](https://github.com/newrelic/newrelic-browser-agent/commit/60ec5bf566d27d68e59c827f08dcd8dd73d8cd2c))
12
+
6
13
  ## [1.315.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.314.0...v1.315.0) (2026-05-26)
7
14
 
8
15
 
@@ -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.315.0";
20
+ const VERSION = exports.VERSION = "1.316.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.315.0";
20
+ const VERSION = exports.VERSION = "1.316.0";
21
21
 
22
22
  /**
23
23
  * Exposes the build type of the agent
@@ -3,13 +3,12 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.SESSION_EVENT_TYPES = exports.SESSION_EVENTS = exports.PREFIX = exports.MODE = exports.DEFAULT_KEY = exports.DEFAULT_INACTIVE_MS = exports.DEFAULT_EXPIRES_MS = void 0;
6
+ exports.SESSION_STORAGE_KEY_PREFIX = exports.SESSION_EVENT_TYPES = exports.SESSION_EVENTS = exports.MODE = exports.DEFAULT_INACTIVE_MS = exports.DEFAULT_EXPIRES_MS = void 0;
7
7
  /**
8
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
8
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
9
9
  * SPDX-License-Identifier: Apache-2.0
10
10
  */
11
- const PREFIX = exports.PREFIX = 'NRBA';
12
- const DEFAULT_KEY = exports.DEFAULT_KEY = 'SESSION';
11
+ const SESSION_STORAGE_KEY_PREFIX = exports.SESSION_STORAGE_KEY_PREFIX = 'NRBA_SESSION::';
13
12
  const DEFAULT_EXPIRES_MS = exports.DEFAULT_EXPIRES_MS = 14400000;
14
13
  const DEFAULT_INACTIVE_MS = exports.DEFAULT_INACTIVE_MS = 1800000;
15
14
  const SESSION_EVENTS = exports.SESSION_EVENTS = {
@@ -45,7 +45,7 @@ const model = {
45
45
  class SessionEntity {
46
46
  /**
47
47
  * Create a self-managing Session Entity. This entity is scoped to the agent which triggered it, allowing for multiple simultaneous session objects to exist.
48
- * 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.
48
+ * Session data is stored at NRBA_SESSION::{key}, where key can be app-scoped by the caller to avoid collisions across multiple agents on the same origin.
49
49
  * The value can be overridden in the constructor, but will default to a unique 16 character hex string
50
50
  * expiresMs and inactiveMs are used to "expire" the session, but can be overridden in the constructor. Pass 0 to disable expiration timers.
51
51
  */
@@ -174,7 +174,7 @@ class SessionEntity {
174
174
 
175
175
  // This is the actual key appended to the storage API
176
176
  get lookupKey() {
177
- return "".concat(_constants.PREFIX, "_").concat(this.key);
177
+ return "".concat(_constants.SESSION_STORAGE_KEY_PREFIX).concat(this.key);
178
178
  }
179
179
  sync(data) {
180
180
  Object.assign(this.state, data);
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getAppSessionHash = getAppSessionHash;
7
+ /**
8
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
9
+ * SPDX-License-Identifier: Apache-2.0
10
+ */
11
+ // Deterministic 32-bit FNV-1a hash, rendered as a fixed 8-char hex string.
12
+ function hashTo8Hex(value) {
13
+ let hash = 0x811c9dc5;
14
+ for (let i = 0; i < value.length; i++) {
15
+ hash ^= value.charCodeAt(i);
16
+ hash = Math.imul(hash, 0x01000193);
17
+ }
18
+ return (hash >>> 0).toString(16).padStart(8, '0');
19
+ }
20
+ function getAppSessionHash(licenseKey, applicationID) {
21
+ return hashTo8Hex("".concat(String(licenseKey), ":").concat(String(applicationID)));
22
+ }
@@ -7,7 +7,6 @@ exports.Aggregate = void 0;
7
7
  var _runtime = require("../../../common/constants/runtime");
8
8
  var _navTiming = require("../../../common/timing/nav-timing");
9
9
  var _stringify = require("../../../common/util/stringify");
10
- var _info = require("../../../common/config/info");
11
10
  var CONSTANTS = _interopRequireWildcard(require("../constants"));
12
11
  var _initializedFeatures = require("./initialized-features");
13
12
  var _featureFlags = require("../../../common/util/feature-flags");
@@ -39,10 +38,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
39
38
  this.firstByteToWindowLoad = 0; // our "frontend" duration
40
39
  this.firstByteToDomContent = 0; // our "dom processing" duration
41
40
  this.retries = 0;
42
- if (!(0, _info.isValid)(agentRef.info)) {
43
- this.ee.abort();
44
- return (0, _console.warn)(43);
45
- }
46
41
  agentRef.runtime.timeKeeper = new _timeKeeper.TimeKeeper(agentRef.runtime.session);
47
42
  if (_runtime.isBrowserScope) {
48
43
  _timeToFirstByte.timeToFirstByte.subscribe(({
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.SessionReplay = exports.Instrument = void 0;
7
7
  var _handle = require("../../../common/event-emitter/handle");
8
8
  var _constants = require("../../../common/session/constants");
9
+ var _sessionKey = require("../../../common/session/session-key");
9
10
  var _instrumentBase = require("../../utils/instrument-base");
10
11
  var _utils = require("../shared/utils");
11
12
  var _constants2 = require("../constants");
@@ -13,7 +14,7 @@ var _recordReplay = require("../../../loaders/api/recordReplay");
13
14
  var _pauseReplay = require("../../../loaders/api/pauseReplay");
14
15
  var _constants3 = require("../../../loaders/api/constants");
15
16
  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); } /**
16
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
17
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
17
18
  * SPDX-License-Identifier: Apache-2.0
18
19
  */ /**
19
20
  * @file Primes the Session Replay feature for lazy loading.
@@ -31,8 +32,9 @@ class Instrument extends _instrumentBase.InstrumentBase {
31
32
  (0, _recordReplay.setupRecordReplayAPI)(agentRef);
32
33
  (0, _pauseReplay.setupPauseReplayAPI)(agentRef);
33
34
  let session;
35
+ const fullNamespacedKey = "".concat(_constants.SESSION_STORAGE_KEY_PREFIX).concat((0, _sessionKey.getAppSessionHash)(agentRef.info.licenseKey, agentRef.info.applicationID));
34
36
  try {
35
- session = JSON.parse(localStorage.getItem("".concat(_constants.PREFIX, "_").concat(_constants.DEFAULT_KEY)));
37
+ session = JSON.parse(localStorage.getItem(fullNamespacedKey));
36
38
  } catch (err) {}
37
39
  if ((0, _utils.hasReplayPrerequisite)(agentRef.init)) {
38
40
  this.ee.on(_constants3.RECORD_REPLAY, () => this.#apiStartOrRestartReplay());
@@ -8,12 +8,12 @@ var _drain = require("../../common/drain/drain");
8
8
  var _registerHandler = require("../../common/event-emitter/register-handler");
9
9
  var _sessionEntity = require("../../common/session/session-entity");
10
10
  var _localStorage = require("../../common/storage/local-storage.js");
11
- var _constants = require("../../common/session/constants");
11
+ var _sessionKey = require("../../common/session/session-key");
12
12
  var _info = require("../../common/config/info");
13
13
  var _attributeSize = require("../../common/util/attribute-size");
14
14
  var _handle = require("../../common/event-emitter/handle");
15
- var _constants2 = require("../../loaders/api/constants");
16
- var _constants3 = require("../metrics/constants");
15
+ var _constants = require("../../loaders/api/constants");
16
+ var _constants2 = require("../metrics/constants");
17
17
  var _features = require("../../loaders/features/features");
18
18
  var _sharedHandlers = require("../../loaders/api/sharedHandlers");
19
19
  /**
@@ -25,9 +25,10 @@ function setupAgentSession(agentRef) {
25
25
  if (agentRef.runtime.session) return agentRef.runtime.session; // already setup
26
26
 
27
27
  const sessionInit = agentRef.init.session;
28
+ const namespacedKey = (0, _sessionKey.getAppSessionHash)(agentRef.info.licenseKey, agentRef.info.applicationID);
28
29
  agentRef.runtime.session = new _sessionEntity.SessionEntity({
29
30
  agentRef,
30
- key: _constants.DEFAULT_KEY,
31
+ key: namespacedKey,
31
32
  storage: new _localStorage.LocalStorage(),
32
33
  expiresMs: sessionInit?.expiresMs,
33
34
  inactiveMs: sessionInit?.inactiveMs
@@ -65,8 +66,8 @@ function setupAgentSession(agentRef) {
65
66
  }, 'session', sharedEE);
66
67
  (0, _registerHandler.registerHandler)('api-setUserIdAndResetSession', value => {
67
68
  agentRef.runtime.session.reset();
68
- (0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, ['API/' + _constants2.SET_USER_ID + '/resetSession/called'], undefined, _features.FEATURE_NAMES.metrics, sharedEE);
69
- (0, _sharedHandlers.appendJsAttribute)(agentRef, 'enduser.id', value, _constants2.SET_USER_ID, true);
69
+ (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['API/' + _constants.SET_USER_ID + '/resetSession/called'], undefined, _features.FEATURE_NAMES.metrics, sharedEE);
70
+ (0, _sharedHandlers.appendJsAttribute)(agentRef, 'enduser.id', value, _constants.SET_USER_ID, true);
70
71
  }, 'session', sharedEE);
71
72
  (0, _registerHandler.registerHandler)('api-consent', accept => {
72
73
  agentRef.runtime.session.write({
@@ -5,9 +5,6 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.AggregateBase = void 0;
7
7
  var _featureBase = require("./feature-base");
8
- var _info = require("../../common/config/info");
9
- var _configure = require("../../loaders/configure/configure");
10
- var _nreum = require("../../common/window/nreum");
11
8
  var _drain = require("../../common/drain/drain");
12
9
  var _obfuscate = require("../../common/util/obfuscate");
13
10
  var _features = require("../../loaders/features/features");
@@ -30,7 +27,6 @@ class AggregateBase extends _featureBase.FeatureBase {
30
27
  */
31
28
  constructor(agentRef, featureName) {
32
29
  super(agentRef, featureName);
33
- this.checkConfiguration(agentRef);
34
30
  this.doOnceForAllAggregate(agentRef);
35
31
 
36
32
  /** @type {Boolean} indicates if custom attributes are combined in each event payload for size estimation purposes. this is set to true in derived classes that need to evaluate custom attributes separately from the event payload */
@@ -182,36 +178,6 @@ class AggregateBase extends _featureBase.FeatureBase {
182
178
  this.events.clearSave(this.harvestOpts);
183
179
  }
184
180
 
185
- /**
186
- * Checks for additional `jsAttributes` items to support backward compatibility with implementations of the agent where
187
- * loader configurations may appear after the loader code is executed.
188
- */
189
- checkConfiguration(existingAgent) {
190
- // NOTE: This check has to happen at aggregator load time
191
- if (!(0, _info.isValid)(existingAgent.info)) {
192
- const cdn = (0, _nreum.gosCDN)();
193
- let jsAttributes = {
194
- ...cdn.info?.jsAttributes
195
- };
196
- try {
197
- jsAttributes = {
198
- ...jsAttributes,
199
- ...existingAgent.info?.jsAttributes
200
- };
201
- } catch (err) {
202
- // do nothing
203
- }
204
- (0, _configure.configure)(existingAgent, {
205
- ...cdn,
206
- info: {
207
- ...cdn.info,
208
- jsAttributes
209
- },
210
- runtime: existingAgent.runtime
211
- }, existingAgent.runtime.loaderType);
212
- }
213
- }
214
-
215
181
  /**
216
182
  * These are actions related to shared resources that should be initialized once by whichever feature Aggregate subclass loads first.
217
183
  * This method should run after checkConfiguration, which may reset the agent's info/runtime object that is used here.
@@ -9,6 +9,9 @@ var _featureBase = require("./feature-base");
9
9
  var _load = require("../../common/window/load");
10
10
  var _runtime = require("../../common/constants/runtime");
11
11
  var _console = require("../../common/util/console");
12
+ var _info = require("../../common/config/info");
13
+ var _configure = require("../../loaders/configure/configure");
14
+ var _nreum = require("../../common/window/nreum");
12
15
  var _features = require("../../loaders/features/features");
13
16
  var _utils = require("../session_replay/shared/utils");
14
17
  var _featureGates = require("./feature-gates");
@@ -20,9 +23,11 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
20
23
  * SPDX-License-Identifier: Apache-2.0
21
24
  */ /**
22
25
  * @file Defines `InstrumentBase` to be used as the super of the Instrument classes implemented by each feature.
23
- * Inherits and executes the `checkConfiguration` method from [FeatureBase]{@link ./feature-base}, which also
24
- * exposes the `blocked` property.
26
+ * Validates and loads feature aggregates, including a one-time late configuration check before import.
27
+ * Inherits `blocked` behavior from [FeatureBase]{@link ./feature-base}.
25
28
  */
29
+ const checkedAgents = new WeakSet();
30
+
26
31
  /**
27
32
  * Base class for instrumenting a feature.
28
33
  * @extends FeatureBase
@@ -89,6 +94,14 @@ class InstrumentBase extends _featureBase.FeatureBase {
89
94
  // this will resolve immediately if the feature is auto-started,
90
95
  // or otherwise when the manual-start-all event is emitted by the start API
91
96
  await this.deferred;
97
+ this.#checkConfiguration(agentRef); // check for late-appearing 'info' config on the page
98
+ if (!(0, _info.isValid)(agentRef.info)) {
99
+ // if there still isn't valid info, then we can't proceed with session setup or importing the aggregates
100
+ (0, _console.warn)(43);
101
+ agentRef.ee.abort();
102
+ this.loadedSuccessfully(false);
103
+ return;
104
+ }
92
105
  let session;
93
106
  try {
94
107
  if ((0, _featureGates.canEnableSessionTracking)(agentRef.init)) {
@@ -153,5 +166,37 @@ class InstrumentBase extends _featureBase.FeatureBase {
153
166
  return true;
154
167
  }
155
168
  }
169
+
170
+ /**
171
+ * Checks for additional `jsAttributes` items to support backward compatibility with implementations of the agent where
172
+ * loader configurations may appear after the loader code is executed.
173
+ */
174
+ #checkConfiguration(existingAgent) {
175
+ if (checkedAgents.has(existingAgent)) return;
176
+ checkedAgents.add(existingAgent);
177
+ // NOTE: This check has to happen at load time
178
+ if (!(0, _info.isValid)(existingAgent.info)) {
179
+ const cdn = (0, _nreum.gosCDN)();
180
+ let jsAttributes = {
181
+ ...cdn.info?.jsAttributes
182
+ };
183
+ try {
184
+ jsAttributes = {
185
+ ...jsAttributes,
186
+ ...existingAgent.info?.jsAttributes
187
+ };
188
+ } catch (err) {
189
+ // do nothing
190
+ }
191
+ (0, _configure.configure)(existingAgent, {
192
+ ...cdn,
193
+ info: {
194
+ ...cdn.info,
195
+ jsAttributes
196
+ },
197
+ runtime: existingAgent.runtime
198
+ }, existingAgent.runtime.loaderType);
199
+ }
200
+ }
156
201
  }
157
202
  exports.InstrumentBase = InstrumentBase;
@@ -235,7 +235,7 @@ class ApiBase {
235
235
 
236
236
  /**
237
237
  * Accepts or rejects consent when the agent is configured to require consent before harvesting.
238
- * The consent state is stored in session storage inside the NRBA_SESSION object.
238
+ * The consent state is stored in the agent session object in browser storage.
239
239
  * {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/consent/}
240
240
  * @param {boolean?} accept Whether to accept or reject consent. Defaults to true (accept) if left undefined.
241
241
  */
@@ -11,7 +11,7 @@
11
11
  /**
12
12
  * Exposes the version of the agent
13
13
  */
14
- export const VERSION = "1.315.0";
14
+ export const VERSION = "1.316.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.315.0";
14
+ export const VERSION = "1.316.0";
15
15
 
16
16
  /**
17
17
  * Exposes the build type of the agent
@@ -1,9 +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
- export const PREFIX = 'NRBA';
6
- export const DEFAULT_KEY = 'SESSION';
5
+ export const SESSION_STORAGE_KEY_PREFIX = 'NRBA_SESSION::';
7
6
  export const DEFAULT_EXPIRES_MS = 14400000;
8
7
  export const DEFAULT_INACTIVE_MS = 1800000;
9
8
  export const SESSION_EVENTS = {
@@ -7,7 +7,7 @@ import { warn } from '../util/console';
7
7
  import { stringify } from '../util/stringify';
8
8
  import { Timer } from '../timer/timer';
9
9
  import { isBrowserScope } from '../constants/runtime';
10
- import { DEFAULT_EXPIRES_MS, DEFAULT_INACTIVE_MS, MODE, PREFIX, SESSION_EVENTS, SESSION_EVENT_TYPES } from './constants';
10
+ import { DEFAULT_EXPIRES_MS, DEFAULT_INACTIVE_MS, MODE, SESSION_EVENTS, SESSION_EVENT_TYPES, SESSION_STORAGE_KEY_PREFIX } from './constants';
11
11
  import { InteractionTimer } from '../timer/interaction-timer';
12
12
  import { wrapEvents } from '../wrap/wrap-events';
13
13
  import { getModeledObject } from '../config/configurable';
@@ -39,7 +39,7 @@ const model = {
39
39
  export class SessionEntity {
40
40
  /**
41
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.
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.
42
+ * Session data is stored at NRBA_SESSION::{key}, where key can be app-scoped by the caller to avoid collisions across multiple agents on the same origin.
43
43
  * The value can be overridden in the constructor, but will default to a unique 16 character hex string
44
44
  * expiresMs and inactiveMs are used to "expire" the session, but can be overridden in the constructor. Pass 0 to disable expiration timers.
45
45
  */
@@ -168,7 +168,7 @@ export class SessionEntity {
168
168
 
169
169
  // This is the actual key appended to the storage API
170
170
  get lookupKey() {
171
- return "".concat(PREFIX, "_").concat(this.key);
171
+ return "".concat(SESSION_STORAGE_KEY_PREFIX).concat(this.key);
172
172
  }
173
173
  sync(data) {
174
174
  Object.assign(this.state, data);
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ // Deterministic 32-bit FNV-1a hash, rendered as a fixed 8-char hex string.
6
+ function hashTo8Hex(value) {
7
+ let hash = 0x811c9dc5;
8
+ for (let i = 0; i < value.length; i++) {
9
+ hash ^= value.charCodeAt(i);
10
+ hash = Math.imul(hash, 0x01000193);
11
+ }
12
+ return (hash >>> 0).toString(16).padStart(8, '0');
13
+ }
14
+ export function getAppSessionHash(licenseKey, applicationID) {
15
+ return hashTo8Hex("".concat(String(licenseKey), ":").concat(String(applicationID)));
16
+ }
@@ -5,7 +5,6 @@
5
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
- import { isValid } from '../../../common/config/info';
9
8
  import * as CONSTANTS from '../constants';
10
9
  import { getActivatedFeaturesFlags } from './initialized-features';
11
10
  import { activateFeatures } from '../../../common/util/feature-flags';
@@ -31,10 +30,6 @@ export class Aggregate extends AggregateBase {
31
30
  this.firstByteToWindowLoad = 0; // our "frontend" duration
32
31
  this.firstByteToDomContent = 0; // our "dom processing" duration
33
32
  this.retries = 0;
34
- if (!isValid(agentRef.info)) {
35
- this.ee.abort();
36
- return warn(43);
37
- }
38
33
  agentRef.runtime.timeKeeper = new TimeKeeper(agentRef.runtime.session);
39
34
  if (isBrowserScope) {
40
35
  timeToFirstByte.subscribe(({
@@ -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,7 +7,8 @@
7
7
  */
8
8
 
9
9
  import { handle } from '../../../common/event-emitter/handle';
10
- import { DEFAULT_KEY, MODE, PREFIX } from '../../../common/session/constants';
10
+ import { MODE, SESSION_STORAGE_KEY_PREFIX } from '../../../common/session/constants';
11
+ import { getAppSessionHash } from '../../../common/session/session-key';
11
12
  import { InstrumentBase } from '../../utils/instrument-base';
12
13
  import { hasReplayPrerequisite, isPreloadAllowed } from '../shared/utils';
13
14
  import { ERROR_DURING_REPLAY, FEATURE_NAME, TRIGGERS } from '../constants';
@@ -27,8 +28,9 @@ export class Instrument extends InstrumentBase {
27
28
  setupRecordReplayAPI(agentRef);
28
29
  setupPauseReplayAPI(agentRef);
29
30
  let session;
31
+ const fullNamespacedKey = "".concat(SESSION_STORAGE_KEY_PREFIX).concat(getAppSessionHash(agentRef.info.licenseKey, agentRef.info.applicationID));
30
32
  try {
31
- session = JSON.parse(localStorage.getItem("".concat(PREFIX, "_").concat(DEFAULT_KEY)));
33
+ session = JSON.parse(localStorage.getItem(fullNamespacedKey));
32
34
  } catch (err) {}
33
35
  if (hasReplayPrerequisite(agentRef.init)) {
34
36
  this.ee.on(RECORD_REPLAY, () => this.#apiStartOrRestartReplay());
@@ -6,7 +6,7 @@ import { drain } from '../../common/drain/drain';
6
6
  import { registerHandler } from '../../common/event-emitter/register-handler';
7
7
  import { SessionEntity } from '../../common/session/session-entity';
8
8
  import { LocalStorage } from '../../common/storage/local-storage.js';
9
- import { DEFAULT_KEY } from '../../common/session/constants';
9
+ import { getAppSessionHash } from '../../common/session/session-key';
10
10
  import { mergeInfo } from '../../common/config/info';
11
11
  import { trackObjectAttributeSize } from '../../common/util/attribute-size';
12
12
  import { handle } from '../../common/event-emitter/handle';
@@ -18,9 +18,10 @@ export function setupAgentSession(agentRef) {
18
18
  if (agentRef.runtime.session) return agentRef.runtime.session; // already setup
19
19
 
20
20
  const sessionInit = agentRef.init.session;
21
+ const namespacedKey = getAppSessionHash(agentRef.info.licenseKey, agentRef.info.applicationID);
21
22
  agentRef.runtime.session = new SessionEntity({
22
23
  agentRef,
23
- key: DEFAULT_KEY,
24
+ key: namespacedKey,
24
25
  storage: new LocalStorage(),
25
26
  expiresMs: sessionInit?.expiresMs,
26
27
  inactiveMs: sessionInit?.inactiveMs
@@ -3,9 +3,6 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { FeatureBase } from './feature-base';
6
- import { isValid } from '../../common/config/info';
7
- import { configure } from '../../loaders/configure/configure';
8
- import { gosCDN } from '../../common/window/nreum';
9
6
  import { drain } from '../../common/drain/drain';
10
7
  import { Obfuscator } from '../../common/util/obfuscate';
11
8
  import { FEATURE_NAMES } from '../../loaders/features/features';
@@ -23,7 +20,6 @@ export class AggregateBase extends FeatureBase {
23
20
  */
24
21
  constructor(agentRef, featureName) {
25
22
  super(agentRef, featureName);
26
- this.checkConfiguration(agentRef);
27
23
  this.doOnceForAllAggregate(agentRef);
28
24
 
29
25
  /** @type {Boolean} indicates if custom attributes are combined in each event payload for size estimation purposes. this is set to true in derived classes that need to evaluate custom attributes separately from the event payload */
@@ -175,36 +171,6 @@ export class AggregateBase extends FeatureBase {
175
171
  this.events.clearSave(this.harvestOpts);
176
172
  }
177
173
 
178
- /**
179
- * Checks for additional `jsAttributes` items to support backward compatibility with implementations of the agent where
180
- * loader configurations may appear after the loader code is executed.
181
- */
182
- checkConfiguration(existingAgent) {
183
- // NOTE: This check has to happen at aggregator load time
184
- if (!isValid(existingAgent.info)) {
185
- const cdn = gosCDN();
186
- let jsAttributes = {
187
- ...cdn.info?.jsAttributes
188
- };
189
- try {
190
- jsAttributes = {
191
- ...jsAttributes,
192
- ...existingAgent.info?.jsAttributes
193
- };
194
- } catch (err) {
195
- // do nothing
196
- }
197
- configure(existingAgent, {
198
- ...cdn,
199
- info: {
200
- ...cdn.info,
201
- jsAttributes
202
- },
203
- runtime: existingAgent.runtime
204
- }, existingAgent.runtime.loaderType);
205
- }
206
- }
207
-
208
174
  /**
209
175
  * These are actions related to shared resources that should be initialized once by whichever feature Aggregate subclass loads first.
210
176
  * This method should run after checkConfiguration, which may reset the agent's info/runtime object that is used here.
@@ -5,8 +5,8 @@
5
5
 
6
6
  /**
7
7
  * @file Defines `InstrumentBase` to be used as the super of the Instrument classes implemented by each feature.
8
- * Inherits and executes the `checkConfiguration` method from [FeatureBase]{@link ./feature-base}, which also
9
- * exposes the `blocked` property.
8
+ * Validates and loads feature aggregates, including a one-time late configuration check before import.
9
+ * Inherits `blocked` behavior from [FeatureBase]{@link ./feature-base}.
10
10
  */
11
11
 
12
12
  import { drain, registerDrain } from '../../common/drain/drain';
@@ -14,12 +14,16 @@ import { FeatureBase } from './feature-base';
14
14
  import { onWindowLoad } from '../../common/window/load';
15
15
  import { isBrowserScope } from '../../common/constants/runtime';
16
16
  import { warn } from '../../common/util/console';
17
+ import { isValid } from '../../common/config/info';
18
+ import { configure } from '../../loaders/configure/configure';
19
+ import { gosCDN } from '../../common/window/nreum';
17
20
  import { FEATURE_NAMES } from '../../loaders/features/features';
18
21
  import { hasReplayPrerequisite } from '../session_replay/shared/utils';
19
22
  import { canEnableSessionTracking } from './feature-gates';
20
23
  import { single } from '../../common/util/invoke';
21
24
  import { SESSION_ERROR } from '../../common/constants/agent-constants';
22
25
  import { handle } from '../../common/event-emitter/handle';
26
+ const checkedAgents = new WeakSet();
23
27
 
24
28
  /**
25
29
  * Base class for instrumenting a feature.
@@ -87,6 +91,14 @@ export class InstrumentBase extends FeatureBase {
87
91
  // this will resolve immediately if the feature is auto-started,
88
92
  // or otherwise when the manual-start-all event is emitted by the start API
89
93
  await this.deferred;
94
+ this.#checkConfiguration(agentRef); // check for late-appearing 'info' config on the page
95
+ if (!isValid(agentRef.info)) {
96
+ // if there still isn't valid info, then we can't proceed with session setup or importing the aggregates
97
+ warn(43);
98
+ agentRef.ee.abort();
99
+ this.loadedSuccessfully(false);
100
+ return;
101
+ }
90
102
  let session;
91
103
  try {
92
104
  if (canEnableSessionTracking(agentRef.init)) {
@@ -151,4 +163,36 @@ export class InstrumentBase extends FeatureBase {
151
163
  return true;
152
164
  }
153
165
  }
166
+
167
+ /**
168
+ * Checks for additional `jsAttributes` items to support backward compatibility with implementations of the agent where
169
+ * loader configurations may appear after the loader code is executed.
170
+ */
171
+ #checkConfiguration(existingAgent) {
172
+ if (checkedAgents.has(existingAgent)) return;
173
+ checkedAgents.add(existingAgent);
174
+ // NOTE: This check has to happen at load time
175
+ if (!isValid(existingAgent.info)) {
176
+ const cdn = gosCDN();
177
+ let jsAttributes = {
178
+ ...cdn.info?.jsAttributes
179
+ };
180
+ try {
181
+ jsAttributes = {
182
+ ...jsAttributes,
183
+ ...existingAgent.info?.jsAttributes
184
+ };
185
+ } catch (err) {
186
+ // do nothing
187
+ }
188
+ configure(existingAgent, {
189
+ ...cdn,
190
+ info: {
191
+ ...cdn.info,
192
+ jsAttributes
193
+ },
194
+ runtime: existingAgent.runtime
195
+ }, existingAgent.runtime.loaderType);
196
+ }
197
+ }
154
198
  }
@@ -229,7 +229,7 @@ export class ApiBase {
229
229
 
230
230
  /**
231
231
  * Accepts or rejects consent when the agent is configured to require consent before harvesting.
232
- * The consent state is stored in session storage inside the NRBA_SESSION object.
232
+ * The consent state is stored in the agent session object in browser storage.
233
233
  * {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/consent/}
234
234
  * @param {boolean?} accept Whether to accept or reject consent. Defaults to true (accept) if left undefined.
235
235
  */
@@ -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/browser-stack-matchers.js","../src/common/util/console.js","../src/common/util/data-size.js","../src/common/util/event-origin.js","../src/common/util/feature-flags.js","../src/common/util/get-or-set.js","../src/common/util/invoke.js","../src/common/util/monkey-patched.js","../src/common/util/obfuscate.js","../src/common/util/short-circuit.js","../src/common/util/stringify.js","../src/common/util/submit-data.js","../src/common/util/text.js","../src/common/util/traverse.js","../src/common/util/type-check.js","../src/common/util/webdriver-detection.js","../src/common/v2/script-correlation.js","../src/common/v2/script-tracker.js","../src/common/v2/utils.js","../src/common/vitals/constants.js","../src/common/vitals/cumulative-layout-shift.js","../src/common/vitals/first-contentful-paint.js","../src/common/vitals/first-paint.js","../src/common/vitals/interaction-to-next-paint.js","../src/common/vitals/largest-contentful-paint.js","../src/common/vitals/load-time.js","../src/common/vitals/time-to-first-byte.js","../src/common/vitals/vital-metric.js","../src/common/window/load.js","../src/common/window/nreum.js","../src/common/window/page-visibility.js","../src/common/wrap/wrap-events.js","../src/common/wrap/wrap-fetch.js","../src/common/wrap/wrap-function.js","../src/common/wrap/wrap-history.js","../src/common/wrap/wrap-logger.js","../src/common/wrap/wrap-websocket.js","../src/common/wrap/wrap-xhr.js","../src/features/ajax/constants.js","../src/features/ajax/index.js","../src/features/ajax/aggregate/gql.js","../src/features/ajax/aggregate/index.js","../src/features/ajax/instrument/distributed-tracing.js","../src/features/ajax/instrument/index.js","../src/features/ajax/instrument/response-size.js","../src/features/generic_events/constants.js","../src/features/generic_events/index.js","../src/features/generic_events/aggregate/index.js","../src/features/generic_events/aggregate/user-actions/aggregated-user-action.js","../src/features/generic_events/aggregate/user-actions/user-actions-aggregator.js","../src/features/generic_events/instrument/index.js","../src/features/jserrors/constants.js","../src/features/jserrors/index.js","../src/features/jserrors/aggregate/canonical-function-name.js","../src/features/jserrors/aggregate/cause-string.js","../src/features/jserrors/aggregate/compute-stack-trace.js","../src/features/jserrors/aggregate/format-stack-trace.js","../src/features/jserrors/aggregate/index.js","../src/features/jserrors/aggregate/internal-errors.js","../src/features/jserrors/aggregate/string-hash-code.js","../src/features/jserrors/instrument/index.js","../src/features/jserrors/shared/cast-error.js","../src/features/jserrors/shared/uncaught-error.js","../src/features/logging/constants.js","../src/features/logging/index.js","../src/features/logging/aggregate/index.js","../src/features/logging/instrument/index.js","../src/features/logging/shared/log.js","../src/features/logging/shared/utils.js","../src/features/metrics/constants.js","../src/features/metrics/index.js","../src/features/metrics/aggregate/framework-detection.js","../src/features/metrics/aggregate/harvest-metadata.js","../src/features/metrics/aggregate/index.js","../src/features/metrics/instrument/index.js","../src/features/page_action/constants.js","../src/features/page_action/index.js","../src/features/page_action/instrument/index.js","../src/features/page_view_event/constants.js","../src/features/page_view_event/index.js","../src/features/page_view_event/aggregate/index.js","../src/features/page_view_event/aggregate/initialized-features.js","../src/features/page_view_event/instrument/index.js","../src/features/page_view_timing/constants.js","../src/features/page_view_timing/index.js","../src/features/page_view_timing/aggregate/index.js","../src/features/page_view_timing/instrument/index.js","../src/features/session_replay/constants.js","../src/features/session_replay/index.js","../src/features/session_replay/aggregate/index.js","../src/features/session_replay/instrument/index.js","../src/features/session_replay/shared/recorder-events.js","../src/features/session_replay/shared/recorder.js","../src/features/session_replay/shared/stylesheet-evaluator.js","../src/features/session_replay/shared/utils.js","../src/features/session_trace/constants.js","../src/features/session_trace/index.js","../src/features/session_trace/aggregate/index.js","../src/features/session_trace/aggregate/trace/node.js","../src/features/session_trace/aggregate/trace/storage.js","../src/features/session_trace/aggregate/trace/utils.js","../src/features/session_trace/instrument/index.js","../src/features/soft_navigations/constants.js","../src/features/soft_navigations/index.js","../src/features/soft_navigations/aggregate/ajax-node.js","../src/features/soft_navigations/aggregate/bel-node.js","../src/features/soft_navigations/aggregate/index.js","../src/features/soft_navigations/aggregate/initial-page-load-interaction.js","../src/features/soft_navigations/aggregate/interaction.js","../src/features/soft_navigations/instrument/index.js","../src/features/utils/agent-session.js","../src/features/utils/aggregate-base.js","../src/features/utils/event-buffer.js","../src/features/utils/feature-base.js","../src/features/utils/feature-gates.js","../src/features/utils/instrument-base.js","../src/interfaces/registered-entity.js","../src/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/session/session-key.js","../src/common/storage/local-storage.js","../src/common/timer/interaction-timer.js","../src/common/timer/timer.js","../src/common/timing/nav-timing.js","../src/common/timing/now.js","../src/common/timing/time-keeper.js","../src/common/unload/eol.js","../src/common/url/canonicalize-url.js","../src/common/url/clean-url.js","../src/common/url/encode.js","../src/common/url/extract-url.js","../src/common/url/location.js","../src/common/url/parse-url.js","../src/common/url/protocol.js","../src/common/util/attribute-size.js","../src/common/util/browser-stack-matchers.js","../src/common/util/console.js","../src/common/util/data-size.js","../src/common/util/event-origin.js","../src/common/util/feature-flags.js","../src/common/util/get-or-set.js","../src/common/util/invoke.js","../src/common/util/monkey-patched.js","../src/common/util/obfuscate.js","../src/common/util/short-circuit.js","../src/common/util/stringify.js","../src/common/util/submit-data.js","../src/common/util/text.js","../src/common/util/traverse.js","../src/common/util/type-check.js","../src/common/util/webdriver-detection.js","../src/common/v2/script-correlation.js","../src/common/v2/script-tracker.js","../src/common/v2/utils.js","../src/common/vitals/constants.js","../src/common/vitals/cumulative-layout-shift.js","../src/common/vitals/first-contentful-paint.js","../src/common/vitals/first-paint.js","../src/common/vitals/interaction-to-next-paint.js","../src/common/vitals/largest-contentful-paint.js","../src/common/vitals/load-time.js","../src/common/vitals/time-to-first-byte.js","../src/common/vitals/vital-metric.js","../src/common/window/load.js","../src/common/window/nreum.js","../src/common/window/page-visibility.js","../src/common/wrap/wrap-events.js","../src/common/wrap/wrap-fetch.js","../src/common/wrap/wrap-function.js","../src/common/wrap/wrap-history.js","../src/common/wrap/wrap-logger.js","../src/common/wrap/wrap-websocket.js","../src/common/wrap/wrap-xhr.js","../src/features/ajax/constants.js","../src/features/ajax/index.js","../src/features/ajax/aggregate/gql.js","../src/features/ajax/aggregate/index.js","../src/features/ajax/instrument/distributed-tracing.js","../src/features/ajax/instrument/index.js","../src/features/ajax/instrument/response-size.js","../src/features/generic_events/constants.js","../src/features/generic_events/index.js","../src/features/generic_events/aggregate/index.js","../src/features/generic_events/aggregate/user-actions/aggregated-user-action.js","../src/features/generic_events/aggregate/user-actions/user-actions-aggregator.js","../src/features/generic_events/instrument/index.js","../src/features/jserrors/constants.js","../src/features/jserrors/index.js","../src/features/jserrors/aggregate/canonical-function-name.js","../src/features/jserrors/aggregate/cause-string.js","../src/features/jserrors/aggregate/compute-stack-trace.js","../src/features/jserrors/aggregate/format-stack-trace.js","../src/features/jserrors/aggregate/index.js","../src/features/jserrors/aggregate/internal-errors.js","../src/features/jserrors/aggregate/string-hash-code.js","../src/features/jserrors/instrument/index.js","../src/features/jserrors/shared/cast-error.js","../src/features/jserrors/shared/uncaught-error.js","../src/features/logging/constants.js","../src/features/logging/index.js","../src/features/logging/aggregate/index.js","../src/features/logging/instrument/index.js","../src/features/logging/shared/log.js","../src/features/logging/shared/utils.js","../src/features/metrics/constants.js","../src/features/metrics/index.js","../src/features/metrics/aggregate/framework-detection.js","../src/features/metrics/aggregate/harvest-metadata.js","../src/features/metrics/aggregate/index.js","../src/features/metrics/instrument/index.js","../src/features/page_action/constants.js","../src/features/page_action/index.js","../src/features/page_action/instrument/index.js","../src/features/page_view_event/constants.js","../src/features/page_view_event/index.js","../src/features/page_view_event/aggregate/index.js","../src/features/page_view_event/aggregate/initialized-features.js","../src/features/page_view_event/instrument/index.js","../src/features/page_view_timing/constants.js","../src/features/page_view_timing/index.js","../src/features/page_view_timing/aggregate/index.js","../src/features/page_view_timing/instrument/index.js","../src/features/session_replay/constants.js","../src/features/session_replay/index.js","../src/features/session_replay/aggregate/index.js","../src/features/session_replay/instrument/index.js","../src/features/session_replay/shared/recorder-events.js","../src/features/session_replay/shared/recorder.js","../src/features/session_replay/shared/stylesheet-evaluator.js","../src/features/session_replay/shared/utils.js","../src/features/session_trace/constants.js","../src/features/session_trace/index.js","../src/features/session_trace/aggregate/index.js","../src/features/session_trace/aggregate/trace/node.js","../src/features/session_trace/aggregate/trace/storage.js","../src/features/session_trace/aggregate/trace/utils.js","../src/features/session_trace/instrument/index.js","../src/features/soft_navigations/constants.js","../src/features/soft_navigations/index.js","../src/features/soft_navigations/aggregate/ajax-node.js","../src/features/soft_navigations/aggregate/bel-node.js","../src/features/soft_navigations/aggregate/index.js","../src/features/soft_navigations/aggregate/initial-page-load-interaction.js","../src/features/soft_navigations/aggregate/interaction.js","../src/features/soft_navigations/instrument/index.js","../src/features/utils/agent-session.js","../src/features/utils/aggregate-base.js","../src/features/utils/event-buffer.js","../src/features/utils/feature-base.js","../src/features/utils/feature-gates.js","../src/features/utils/instrument-base.js","../src/interfaces/registered-entity.js","../src/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,9 +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
- export const PREFIX: "NRBA";
6
- export const DEFAULT_KEY: "SESSION";
5
+ export const SESSION_STORAGE_KEY_PREFIX: "NRBA_SESSION::";
7
6
  export const DEFAULT_EXPIRES_MS: 14400000;
8
7
  export const DEFAULT_INACTIVE_MS: 1800000;
9
8
  export namespace SESSION_EVENTS {
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../src/common/session/constants.js"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAsB,MAAM,CAAA;AAC5B,0BAA2B,SAAS,CAAA;AACpC,iCAAkC,QAAQ,CAAA;AAC1C,kCAAmC,OAAO,CAAA"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../src/common/session/constants.js"],"names":[],"mappings":"AAAA;;;GAGG;AACH,yCAA0C,gBAAgB,CAAA;AAC1D,iCAAkC,QAAQ,CAAA;AAC1C,kCAAmC,OAAO,CAAA"}
@@ -1,7 +1,7 @@
1
1
  export class SessionEntity {
2
2
  /**
3
3
  * Create a self-managing Session Entity. This entity is scoped to the agent which triggered it, allowing for multiple simultaneous session objects to exist.
4
- * 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.
4
+ * Session data is stored at NRBA_SESSION::{key}, where key can be app-scoped by the caller to avoid collisions across multiple agents on the same origin.
5
5
  * The value can be overridden in the constructor, but will default to a unique 16 character hex string
6
6
  * expiresMs and inactiveMs are used to "expire" the session, but can be overridden in the constructor. Pass 0 to disable expiration timers.
7
7
  */
@@ -0,0 +1,2 @@
1
+ export function getAppSessionHash(licenseKey: any, applicationID: any): string;
2
+ //# sourceMappingURL=session-key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-key.d.ts","sourceRoot":"","sources":["../../../../src/common/session/session-key.js"],"names":[],"mappings":"AAcA,+EAEC"}
@@ -1 +1 @@
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
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_event/aggregate/index.js"],"names":[],"mappings":"AAuBA;IACE,2BAA2C;IAE3C,2BAwBC;IArBC,iBAAoB;IAEpB,wBAAwB;IACxB,8BAA8B;IAC9B,8BAA8B;IAC9B,gBAAgB;IAkBlB;;;;OAIG;IACH,2BAFW,GAAC,QAiEX;IAbC;;;;;;;;;;;;;;mBAOC;IANC,iCAAyB;IAc7B,kCAEC;IAED;;;;;;aAiGC;CACF;8BArN6B,4BAA4B"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/instrument/index.js"],"names":[],"mappings":"AAiBA;IACE,2BAAiC;IAMjC,2BAgCC;IAnCD,+CAA+C;IAC/C,cAAQ;IA8BF,sBAAwB;IAmB9B;;;OAGG;IACH,+BAkBC;;CAgBF;AAED,8CAAuC;+BArGR,6BAA6B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/instrument/index.js"],"names":[],"mappings":"AAkBA;IACE,2BAAiC;IAMjC,2BAiCC;IApCD,+CAA+C;IAC/C,cAAQ;IA+BF,sBAAwB;IAmB9B;;;OAGG;IACH,+BAkBC;;CAgBF;AAED,8CAAuC;+BAtGR,6BAA6B"}
@@ -1 +1 @@
1
- {"version":3,"file":"agent-session.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/agent-session.js"],"names":[],"mappings":"AAiBA,sDA0DC"}
1
+ {"version":3,"file":"agent-session.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/agent-session.js"],"names":[],"mappings":"AAiBA,sDA2DC"}
@@ -58,11 +58,6 @@ export class AggregateBase extends FeatureBase {
58
58
  sent?: boolean | undefined;
59
59
  retry?: boolean | undefined;
60
60
  }): void;
61
- /**
62
- * Checks for additional `jsAttributes` items to support backward compatibility with implementations of the agent where
63
- * loader configurations may appear after the loader code is executed.
64
- */
65
- checkConfiguration(existingAgent: any): void;
66
61
  /**
67
62
  * These are actions related to shared resources that should be initialized once by whichever feature Aggregate subclass loads first.
68
63
  * This method should run after checkConfiguration, which may reset the agent's info/runtime object that is used here.
@@ -1 +1 @@
1
- {"version":3,"file":"aggregate-base.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/aggregate-base.js"],"names":[],"mappings":"AAkBA;IACE;;;;OAIG;IACH,sBAHW,MAAM,eACN,MAAM,EAmBhB;IAZC,uOAAuO;IACvO,qCAAwC;IACxC,gLAAgL;IAChL,yBAA2B;IAC3B,sFAAsF;IACtF,oBAAuB;IAEvB,gBAAqB;IAqBjB,YAAsE;IAW5E,iNAAiN;IACjN,0CAEC;IAED;;;;OAIG;IACH,8BAHU,MAAM,CAKf;IAED,qBAOC;IAFG,6BAAmB;IAIvB;;;;OAIG;IACH,2BAOC;IAED;;;;OAIG;IACH,yBAHW,MAAM,EAAE,gBAwBlB;IAED;;OAEG;IACH,cAEC;IAED,qCAEC;IAED;;;;;;OAMG;IACH,uDAJW,MAAM,GAAC,SAAS,SAsB1B;IAED;;;;;OAKG;IACH,4BAHG;QAAyB,IAAI,GAArB,OAAO,YAAC;QACS,KAAK,GAAtB,OAAO,YAAC;KAClB,QAKA;IAED;;;OAGG;IACH,6CAsBC;IAED;;;OAGG;IACH,2CAKC;IAHC,gBAA6C;IAK/C;;;;OAIG;IACH,uCAHW,GAAC,UACD,GAAC,QAIX;;CACF;4BAlO2B,gBAAgB"}
1
+ {"version":3,"file":"aggregate-base.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/aggregate-base.js"],"names":[],"mappings":"AAeA;IACE;;;;OAIG;IACH,sBAHW,MAAM,eACN,MAAM,EAkBhB;IAZC,uOAAuO;IACvO,qCAAwC;IACxC,gLAAgL;IAChL,yBAA2B;IAC3B,sFAAsF;IACtF,oBAAuB;IAEvB,gBAAqB;IAqBjB,YAAsE;IAW5E,iNAAiN;IACjN,0CAEC;IAED;;;;OAIG;IACH,8BAHU,MAAM,CAKf;IAED,qBAOC;IAFG,6BAAmB;IAIvB;;;;OAIG;IACH,2BAOC;IAED;;;;OAIG;IACH,yBAHW,MAAM,EAAE,gBAwBlB;IAED;;OAEG;IACH,cAEC;IAED,qCAEC;IAED;;;;;;OAMG;IACH,uDAJW,MAAM,GAAC,SAAS,SAsB1B;IAED;;;;;OAKG;IACH,4BAHG;QAAyB,IAAI,GAArB,OAAO,YAAC;QACS,KAAK,GAAtB,OAAO,YAAC;KAClB,QAKA;IAED;;;OAGG;IACH,2CAKC;IAHC,gBAA6C;IAK/C;;;;OAIG;IACH,uCAHW,GAAC,UACD,GAAC,QAIX;;CACF;4BAlM2B,gBAAgB"}
@@ -1 +1 @@
1
- {"version":3,"file":"instrument-base.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/instrument-base.js"],"names":[],"mappings":"AAuBA;;;GAGG;AACH;IACE;;;;OAIG;IACH,sBAHW,MAAM,eACN,MAAM,EA0ChB;IArCC,8IAA8I;IAC9I,cADW,WAAW,SAAS,CACF;IAE7B;;;MAGE;IACF,eAHU,OAAO,kBAAkB,EAAE,aAAa,CAGpB;IAE9B;;;MAGE;IACF,iCAAmC;IACnC,kCAEE;IAEF;;;MAGE;IACF,uBAAiC;IAiBnC;;;;;;;OAOG;IACH,2BALW,MAAM,qDAEN,MAAM,QAqDhB;;CAmBF;4BA5I2B,gBAAgB"}
1
+ {"version":3,"file":"instrument-base.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/instrument-base.js"],"names":[],"mappings":"AA4BA;;;GAGG;AACH;IACE;;;;OAIG;IACH,sBAHW,MAAM,eACN,MAAM,EA0ChB;IArCC,8IAA8I;IAC9I,cADW,WAAW,SAAS,CACF;IAE7B;;;MAGE;IACF,eAHU,OAAO,kBAAkB,EAAE,aAAa,CAGpB;IAE9B;;;MAGE;IACF,iCAAmC;IACnC,kCAEE;IAEF;;;MAGE;IACF,uBAAiC;IAiBnC;;;;;;;OAOG;IACH,2BALW,MAAM,qDAEN,MAAM,QA6DhB;;CAiDF;4BAvL2B,gBAAgB"}
@@ -182,7 +182,7 @@ export class ApiBase {
182
182
  };
183
183
  /**
184
184
  * Accepts or rejects consent when the agent is configured to require consent before harvesting.
185
- * The consent state is stored in session storage inside the NRBA_SESSION object.
185
+ * The consent state is stored in the agent session object in browser storage.
186
186
  * {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/consent/}
187
187
  * @param {boolean?} accept Whether to accept or reject consent. Defaults to true (accept) if left undefined.
188
188
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/browser-agent",
3
- "version": "1.315.0",
3
+ "version": "1.316.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,9 +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
- export const PREFIX = 'NRBA'
6
- export const DEFAULT_KEY = 'SESSION'
5
+ export const SESSION_STORAGE_KEY_PREFIX = 'NRBA_SESSION::'
7
6
  export const DEFAULT_EXPIRES_MS = 14400000
8
7
  export const DEFAULT_INACTIVE_MS = 1800000
9
8
 
@@ -7,7 +7,7 @@ import { warn } from '../util/console'
7
7
  import { stringify } from '../util/stringify'
8
8
  import { Timer } from '../timer/timer'
9
9
  import { isBrowserScope } from '../constants/runtime'
10
- import { DEFAULT_EXPIRES_MS, DEFAULT_INACTIVE_MS, MODE, PREFIX, SESSION_EVENTS, SESSION_EVENT_TYPES } from './constants'
10
+ import { DEFAULT_EXPIRES_MS, DEFAULT_INACTIVE_MS, MODE, SESSION_EVENTS, SESSION_EVENT_TYPES, SESSION_STORAGE_KEY_PREFIX } from './constants'
11
11
  import { InteractionTimer } from '../timer/interaction-timer'
12
12
  import { wrapEvents } from '../wrap/wrap-events'
13
13
  import { getModeledObject } from '../config/configurable'
@@ -39,7 +39,7 @@ const model = {
39
39
  export class SessionEntity {
40
40
  /**
41
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.
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.
42
+ * Session data is stored at NRBA_SESSION::{key}, where key can be app-scoped by the caller to avoid collisions across multiple agents on the same origin.
43
43
  * The value can be overridden in the constructor, but will default to a unique 16 character hex string
44
44
  * expiresMs and inactiveMs are used to "expire" the session, but can be overridden in the constructor. Pass 0 to disable expiration timers.
45
45
  */
@@ -160,7 +160,7 @@ export class SessionEntity {
160
160
 
161
161
  // This is the actual key appended to the storage API
162
162
  get lookupKey () {
163
- return `${PREFIX}_${this.key}`
163
+ return `${SESSION_STORAGE_KEY_PREFIX}${this.key}`
164
164
  }
165
165
 
166
166
  sync (data) {
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ // Deterministic 32-bit FNV-1a hash, rendered as a fixed 8-char hex string.
6
+ function hashTo8Hex (value) {
7
+ let hash = 0x811c9dc5
8
+ for (let i = 0; i < value.length; i++) {
9
+ hash ^= value.charCodeAt(i)
10
+ hash = Math.imul(hash, 0x01000193)
11
+ }
12
+ return (hash >>> 0).toString(16).padStart(8, '0')
13
+ }
14
+
15
+ export function getAppSessionHash (licenseKey, applicationID) {
16
+ return hashTo8Hex(`${String(licenseKey)}:${String(applicationID)}`)
17
+ }
@@ -5,7 +5,6 @@
5
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
- import { isValid } from '../../../common/config/info'
9
8
  import * as CONSTANTS from '../constants'
10
9
  import { getActivatedFeaturesFlags } from './initialized-features'
11
10
  import { activateFeatures } from '../../../common/util/feature-flags'
@@ -35,10 +34,6 @@ export class Aggregate extends AggregateBase {
35
34
  this.firstByteToDomContent = 0 // our "dom processing" duration
36
35
  this.retries = 0
37
36
 
38
- if (!isValid(agentRef.info)) {
39
- this.ee.abort()
40
- return warn(43)
41
- }
42
37
  agentRef.runtime.timeKeeper = new TimeKeeper(agentRef.runtime.session)
43
38
 
44
39
  if (isBrowserScope) {
@@ -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,7 +7,8 @@
7
7
  */
8
8
 
9
9
  import { handle } from '../../../common/event-emitter/handle'
10
- import { DEFAULT_KEY, MODE, PREFIX } from '../../../common/session/constants'
10
+ import { MODE, SESSION_STORAGE_KEY_PREFIX } from '../../../common/session/constants'
11
+ import { getAppSessionHash } from '../../../common/session/session-key'
11
12
  import { InstrumentBase } from '../../utils/instrument-base'
12
13
  import { hasReplayPrerequisite, isPreloadAllowed } from '../shared/utils'
13
14
  import { ERROR_DURING_REPLAY, FEATURE_NAME, TRIGGERS } from '../constants'
@@ -30,8 +31,9 @@ export class Instrument extends InstrumentBase {
30
31
  setupPauseReplayAPI(agentRef)
31
32
 
32
33
  let session
34
+ const fullNamespacedKey = `${SESSION_STORAGE_KEY_PREFIX}${getAppSessionHash(agentRef.info.licenseKey, agentRef.info.applicationID)}`
33
35
  try {
34
- session = JSON.parse(localStorage.getItem(`${PREFIX}_${DEFAULT_KEY}`))
36
+ session = JSON.parse(localStorage.getItem(fullNamespacedKey))
35
37
  } catch (err) { }
36
38
 
37
39
  if (hasReplayPrerequisite(agentRef.init)) {
@@ -6,7 +6,7 @@ import { drain } from '../../common/drain/drain'
6
6
  import { registerHandler } from '../../common/event-emitter/register-handler'
7
7
  import { SessionEntity } from '../../common/session/session-entity'
8
8
  import { LocalStorage } from '../../common/storage/local-storage.js'
9
- import { DEFAULT_KEY } from '../../common/session/constants'
9
+ import { getAppSessionHash } from '../../common/session/session-key'
10
10
  import { mergeInfo } from '../../common/config/info'
11
11
  import { trackObjectAttributeSize } from '../../common/util/attribute-size'
12
12
  import { handle } from '../../common/event-emitter/handle'
@@ -19,10 +19,11 @@ export function setupAgentSession (agentRef) {
19
19
  if (agentRef.runtime.session) return agentRef.runtime.session // already setup
20
20
 
21
21
  const sessionInit = agentRef.init.session
22
+ const namespacedKey = getAppSessionHash(agentRef.info.licenseKey, agentRef.info.applicationID)
22
23
 
23
24
  agentRef.runtime.session = new SessionEntity({
24
25
  agentRef,
25
- key: DEFAULT_KEY,
26
+ key: namespacedKey,
26
27
  storage: new LocalStorage(),
27
28
  expiresMs: sessionInit?.expiresMs,
28
29
  inactiveMs: sessionInit?.inactiveMs
@@ -3,9 +3,6 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { FeatureBase } from './feature-base'
6
- import { isValid } from '../../common/config/info'
7
- import { configure } from '../../loaders/configure/configure'
8
- import { gosCDN } from '../../common/window/nreum'
9
6
  import { drain } from '../../common/drain/drain'
10
7
  import { Obfuscator } from '../../common/util/obfuscate'
11
8
  import { FEATURE_NAMES } from '../../loaders/features/features'
@@ -24,7 +21,6 @@ export class AggregateBase extends FeatureBase {
24
21
  */
25
22
  constructor (agentRef, featureName) {
26
23
  super(agentRef, featureName)
27
- this.checkConfiguration(agentRef)
28
24
  this.doOnceForAllAggregate(agentRef)
29
25
 
30
26
  /** @type {Boolean} indicates if custom attributes are combined in each event payload for size estimation purposes. this is set to true in derived classes that need to evaluate custom attributes separately from the event payload */
@@ -181,34 +177,6 @@ export class AggregateBase extends FeatureBase {
181
177
  this.events.clearSave(this.harvestOpts)
182
178
  }
183
179
 
184
- /**
185
- * Checks for additional `jsAttributes` items to support backward compatibility with implementations of the agent where
186
- * loader configurations may appear after the loader code is executed.
187
- */
188
- checkConfiguration (existingAgent) {
189
- // NOTE: This check has to happen at aggregator load time
190
- if (!isValid(existingAgent.info)) {
191
- const cdn = gosCDN()
192
- let jsAttributes = { ...cdn.info?.jsAttributes }
193
- try {
194
- jsAttributes = {
195
- ...jsAttributes,
196
- ...existingAgent.info?.jsAttributes
197
- }
198
- } catch (err) {
199
- // do nothing
200
- }
201
- configure(existingAgent, {
202
- ...cdn,
203
- info: {
204
- ...cdn.info,
205
- jsAttributes
206
- },
207
- runtime: existingAgent.runtime
208
- }, existingAgent.runtime.loaderType)
209
- }
210
- }
211
-
212
180
  /**
213
181
  * These are actions related to shared resources that should be initialized once by whichever feature Aggregate subclass loads first.
214
182
  * This method should run after checkConfiguration, which may reset the agent's info/runtime object that is used here.
@@ -5,8 +5,8 @@
5
5
 
6
6
  /**
7
7
  * @file Defines `InstrumentBase` to be used as the super of the Instrument classes implemented by each feature.
8
- * Inherits and executes the `checkConfiguration` method from [FeatureBase]{@link ./feature-base}, which also
9
- * exposes the `blocked` property.
8
+ * Validates and loads feature aggregates, including a one-time late configuration check before import.
9
+ * Inherits `blocked` behavior from [FeatureBase]{@link ./feature-base}.
10
10
  */
11
11
 
12
12
  import { drain, registerDrain } from '../../common/drain/drain'
@@ -14,6 +14,9 @@ import { FeatureBase } from './feature-base'
14
14
  import { onWindowLoad } from '../../common/window/load'
15
15
  import { isBrowserScope } from '../../common/constants/runtime'
16
16
  import { warn } from '../../common/util/console'
17
+ import { isValid } from '../../common/config/info'
18
+ import { configure } from '../../loaders/configure/configure'
19
+ import { gosCDN } from '../../common/window/nreum'
17
20
  import { FEATURE_NAMES } from '../../loaders/features/features'
18
21
  import { hasReplayPrerequisite } from '../session_replay/shared/utils'
19
22
  import { canEnableSessionTracking } from './feature-gates'
@@ -21,6 +24,8 @@ import { single } from '../../common/util/invoke'
21
24
  import { SESSION_ERROR } from '../../common/constants/agent-constants'
22
25
  import { handle } from '../../common/event-emitter/handle'
23
26
 
27
+ const checkedAgents = new WeakSet()
28
+
24
29
  /**
25
30
  * Base class for instrumenting a feature.
26
31
  * @extends FeatureBase
@@ -90,6 +95,14 @@ export class InstrumentBase extends FeatureBase {
90
95
  // or otherwise when the manual-start-all event is emitted by the start API
91
96
  await this.deferred
92
97
 
98
+ this.#checkConfiguration(agentRef) // check for late-appearing 'info' config on the page
99
+ if (!isValid(agentRef.info)) { // if there still isn't valid info, then we can't proceed with session setup or importing the aggregates
100
+ warn(43)
101
+ agentRef.ee.abort()
102
+ this.loadedSuccessfully(false)
103
+ return
104
+ }
105
+
93
106
  let session
94
107
  try {
95
108
  if (canEnableSessionTracking(agentRef.init)) { // would require some setup before certain features start
@@ -150,4 +163,34 @@ export class InstrumentBase extends FeatureBase {
150
163
  return true
151
164
  }
152
165
  }
166
+
167
+ /**
168
+ * Checks for additional `jsAttributes` items to support backward compatibility with implementations of the agent where
169
+ * loader configurations may appear after the loader code is executed.
170
+ */
171
+ #checkConfiguration (existingAgent) {
172
+ if (checkedAgents.has(existingAgent)) return
173
+ checkedAgents.add(existingAgent)
174
+ // NOTE: This check has to happen at load time
175
+ if (!isValid(existingAgent.info)) {
176
+ const cdn = gosCDN()
177
+ let jsAttributes = { ...cdn.info?.jsAttributes }
178
+ try {
179
+ jsAttributes = {
180
+ ...jsAttributes,
181
+ ...existingAgent.info?.jsAttributes
182
+ }
183
+ } catch (err) {
184
+ // do nothing
185
+ }
186
+ configure(existingAgent, {
187
+ ...cdn,
188
+ info: {
189
+ ...cdn.info,
190
+ jsAttributes
191
+ },
192
+ runtime: existingAgent.runtime
193
+ }, existingAgent.runtime.loaderType)
194
+ }
195
+ }
153
196
  }
@@ -230,7 +230,7 @@ export class ApiBase {
230
230
 
231
231
  /**
232
232
  * Accepts or rejects consent when the agent is configured to require consent before harvesting.
233
- * The consent state is stored in session storage inside the NRBA_SESSION object.
233
+ * The consent state is stored in the agent session object in browser storage.
234
234
  * {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/consent/}
235
235
  * @param {boolean?} accept Whether to accept or reject consent. Defaults to true (accept) if left undefined.
236
236
  */