@newrelic/browser-agent 1.290.1 → 1.291.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +22 -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/harvest/harvester.js +0 -7
  5. package/dist/cjs/common/session/session-entity.js +5 -5
  6. package/dist/cjs/common/util/console.js +12 -0
  7. package/dist/cjs/features/generic_events/aggregate/index.js +18 -2
  8. package/dist/cjs/features/generic_events/instrument/index.js +2 -0
  9. package/dist/cjs/features/session_replay/aggregate/index.js +3 -0
  10. package/dist/cjs/features/session_replay/shared/recorder.js +0 -4
  11. package/dist/cjs/features/session_trace/aggregate/index.js +7 -3
  12. package/dist/cjs/features/session_trace/aggregate/trace/storage.js +36 -23
  13. package/dist/cjs/loaders/api/addToTrace.js +8 -0
  14. package/dist/cjs/loaders/api/constants.js +3 -2
  15. package/dist/cjs/loaders/api/measure.js +60 -0
  16. package/dist/cjs/loaders/api-base.js +11 -0
  17. package/dist/esm/common/constants/env.cdn.js +1 -1
  18. package/dist/esm/common/constants/env.npm.js +1 -1
  19. package/dist/esm/common/harvest/harvester.js +0 -7
  20. package/dist/esm/common/session/session-entity.js +5 -5
  21. package/dist/esm/common/util/console.js +13 -0
  22. package/dist/esm/features/generic_events/aggregate/index.js +18 -2
  23. package/dist/esm/features/generic_events/instrument/index.js +2 -0
  24. package/dist/esm/features/session_replay/aggregate/index.js +3 -0
  25. package/dist/esm/features/session_replay/shared/recorder.js +0 -4
  26. package/dist/esm/features/session_trace/aggregate/index.js +7 -3
  27. package/dist/esm/features/session_trace/aggregate/trace/storage.js +36 -23
  28. package/dist/esm/loaders/api/addToTrace.js +8 -0
  29. package/dist/esm/loaders/api/constants.js +2 -1
  30. package/dist/esm/loaders/api/measure.js +53 -0
  31. package/dist/esm/loaders/api-base.js +12 -1
  32. package/dist/tsconfig.tsbuildinfo +1 -1
  33. package/dist/types/common/harvest/harvester.d.ts.map +1 -1
  34. package/dist/types/common/session/session-entity.d.ts +0 -1
  35. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  36. package/dist/types/common/util/console.d.ts +0 -4
  37. package/dist/types/common/util/console.d.ts.map +1 -1
  38. package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
  39. package/dist/types/features/generic_events/instrument/index.d.ts.map +1 -1
  40. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  41. package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
  42. package/dist/types/features/session_trace/aggregate/index.d.ts +1 -1
  43. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  44. package/dist/types/features/session_trace/aggregate/trace/storage.d.ts +1 -3
  45. package/dist/types/features/session_trace/aggregate/trace/storage.d.ts.map +1 -1
  46. package/dist/types/loaders/api/addToTrace.d.ts.map +1 -1
  47. package/dist/types/loaders/api/constants.d.ts +1 -0
  48. package/dist/types/loaders/api/constants.d.ts.map +1 -1
  49. package/dist/types/loaders/api/measure.d.ts +2 -0
  50. package/dist/types/loaders/api/measure.d.ts.map +1 -0
  51. package/dist/types/loaders/api-base.d.ts +13 -0
  52. package/dist/types/loaders/api-base.d.ts.map +1 -1
  53. package/package.json +1 -1
  54. package/src/common/harvest/harvester.js +0 -5
  55. package/src/common/session/session-entity.js +5 -6
  56. package/src/common/util/console.js +13 -0
  57. package/src/features/generic_events/aggregate/index.js +17 -2
  58. package/src/features/generic_events/instrument/index.js +2 -0
  59. package/src/features/session_replay/aggregate/index.js +4 -0
  60. package/src/features/session_replay/shared/recorder.js +0 -4
  61. package/src/features/session_trace/aggregate/index.js +7 -3
  62. package/src/features/session_trace/aggregate/trace/storage.js +37 -23
  63. package/src/loaders/api/addToTrace.js +6 -0
  64. package/src/loaders/api/constants.js +1 -0
  65. package/src/loaders/api/measure.js +53 -0
  66. package/src/loaders/api-base.js +12 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,28 @@
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.291.1](https://github.com/newrelic/newrelic-browser-agent/compare/v1.291.0...v1.291.1) (2025-06-06)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * Add safeguards for addToTrace ([#1490](https://github.com/newrelic/newrelic-browser-agent/issues/1490)) ([e94a36e](https://github.com/newrelic/newrelic-browser-agent/commit/e94a36efaf37f69dfdb8134bc27ee0df0a734e83))
12
+ * Clean BrowserPerformance entryName for resources ([#1493](https://github.com/newrelic/newrelic-browser-agent/issues/1493)) ([09ff0ad](https://github.com/newrelic/newrelic-browser-agent/commit/09ff0adb27c12e99c02c81462aeb921af8686ce8))
13
+ * Prevent ST from holding onto Event refs in memory when aborted ([#1491](https://github.com/newrelic/newrelic-browser-agent/issues/1491)) ([a1d15cb](https://github.com/newrelic/newrelic-browser-agent/commit/a1d15cb9d972a2fa421a648b83e150489799d437))
14
+
15
+ ## [1.291.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.290.1...v1.291.0) (2025-05-30)
16
+
17
+
18
+ ### Features
19
+
20
+ * Create the measure API ([#1476](https://github.com/newrelic/newrelic-browser-agent/issues/1476)) ([f944b76](https://github.com/newrelic/newrelic-browser-agent/commit/f944b76c2137e6d75e47e692ded4ba5f04bb4b6d))
21
+
22
+
23
+ ### Bug Fixes
24
+
25
+ * Fix race between end of session and features aborting ([#1487](https://github.com/newrelic/newrelic-browser-agent/issues/1487)) ([531f8d4](https://github.com/newrelic/newrelic-browser-agent/commit/531f8d4228f6c7ead8e2342c8a8dd25c651e9ec6))
26
+ * Harvest first session trace payload immediately ([#1483](https://github.com/newrelic/newrelic-browser-agent/issues/1483)) ([50f4ace](https://github.com/newrelic/newrelic-browser-agent/commit/50f4acea3e27c6b5e65d02ffb46af02b351e4500))
27
+
6
28
  ## [1.290.1](https://github.com/newrelic/newrelic-browser-agent/compare/v1.290.0...v1.290.1) (2025-05-21)
7
29
 
8
30
 
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
17
17
  /**
18
18
  * Exposes the version of the agent
19
19
  */
20
- const VERSION = exports.VERSION = "1.290.1";
20
+ const VERSION = exports.VERSION = "1.291.1";
21
21
 
22
22
  /**
23
23
  * Exposes the build type of the agent
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
17
17
  /**
18
18
  * Exposes the version of the agent
19
19
  */
20
- const VERSION = exports.VERSION = "1.290.1";
20
+ const VERSION = exports.VERSION = "1.291.1";
21
21
 
22
22
  /**
23
23
  * Exposes the build type of the agent
@@ -10,7 +10,6 @@ var _env = require("../constants/env.npm");
10
10
  var _runtime = require("../constants/runtime");
11
11
  var _handle = require("../event-emitter/handle");
12
12
  var _eventListenerOpts = require("../event-listener/event-listener-opts");
13
- var _constants2 = require("../session/constants");
14
13
  var _now = require("../timing/now");
15
14
  var _eol = require("../unload/eol");
16
15
  var _cleanUrl = require("../url/clean-url");
@@ -43,12 +42,6 @@ class Harvester {
43
42
  }));
44
43
  /* This callback should run in bubble phase, so that that CWV api, like "onLCP", is called before the final harvest so that emitted timings are part of last outgoing. */
45
44
  }, false);
46
-
47
- /* Flush all buffered data if session resets and give up retries. This should be synchronous to ensure that the correct `session` value is sent.
48
- Since session-reset generates a new session ID and the ID is grabbed at send-time, any delays or retries would cause the payload to be sent under the wrong session ID. */
49
- agentRef.ee.on(_constants2.SESSION_EVENTS.RESET, () => this.initializedAggregates.forEach(aggregateInst => this.triggerHarvestFor(aggregateInst, {
50
- forceNoRetry: true
51
- })));
52
45
  }
53
46
  startTimer(harvestInterval = this.agentRef.init.harvest.interval) {
54
47
  if (this.#started) return;
@@ -240,7 +240,10 @@ class SessionEntity {
240
240
  // * stop recording (stn and sr)...
241
241
  // * delete the session and start over
242
242
  try {
243
- if (this.initialized) this.ee.emit(_constants.SESSION_EVENTS.RESET);
243
+ if (this.initialized) {
244
+ this.ee.emit(_constants.SESSION_EVENTS.RESET);
245
+ this.state.numOfResets++;
246
+ }
244
247
  this.storage.remove(this.lookupKey);
245
248
  this.inactiveTimer?.abort?.();
246
249
  this.expiresTimer?.clear?.();
@@ -251,7 +254,7 @@ class SessionEntity {
251
254
  storage: this.storage,
252
255
  expiresMs: this.expiresMs,
253
256
  inactiveMs: this.inactiveMs,
254
- numOfResets: ++this.state.numOfResets
257
+ numOfResets: this.state.numOfResets
255
258
  });
256
259
  return this.read();
257
260
  } catch (e) {
@@ -270,9 +273,6 @@ class SessionEntity {
270
273
  inactiveAt: this.getFutureTimestamp(this.inactiveMs)
271
274
  });
272
275
  }
273
- isAfterSessionExpiry(timestamp) {
274
- return this.state.numOfResets > 0 || typeof timestamp === 'number' && typeof this.state.expiresAt === 'number' && timestamp >= this.state.expiresAt;
275
- }
276
276
 
277
277
  /**
278
278
  * @param {number} timestamp
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.warn = warn;
7
+ var _globalEvent = require("../dispatch/global-event");
7
8
  /**
8
9
  * Copyright 2020-2025 New Relic, Inc. All rights reserved.
9
10
  * SPDX-License-Identifier: Apache-2.0
@@ -20,4 +21,15 @@ exports.warn = warn;
20
21
  function warn(code, secondary) {
21
22
  if (typeof console.debug !== 'function') return;
22
23
  console.debug("New Relic Warning: https://github.com/newrelic/newrelic-browser-agent/blob/main/docs/warning-codes.md#".concat(code), secondary);
24
+ (0, _globalEvent.dispatchGlobalEvent)({
25
+ agentIdentifier: null,
26
+ drained: null,
27
+ type: 'data',
28
+ name: 'warn',
29
+ feature: 'warn',
30
+ data: {
31
+ code,
32
+ secondary
33
+ }
34
+ });
23
35
  }
@@ -148,7 +148,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
148
148
  ...detailObj,
149
149
  eventType: 'BrowserPerformance',
150
150
  timestamp: this.toEpoch(entry.startTime),
151
- entryName: (0, _cleanUrl.cleanURL)(entry.name),
151
+ entryName: entry.name,
152
152
  entryDuration: entry.duration,
153
153
  entryType: type
154
154
  });
@@ -214,7 +214,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
214
214
  ...entryObject,
215
215
  eventType: 'BrowserPerformance',
216
216
  timestamp: Math.floor(agentRef.runtime.timeKeeper.correctRelativeTimestamp(entryObject.startTime)),
217
- entryName: name,
217
+ entryName: (0, _cleanUrl.cleanURL)(name),
218
218
  entryDuration: duration,
219
219
  firstParty
220
220
  };
@@ -224,6 +224,22 @@ class Aggregate extends _aggregateBase.AggregateBase {
224
224
  }
225
225
  }, this.featureName, this.ee);
226
226
  }
227
+ (0, _registerHandler.registerHandler)('api-measure', (args, n) => {
228
+ const {
229
+ start,
230
+ duration,
231
+ customAttributes
232
+ } = args;
233
+ const event = {
234
+ ...customAttributes,
235
+ eventType: 'BrowserPerformance',
236
+ timestamp: Math.floor(agentRef.runtime.timeKeeper.correctRelativeTimestamp(start)),
237
+ entryName: n,
238
+ entryDuration: duration,
239
+ entryType: 'measure'
240
+ };
241
+ this.addEvent(event);
242
+ }, this.featureName, this.ee);
227
243
  agentRef.runtime.harvester.triggerHarvestFor(this);
228
244
  this.drain();
229
245
  });
@@ -12,6 +12,7 @@ var _addPageAction = require("../../../loaders/api/addPageAction");
12
12
  var _finished = require("../../../loaders/api/finished");
13
13
  var _recordCustomEvent = require("../../../loaders/api/recordCustomEvent");
14
14
  var _register = require("../../../loaders/api/register");
15
+ var _measure = require("../../../loaders/api/measure");
15
16
  var _instrumentBase = require("../../utils/instrument-base");
16
17
  var _constants = require("../constants");
17
18
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
@@ -31,6 +32,7 @@ class Instrument extends _instrumentBase.InstrumentBase {
31
32
  (0, _recordCustomEvent.setupRecordCustomEventAPI)(agentRef);
32
33
  (0, _finished.setupFinishedAPI)(agentRef);
33
34
  (0, _register.setupRegisterAPI)(agentRef);
35
+ (0, _measure.setupMeasureAPI)(agentRef);
34
36
  if (_runtime.isBrowserScope) {
35
37
  if (agentRef.init.user_actions.enabled) {
36
38
  _constants.OBSERVED_EVENTS.forEach(eventType => (0, _eventListenerOpts.windowAddEventListener)(eventType, evt => (0, _handle.handle)('ua', [evt], undefined, this.featureName, this.ee), true));
@@ -281,6 +281,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
281
281
  this.recorder.clearBuffer();
282
282
  if (recorderEvents.type === 'preloaded') this.agentRef.runtime.harvester.triggerHarvestFor(this);
283
283
  payloadOutput.payload = payload;
284
+ if (!this.agentRef.runtime.session.state.traceHarvestStarted) {
285
+ (0, _console.warn)(59, JSON.stringify(this.agentRef.runtime.session.state));
286
+ }
284
287
  return [payloadOutput];
285
288
  }
286
289
  getCorrectedTimestamp(node) {
@@ -168,10 +168,6 @@ class Recorder {
168
168
  /** Store a payload in the buffer (this.#events). This should be the callback to the recording lib noticing a mutation */
169
169
  store(event, isCheckout) {
170
170
  if (!event) return;
171
- if (this.parent.agentRef.runtime.session?.isAfterSessionExpiry(event.timestamp)) {
172
- (0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, ['Session/Expired/SessionReplay/Seen'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
173
- return;
174
- }
175
171
  if (!(this.parent instanceof _aggregateBase.AggregateBase) && this.#preloaded.length) this.currentBufferTarget = this.#preloaded[this.#preloaded.length - 1];else this.currentBufferTarget = this.#events;
176
172
  if (this.parent.blocked) return;
177
173
  if (this.parent.timeKeeper?.ready && !event.__newrelic) {
@@ -13,6 +13,7 @@ var _runtime = require("../../../common/constants/runtime");
13
13
  var _constants2 = require("../../../common/session/constants");
14
14
  var _traverse = require("../../../common/util/traverse");
15
15
  var _cleanUrl = require("../../../common/url/clean-url");
16
+ var _console = require("../../../common/util/console");
16
17
  /**
17
18
  * Copyright 2020-2025 New Relic, Inc. All rights reserved.
18
19
  * SPDX-License-Identifier: Apache-2.0
@@ -85,7 +86,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
85
86
  (0, _registerHandler.registerHandler)('bstResource', (...args) => this.events.storeResources(...args), this.featureName, this.ee);
86
87
  (0, _registerHandler.registerHandler)('bstHist', (...args) => this.events.storeHist(...args), this.featureName, this.ee);
87
88
  (0, _registerHandler.registerHandler)('bstXhrAgg', (...args) => this.events.storeXhrAgg(...args), this.featureName, this.ee);
88
- (0, _registerHandler.registerHandler)('bstApi', (...args) => this.events.storeSTN(...args), this.featureName, this.ee);
89
+ (0, _registerHandler.registerHandler)('bstApi', (...args) => this.events.storeNode(...args), this.featureName, this.ee);
89
90
  (0, _registerHandler.registerHandler)('trace-jserror', (...args) => this.events.storeErrorAgg(...args), this.featureName, this.ee);
90
91
  (0, _registerHandler.registerHandler)('pvtAdded', (...args) => this.events.processPVT(...args), this.featureName, this.ee);
91
92
  if (this.mode !== _constants2.MODE.FULL) {
@@ -98,9 +99,11 @@ class Aggregate extends _aggregateBase.AggregateBase {
98
99
  sessionTraceMode: this.mode
99
100
  });
100
101
  this.drain();
102
+ /** try to harvest immediately. This will not send if the trace is not running in FULL mode due to the pre-harvest checks. */
103
+ this.agentRef.runtime.harvester.triggerHarvestFor(this);
101
104
  }
102
105
  preHarvestChecks() {
103
- if (this.mode !== _constants2.MODE.FULL) return; // only allow harvest if running in full mode
106
+ if (this.blocked || this.mode !== _constants2.MODE.FULL) return; // only allow harvest if running in full mode
104
107
  if (!this.timeKeeper?.ready) return; // this should likely never happen, but just to be safe, we should never harvest if we cant correct time
105
108
  if (!this.agentRef.runtime.session) return; // session entity is required for trace to run and continue running
106
109
  if (this.sessionId !== this.agentRef.runtime.session.state.value || this.ptid !== this.agentRef.runtime.ptid) {
@@ -194,7 +197,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
194
197
  }
195
198
 
196
199
  /** Stop running for the remainder of the page lifecycle */
197
- abort() {
200
+ abort(code) {
201
+ (0, _console.warn)(60, code);
198
202
  this.blocked = true;
199
203
  this.mode = _constants2.MODE.OFF;
200
204
  this.agentRef.runtime.session.write({
@@ -53,23 +53,19 @@ class TraceStorage {
53
53
  constructor(parent) {
54
54
  this.parent = parent;
55
55
  }
56
- isAfterSessionExpiry(entryTimestamp) {
57
- return this.parent.agentRef.runtime?.session?.isAfterSessionExpiry((this.parent.timeKeeper?.ready && this.parent.timeKeeper.convertRelativeTimestamp(entryTimestamp)) ?? undefined);
58
- }
59
-
60
- /** Central function called by all the other store__ & addToTrace API to append a trace node. */
61
- storeSTN(stn) {
62
- if (this.parent.blocked) return;
56
+ #canStoreNewNode() {
57
+ if (this.parent.blocked) return false;
63
58
  if (this.nodeCount >= _constants2.MAX_NODES_PER_HARVEST) {
64
59
  // limit the amount of pending data awaiting next harvest
65
- if (this.parent.mode !== _constants.MODE.ERROR) return;
60
+ if (this.parent.mode !== _constants.MODE.ERROR) return false;
66
61
  const openedSpace = this.trimSTNs(ERROR_MODE_SECONDS_WINDOW); // but maybe we could make some space by discarding irrelevant nodes if we're in sessioned Error mode
67
- if (openedSpace === 0) return;
68
- }
69
- if (this.isAfterSessionExpiry(stn.s)) {
70
- this.parent.reportSupportabilityMetric('Session/Expired/SessionTrace/Seen');
71
- return;
62
+ if (openedSpace === 0) return false;
72
63
  }
64
+ return true;
65
+ }
66
+
67
+ /** Central internal function called by all the other store__ & addToTrace API to append a trace node. They MUST all have checked #canStoreNewNode before calling this func!! */
68
+ #storeSTN(stn) {
73
69
  if (this.trace[stn.n]) this.trace[stn.n].push(stn);else this.trace[stn.n] = [stn];
74
70
  if (stn.s < this.earliestTimeStamp) this.earliestTimeStamp = stn.s;
75
71
  if (stn.s > this.latestTimeStamp) this.latestTimeStamp = stn.s;
@@ -149,6 +145,10 @@ class TraceStorage {
149
145
  return !!(node && typeof node.e === 'number' && typeof node.s === 'number' && node.e - node.s < limit);
150
146
  }
151
147
  }
148
+ storeNode(node) {
149
+ if (!this.#canStoreNewNode()) return;
150
+ this.#storeSTN(node);
151
+ }
152
152
  processPVT(name, value, attrs) {
153
153
  this.storeTiming({
154
154
  [name]: value
@@ -172,13 +172,16 @@ class TraceStorage {
172
172
  if (this.parent.timeKeeper && this.parent.timeKeeper.ready && isAbsoluteTimestamp) {
173
173
  val = this.parent.timeKeeper.convertAbsoluteTimestamp(Math.floor(this.parent.timeKeeper.correctAbsoluteTimestamp(val)));
174
174
  }
175
- this.storeSTN(new _node.TraceNode(key, val, val, 'document', 'timing'));
175
+ if (!this.#canStoreNewNode()) return; // at any point when no new nodes can be stored, there's no point in processing the rest of the timing entries
176
+ this.#storeSTN(new _node.TraceNode(key, val, val, 'document', 'timing'));
176
177
  }
177
178
  }
178
179
 
179
180
  // Tracks the events and their listener's duration on objects wrapped by wrap-events.
180
181
  storeEvent(currentEvent, target, start, end) {
181
182
  if (this.shouldIgnoreEvent(currentEvent, target)) return;
183
+ if (!this.#canStoreNewNode()) return; // need to check if adding node will succeed BEFORE storing event ref below (*cli Jun'25 - addressing memory leak in aborted ST issue #NR-420780)
184
+
182
185
  if (this.prevStoredEvents.has(currentEvent)) return; // prevent multiple listeners of an event from creating duplicate trace nodes per occurrence. Cleared every harvest. near-zero chance for re-duplication after clearing per harvest since the timestamps of the event are considered for uniqueness.
183
186
  this.prevStoredEvents.add(currentEvent);
184
187
  const evt = new _node.TraceNode(this.evtName(currentEvent.type), start, end, undefined, 'event');
@@ -189,7 +192,7 @@ class TraceStorage {
189
192
  } catch (e) {
190
193
  evt.o = (0, _eventOrigin.eventOrigin)(null, target, this.parent.ee);
191
194
  }
192
- this.storeSTN(evt);
195
+ this.#storeSTN(evt);
193
196
  }
194
197
  shouldIgnoreEvent(event, target) {
195
198
  if (event.type in ignoredEvents.global) return true;
@@ -225,14 +228,17 @@ class TraceStorage {
225
228
 
226
229
  // Tracks when the window history API specified by wrap-history is used.
227
230
  storeHist(path, old, time) {
228
- this.storeSTN(new _node.TraceNode('history.pushState', time, time, path, old));
231
+ if (!this.#canStoreNewNode()) return;
232
+ this.#storeSTN(new _node.TraceNode('history.pushState', time, time, path, old));
229
233
  }
230
234
  #laststart = 0;
231
235
  // Processes all the PerformanceResourceTiming entries captured (by observer).
232
236
  storeResources(resources) {
233
237
  if (!resources || resources.length === 0) return;
234
- resources.forEach(currentResource => {
235
- if ((currentResource.fetchStart | 0) <= this.#laststart) return; // don't recollect already-seen resources
238
+ for (let i = 0; i < resources.length; i++) {
239
+ const currentResource = resources[i];
240
+ if ((currentResource.fetchStart | 0) <= this.#laststart) continue; // don't recollect already-seen resources
241
+ if (!this.#canStoreNewNode()) break; // stop processing if we can't store any more resource nodes anyways
236
242
 
237
243
  const {
238
244
  initiatorType,
@@ -247,21 +253,23 @@ class TraceStorage {
247
253
  pathname
248
254
  } = (0, _parseUrl.parseUrl)(currentResource.name);
249
255
  const res = new _node.TraceNode(initiatorType, fetchStart | 0, responseEnd | 0, "".concat(protocol, "://").concat(hostname, ":").concat(port).concat(pathname), entryType);
250
- this.storeSTN(res);
251
- });
256
+ this.#storeSTN(res);
257
+ }
252
258
  this.#laststart = resources[resources.length - 1].fetchStart | 0;
253
259
  }
254
260
 
255
261
  // JavascriptError (FEATURE) events pipes into ST here.
256
262
  storeErrorAgg(type, name, params, metrics) {
257
263
  if (type !== 'err') return; // internal errors are purposefully ignored
258
- this.storeSTN(new _node.TraceNode('error', metrics.time, metrics.time, params.message, params.stackHash));
264
+ if (!this.#canStoreNewNode()) return;
265
+ this.#storeSTN(new _node.TraceNode('error', metrics.time, metrics.time, params.message, params.stackHash));
259
266
  }
260
267
 
261
268
  // Ajax (FEATURE) events--XML & fetches--pipes into ST here.
262
269
  storeXhrAgg(type, name, params, metrics) {
263
270
  if (type !== 'xhr') return;
264
- this.storeSTN(new _node.TraceNode('Ajax', metrics.time, metrics.time + metrics.duration, "".concat(params.status, " ").concat(params.method, ": ").concat(params.host).concat(params.pathname), 'ajax'));
271
+ if (!this.#canStoreNewNode()) return;
272
+ this.#storeSTN(new _node.TraceNode('Ajax', metrics.time, metrics.time + metrics.duration, "".concat(params.status, " ").concat(params.method, ": ").concat(params.host).concat(params.pathname), 'ajax'));
265
273
  }
266
274
 
267
275
  /* Below are the interface expected & required of whatever storage is used across all features on an individual basis. This allows a common `.events` property on Trace shared with AggregateBase.
@@ -286,7 +294,12 @@ class TraceStorage {
286
294
  this.latestTimeStamp = 0;
287
295
  }
288
296
  reloadSave() {
289
- Object.values(this.#backupTrace).forEach(stnsArray => stnsArray.forEach(stn => this.storeSTN(stn)));
297
+ for (const stnsArray of Object.values(this.#backupTrace)) {
298
+ for (const stn of stnsArray) {
299
+ if (!this.#canStoreNewNode()) return; // stop attempting to re-store nodes
300
+ this.#storeSTN(stn);
301
+ }
302
+ }
290
303
  }
291
304
  clearSave() {
292
305
  this.#backupTrace = undefined;
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.setupAddToTraceAPI = setupAddToTraceAPI;
7
7
  var _runtime = require("../../common/constants/runtime");
8
8
  var _handle = require("../../common/event-emitter/handle");
9
+ var _console = require("../../common/util/console");
9
10
  var _features = require("../features/features");
10
11
  var _constants = require("./constants");
11
12
  var _sharedHandlers = require("./sharedHandlers");
@@ -24,6 +25,13 @@ function setupAddToTraceAPI(agent) {
24
25
  o: evt.origin || '',
25
26
  t: 'api'
26
27
  };
28
+ if (report.s < 0 || report.e < 0 || report.e < report.s) {
29
+ (0, _console.warn)(61, {
30
+ start: report.s,
31
+ end: report.e
32
+ });
33
+ return;
34
+ }
27
35
  (0, _handle.handle)('bstApi', [report], undefined, _features.FEATURE_NAMES.sessionTrace, agent.ee);
28
36
  }, agent);
29
37
  }
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.spaPrefix = exports.prefix = exports.WRAP_LOGGER = exports.START = exports.SET_USER_ID = exports.SET_PAGE_VIEW_NAME = exports.SET_ERROR_HANDLER = exports.SET_CUSTOM_ATTRIBUTE = exports.SET_CURRENT_ROUTE_NAME = exports.SET_APPLICATION_VERSION = exports.REGISTER = exports.RECORD_REPLAY = exports.RECORD_CUSTOM_EVENT = exports.PAUSE_REPLAY = exports.NOTICE_ERROR = exports.LOG = exports.INTERACTION = exports.FINISHED = exports.ADD_TO_TRACE = exports.ADD_RELEASE = exports.ADD_PAGE_ACTION = void 0;
6
+ exports.spaPrefix = exports.prefix = exports.WRAP_LOGGER = exports.START = exports.SET_USER_ID = exports.SET_PAGE_VIEW_NAME = exports.SET_ERROR_HANDLER = exports.SET_CUSTOM_ATTRIBUTE = exports.SET_CURRENT_ROUTE_NAME = exports.SET_APPLICATION_VERSION = exports.REGISTER = exports.RECORD_REPLAY = exports.RECORD_CUSTOM_EVENT = exports.PAUSE_REPLAY = exports.NOTICE_ERROR = exports.MEASURE = exports.LOG = exports.INTERACTION = exports.FINISHED = exports.ADD_TO_TRACE = exports.ADD_RELEASE = exports.ADD_PAGE_ACTION = void 0;
7
7
  /**
8
8
  * Copyright 2020-2025 New Relic, Inc. All rights reserved.
9
9
  * SPDX-License-Identifier: Apache-2.0
@@ -28,4 +28,5 @@ const SET_ERROR_HANDLER = exports.SET_ERROR_HANDLER = 'setErrorHandler';
28
28
  const SET_PAGE_VIEW_NAME = exports.SET_PAGE_VIEW_NAME = 'setPageViewName';
29
29
  const SET_USER_ID = exports.SET_USER_ID = 'setUserId';
30
30
  const START = exports.START = 'start';
31
- const WRAP_LOGGER = exports.WRAP_LOGGER = 'wrapLogger';
31
+ const WRAP_LOGGER = exports.WRAP_LOGGER = 'wrapLogger';
32
+ const MEASURE = exports.MEASURE = 'measure';
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.setupMeasureAPI = setupMeasureAPI;
7
+ var _handle = require("../../common/event-emitter/handle");
8
+ var _now = require("../../common/timing/now");
9
+ var _console = require("../../common/util/console");
10
+ var _features = require("../features/features");
11
+ var _constants = require("./constants");
12
+ var _sharedHandlers = require("./sharedHandlers");
13
+ /**
14
+ * Copyright 2020-2025 New Relic, Inc. All rights reserved.
15
+ * SPDX-License-Identifier: Apache-2.0
16
+ */
17
+
18
+ function setupMeasureAPI(agent) {
19
+ (0, _sharedHandlers.setupAPI)(_constants.MEASURE, function (name, options) {
20
+ const n = (0, _now.now)();
21
+ const {
22
+ start,
23
+ end,
24
+ customAttributes
25
+ } = options || {};
26
+ const returnObj = {
27
+ customAttributes: customAttributes || {}
28
+ };
29
+ if (typeof returnObj.customAttributes !== 'object' || typeof name !== 'string' || name.length === 0) {
30
+ (0, _console.warn)(57);
31
+ return;
32
+ }
33
+
34
+ /**
35
+ * getValueFromTiming - Helper function to extract a numeric value from a supplied option.
36
+ * @param {Number|PerformanceMark} [timing] The timing value
37
+ * @param {Number} [d] The default value to return if timing is invalid
38
+ * @returns {Number} The timing value or the default value
39
+ */
40
+ const getValueFromTiming = (timing, d) => {
41
+ if (timing == null) return d;
42
+ if (typeof timing === 'number') return timing;
43
+ if (timing instanceof PerformanceMark) return timing.startTime;
44
+ return Number.NaN;
45
+ };
46
+ returnObj.start = getValueFromTiming(start, 0);
47
+ returnObj.end = getValueFromTiming(end, n);
48
+ if (Number.isNaN(returnObj.start) || Number.isNaN(returnObj.end)) {
49
+ (0, _console.warn)(57);
50
+ return;
51
+ }
52
+ returnObj.duration = returnObj.end - returnObj.start;
53
+ if (returnObj.duration < 0) {
54
+ (0, _console.warn)(58);
55
+ return;
56
+ }
57
+ (0, _handle.handle)(_constants.prefix + _constants.MEASURE, [returnObj, name], undefined, _features.FEATURE_NAMES.genericEvents, agent.ee);
58
+ return returnObj;
59
+ }, agent);
60
+ }
@@ -220,5 +220,16 @@ class ApiBase {
220
220
  wrapLogger(parent, functionName, options) {
221
221
  return this.#callMethod(_constants.WRAP_LOGGER, parent, functionName, options);
222
222
  }
223
+
224
+ /**
225
+ * Measures a task that is recorded as a BrowserPerformance event.
226
+ * {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/measure/}
227
+ * @param {string} name The name of the task
228
+ * @param {object?} options An object used to control the way the measure API operates
229
+ * @returns {{start: number, end: number, duration: number, customAttributes: object}} Measurement details
230
+ */
231
+ measure(name, options) {
232
+ return this.#callMethod(_constants.MEASURE, name, options);
233
+ }
223
234
  }
224
235
  exports.ApiBase = ApiBase;
@@ -11,7 +11,7 @@
11
11
  /**
12
12
  * Exposes the version of the agent
13
13
  */
14
- export const VERSION = "1.290.1";
14
+ export const VERSION = "1.291.1";
15
15
 
16
16
  /**
17
17
  * Exposes the build type of the agent
@@ -11,7 +11,7 @@
11
11
  /**
12
12
  * Exposes the version of the agent
13
13
  */
14
- export const VERSION = "1.290.1";
14
+ export const VERSION = "1.291.1";
15
15
 
16
16
  /**
17
17
  * Exposes the build type of the agent
@@ -8,7 +8,6 @@ import { VERSION } from "../constants/env.npm";
8
8
  import { globalScope, isWorkerScope } from '../constants/runtime';
9
9
  import { handle } from '../event-emitter/handle';
10
10
  import { eventListenerOpts } from '../event-listener/event-listener-opts';
11
- import { SESSION_EVENTS } from '../session/constants';
12
11
  import { now } from '../timing/now';
13
12
  import { subscribeToEOL } from '../unload/eol';
14
13
  import { cleanURL } from '../url/clean-url';
@@ -36,12 +35,6 @@ export class Harvester {
36
35
  }));
37
36
  /* This callback should run in bubble phase, so that that CWV api, like "onLCP", is called before the final harvest so that emitted timings are part of last outgoing. */
38
37
  }, false);
39
-
40
- /* Flush all buffered data if session resets and give up retries. This should be synchronous to ensure that the correct `session` value is sent.
41
- Since session-reset generates a new session ID and the ID is grabbed at send-time, any delays or retries would cause the payload to be sent under the wrong session ID. */
42
- agentRef.ee.on(SESSION_EVENTS.RESET, () => this.initializedAggregates.forEach(aggregateInst => this.triggerHarvestFor(aggregateInst, {
43
- forceNoRetry: true
44
- })));
45
38
  }
46
39
  startTimer(harvestInterval = this.agentRef.init.harvest.interval) {
47
40
  if (this.#started) return;
@@ -234,7 +234,10 @@ export class SessionEntity {
234
234
  // * stop recording (stn and sr)...
235
235
  // * delete the session and start over
236
236
  try {
237
- if (this.initialized) this.ee.emit(SESSION_EVENTS.RESET);
237
+ if (this.initialized) {
238
+ this.ee.emit(SESSION_EVENTS.RESET);
239
+ this.state.numOfResets++;
240
+ }
238
241
  this.storage.remove(this.lookupKey);
239
242
  this.inactiveTimer?.abort?.();
240
243
  this.expiresTimer?.clear?.();
@@ -245,7 +248,7 @@ export class SessionEntity {
245
248
  storage: this.storage,
246
249
  expiresMs: this.expiresMs,
247
250
  inactiveMs: this.inactiveMs,
248
- numOfResets: ++this.state.numOfResets
251
+ numOfResets: this.state.numOfResets
249
252
  });
250
253
  return this.read();
251
254
  } catch (e) {
@@ -264,9 +267,6 @@ export class SessionEntity {
264
267
  inactiveAt: this.getFutureTimestamp(this.inactiveMs)
265
268
  });
266
269
  }
267
- isAfterSessionExpiry(timestamp) {
268
- return this.state.numOfResets > 0 || typeof timestamp === 'number' && typeof this.state.expiresAt === 'number' && timestamp >= this.state.expiresAt;
269
- }
270
270
 
271
271
  /**
272
272
  * @param {number} timestamp
@@ -3,6 +3,8 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
 
6
+ import { dispatchGlobalEvent } from '../dispatch/global-event';
7
+
6
8
  /* eslint no-console: ["error", { allow: ["debug"] }] */
7
9
 
8
10
  /**
@@ -14,4 +16,15 @@
14
16
  export function warn(code, secondary) {
15
17
  if (typeof console.debug !== 'function') return;
16
18
  console.debug("New Relic Warning: https://github.com/newrelic/newrelic-browser-agent/blob/main/docs/warning-codes.md#".concat(code), secondary);
19
+ dispatchGlobalEvent({
20
+ agentIdentifier: null,
21
+ drained: null,
22
+ type: 'data',
23
+ name: 'warn',
24
+ feature: 'warn',
25
+ data: {
26
+ code,
27
+ secondary
28
+ }
29
+ });
17
30
  }
@@ -141,7 +141,7 @@ export class Aggregate extends AggregateBase {
141
141
  ...detailObj,
142
142
  eventType: 'BrowserPerformance',
143
143
  timestamp: this.toEpoch(entry.startTime),
144
- entryName: cleanURL(entry.name),
144
+ entryName: entry.name,
145
145
  entryDuration: entry.duration,
146
146
  entryType: type
147
147
  });
@@ -207,7 +207,7 @@ export class Aggregate extends AggregateBase {
207
207
  ...entryObject,
208
208
  eventType: 'BrowserPerformance',
209
209
  timestamp: Math.floor(agentRef.runtime.timeKeeper.correctRelativeTimestamp(entryObject.startTime)),
210
- entryName: name,
210
+ entryName: cleanURL(name),
211
211
  entryDuration: duration,
212
212
  firstParty
213
213
  };
@@ -217,6 +217,22 @@ export class Aggregate extends AggregateBase {
217
217
  }
218
218
  }, this.featureName, this.ee);
219
219
  }
220
+ registerHandler('api-measure', (args, n) => {
221
+ const {
222
+ start,
223
+ duration,
224
+ customAttributes
225
+ } = args;
226
+ const event = {
227
+ ...customAttributes,
228
+ eventType: 'BrowserPerformance',
229
+ timestamp: Math.floor(agentRef.runtime.timeKeeper.correctRelativeTimestamp(start)),
230
+ entryName: n,
231
+ entryDuration: duration,
232
+ entryType: 'measure'
233
+ };
234
+ this.addEvent(event);
235
+ }, this.featureName, this.ee);
220
236
  agentRef.runtime.harvester.triggerHarvestFor(this);
221
237
  this.drain();
222
238
  });