@newrelic/browser-agent 1.295.0-rc.4 → 1.295.0-rc.6

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 (44) hide show
  1. package/dist/cjs/common/constants/env.cdn.js +1 -1
  2. package/dist/cjs/common/constants/env.npm.js +1 -1
  3. package/dist/cjs/common/wrap/wrap-events.js +2 -1
  4. package/dist/cjs/features/session_trace/aggregate/index.js +20 -21
  5. package/dist/cjs/features/session_trace/aggregate/trace/storage.js +198 -185
  6. package/dist/cjs/features/session_trace/aggregate/trace/utils.js +41 -0
  7. package/dist/cjs/features/session_trace/constants.js +3 -2
  8. package/dist/cjs/features/utils/aggregate-base.js +1 -2
  9. package/dist/cjs/features/utils/event-buffer.js +34 -3
  10. package/dist/cjs/features/utils/event-store-manager.js +18 -1
  11. package/dist/esm/common/constants/env.cdn.js +1 -1
  12. package/dist/esm/common/constants/env.npm.js +1 -1
  13. package/dist/esm/common/wrap/wrap-events.js +2 -1
  14. package/dist/esm/features/session_trace/aggregate/index.js +21 -21
  15. package/dist/esm/features/session_trace/aggregate/trace/storage.js +199 -186
  16. package/dist/esm/features/session_trace/aggregate/trace/utils.js +34 -0
  17. package/dist/esm/features/session_trace/constants.js +2 -1
  18. package/dist/esm/features/utils/aggregate-base.js +1 -2
  19. package/dist/esm/features/utils/event-buffer.js +34 -3
  20. package/dist/esm/features/utils/event-store-manager.js +18 -1
  21. package/dist/tsconfig.tsbuildinfo +1 -1
  22. package/dist/types/common/wrap/wrap-events.d.ts.map +1 -1
  23. package/dist/types/features/session_trace/aggregate/index.d.ts +5 -10
  24. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  25. package/dist/types/features/session_trace/aggregate/trace/storage.d.ts +81 -39
  26. package/dist/types/features/session_trace/aggregate/trace/storage.d.ts.map +1 -1
  27. package/dist/types/features/session_trace/aggregate/trace/utils.d.ts +7 -0
  28. package/dist/types/features/session_trace/aggregate/trace/utils.d.ts.map +1 -0
  29. package/dist/types/features/session_trace/constants.d.ts +1 -0
  30. package/dist/types/features/session_trace/constants.d.ts.map +1 -1
  31. package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
  32. package/dist/types/features/utils/event-buffer.d.ts +18 -1
  33. package/dist/types/features/utils/event-buffer.d.ts.map +1 -1
  34. package/dist/types/features/utils/event-store-manager.d.ts +12 -0
  35. package/dist/types/features/utils/event-store-manager.d.ts.map +1 -1
  36. package/package.json +1 -1
  37. package/src/common/wrap/wrap-events.js +2 -1
  38. package/src/features/session_trace/aggregate/index.js +23 -15
  39. package/src/features/session_trace/aggregate/trace/storage.js +186 -189
  40. package/src/features/session_trace/aggregate/trace/utils.js +35 -0
  41. package/src/features/session_trace/constants.js +1 -0
  42. package/src/features/utils/aggregate-base.js +1 -2
  43. package/src/features/utils/event-buffer.js +35 -3
  44. package/src/features/utils/event-store-manager.js +18 -1
@@ -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.295.0-rc.4";
20
+ const VERSION = exports.VERSION = "1.295.0-rc.6";
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.295.0-rc.4";
20
+ const VERSION = exports.VERSION = "1.295.0-rc.6";
21
21
 
22
22
  /**
23
23
  * Exposes the build type of the agent
@@ -50,7 +50,8 @@ function wrapEvents(sharedEE) {
50
50
  }
51
51
  ee.on(ADD_EVENT_LISTENER + '-start', function (args, target) {
52
52
  var originalListener = args[1];
53
- if (originalListener === null || typeof originalListener !== 'function' && typeof originalListener !== 'object') {
53
+ if (originalListener === null || typeof originalListener !== 'function' && typeof originalListener !== 'object' || args[0] === 'newrelic' // ignore our own window events
54
+ ) {
54
55
  return;
55
56
  }
56
57
  var wrapped = (0, _getOrSet.getOrSet)(originalListener, flag, function () {
@@ -19,7 +19,6 @@ var _console = require("../../../common/util/console");
19
19
  * SPDX-License-Identifier: Apache-2.0
20
20
  */
21
21
 
22
- const ERROR_MODE_SECONDS_WINDOW = 30 * 1000; // sliding window of nodes to track when simply monitoring (but not harvesting) in error mode
23
22
  /** Reserved room for query param attrs */
24
23
  const QUERY_PARAM_PADDING = 5000;
25
24
  class Aggregate extends _aggregateBase.AggregateBase {
@@ -34,8 +33,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
34
33
  this.everHarvested = false;
35
34
  /** If the harvest module is harvesting */
36
35
  this.harvesting = false;
37
- /** TraceStorage is the mechanism that holds, normalizes and aggregates ST nodes. It will be accessed and purged when harvests occur */
38
- this.events = new _storage.TraceStorage(this);
36
+ /** TraceStorage is a middleware that decides how to format data before passing events to `this.events` */
37
+ this.traceStorage = new _storage.TraceStorage(this);
39
38
 
40
39
  /* This agg needs information about sampling (sts) and entitlements (st) to make the appropriate decisions on running */
41
40
  this.waitForFlags(['sts', 'st']).then(([stMode, stEntitled]) => this.initialize(stMode, stEntitled));
@@ -66,9 +65,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
66
65
  if (this.sessionId !== sessionState.value || eventType === 'cross-tab' && sessionState.sessionTraceMode === _constants2.MODE.OFF) this.abort(2);
67
66
  });
68
67
  if (typeof PerformanceNavigationTiming !== 'undefined') {
69
- this.events.storeTiming(_runtime.globalScope.performance?.getEntriesByType?.('navigation')[0]);
68
+ this.traceStorage.storeTiming(_runtime.globalScope.performance?.getEntriesByType?.('navigation')[0]);
70
69
  } else {
71
- this.events.storeTiming(_runtime.globalScope.performance?.timing, true);
70
+ this.traceStorage.storeTiming(_runtime.globalScope.performance?.timing, true);
72
71
  }
73
72
  }
74
73
 
@@ -82,13 +81,13 @@ class Aggregate extends _aggregateBase.AggregateBase {
82
81
  this.timeKeeper ??= this.agentRef.runtime.timeKeeper;
83
82
 
84
83
  /** The handlers set up by the Inst file */
85
- (0, _registerHandler.registerHandler)('bst', (...args) => this.events.storeEvent(...args), this.featureName, this.ee);
86
- (0, _registerHandler.registerHandler)('bstResource', (...args) => this.events.storeResources(...args), this.featureName, this.ee);
87
- (0, _registerHandler.registerHandler)('bstHist', (...args) => this.events.storeHist(...args), this.featureName, this.ee);
88
- (0, _registerHandler.registerHandler)('bstXhrAgg', (...args) => this.events.storeXhrAgg(...args), this.featureName, this.ee);
89
- (0, _registerHandler.registerHandler)('bstApi', (...args) => this.events.storeNode(...args), this.featureName, this.ee);
90
- (0, _registerHandler.registerHandler)('trace-jserror', (...args) => this.events.storeErrorAgg(...args), this.featureName, this.ee);
91
- (0, _registerHandler.registerHandler)('pvtAdded', (...args) => this.events.processPVT(...args), this.featureName, this.ee);
84
+ (0, _registerHandler.registerHandler)('bst', (...args) => this.traceStorage.storeEvent(...args), this.featureName, this.ee);
85
+ (0, _registerHandler.registerHandler)('bstResource', (...args) => this.traceStorage.storeResources(...args), this.featureName, this.ee);
86
+ (0, _registerHandler.registerHandler)('bstHist', (...args) => this.traceStorage.storeHist(...args), this.featureName, this.ee);
87
+ (0, _registerHandler.registerHandler)('bstXhrAgg', (...args) => this.traceStorage.storeXhrAgg(...args), this.featureName, this.ee);
88
+ (0, _registerHandler.registerHandler)('bstApi', (...args) => this.traceStorage.storeNode(...args), this.featureName, this.ee);
89
+ (0, _registerHandler.registerHandler)('trace-jserror', (...args) => this.traceStorage.storeErrorAgg(...args), this.featureName, this.ee);
90
+ (0, _registerHandler.registerHandler)('pvtAdded', (...args) => this.traceStorage.processPVT(...args), this.featureName, this.ee);
92
91
  if (this.mode !== _constants2.MODE.FULL) {
93
92
  /** A separate handler for noticing errors, and switching to "full" mode if running in "error" mode */
94
93
  (0, _registerHandler.registerHandler)('trace-jserror', () => {
@@ -113,18 +112,12 @@ class Aggregate extends _aggregateBase.AggregateBase {
113
112
  }
114
113
  return true;
115
114
  }
116
- serializer({
117
- stns
118
- }) {
115
+ serializer(stns) {
119
116
  if (!stns.length) return; // there are no processed nodes
120
117
  this.everHarvested = true;
121
118
  return (0, _traverse.applyFnToProps)(stns, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string');
122
119
  }
123
- queryStringsBuilder({
124
- stns,
125
- earliestTimeStamp,
126
- latestTimeStamp
127
- }) {
120
+ queryStringsBuilder(stns) {
128
121
  const firstSessionHarvest = !this.agentRef.runtime.session.state.traceHarvestStarted;
129
122
  if (firstSessionHarvest) this.agentRef.runtime.session.write({
130
123
  traceHarvestStarted: true
@@ -132,6 +125,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
132
125
  const hasReplay = this.agentRef.runtime.session.state.sessionReplayMode === 1;
133
126
  const endUserId = this.agentRef.info.jsAttributes['enduser.id'];
134
127
  const entityGuid = this.agentRef.runtime.appMetadata.agents?.[0]?.entityGuid;
128
+ const earliestTimeStamp = stns.reduce((earliest, stn) => Math.min(earliest, stn.s), Infinity);
129
+ const latestTimeStamp = stns.reduce((latest, stn) => Math.max(latest, stn.s), -Infinity);
135
130
 
136
131
  /* The blob consumer expects the following and will reject if not supplied:
137
132
  * browser_monitoring_key
@@ -191,7 +186,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
191
186
  });
192
187
  if (prevMode === _constants2.MODE.OFF || !this.initialized) return this.initialize(this.mode, this.entitled);
193
188
  if (this.initialized) {
194
- this.events.trimSTNs(ERROR_MODE_SECONDS_WINDOW); // up until now, Trace would've been just buffering nodes up to max, which needs to be trimmed to last X seconds
189
+ this.traceStorage.trimSTNsByTime(); // up until now, Trace would've been just buffering nodes up to max, which needs to be trimmed to last X seconds
195
190
  this.agentRef.runtime.harvester.triggerHarvestFor(this);
196
191
  }
197
192
  }
@@ -206,5 +201,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
206
201
  });
207
202
  this.events.clear();
208
203
  }
204
+ postHarvestCleanup(result) {
205
+ this.traceStorage.clear(); // clear the trace storage state
206
+ super.postHarvestCleanup(result);
207
+ }
209
208
  }
210
209
  exports.Aggregate = Aggregate;
@@ -4,26 +4,24 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.TraceStorage = void 0;
7
- var _runtime = require("../../../../common/constants/runtime");
8
7
  var _constants = require("../../../../common/session/constants");
9
8
  var _now = require("../../../../common/timing/now");
10
9
  var _parseUrl = require("../../../../common/url/parse-url");
11
10
  var _eventOrigin = require("../../../../common/util/event-origin");
12
11
  var _constants2 = require("../../constants");
13
12
  var _node = require("./node");
13
+ var _utils = require("./utils");
14
14
  /**
15
15
  * Copyright 2020-2025 New Relic, Inc. All rights reserved.
16
16
  * SPDX-License-Identifier: Apache-2.0
17
17
  */
18
18
 
19
- const ERROR_MODE_SECONDS_WINDOW = 30 * 1000; // sliding window of nodes to track when simply monitoring (but not harvesting) in error mode
20
- const SUPPORTS_PERFORMANCE_OBSERVER = typeof _runtime.globalScope.PerformanceObserver === 'function';
21
19
  const ignoredEvents = {
22
- // we find that certain events make the data too noisy to be useful
20
+ // we find that certain events are noisy (and not easily smearable like mousemove) and/or duplicative (like with click vs mousedown/mouseup).
21
+ // These would ONLY ever be tracked in ST if the application has event listeners defined for these events... however, just in case - ignore these anyway.
23
22
  global: {
24
23
  mouseup: true,
25
- mousedown: true,
26
- mousemove: true
24
+ mousedown: true
27
25
  },
28
26
  // certain events are present both in the window and in PVT metrics. PVT metrics are prefered so the window events should be ignored
29
27
  window: {
@@ -35,128 +33,146 @@ const ignoredEvents = {
35
33
  ignoreAll: true
36
34
  }
37
35
  };
38
- const toAggregate = {
39
- typing: [1000, 2000],
40
- scrolling: [100, 1000],
41
- mousing: [1000, 2000],
42
- touching: [1000, 2000]
36
+ const SMEARABLES = {
37
+ typing: 'typing',
38
+ scrolling: 'scrolling',
39
+ mousing: 'mousing',
40
+ touching: 'touching'
41
+ };
42
+ const GAPS = {
43
+ [SMEARABLES.typing]: 1000,
44
+ // 1 second gap between typing events
45
+ [SMEARABLES.scrolling]: 100,
46
+ // 100ms gap between scrolling events
47
+ [SMEARABLES.mousing]: 1000,
48
+ // 1 second gap between mousing events
49
+ [SMEARABLES.touching]: 1000 // 1 second gap between touching events
50
+ };
51
+ const LENGTHS = {
52
+ [SMEARABLES.typing]: 2000,
53
+ // 2 seconds max length for typing events
54
+ [SMEARABLES.scrolling]: 1000,
55
+ // 1 second max length for scrolling events
56
+ [SMEARABLES.mousing]: 2000,
57
+ // 2 seconds max length for mousing events
58
+ [SMEARABLES.touching]: 2000 // 2 seconds max length for touching events
43
59
  };
44
60
 
45
- /** The purpose of this class is to manage, normalize, and retrieve ST nodes as needed without polluting the main ST modules */
61
+ /** The purpose of this class is to manage, normalize, and drop various ST nodes as needed without polluting the main ST modules */
46
62
  class TraceStorage {
47
- nodeCount = 0;
48
- trace = {};
49
- earliestTimeStamp = Infinity;
50
- latestTimeStamp = 0;
63
+ /** prevents duplication of event nodes by keeping a reference of each one seen per harvest cycle */
51
64
  prevStoredEvents = new Set();
52
- #backupTrace;
53
65
  constructor(parent) {
54
66
  this.parent = parent;
55
67
  }
68
+
69
+ /**
70
+ * Checks if a trace node is smearable with previously stored nodes.
71
+ * @param {TraceNode} stn
72
+ * @returns {boolean} true if the node is smearable, false otherwise
73
+ */
74
+ #isSmearable(stn) {
75
+ return stn.n in SMEARABLES;
76
+ }
77
+
78
+ /**
79
+ * Attempts to smear the current trace node with the last stored event in the event buffer.
80
+ * If the last stored event is smearable and matches the current node's origin and type, it will merge the two nodes and return true.
81
+ * If not, it will return false.
82
+ * This is used to reduce the number of smearable trace nodes created for events that occur in quick succession.
83
+ * @param {TraceNode} stn
84
+ * @returns {boolean} true if the node was successfully smeared, false otherwise
85
+ */
86
+ #smear(stn) {
87
+ /**
88
+ * The matcher function to be executed by the event buffer merge method. It must be the same origin and node type,
89
+ * the start time of the new node must be within a certain length of the last seen node's start time,
90
+ * and the end time of the last seen node must be within a certain gap of the new node's start time.
91
+ * If all these conditions are met, we can merge the last seen node's end time with the new one.
92
+ * @param {TraceNode} storedEvent - the event already stored in the event buffer to potentially be merged with
93
+ */
94
+ const matcher = storedEvent => {
95
+ return !(storedEvent.o !== stn.o || storedEvent.n !== stn.n || stn.s - storedEvent.s < LENGTHS[stn.o] || storedEvent.e > stn.s - GAPS[stn.o]);
96
+ };
97
+
98
+ /** the data to be smeared together with a matching event -- if one is found in the event buffer using the matcher defined above */
99
+ const smearableData = {
100
+ e: stn.e
101
+ };
102
+ return this.parent.events.merge(matcher, smearableData);
103
+ }
104
+
105
+ /**
106
+ * Checks if the event should be ignored based on rules around its type and/or origin.
107
+ * @param {TraceNode} stn
108
+ * @returns {boolean} true if the event should be ignored, false otherwise
109
+ */
110
+ #shouldIgnoreEvent(stn) {
111
+ if (stn.n in ignoredEvents.global) return true; // ignore noisy global events or window events that are already captured by PVT metrics
112
+ const origin = stn.o;
113
+ if (ignoredEvents[origin]?.ignoreAll || ignoredEvents[origin]?.[stn.n]) return true;
114
+ return origin === 'xhrOriginMissing' && stn.n === 'Ajax'; // ignore XHR events when the origin is missing
115
+ }
116
+
117
+ /**
118
+ * Checks if a new node can be stored based on the current state of the trace storage class itself as well as the parent class.
119
+ * @returns {boolean} true if a new node can be stored, false otherwise
120
+ */
56
121
  #canStoreNewNode() {
57
122
  if (this.parent.blocked) return false;
58
- if (this.nodeCount >= _constants2.MAX_NODES_PER_HARVEST) {
123
+ if (this.parent.events.length >= _constants2.MAX_NODES_PER_HARVEST) {
59
124
  // limit the amount of pending data awaiting next harvest
60
125
  if (this.parent.mode !== _constants.MODE.ERROR) return false;
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
62
- if (openedSpace === 0) return false;
126
+ this.trimSTNsByTime(); // if we're in error mode, we can try to clear the trace storage to make room for new nodes
127
+ if (this.parent.events.length >= _constants2.MAX_NODES_PER_HARVEST) this.trimSTNsByIndex(1); // if we still can't store new nodes, trim before index 1 to make room for new nodes
63
128
  }
64
129
  return true;
65
130
  }
66
131
 
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!! */
132
+ /**
133
+ * Attempts to store a new trace node in the event buffer.
134
+ * @param {TraceNode} stn
135
+ * @returns {boolean} true if the node was successfully stored, false otherwise
136
+ */
68
137
  #storeSTN(stn) {
69
- if (this.trace[stn.n]) this.trace[stn.n].push(stn);else this.trace[stn.n] = [stn];
70
- if (stn.s < this.earliestTimeStamp) this.earliestTimeStamp = stn.s;
71
- if (stn.s > this.latestTimeStamp) this.latestTimeStamp = stn.s;
72
- this.nodeCount++;
138
+ if (this.#shouldIgnoreEvent(stn) || !this.#canStoreNewNode()) return false;
139
+
140
+ /** attempt to smear -- if not possible or it doesnt find a match -- just add it directly to the event buffer */
141
+ if (!this.#isSmearable(stn) || !this.#smear(stn)) this.parent.events.add(stn);
142
+ return true;
73
143
  }
74
144
 
75
145
  /**
76
- * Trim the collection of nodes awaiting harvest such that those seen outside a certain span of time are discarded.
77
- * @param {number} lookbackDuration Past length of time until now for which we care about nodes, in milliseconds
78
- * @returns {number} However many nodes were discarded after trimming.
146
+ * Stores a new trace node in the event buffer.
147
+ * @param {TraceNode} node
148
+ * @returns {boolean} true if the node was successfully stored, false otherwise
79
149
  */
80
- trimSTNs(lookbackDuration) {
81
- let prunedNodes = 0;
82
- const cutoffHighResTime = Math.max((0, _now.now)() - lookbackDuration, 0);
83
- Object.keys(this.trace).forEach(nameCategory => {
84
- const nodeList = this.trace[nameCategory];
85
- /* Notice nodes are appending under their name's list as they end and are stored. This means each list is already (roughly) sorted in chronological order by end time.
86
- * This isn't exact since nodes go through some processing & EE handlers chain, but it's close enough as we still capture nodes whose duration overlaps the lookback window.
87
- * ASSUMPTION: all 'end' timings stored are relative to timeOrigin (DOMHighResTimeStamp) and not Unix epoch based. */
88
- let cutoffIdx = nodeList.findIndex(node => cutoffHighResTime <= node.e);
89
- if (cutoffIdx === 0) return;else if (cutoffIdx < 0) {
90
- // whole list falls outside lookback window and is irrelevant
91
- cutoffIdx = nodeList.length;
92
- delete this.trace[nameCategory];
93
- } else nodeList.splice(0, cutoffIdx); // chop off everything outside our window i.e. before the last <lookbackDuration> timeframe
94
-
95
- this.nodeCount -= cutoffIdx;
96
- prunedNodes += cutoffIdx;
97
- });
98
- return prunedNodes;
99
- }
100
-
101
- /** Used by session trace's harvester to create the payload body. */
102
- takeSTNs() {
103
- if (!SUPPORTS_PERFORMANCE_OBSERVER) {
104
- // if PO isn't supported, this checks resourcetiming buffer every harvest.
105
- this.storeResources(_runtime.globalScope.performance?.getEntriesByType?.('resource'));
106
- }
107
- const stns = Object.entries(this.trace).flatMap(([name, listOfSTNodes]) => {
108
- // basically take the "this.trace" map-obj and concat all the list-type values
109
- if (!(name in toAggregate)) return listOfSTNodes;
110
- // Special processing for event nodes dealing with user inputs:
111
- const reindexByOriginFn = this.smearEvtsByOrigin(name);
112
- const partitionListByOriginMap = listOfSTNodes.sort((a, b) => a.s - b.s).reduce(reindexByOriginFn, {});
113
- return Object.values(partitionListByOriginMap).flat(); // join the partitions back into 1-D, now ordered by origin then start time
114
- }, this);
115
- const earliestTimeStamp = this.earliestTimeStamp;
116
- const latestTimeStamp = this.latestTimeStamp;
117
- return {
118
- stns,
119
- earliestTimeStamp,
120
- latestTimeStamp
121
- };
122
- }
123
- smearEvtsByOrigin(name) {
124
- const maxGap = toAggregate[name][0];
125
- const maxLen = toAggregate[name][1];
126
- const lastO = {};
127
- return (byOrigin, evtNode) => {
128
- let lastArr = byOrigin[evtNode.o];
129
- if (!lastArr) lastArr = byOrigin[evtNode.o] = [];
130
- const last = lastO[evtNode.o];
131
- if (name === 'scrolling' && !trivial(evtNode)) {
132
- lastO[evtNode.o] = null;
133
- evtNode.n = 'scroll';
134
- lastArr.push(evtNode);
135
- } else if (last && evtNode.s - last.s < maxLen && last.e > evtNode.s - maxGap) {
136
- last.e = evtNode.e;
137
- } else {
138
- lastO[evtNode.o] = evtNode;
139
- lastArr.push(evtNode);
140
- }
141
- return byOrigin;
142
- };
143
- function trivial(node) {
144
- const limit = 4;
145
- return !!(node && typeof node.e === 'number' && typeof node.s === 'number' && node.e - node.s < limit);
146
- }
147
- }
148
150
  storeNode(node) {
149
- if (!this.#canStoreNewNode()) return;
150
- this.#storeSTN(node);
151
+ return this.#storeSTN(node);
151
152
  }
153
+
154
+ /**
155
+ * Processes a PVT (Page Visibility Timing) entry.
156
+ * @param {*} name
157
+ * @param {*} value
158
+ * @param {*} attrs
159
+ * @returns {boolean} true if the node was successfully stored, false otherwise
160
+ */
152
161
  processPVT(name, value, attrs) {
153
- this.storeTiming({
162
+ return this.storeTiming({
154
163
  [name]: value
155
164
  });
156
165
  }
157
- storeTiming(timingEntry, isAbsoluteTimestamp = false) {
158
- if (!timingEntry) return;
159
166
 
167
+ /**
168
+ * Stores a timing entry in the event buffer.
169
+ * @param {*} timingEntry
170
+ * @param {*} isAbsoluteTimestamp
171
+ * @returns {boolean} true if ALL possible nodes were successfully stored, false otherwise
172
+ */
173
+ storeTiming(timingEntry, isAbsoluteTimestamp = false) {
174
+ if (!timingEntry) return false;
175
+ let allStored = true;
160
176
  // loop iterates through prototype also (for FF)
161
177
  for (let key in timingEntry) {
162
178
  let val = timingEntry[key];
@@ -172,19 +188,27 @@ class TraceStorage {
172
188
  if (this.parent.timeKeeper && this.parent.timeKeeper.ready && isAbsoluteTimestamp) {
173
189
  val = this.parent.timeKeeper.convertAbsoluteTimestamp(Math.floor(this.parent.timeKeeper.correctAbsoluteTimestamp(val)));
174
190
  }
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'));
191
+ if (!this.#storeSTN(new _node.TraceNode(key, val, val, 'document', 'timing'))) allStored = false;
177
192
  }
193
+ return allStored;
178
194
  }
179
195
 
180
- // Tracks the events and their listener's duration on objects wrapped by wrap-events.
196
+ /**
197
+ * Tracks the events and their listener's duration on objects wrapped by wrap-events.
198
+ * @param {*} currentEvent - the event to be stored
199
+ * @param {*} target - the target of the event
200
+ * @param {*} start - the start time of the event
201
+ * @param {*} end - the end time of the event
202
+ * @returns {boolean} true if the event was successfully stored, false otherwise
203
+ */
181
204
  storeEvent(currentEvent, target, start, end) {
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
-
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.
205
+ /**
206
+ * Important! -- This check NEEDS to be done directly in this handler, before converting to a TraceNode so that the
207
+ * reference pointer to the Event node itself is the object being checked for duplication
208
+ * **/
209
+ if (this.prevStoredEvents.has(currentEvent) || !this.#canStoreNewNode()) return false;
186
210
  this.prevStoredEvents.add(currentEvent);
187
- const evt = new _node.TraceNode(this.evtName(currentEvent.type), start, end, undefined, 'event');
211
+ const evt = new _node.TraceNode((0, _utils.evtName)(currentEvent.type), start, end, undefined, 'event');
188
212
  try {
189
213
  // webcomponents-lite.js can trigger an exception on currentEvent.target getter because
190
214
  // it does not check currentEvent.currentTarget before calling getRootNode() on it
@@ -192,52 +216,30 @@ class TraceStorage {
192
216
  } catch (e) {
193
217
  evt.o = (0, _eventOrigin.eventOrigin)(null, target, this.parent.ee);
194
218
  }
195
- this.#storeSTN(evt);
196
- }
197
- shouldIgnoreEvent(event, target) {
198
- if (event.type in ignoredEvents.global) return true;
199
- const origin = (0, _eventOrigin.eventOrigin)(event.target, target, this.parent.ee);
200
- if (!!ignoredEvents[origin] && ignoredEvents[origin].ignoreAll) return true;
201
- return !!(!!ignoredEvents[origin] && event.type in ignoredEvents[origin]);
202
- }
203
- evtName(type) {
204
- switch (type) {
205
- case 'keydown':
206
- case 'keyup':
207
- case 'keypress':
208
- return 'typing';
209
- case 'mousemove':
210
- case 'mouseenter':
211
- case 'mouseleave':
212
- case 'mouseover':
213
- case 'mouseout':
214
- return 'mousing';
215
- case 'scroll':
216
- return 'scrolling';
217
- case 'touchstart':
218
- case 'touchmove':
219
- case 'touchend':
220
- case 'touchcancel':
221
- case 'touchenter':
222
- case 'touchleave':
223
- return 'touching';
224
- default:
225
- return type;
226
- }
219
+ return this.#storeSTN(evt);
227
220
  }
228
221
 
229
- // Tracks when the window history API specified by wrap-history is used.
222
+ /**
223
+ * Tracks when the window history API specified by wrap-history is used.
224
+ * @param {*} path
225
+ * @param {*} old
226
+ * @param {*} time
227
+ * @returns {boolean} true if the history node was successfully stored, false otherwise
228
+ */
230
229
  storeHist(path, old, time) {
231
- if (!this.#canStoreNewNode()) return;
232
- this.#storeSTN(new _node.TraceNode('history.pushState', time, time, path, old));
230
+ return this.#storeSTN(new _node.TraceNode('history.pushState', time, time, path, old));
233
231
  }
234
- #laststart = 0;
235
- // Processes all the PerformanceResourceTiming entries captured (by observer).
232
+
233
+ /**
234
+ * Processes all the PerformanceResourceTiming entries captured (by observer).
235
+ * @param {*[]} resources
236
+ * @returns {boolean} true if all resource nodes were successfully stored, false otherwise
237
+ */
236
238
  storeResources(resources) {
237
- if (!resources || resources.length === 0) return;
239
+ if (!resources || resources.length === 0) return false;
240
+ let allStored = true;
238
241
  for (let i = 0; i < resources.length; i++) {
239
242
  const currentResource = resources[i];
240
- if ((currentResource.fetchStart | 0) <= this.#laststart) continue; // don't recollect already-seen resources
241
243
  if (!this.#canStoreNewNode()) break; // stop processing if we can't store any more resource nodes anyways
242
244
 
243
245
  const {
@@ -253,56 +255,67 @@ class TraceStorage {
253
255
  pathname
254
256
  } = (0, _parseUrl.parseUrl)(currentResource.name);
255
257
  const res = new _node.TraceNode(initiatorType, fetchStart | 0, responseEnd | 0, "".concat(protocol, "://").concat(hostname, ":").concat(port).concat(pathname), entryType);
256
- this.#storeSTN(res);
258
+ if (!this.#storeSTN(res)) allStored = false;
257
259
  }
258
- this.#laststart = resources[resources.length - 1].fetchStart | 0;
260
+ return allStored;
259
261
  }
260
262
 
261
- // JavascriptError (FEATURE) events pipes into ST here.
263
+ /**
264
+ * JavascriptError (FEATURE) events pipes into ST here.
265
+ * @param {*} type
266
+ * @param {*} name
267
+ * @param {*} params
268
+ * @param {*} metrics
269
+ * @returns {boolean} true if the error node was successfully stored, false otherwise
270
+ */
262
271
  storeErrorAgg(type, name, params, metrics) {
263
- if (type !== 'err') return; // internal errors are purposefully ignored
264
- if (!this.#canStoreNewNode()) return;
265
- this.#storeSTN(new _node.TraceNode('error', metrics.time, metrics.time, params.message, params.stackHash));
272
+ if (type !== 'err') return false; // internal errors are purposefully ignored
273
+ return this.#storeSTN(new _node.TraceNode('error', metrics.time, metrics.time, params.message, params.stackHash));
266
274
  }
267
275
 
268
- // Ajax (FEATURE) events--XML & fetches--pipes into ST here.
276
+ /**
277
+ * Ajax (FEATURE) events--XML & fetches--pipes into ST here.
278
+ * @param {*} type
279
+ * @param {*} name
280
+ * @param {*} params
281
+ * @param {*} metrics
282
+ * @returns {boolean} true if the Ajax node was successfully stored, false otherwise
283
+ */
269
284
  storeXhrAgg(type, name, params, metrics) {
270
- if (type !== 'xhr') return;
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'));
285
+ if (type !== 'xhr') return false;
286
+ return 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'));
273
287
  }
274
288
 
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.
276
- Note that the usage must be in sync with the EventStoreManager class such that AggregateBase.makeHarvestPayload can run the same regardless of which storage class a feature is using. */
277
- isEmpty() {
278
- return this.nodeCount === 0;
279
- }
280
- save() {
281
- this.#backupTrace = this.trace;
289
+ /**
290
+ * Trims stored trace nodes in the event buffer by start time.
291
+ * @param {number} lookbackDuration
292
+ * @returns {void}
293
+ */
294
+ trimSTNsByTime(lookbackDuration = _constants2.ERROR_MODE_SECONDS_WINDOW) {
295
+ this.parent.events.clear({
296
+ clearBeforeTime: Math.max(_now.now - lookbackDuration, 0),
297
+ timestampKey: 'e'
298
+ });
282
299
  }
283
- get() {
284
- return [{
285
- targetApp: this.parent.agentRef.runtime.entityManager.get(),
286
- data: this.takeSTNs()
287
- }];
300
+
301
+ /**
302
+ * Trims stored trace nodes in the event buffer before a given index value.
303
+ * @param {number} index
304
+ * @returns {void}
305
+ */
306
+ trimSTNsByIndex(index = 0) {
307
+ this.parent.events.clear({
308
+ clearBeforeIndex: index // trims before index value
309
+ });
288
310
  }
311
+
312
+ /**
313
+ * clears the stored events in the event buffer.
314
+ * This is used to release references to past events for garbage collection.
315
+ * @returns {void}
316
+ */
289
317
  clear() {
290
- this.trace = {};
291
- this.nodeCount = 0;
292
318
  this.prevStoredEvents.clear(); // release references to past events for GC
293
- this.earliestTimeStamp = Infinity;
294
- this.latestTimeStamp = 0;
295
- }
296
- reloadSave() {
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
- }
303
- }
304
- clearSave() {
305
- this.#backupTrace = undefined;
306
319
  }
307
320
  }
308
321
  exports.TraceStorage = TraceStorage;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.evtName = evtName;
7
+ exports.isTrivial = isTrivial;
8
+ /**
9
+ * Copyright 2020-2025 New Relic, Inc. All rights reserved.
10
+ * SPDX-License-Identifier: Apache-2.0
11
+ */
12
+ function evtName(type) {
13
+ switch (type) {
14
+ case 'keydown':
15
+ case 'keyup':
16
+ case 'keypress':
17
+ return 'typing';
18
+ case 'mousemove':
19
+ case 'mouseenter':
20
+ case 'mouseleave':
21
+ case 'mouseover':
22
+ case 'mouseout':
23
+ return 'mousing';
24
+ case 'touchstart':
25
+ case 'touchmove':
26
+ case 'touchend':
27
+ case 'touchcancel':
28
+ case 'touchenter':
29
+ case 'touchleave':
30
+ return 'touching';
31
+ case 'scroll':
32
+ case 'scrollend':
33
+ return 'scrolling';
34
+ default:
35
+ return type;
36
+ }
37
+ }
38
+ function isTrivial(node) {
39
+ const limit = 4;
40
+ return !!(node && typeof node.e === 'number' && typeof node.s === 'number' && node.e - node.s < limit);
41
+ }