@newrelic/browser-agent 1.291.0 → 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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,15 @@
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
+
6
15
  ## [1.291.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.290.1...v1.291.0) (2025-05-30)
7
16
 
8
17
 
@@ -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.291.0";
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.291.0";
20
+ const VERSION = exports.VERSION = "1.291.1";
21
21
 
22
22
  /**
23
23
  * Exposes the build type of the agent
@@ -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
  };
@@ -86,7 +86,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
86
86
  (0, _registerHandler.registerHandler)('bstResource', (...args) => this.events.storeResources(...args), this.featureName, this.ee);
87
87
  (0, _registerHandler.registerHandler)('bstHist', (...args) => this.events.storeHist(...args), this.featureName, this.ee);
88
88
  (0, _registerHandler.registerHandler)('bstXhrAgg', (...args) => this.events.storeXhrAgg(...args), this.featureName, this.ee);
89
- (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);
90
90
  (0, _registerHandler.registerHandler)('trace-jserror', (...args) => this.events.storeErrorAgg(...args), this.featureName, this.ee);
91
91
  (0, _registerHandler.registerHandler)('pvtAdded', (...args) => this.events.processPVT(...args), this.featureName, this.ee);
92
92
  if (this.mode !== _constants2.MODE.FULL) {
@@ -53,16 +53,19 @@ class TraceStorage {
53
53
  constructor(parent) {
54
54
  this.parent = parent;
55
55
  }
56
-
57
- /** Central function called by all the other store__ & addToTrace API to append a trace node. */
58
- storeSTN(stn) {
59
- if (this.parent.blocked) return;
56
+ #canStoreNewNode() {
57
+ if (this.parent.blocked) return false;
60
58
  if (this.nodeCount >= _constants2.MAX_NODES_PER_HARVEST) {
61
59
  // limit the amount of pending data awaiting next harvest
62
- if (this.parent.mode !== _constants.MODE.ERROR) return;
60
+ if (this.parent.mode !== _constants.MODE.ERROR) return false;
63
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
64
- if (openedSpace === 0) return;
62
+ if (openedSpace === 0) return false;
65
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) {
66
69
  if (this.trace[stn.n]) this.trace[stn.n].push(stn);else this.trace[stn.n] = [stn];
67
70
  if (stn.s < this.earliestTimeStamp) this.earliestTimeStamp = stn.s;
68
71
  if (stn.s > this.latestTimeStamp) this.latestTimeStamp = stn.s;
@@ -142,6 +145,10 @@ class TraceStorage {
142
145
  return !!(node && typeof node.e === 'number' && typeof node.s === 'number' && node.e - node.s < limit);
143
146
  }
144
147
  }
148
+ storeNode(node) {
149
+ if (!this.#canStoreNewNode()) return;
150
+ this.#storeSTN(node);
151
+ }
145
152
  processPVT(name, value, attrs) {
146
153
  this.storeTiming({
147
154
  [name]: value
@@ -165,13 +172,16 @@ class TraceStorage {
165
172
  if (this.parent.timeKeeper && this.parent.timeKeeper.ready && isAbsoluteTimestamp) {
166
173
  val = this.parent.timeKeeper.convertAbsoluteTimestamp(Math.floor(this.parent.timeKeeper.correctAbsoluteTimestamp(val)));
167
174
  }
168
- 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'));
169
177
  }
170
178
  }
171
179
 
172
180
  // Tracks the events and their listener's duration on objects wrapped by wrap-events.
173
181
  storeEvent(currentEvent, target, start, end) {
174
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
+
175
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.
176
186
  this.prevStoredEvents.add(currentEvent);
177
187
  const evt = new _node.TraceNode(this.evtName(currentEvent.type), start, end, undefined, 'event');
@@ -182,7 +192,7 @@ class TraceStorage {
182
192
  } catch (e) {
183
193
  evt.o = (0, _eventOrigin.eventOrigin)(null, target, this.parent.ee);
184
194
  }
185
- this.storeSTN(evt);
195
+ this.#storeSTN(evt);
186
196
  }
187
197
  shouldIgnoreEvent(event, target) {
188
198
  if (event.type in ignoredEvents.global) return true;
@@ -218,14 +228,17 @@ class TraceStorage {
218
228
 
219
229
  // Tracks when the window history API specified by wrap-history is used.
220
230
  storeHist(path, old, time) {
221
- 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));
222
233
  }
223
234
  #laststart = 0;
224
235
  // Processes all the PerformanceResourceTiming entries captured (by observer).
225
236
  storeResources(resources) {
226
237
  if (!resources || resources.length === 0) return;
227
- resources.forEach(currentResource => {
228
- 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
229
242
 
230
243
  const {
231
244
  initiatorType,
@@ -240,21 +253,23 @@ class TraceStorage {
240
253
  pathname
241
254
  } = (0, _parseUrl.parseUrl)(currentResource.name);
242
255
  const res = new _node.TraceNode(initiatorType, fetchStart | 0, responseEnd | 0, "".concat(protocol, "://").concat(hostname, ":").concat(port).concat(pathname), entryType);
243
- this.storeSTN(res);
244
- });
256
+ this.#storeSTN(res);
257
+ }
245
258
  this.#laststart = resources[resources.length - 1].fetchStart | 0;
246
259
  }
247
260
 
248
261
  // JavascriptError (FEATURE) events pipes into ST here.
249
262
  storeErrorAgg(type, name, params, metrics) {
250
263
  if (type !== 'err') return; // internal errors are purposefully ignored
251
- 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));
252
266
  }
253
267
 
254
268
  // Ajax (FEATURE) events--XML & fetches--pipes into ST here.
255
269
  storeXhrAgg(type, name, params, metrics) {
256
270
  if (type !== 'xhr') return;
257
- 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'));
258
273
  }
259
274
 
260
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.
@@ -279,7 +294,12 @@ class TraceStorage {
279
294
  this.latestTimeStamp = 0;
280
295
  }
281
296
  reloadSave() {
282
- 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
+ }
283
303
  }
284
304
  clearSave() {
285
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
  }
@@ -11,7 +11,7 @@
11
11
  /**
12
12
  * Exposes the version of the agent
13
13
  */
14
- export const VERSION = "1.291.0";
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.291.0";
14
+ export const VERSION = "1.291.1";
15
15
 
16
16
  /**
17
17
  * Exposes the build type of the agent
@@ -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
  };
@@ -79,7 +79,7 @@ export class Aggregate extends AggregateBase {
79
79
  registerHandler('bstResource', (...args) => this.events.storeResources(...args), this.featureName, this.ee);
80
80
  registerHandler('bstHist', (...args) => this.events.storeHist(...args), this.featureName, this.ee);
81
81
  registerHandler('bstXhrAgg', (...args) => this.events.storeXhrAgg(...args), this.featureName, this.ee);
82
- registerHandler('bstApi', (...args) => this.events.storeSTN(...args), this.featureName, this.ee);
82
+ registerHandler('bstApi', (...args) => this.events.storeNode(...args), this.featureName, this.ee);
83
83
  registerHandler('trace-jserror', (...args) => this.events.storeErrorAgg(...args), this.featureName, this.ee);
84
84
  registerHandler('pvtAdded', (...args) => this.events.processPVT(...args), this.featureName, this.ee);
85
85
  if (this.mode !== MODE.FULL) {
@@ -46,16 +46,19 @@ export class TraceStorage {
46
46
  constructor(parent) {
47
47
  this.parent = parent;
48
48
  }
49
-
50
- /** Central function called by all the other store__ & addToTrace API to append a trace node. */
51
- storeSTN(stn) {
52
- if (this.parent.blocked) return;
49
+ #canStoreNewNode() {
50
+ if (this.parent.blocked) return false;
53
51
  if (this.nodeCount >= MAX_NODES_PER_HARVEST) {
54
52
  // limit the amount of pending data awaiting next harvest
55
- if (this.parent.mode !== MODE.ERROR) return;
53
+ if (this.parent.mode !== MODE.ERROR) return false;
56
54
  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
57
- if (openedSpace === 0) return;
55
+ if (openedSpace === 0) return false;
58
56
  }
57
+ return true;
58
+ }
59
+
60
+ /** 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!! */
61
+ #storeSTN(stn) {
59
62
  if (this.trace[stn.n]) this.trace[stn.n].push(stn);else this.trace[stn.n] = [stn];
60
63
  if (stn.s < this.earliestTimeStamp) this.earliestTimeStamp = stn.s;
61
64
  if (stn.s > this.latestTimeStamp) this.latestTimeStamp = stn.s;
@@ -135,6 +138,10 @@ export class TraceStorage {
135
138
  return !!(node && typeof node.e === 'number' && typeof node.s === 'number' && node.e - node.s < limit);
136
139
  }
137
140
  }
141
+ storeNode(node) {
142
+ if (!this.#canStoreNewNode()) return;
143
+ this.#storeSTN(node);
144
+ }
138
145
  processPVT(name, value, attrs) {
139
146
  this.storeTiming({
140
147
  [name]: value
@@ -158,13 +165,16 @@ export class TraceStorage {
158
165
  if (this.parent.timeKeeper && this.parent.timeKeeper.ready && isAbsoluteTimestamp) {
159
166
  val = this.parent.timeKeeper.convertAbsoluteTimestamp(Math.floor(this.parent.timeKeeper.correctAbsoluteTimestamp(val)));
160
167
  }
161
- this.storeSTN(new TraceNode(key, val, val, 'document', 'timing'));
168
+ 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
169
+ this.#storeSTN(new TraceNode(key, val, val, 'document', 'timing'));
162
170
  }
163
171
  }
164
172
 
165
173
  // Tracks the events and their listener's duration on objects wrapped by wrap-events.
166
174
  storeEvent(currentEvent, target, start, end) {
167
175
  if (this.shouldIgnoreEvent(currentEvent, target)) return;
176
+ 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)
177
+
168
178
  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.
169
179
  this.prevStoredEvents.add(currentEvent);
170
180
  const evt = new TraceNode(this.evtName(currentEvent.type), start, end, undefined, 'event');
@@ -175,7 +185,7 @@ export class TraceStorage {
175
185
  } catch (e) {
176
186
  evt.o = eventOrigin(null, target, this.parent.ee);
177
187
  }
178
- this.storeSTN(evt);
188
+ this.#storeSTN(evt);
179
189
  }
180
190
  shouldIgnoreEvent(event, target) {
181
191
  if (event.type in ignoredEvents.global) return true;
@@ -211,14 +221,17 @@ export class TraceStorage {
211
221
 
212
222
  // Tracks when the window history API specified by wrap-history is used.
213
223
  storeHist(path, old, time) {
214
- this.storeSTN(new TraceNode('history.pushState', time, time, path, old));
224
+ if (!this.#canStoreNewNode()) return;
225
+ this.#storeSTN(new TraceNode('history.pushState', time, time, path, old));
215
226
  }
216
227
  #laststart = 0;
217
228
  // Processes all the PerformanceResourceTiming entries captured (by observer).
218
229
  storeResources(resources) {
219
230
  if (!resources || resources.length === 0) return;
220
- resources.forEach(currentResource => {
221
- if ((currentResource.fetchStart | 0) <= this.#laststart) return; // don't recollect already-seen resources
231
+ for (let i = 0; i < resources.length; i++) {
232
+ const currentResource = resources[i];
233
+ if ((currentResource.fetchStart | 0) <= this.#laststart) continue; // don't recollect already-seen resources
234
+ if (!this.#canStoreNewNode()) break; // stop processing if we can't store any more resource nodes anyways
222
235
 
223
236
  const {
224
237
  initiatorType,
@@ -233,21 +246,23 @@ export class TraceStorage {
233
246
  pathname
234
247
  } = parseUrl(currentResource.name);
235
248
  const res = new TraceNode(initiatorType, fetchStart | 0, responseEnd | 0, "".concat(protocol, "://").concat(hostname, ":").concat(port).concat(pathname), entryType);
236
- this.storeSTN(res);
237
- });
249
+ this.#storeSTN(res);
250
+ }
238
251
  this.#laststart = resources[resources.length - 1].fetchStart | 0;
239
252
  }
240
253
 
241
254
  // JavascriptError (FEATURE) events pipes into ST here.
242
255
  storeErrorAgg(type, name, params, metrics) {
243
256
  if (type !== 'err') return; // internal errors are purposefully ignored
244
- this.storeSTN(new TraceNode('error', metrics.time, metrics.time, params.message, params.stackHash));
257
+ if (!this.#canStoreNewNode()) return;
258
+ this.#storeSTN(new TraceNode('error', metrics.time, metrics.time, params.message, params.stackHash));
245
259
  }
246
260
 
247
261
  // Ajax (FEATURE) events--XML & fetches--pipes into ST here.
248
262
  storeXhrAgg(type, name, params, metrics) {
249
263
  if (type !== 'xhr') return;
250
- this.storeSTN(new TraceNode('Ajax', metrics.time, metrics.time + metrics.duration, "".concat(params.status, " ").concat(params.method, ": ").concat(params.host).concat(params.pathname), 'ajax'));
264
+ if (!this.#canStoreNewNode()) return;
265
+ this.#storeSTN(new TraceNode('Ajax', metrics.time, metrics.time + metrics.duration, "".concat(params.status, " ").concat(params.method, ": ").concat(params.host).concat(params.pathname), 'ajax'));
251
266
  }
252
267
 
253
268
  /* 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.
@@ -272,7 +287,12 @@ export class TraceStorage {
272
287
  this.latestTimeStamp = 0;
273
288
  }
274
289
  reloadSave() {
275
- Object.values(this.#backupTrace).forEach(stnsArray => stnsArray.forEach(stn => this.storeSTN(stn)));
290
+ for (const stnsArray of Object.values(this.#backupTrace)) {
291
+ for (const stn of stnsArray) {
292
+ if (!this.#canStoreNewNode()) return; // stop attempting to re-store nodes
293
+ this.#storeSTN(stn);
294
+ }
295
+ }
276
296
  }
277
297
  clearSave() {
278
298
  this.#backupTrace = undefined;
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { originTime } from '../../common/constants/runtime';
6
6
  import { handle } from '../../common/event-emitter/handle';
7
+ import { warn } from '../../common/util/console';
7
8
  import { FEATURE_NAMES } from '../features/features';
8
9
  import { ADD_TO_TRACE } from './constants';
9
10
  import { setupAPI } from './sharedHandlers';
@@ -17,6 +18,13 @@ export function setupAddToTraceAPI(agent) {
17
18
  o: evt.origin || '',
18
19
  t: 'api'
19
20
  };
21
+ if (report.s < 0 || report.e < 0 || report.e < report.s) {
22
+ warn(61, {
23
+ start: report.s,
24
+ end: report.e
25
+ });
26
+ return;
27
+ }
20
28
  handle('bstApi', [report], undefined, FEATURE_NAMES.sessionTrace, agent.ee);
21
29
  }, agent);
22
30
  }
@@ -7,8 +7,6 @@ export class TraceStorage {
7
7
  latestTimeStamp: number;
8
8
  prevStoredEvents: Set<any>;
9
9
  parent: any;
10
- /** Central function called by all the other store__ & addToTrace API to append a trace node. */
11
- storeSTN(stn: any): void;
12
10
  /**
13
11
  * Trim the collection of nodes awaiting harvest such that those seen outside a certain span of time are discarded.
14
12
  * @param {number} lookbackDuration Past length of time until now for which we care about nodes, in milliseconds
@@ -22,6 +20,7 @@ export class TraceStorage {
22
20
  latestTimeStamp: number;
23
21
  };
24
22
  smearEvtsByOrigin(name: any): (byOrigin: any, evtNode: any) => any;
23
+ storeNode(node: any): void;
25
24
  processPVT(name: any, value: any, attrs: any): void;
26
25
  storeTiming(timingEntry: any, isAbsoluteTimestamp?: boolean): void;
27
26
  storeEvent(currentEvent: any, target: any, start: any, end: any): void;
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../../../../src/features/session_trace/aggregate/trace/storage.js"],"names":[],"mappings":"AA8BA,+HAA+H;AAC/H;IAQE,yBAEC;IATD,kBAAa;IACb,UAAU;IACV,0BAA4B;IAC5B,wBAAmB;IACnB,2BAA4B;IAI1B,YAAoB;IAGtB,gGAAgG;IAChG,yBAcC;IAED;;;;OAIG;IACH,2BAHW,MAAM,GACJ,MAAM,CAsBlB;IAED,oEAAoE;IACpE;;;;MAgBC;IAED,mEA6BC;IAED,oDAEC;IAED,mEAuBC;IAGD,uEAcC;IAED,oDAKC;IAED,wBAwBC;IAGD,gDAEC;IAID,qCAaC;IAGD,qEAGC;IAGD,mEAGC;IAID,mBAEC;IAED,aAEC;IAED;;;;;;;QAEC;IAED,cAMC;IAED,mBAEC;IAED,kBAEC;;CACF"}
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../../../../src/features/session_trace/aggregate/trace/storage.js"],"names":[],"mappings":"AA8BA,+HAA+H;AAC/H;IAQE,yBAEC;IATD,kBAAa;IACb,UAAU;IACV,0BAA4B;IAC5B,wBAAmB;IACnB,2BAA4B;IAI1B,YAAoB;IAuBtB;;;;OAIG;IACH,2BAHW,MAAM,GACJ,MAAM,CAsBlB;IAED,oEAAoE;IACpE;;;;MAgBC;IAED,mEA6BC;IAED,2BAGC;IAED,oDAEC;IAED,mEAwBC;IAGD,uEAgBC;IAED,oDAKC;IAED,wBAwBC;IAGD,gDAGC;IAID,qCAgBC;IAGD,qEAIC;IAGD,mEAIC;IAID,mBAEC;IAED,aAEC;IAED;;;;;;;QAEC;IAED,cAMC;IAED,mBAOC;IAED,kBAEC;;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"addToTrace.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/addToTrace.js"],"names":[],"mappings":"AAUA,qDAcC"}
1
+ {"version":3,"file":"addToTrace.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/addToTrace.js"],"names":[],"mappings":"AAWA,qDAmBC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/browser-agent",
3
- "version": "1.291.0",
3
+ "version": "1.291.1",
4
4
  "private": false,
5
5
  "author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
6
6
  "description": "New Relic Browser Agent",
@@ -143,7 +143,7 @@ export class Aggregate extends AggregateBase {
143
143
  ...detailObj,
144
144
  eventType: 'BrowserPerformance',
145
145
  timestamp: this.toEpoch(entry.startTime),
146
- entryName: cleanURL(entry.name),
146
+ entryName: entry.name,
147
147
  entryDuration: entry.duration,
148
148
  entryType: type
149
149
  })
@@ -208,7 +208,7 @@ export class Aggregate extends AggregateBase {
208
208
  ...entryObject,
209
209
  eventType: 'BrowserPerformance',
210
210
  timestamp: Math.floor(agentRef.runtime.timeKeeper.correctRelativeTimestamp(entryObject.startTime)),
211
- entryName: name,
211
+ entryName: cleanURL(name),
212
212
  entryDuration: duration,
213
213
  firstParty
214
214
  }
@@ -86,7 +86,7 @@ export class Aggregate extends AggregateBase {
86
86
  registerHandler('bstResource', (...args) => this.events.storeResources(...args), this.featureName, this.ee)
87
87
  registerHandler('bstHist', (...args) => this.events.storeHist(...args), this.featureName, this.ee)
88
88
  registerHandler('bstXhrAgg', (...args) => this.events.storeXhrAgg(...args), this.featureName, this.ee)
89
- registerHandler('bstApi', (...args) => this.events.storeSTN(...args), this.featureName, this.ee)
89
+ registerHandler('bstApi', (...args) => this.events.storeNode(...args), this.featureName, this.ee)
90
90
  registerHandler('trace-jserror', (...args) => this.events.storeErrorAgg(...args), this.featureName, this.ee)
91
91
  registerHandler('pvtAdded', (...args) => this.events.processPVT(...args), this.featureName, this.ee)
92
92
 
@@ -41,15 +41,18 @@ export class TraceStorage {
41
41
  this.parent = parent
42
42
  }
43
43
 
44
- /** Central function called by all the other store__ & addToTrace API to append a trace node. */
45
- storeSTN (stn) {
46
- if (this.parent.blocked) return
44
+ #canStoreNewNode () {
45
+ if (this.parent.blocked) return false
47
46
  if (this.nodeCount >= MAX_NODES_PER_HARVEST) { // limit the amount of pending data awaiting next harvest
48
- if (this.parent.mode !== MODE.ERROR) return
47
+ if (this.parent.mode !== MODE.ERROR) return false
49
48
  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
50
- if (openedSpace === 0) return
49
+ if (openedSpace === 0) return false
51
50
  }
51
+ return true
52
+ }
52
53
 
54
+ /** 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!! */
55
+ #storeSTN (stn) {
53
56
  if (this.trace[stn.n]) this.trace[stn.n].push(stn)
54
57
  else this.trace[stn.n] = [stn]
55
58
 
@@ -135,6 +138,11 @@ export class TraceStorage {
135
138
  }
136
139
  }
137
140
 
141
+ storeNode (node) {
142
+ if (!this.#canStoreNewNode()) return
143
+ this.#storeSTN(node)
144
+ }
145
+
138
146
  processPVT (name, value, attrs) {
139
147
  this.storeTiming({ [name]: value })
140
148
  }
@@ -160,13 +168,16 @@ export class TraceStorage {
160
168
  Math.floor(this.parent.timeKeeper.correctAbsoluteTimestamp(val))
161
169
  )
162
170
  }
163
- this.storeSTN(new TraceNode(key, val, val, 'document', 'timing'))
171
+ 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
172
+ this.#storeSTN(new TraceNode(key, val, val, 'document', 'timing'))
164
173
  }
165
174
  }
166
175
 
167
176
  // Tracks the events and their listener's duration on objects wrapped by wrap-events.
168
177
  storeEvent (currentEvent, target, start, end) {
169
178
  if (this.shouldIgnoreEvent(currentEvent, target)) return
179
+ 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)
180
+
170
181
  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.
171
182
  this.prevStoredEvents.add(currentEvent)
172
183
 
@@ -178,7 +189,7 @@ export class TraceStorage {
178
189
  } catch (e) {
179
190
  evt.o = eventOrigin(null, target, this.parent.ee)
180
191
  }
181
- this.storeSTN(evt)
192
+ this.#storeSTN(evt)
182
193
  }
183
194
 
184
195
  shouldIgnoreEvent (event, target) {
@@ -216,7 +227,8 @@ export class TraceStorage {
216
227
 
217
228
  // Tracks when the window history API specified by wrap-history is used.
218
229
  storeHist (path, old, time) {
219
- this.storeSTN(new TraceNode('history.pushState', time, time, path, old))
230
+ if (!this.#canStoreNewNode()) return
231
+ this.#storeSTN(new TraceNode('history.pushState', time, time, path, old))
220
232
  }
221
233
 
222
234
  #laststart = 0
@@ -224,14 +236,17 @@ export class TraceStorage {
224
236
  storeResources (resources) {
225
237
  if (!resources || resources.length === 0) return
226
238
 
227
- resources.forEach((currentResource) => {
228
- if ((currentResource.fetchStart | 0) <= this.#laststart) return // don't recollect already-seen resources
239
+ for (let i = 0; i < resources.length; i++) {
240
+ const currentResource = resources[i]
241
+ if ((currentResource.fetchStart | 0) <= this.#laststart) continue // don't recollect already-seen resources
242
+ if (!this.#canStoreNewNode()) break // stop processing if we can't store any more resource nodes anyways
229
243
 
230
244
  const { initiatorType, fetchStart, responseEnd, entryType } = currentResource
231
245
  const { protocol, hostname, port, pathname } = parseUrl(currentResource.name)
232
246
  const res = new TraceNode(initiatorType, fetchStart | 0, responseEnd | 0, `${protocol}://${hostname}:${port}${pathname}`, entryType)
233
- this.storeSTN(res)
234
- })
247
+
248
+ this.#storeSTN(res)
249
+ }
235
250
 
236
251
  this.#laststart = resources[resources.length - 1].fetchStart | 0
237
252
  }
@@ -239,13 +254,15 @@ export class TraceStorage {
239
254
  // JavascriptError (FEATURE) events pipes into ST here.
240
255
  storeErrorAgg (type, name, params, metrics) {
241
256
  if (type !== 'err') return // internal errors are purposefully ignored
242
- this.storeSTN(new TraceNode('error', metrics.time, metrics.time, params.message, params.stackHash))
257
+ if (!this.#canStoreNewNode()) return
258
+ this.#storeSTN(new TraceNode('error', metrics.time, metrics.time, params.message, params.stackHash))
243
259
  }
244
260
 
245
261
  // Ajax (FEATURE) events--XML & fetches--pipes into ST here.
246
262
  storeXhrAgg (type, name, params, metrics) {
247
263
  if (type !== 'xhr') return
248
- this.storeSTN(new TraceNode('Ajax', metrics.time, metrics.time + metrics.duration, `${params.status} ${params.method}: ${params.host}${params.pathname}`, 'ajax'))
264
+ if (!this.#canStoreNewNode()) return
265
+ this.#storeSTN(new TraceNode('Ajax', metrics.time, metrics.time + metrics.duration, `${params.status} ${params.method}: ${params.host}${params.pathname}`, 'ajax'))
249
266
  }
250
267
 
251
268
  /* 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.
@@ -271,7 +288,12 @@ export class TraceStorage {
271
288
  }
272
289
 
273
290
  reloadSave () {
274
- Object.values(this.#backupTrace).forEach(stnsArray => stnsArray.forEach(stn => this.storeSTN(stn)))
291
+ for (const stnsArray of Object.values(this.#backupTrace)) {
292
+ for (const stn of stnsArray) {
293
+ if (!this.#canStoreNewNode()) return // stop attempting to re-store nodes
294
+ this.#storeSTN(stn)
295
+ }
296
+ }
275
297
  }
276
298
 
277
299
  clearSave () {
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { originTime } from '../../common/constants/runtime'
6
6
  import { handle } from '../../common/event-emitter/handle'
7
+ import { warn } from '../../common/util/console'
7
8
  import { FEATURE_NAMES } from '../features/features'
8
9
  import { ADD_TO_TRACE } from './constants'
9
10
  import { setupAPI } from './sharedHandlers'
@@ -20,6 +21,11 @@ export function setupAddToTraceAPI (agent) {
20
21
  t: 'api'
21
22
  }
22
23
 
24
+ if (report.s < 0 || report.e < 0 || report.e < report.s) {
25
+ warn(61, { start: report.s, end: report.e })
26
+ return
27
+ }
28
+
23
29
  handle('bstApi', [report], undefined, FEATURE_NAMES.sessionTrace, agent.ee)
24
30
  }, agent)
25
31
  }