@newrelic/browser-agent 1.271.0 → 1.273.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/cjs/common/aggregate/aggregator.js +23 -30
  3. package/dist/cjs/common/aggregate/event-aggregator.js +84 -0
  4. package/dist/cjs/common/config/init.js +8 -4
  5. package/dist/cjs/common/constants/env.cdn.js +1 -1
  6. package/dist/cjs/common/constants/env.npm.js +1 -1
  7. package/dist/cjs/common/harvest/harvest-scheduler.js +1 -1
  8. package/dist/cjs/common/harvest/harvest.js +1 -5
  9. package/dist/cjs/common/harvest/types.js +0 -1
  10. package/dist/cjs/features/ajax/aggregate/index.js +52 -62
  11. package/dist/cjs/features/generic_events/aggregate/index.js +57 -36
  12. package/dist/cjs/features/generic_events/instrument/index.js +1 -1
  13. package/dist/cjs/features/jserrors/aggregate/index.js +23 -69
  14. package/dist/cjs/features/logging/aggregate/index.js +52 -59
  15. package/dist/cjs/features/metrics/aggregate/index.js +8 -5
  16. package/dist/cjs/features/page_view_timing/aggregate/index.js +8 -25
  17. package/dist/cjs/features/session_replay/aggregate/index.js +11 -10
  18. package/dist/cjs/features/session_replay/shared/recorder-events.js +2 -2
  19. package/dist/cjs/features/session_trace/aggregate/index.js +77 -88
  20. package/dist/cjs/features/session_trace/aggregate/trace/storage.js +22 -13
  21. package/dist/cjs/features/soft_navigations/aggregate/index.js +10 -20
  22. package/dist/cjs/features/soft_navigations/instrument/index.js +5 -9
  23. package/dist/cjs/features/spa/aggregate/index.js +10 -26
  24. package/dist/cjs/features/utils/aggregate-base.js +37 -0
  25. package/dist/cjs/features/utils/event-buffer.js +36 -87
  26. package/dist/cjs/features/utils/instrument-base.js +3 -3
  27. package/dist/cjs/loaders/features/features.js +13 -1
  28. package/dist/esm/common/aggregate/aggregator.js +23 -30
  29. package/dist/esm/common/aggregate/event-aggregator.js +78 -0
  30. package/dist/esm/common/config/init.js +8 -4
  31. package/dist/esm/common/constants/env.cdn.js +1 -1
  32. package/dist/esm/common/constants/env.npm.js +1 -1
  33. package/dist/esm/common/harvest/harvest-scheduler.js +1 -1
  34. package/dist/esm/common/harvest/harvest.js +1 -5
  35. package/dist/esm/common/harvest/types.js +0 -1
  36. package/dist/esm/features/ajax/aggregate/index.js +53 -62
  37. package/dist/esm/features/generic_events/aggregate/index.js +57 -36
  38. package/dist/esm/features/generic_events/instrument/index.js +1 -1
  39. package/dist/esm/features/jserrors/aggregate/index.js +24 -70
  40. package/dist/esm/features/logging/aggregate/index.js +52 -59
  41. package/dist/esm/features/metrics/aggregate/index.js +8 -5
  42. package/dist/esm/features/page_view_timing/aggregate/index.js +9 -26
  43. package/dist/esm/features/session_replay/aggregate/index.js +12 -11
  44. package/dist/esm/features/session_replay/shared/recorder-events.js +2 -2
  45. package/dist/esm/features/session_trace/aggregate/index.js +77 -88
  46. package/dist/esm/features/session_trace/aggregate/trace/storage.js +22 -13
  47. package/dist/esm/features/soft_navigations/aggregate/index.js +11 -21
  48. package/dist/esm/features/soft_navigations/instrument/index.js +5 -9
  49. package/dist/esm/features/spa/aggregate/index.js +11 -27
  50. package/dist/esm/features/utils/aggregate-base.js +37 -0
  51. package/dist/esm/features/utils/event-buffer.js +36 -88
  52. package/dist/esm/features/utils/instrument-base.js +3 -3
  53. package/dist/esm/loaders/features/features.js +12 -0
  54. package/dist/types/common/aggregate/aggregator.d.ts +4 -6
  55. package/dist/types/common/aggregate/aggregator.d.ts.map +1 -1
  56. package/dist/types/common/aggregate/event-aggregator.d.ts +26 -0
  57. package/dist/types/common/aggregate/event-aggregator.d.ts.map +1 -0
  58. package/dist/types/common/config/init.d.ts.map +1 -1
  59. package/dist/types/common/harvest/harvest.d.ts.map +1 -1
  60. package/dist/types/common/harvest/types.d.ts +1 -4
  61. package/dist/types/common/harvest/types.d.ts.map +1 -1
  62. package/dist/types/features/ajax/aggregate/index.d.ts +2 -10
  63. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  64. package/dist/types/features/generic_events/aggregate/index.d.ts +5 -11
  65. package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
  66. package/dist/types/features/generic_events/instrument/index.d.ts.map +1 -1
  67. package/dist/types/features/jserrors/aggregate/index.d.ts +4 -7
  68. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  69. package/dist/types/features/logging/aggregate/index.d.ts +10 -28
  70. package/dist/types/features/logging/aggregate/index.d.ts.map +1 -1
  71. package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
  72. package/dist/types/features/page_view_timing/aggregate/index.d.ts +1 -9
  73. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  74. package/dist/types/features/session_replay/aggregate/index.d.ts +3 -4
  75. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  76. package/dist/types/features/session_replay/shared/recorder-events.d.ts +1 -1
  77. package/dist/types/features/session_replay/shared/recorder-events.d.ts.map +1 -1
  78. package/dist/types/features/session_replay/shared/recorder.d.ts +1 -1
  79. package/dist/types/features/session_trace/aggregate/index.d.ts +17 -19
  80. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  81. package/dist/types/features/session_trace/aggregate/trace/storage.d.ts +10 -6
  82. package/dist/types/features/session_trace/aggregate/trace/storage.d.ts.map +1 -1
  83. package/dist/types/features/soft_navigations/aggregate/index.d.ts +3 -9
  84. package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -1
  85. package/dist/types/features/soft_navigations/instrument/index.d.ts.map +1 -1
  86. package/dist/types/features/spa/aggregate/index.d.ts +2 -3
  87. package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
  88. package/dist/types/features/utils/aggregate-base.d.ts +14 -0
  89. package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
  90. package/dist/types/features/utils/event-buffer.d.ts +19 -56
  91. package/dist/types/features/utils/event-buffer.d.ts.map +1 -1
  92. package/dist/types/loaders/features/features.d.ts +3 -0
  93. package/dist/types/loaders/features/features.d.ts.map +1 -1
  94. package/package.json +3 -2
  95. package/src/common/aggregate/aggregator.js +22 -32
  96. package/src/common/aggregate/event-aggregator.js +76 -0
  97. package/src/common/config/init.js +6 -2
  98. package/src/common/harvest/harvest-scheduler.js +1 -1
  99. package/src/common/harvest/harvest.js +1 -5
  100. package/src/common/harvest/types.js +0 -1
  101. package/src/features/ajax/aggregate/index.js +60 -67
  102. package/src/features/generic_events/aggregate/index.js +48 -38
  103. package/src/features/generic_events/instrument/index.js +2 -0
  104. package/src/features/jserrors/aggregate/index.js +21 -77
  105. package/src/features/logging/aggregate/index.js +46 -60
  106. package/src/features/metrics/aggregate/index.js +6 -4
  107. package/src/features/page_view_timing/aggregate/index.js +9 -30
  108. package/src/features/session_replay/aggregate/index.js +10 -14
  109. package/src/features/session_replay/shared/recorder-events.js +2 -2
  110. package/src/features/session_trace/aggregate/index.js +64 -73
  111. package/src/features/session_trace/aggregate/trace/storage.js +25 -14
  112. package/src/features/soft_navigations/aggregate/index.js +11 -22
  113. package/src/features/soft_navigations/instrument/index.js +6 -9
  114. package/src/features/spa/aggregate/index.js +12 -27
  115. package/src/features/utils/aggregate-base.js +39 -0
  116. package/src/features/utils/event-buffer.js +36 -83
  117. package/src/features/utils/instrument-base.js +3 -3
  118. package/src/loaders/features/features.js +13 -0
  119. package/dist/cjs/features/ajax/aggregate/chunk.js +0 -51
  120. package/dist/esm/features/ajax/aggregate/chunk.js +0 -44
  121. package/dist/types/features/ajax/aggregate/chunk.d.ts +0 -8
  122. package/dist/types/features/ajax/aggregate/chunk.d.ts.map +0 -1
  123. package/src/features/ajax/aggregate/chunk.js +0 -52
@@ -13,6 +13,7 @@ var _encode = require("../../../common/url/encode");
13
13
  var _runtime = require("../../../common/constants/runtime");
14
14
  var _constants2 = require("../../../common/session/constants");
15
15
  var _traverse = require("../../../common/util/traverse");
16
+ var _features = require("../../../loaders/features/features");
16
17
  var _cleanUrl = require("../../../common/url/clean-url");
17
18
  const ERROR_MODE_SECONDS_WINDOW = 30 * 1000; // sliding window of nodes to track when simply monitoring (but not harvesting) in error mode
18
19
  /** Reserved room for query param attrs */
@@ -21,9 +22,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
21
22
  static featureName = _constants.FEATURE_NAME;
22
23
  constructor(agentRef) {
23
24
  super(agentRef, _constants.FEATURE_NAME);
24
-
25
- /** A buffer to hold on to harvested traces in the case that a retry must be made later */
26
- this.sentTrace = null;
27
25
  this.harvestTimeSeconds = agentRef.init.session_trace.harvestTimeSeconds || 30;
28
26
  /** Tied to the entitlement flag response from BCS. Will short circuit operations of the agg if false */
29
27
  this.entitled = undefined;
@@ -32,7 +30,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
32
30
  /** If the harvest module is harvesting */
33
31
  this.harvesting = false;
34
32
  /** TraceStorage is the mechanism that holds, normalizes and aggregates ST nodes. It will be accessed and purged when harvests occur */
35
- this.traceStorage = new _storage.TraceStorage(this);
33
+ this.events = new _storage.TraceStorage(this);
36
34
  /** This agg needs information about sampling (sts) and entitlements (st) to make the appropriate decisions on running */
37
35
  this.waitForFlags(['sts', 'st']).then(([stMode, stEntitled]) => this.initialize(stMode, stEntitled));
38
36
  }
@@ -61,9 +59,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
61
59
  if (this.sessionId !== sessionState.value || eventType === 'cross-tab' && this.scheduler?.started && sessionState.sessionTraceMode === _constants2.MODE.OFF) this.abort(2);
62
60
  });
63
61
  if (typeof PerformanceNavigationTiming !== 'undefined') {
64
- this.traceStorage.storeTiming(_runtime.globalScope.performance?.getEntriesByType?.('navigation')[0]);
62
+ this.events.storeTiming(_runtime.globalScope.performance?.getEntriesByType?.('navigation')[0]);
65
63
  } else {
66
- this.traceStorage.storeTiming(_runtime.globalScope.performance?.timing, true);
64
+ this.events.storeTiming(_runtime.globalScope.performance?.timing, true);
67
65
  }
68
66
  }
69
67
 
@@ -75,21 +73,21 @@ class Aggregate extends _aggregateBase.AggregateBase {
75
73
  * If it drains later (due to a mode change), data and handlers will instantly drain instead of waiting for the registry. */
76
74
  if (this.mode === _constants2.MODE.OFF) return this.deregisterDrain();
77
75
  this.timeKeeper ??= this.agentRef.runtime.timeKeeper;
78
- this.scheduler = new _harvestScheduler.HarvestScheduler('browser/blobs', {
79
- onFinished: this.onHarvestFinished.bind(this),
76
+ this.scheduler = new _harvestScheduler.HarvestScheduler(_features.FEATURE_TO_ENDPOINT[this.featureName], {
77
+ onFinished: result => this.postHarvestCleanup(result.sent && result.retry),
80
78
  retryDelay: this.harvestTimeSeconds,
81
- getPayload: this.prepareHarvest.bind(this),
79
+ getPayload: options => this.makeHarvestPayload(options.retry),
82
80
  raw: true
83
81
  }, this);
84
82
 
85
83
  /** The handlers set up by the Inst file */
86
- (0, _registerHandler.registerHandler)('bst', (...args) => this.traceStorage.storeEvent(...args), this.featureName, this.ee);
87
- (0, _registerHandler.registerHandler)('bstResource', (...args) => this.traceStorage.storeResources(...args), this.featureName, this.ee);
88
- (0, _registerHandler.registerHandler)('bstHist', (...args) => this.traceStorage.storeHist(...args), this.featureName, this.ee);
89
- (0, _registerHandler.registerHandler)('bstXhrAgg', (...args) => this.traceStorage.storeXhrAgg(...args), this.featureName, this.ee);
90
- (0, _registerHandler.registerHandler)('bstApi', (...args) => this.traceStorage.storeSTN(...args), this.featureName, this.ee);
91
- (0, _registerHandler.registerHandler)('trace-jserror', (...args) => this.traceStorage.storeErrorAgg(...args), this.featureName, this.ee);
92
- (0, _registerHandler.registerHandler)('pvtAdded', (...args) => this.traceStorage.processPVT(...args), this.featureName, this.ee);
84
+ (0, _registerHandler.registerHandler)('bst', (...args) => this.events.storeEvent(...args), this.featureName, this.ee);
85
+ (0, _registerHandler.registerHandler)('bstResource', (...args) => this.events.storeResources(...args), this.featureName, this.ee);
86
+ (0, _registerHandler.registerHandler)('bstHist', (...args) => this.events.storeHist(...args), this.featureName, this.ee);
87
+ (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)('trace-jserror', (...args) => this.events.storeErrorAgg(...args), this.featureName, this.ee);
90
+ (0, _registerHandler.registerHandler)('pvtAdded', (...args) => this.events.processPVT(...args), this.featureName, this.ee);
93
91
 
94
92
  /** Only start actually harvesting if running in full mode at init time */
95
93
  if (this.mode === _constants2.MODE.FULL) this.startHarvesting();else {
@@ -110,32 +108,38 @@ class Aggregate extends _aggregateBase.AggregateBase {
110
108
  this.scheduler.runHarvest();
111
109
  this.scheduler.startTimer(this.harvestTimeSeconds);
112
110
  }
113
-
114
- /** Called by the harvest scheduler at harvest time to retrieve the payload. This will only actually return a payload if running in full mode */
115
- prepareHarvest(options = {}) {
116
- this.traceStorage.prevStoredEvents.clear(); // release references to past events for GC
111
+ preHarvestChecks() {
112
+ if (this.mode !== _constants2.MODE.FULL) return; // only allow harvest if running in full mode
117
113
  if (!this.timeKeeper?.ready) return; // this should likely never happen, but just to be safe, we should never harvest if we cant correct time
118
- if (this.blocked || this.mode !== _constants2.MODE.FULL || this.traceStorage.nodeCount === 0) return;
119
- if (this.sessionId !== this.agentRef.runtime.session?.state.value || this.ptid !== this.agentRef.runtime.ptid) return this.abort(3); // if something unexpected happened and we somehow still got to the point of harvesting after a session identifier changed, we should force-exit instead of harvesting
120
- /** Get the ST nodes from the traceStorage buffer. This also returns helpful metadata about the payload. */
121
- const {
122
- stns,
123
- earliestTimeStamp,
124
- latestTimeStamp
125
- } = this.traceStorage.takeSTNs();
126
- if (!stns) return; // there are no trace nodes
127
- if (options.retry) {
128
- this.sentTrace = stns;
114
+ if (!this.agentRef.runtime.session) return; // session entity is required for trace to run and continue running
115
+ if (this.sessionId !== this.agentRef.runtime.session.state.value || this.ptid !== this.agentRef.runtime.ptid) {
116
+ // If something unexpected happened and we somehow still got to harvesting after a session identifier changed, we should force-exit instead of harvesting:
117
+ this.abort(3);
118
+ return;
129
119
  }
120
+ return true;
121
+ }
122
+ serializer({
123
+ stns
124
+ }) {
125
+ if (!stns.length) return; // there are no processed nodes
126
+ this.everHarvested = true;
127
+ return (0, _traverse.applyFnToProps)(stns, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string');
128
+ }
129
+ queryStringsBuilder({
130
+ stns,
131
+ earliestTimeStamp,
132
+ latestTimeStamp
133
+ }) {
130
134
  const firstSessionHarvest = !this.agentRef.runtime.session.state.traceHarvestStarted;
131
135
  if (firstSessionHarvest) this.agentRef.runtime.session.write({
132
136
  traceHarvestStarted: true
133
137
  });
134
- const hasReplay = this.agentRef.runtime.session?.state.sessionReplayMode === 1;
135
- const endUserId = this.agentRef.info?.jsAttributes?.['enduser.id'];
136
- this.everHarvested = true;
138
+ const hasReplay = this.agentRef.runtime.session.state.sessionReplayMode === 1;
139
+ const endUserId = this.agentRef.info.jsAttributes['enduser.id'];
140
+ const entityGuid = this.agentRef.runtime.appMetadata.agents?.[0]?.entityGuid;
137
141
 
138
- /** The blob consumer expects the following and will reject if not supplied:
142
+ /* The blob consumer expects the following and will reject if not supplied:
139
143
  * browser_monitoring_key
140
144
  * type
141
145
  * app_id
@@ -144,61 +148,45 @@ class Aggregate extends _aggregateBase.AggregateBase {
144
148
  *
145
149
  * For data that does not fit the schema of the above, it should be url-encoded and placed into `attributes`
146
150
  */
147
- const agentMetadata = this.agentRef.runtime.appMetadata?.agents?.[0] || {};
151
+
148
152
  return {
149
- qs: {
150
- browser_monitoring_key: this.agentRef.info.licenseKey,
151
- type: 'BrowserSessionChunk',
152
- app_id: this.agentRef.info.applicationID,
153
- protocol_version: '0',
154
- timestamp: Math.floor(this.timeKeeper.correctRelativeTimestamp(earliestTimeStamp)),
155
- attributes: (0, _encode.obj)({
156
- ...(agentMetadata.entityGuid && {
157
- entityGuid: agentMetadata.entityGuid
158
- }),
159
- harvestId: "".concat(this.agentRef.runtime.session?.state.value, "_").concat(this.agentRef.runtime.ptid, "_").concat(this.agentRef.runtime.harvestCount),
160
- // this section of attributes must be controllable and stay below the query param padding limit -- see QUERY_PARAM_PADDING
161
- // if not, data could be lost to truncation at time of sending, potentially breaking parsing / API behavior in NR1
162
- // trace payload metadata
163
- 'trace.firstTimestamp': Math.floor(this.timeKeeper.correctRelativeTimestamp(earliestTimeStamp)),
164
- 'trace.lastTimestamp': Math.floor(this.timeKeeper.correctRelativeTimestamp(latestTimeStamp)),
165
- 'trace.nodes': stns.length,
166
- 'trace.originTimestamp': this.timeKeeper.correctedOriginTime,
167
- // other payload metadata
168
- agentVersion: this.agentRef.runtime.version,
169
- ...(firstSessionHarvest && {
170
- firstSessionHarvest
171
- }),
172
- ...(hasReplay && {
173
- hasReplay
174
- }),
175
- ptid: "".concat(this.ptid),
176
- session: "".concat(this.sessionId),
177
- // customer-defined data should go last so that if it exceeds the query param padding limit it will be truncated instead of important attrs
178
- ...(endUserId && {
179
- 'enduser.id': this.obfuscator.obfuscateString(endUserId)
180
- }),
181
- currentUrl: this.obfuscator.obfuscateString((0, _cleanUrl.cleanURL)('' + location))
182
- // The Query Param is being arbitrarily limited in length here. It is also applied when estimating the size of the payload in getPayloadSize()
183
- }, QUERY_PARAM_PADDING).substring(1) // remove the leading '&'
184
- },
185
- body: (0, _traverse.applyFnToProps)(stns, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string')
153
+ browser_monitoring_key: this.agentRef.info.licenseKey,
154
+ type: 'BrowserSessionChunk',
155
+ app_id: this.agentRef.info.applicationID,
156
+ protocol_version: '0',
157
+ timestamp: Math.floor(this.timeKeeper.correctRelativeTimestamp(earliestTimeStamp)),
158
+ attributes: (0, _encode.obj)({
159
+ ...(entityGuid && {
160
+ entityGuid
161
+ }),
162
+ harvestId: "".concat(this.agentRef.runtime.session.state.value, "_").concat(this.agentRef.runtime.ptid, "_").concat(this.agentRef.runtime.harvestCount),
163
+ // this section of attributes must be controllable and stay below the query param padding limit -- see QUERY_PARAM_PADDING
164
+ // if not, data could be lost to truncation at time of sending, potentially breaking parsing / API behavior in NR1
165
+ // trace payload metadata
166
+ 'trace.firstTimestamp': Math.floor(this.timeKeeper.correctRelativeTimestamp(earliestTimeStamp)),
167
+ 'trace.lastTimestamp': Math.floor(this.timeKeeper.correctRelativeTimestamp(latestTimeStamp)),
168
+ 'trace.nodes': stns.length,
169
+ 'trace.originTimestamp': this.timeKeeper.correctedOriginTime,
170
+ // other payload metadata
171
+ agentVersion: this.agentRef.runtime.version,
172
+ ...(firstSessionHarvest && {
173
+ firstSessionHarvest
174
+ }),
175
+ ...(hasReplay && {
176
+ hasReplay
177
+ }),
178
+ ptid: "".concat(this.ptid),
179
+ session: "".concat(this.sessionId),
180
+ // customer-defined data should go last so that if it exceeds the query param padding limit it will be truncated instead of important attrs
181
+ ...(endUserId && {
182
+ 'enduser.id': this.obfuscator.obfuscateString(endUserId)
183
+ }),
184
+ currentUrl: this.obfuscator.obfuscateString((0, _cleanUrl.cleanURL)('' + location))
185
+ // The Query Param is being arbitrarily limited in length here. It is also applied when estimating the size of the payload in getPayloadSize()
186
+ }, QUERY_PARAM_PADDING).substring(1) // remove the leading '&'
186
187
  };
187
188
  }
188
189
 
189
- /** When the harvest scheduler finishes, this callback is executed. It's main purpose is to determine if the payload needs to be retried
190
- * and if so, it will take all data from the temporary buffer and place it back into the traceStorage module
191
- */
192
- onHarvestFinished(result) {
193
- if (result.sent && result.retry && this.sentTrace) {
194
- // merge previous trace back into buffer to retry for next harvest
195
- Object.entries(this.sentTrace).forEach(([name, listOfSTNodes]) => {
196
- this.traceStorage.restoreNode(name, listOfSTNodes);
197
- });
198
- this.sentTrace = null;
199
- }
200
- }
201
-
202
190
  /** Switch from "off" or "error" to full mode (if entitled) */
203
191
  switchToFull() {
204
192
  if (this.mode === _constants2.MODE.FULL || !this.entitled || this.blocked) return;
@@ -209,19 +197,20 @@ class Aggregate extends _aggregateBase.AggregateBase {
209
197
  });
210
198
  if (prevMode === _constants2.MODE.OFF || !this.initialized) return this.initialize(this.mode, this.entitled);
211
199
  if (this.initialized) {
212
- this.traceStorage.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
200
+ 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
213
201
  }
214
202
  this.startHarvesting();
215
203
  }
216
204
 
217
205
  /** Stop running for the remainder of the page lifecycle */
218
- abort(reason) {
206
+ abort() {
219
207
  this.blocked = true;
220
208
  this.mode = _constants2.MODE.OFF;
221
209
  this.agentRef.runtime.session.write({
222
210
  sessionTraceMode: this.mode
223
211
  });
224
212
  this.scheduler?.stopTimer();
213
+ this.events.clear();
225
214
  }
226
215
  }
227
216
  exports.Aggregate = Aggregate;
@@ -41,8 +41,8 @@ class TraceStorage {
41
41
  trace = {};
42
42
  earliestTimeStamp = Infinity;
43
43
  latestTimeStamp = 0;
44
- tempStorage = [];
45
44
  prevStoredEvents = new Set();
45
+ #backupTrace;
46
46
  constructor(parent) {
47
47
  this.parent = parent;
48
48
  }
@@ -56,9 +56,6 @@ class TraceStorage {
56
56
  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
57
  if (openedSpace === 0) return;
58
58
  }
59
- while (this.tempStorage.length) {
60
- this.storeSTN(this.tempStorage.shift());
61
- }
62
59
  if (this.trace[stn.n]) this.trace[stn.n].push(stn);else this.trace[stn.n] = [stn];
63
60
  if (stn.s < this.earliestTimeStamp) this.earliestTimeStamp = stn.s;
64
61
  if (stn.s > this.latestTimeStamp) this.latestTimeStamp = stn.s;
@@ -105,13 +102,8 @@ class TraceStorage {
105
102
  const partitionListByOriginMap = listOfSTNodes.sort((a, b) => a.s - b.s).reduce(reindexByOriginFn, {});
106
103
  return Object.values(partitionListByOriginMap).flat(); // join the partitions back into 1-D, now ordered by origin then start time
107
104
  }, this);
108
- if (stns.length === 0) return {};
109
- this.trace = {};
110
- this.nodeCount = 0;
111
105
  const earliestTimeStamp = this.earliestTimeStamp;
112
- this.earliestTimeStamp = Infinity;
113
106
  const latestTimeStamp = this.latestTimeStamp;
114
- this.latestTimeStamp = 0;
115
107
  return {
116
108
  stns,
117
109
  earliestTimeStamp,
@@ -282,10 +274,27 @@ class TraceStorage {
282
274
  if (type !== 'xhr') return;
283
275
  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'));
284
276
  }
285
- restoreNode(name, listOfSTNodes) {
286
- if (this.nodeCount >= _constants2.MAX_NODES_PER_HARVEST) return;
287
- this.nodeCount += listOfSTNodes.length;
288
- this.trace[name] = this.trace[name] ? listOfSTNodes.concat(this.trace[name]) : listOfSTNodes;
277
+
278
+ /* 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. */
279
+ isEmpty() {
280
+ return this.nodeCount === 0;
281
+ }
282
+ save() {
283
+ this.#backupTrace = this.trace;
284
+ }
285
+ get = this.takeSTNs;
286
+ clear() {
287
+ this.trace = {};
288
+ this.nodeCount = 0;
289
+ this.prevStoredEvents.clear(); // release references to past events for GC
290
+ this.earliestTimeStamp = Infinity;
291
+ this.latestTimeStamp = 0;
292
+ }
293
+ reloadSave() {
294
+ Object.values(this.#backupTrace).forEach(stnsArray => stnsArray.forEach(stn => this.storeSTN(stn)));
295
+ }
296
+ clearSave() {
297
+ this.#backupTrace = undefined;
289
298
  }
290
299
  }
291
300
  exports.TraceStorage = TraceStorage;
@@ -12,7 +12,6 @@ var _timeToFirstByte = require("../../../common/vitals/time-to-first-byte");
12
12
  var _features = require("../../../loaders/features/features");
13
13
  var _constants = require("../../metrics/constants");
14
14
  var _aggregateBase = require("../../utils/aggregate-base");
15
- var _eventBuffer = require("../../utils/event-buffer");
16
15
  var _constants2 = require("../constants");
17
16
  var _ajaxNode = require("./ajax-node");
18
17
  var _initialPageLoadInteraction = require("./initial-page-load-interaction");
@@ -24,7 +23,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
24
23
  }) {
25
24
  super(agentRef, _constants2.FEATURE_NAME);
26
25
  const harvestTimeSeconds = agentRef.init.soft_navigations.harvestTimeSeconds || 10;
27
- this.interactionsToHarvest = new _eventBuffer.EventBuffer();
26
+ this.interactionsToHarvest = this.events;
28
27
  this.domObserver = domObserver;
29
28
  this.initialPageLoadInteraction = new _initialPageLoadInteraction.InitialPageLoadInteraction(agentRef.agentIdentifier);
30
29
  _timeToFirstByte.timeToFirstByte.subscribe(({
@@ -45,12 +44,12 @@ class Aggregate extends _aggregateBase.AggregateBase {
45
44
  this.waitForFlags(['spa']).then(([spaOn]) => {
46
45
  if (spaOn) {
47
46
  this.drain();
48
- const scheduler = new _harvestScheduler.HarvestScheduler('events', {
49
- onFinished: this.onHarvestFinished.bind(this),
47
+ const scheduler = new _harvestScheduler.HarvestScheduler(_features.FEATURE_TO_ENDPOINT[this.featureName], {
48
+ onFinished: result => this.postHarvestCleanup(result.sent && result.retry),
49
+ getPayload: options => this.makeHarvestPayload(options.retry),
50
50
  retryDelay: harvestTimeSeconds,
51
51
  onUnload: () => this.interactionInProgress?.done() // return any held ajax or jserr events so they can be sent with EoL harvest
52
52
  }, this);
53
- scheduler.harvest.on('events', this.onHarvestStarted.bind(this));
54
53
  scheduler.startTimer(harvestTimeSeconds, 0);
55
54
  } else {
56
55
  this.blocked = true; // if rum response determines that customer lacks entitlements for spa endpoint, this feature shouldn't harvest
@@ -69,26 +68,16 @@ class Aggregate extends _aggregateBase.AggregateBase {
69
68
  (0, _registerHandler.registerHandler)('ajax', this.#handleAjaxEvent.bind(this), this.featureName, this.ee);
70
69
  (0, _registerHandler.registerHandler)('jserror', this.#handleJserror.bind(this), this.featureName, this.ee);
71
70
  }
72
- onHarvestStarted(options) {
73
- if (!this.interactionsToHarvest.hasData || this.blocked) return;
71
+ serializer(eventBuffer) {
74
72
  // The payload depacker takes the first ixn of a payload (if there are multiple ixns) and positively offset the subsequent ixns timestamps by that amount.
75
73
  // In order to accurately portray the real start & end times of the 2nd & onward ixns, we hence need to negatively offset their start timestamps with that of the 1st ixn.
76
74
  let firstIxnStartTime = 0; // the very 1st ixn does not require any offsetting
77
75
  const serializedIxnList = [];
78
- for (const interaction of this.interactionsToHarvest.buffer) {
76
+ for (const interaction of eventBuffer) {
79
77
  serializedIxnList.push(interaction.serialize(firstIxnStartTime));
80
78
  if (!firstIxnStartTime) firstIxnStartTime = Math.floor(interaction.start);
81
79
  }
82
- const payload = "bel.7;".concat(serializedIxnList.join(';'));
83
- if (options.retry) this.interactionsToHarvest.hold();else this.interactionsToHarvest.clear();
84
- return {
85
- body: {
86
- e: payload
87
- }
88
- };
89
- }
90
- onHarvestFinished(result) {
91
- if (result.sent && result.retry && this.interactionsToHarvest.held.hasData) this.interactionsToHarvest.unhold();else this.interactionsToHarvest.held.clear();
80
+ return "bel.7;".concat(serializedIxnList.join(';'));
92
81
  }
93
82
  startUIInteraction(eventName, startedAt, sourceElem) {
94
83
  // this is throttled by instrumentation so that it isn't excessively called
@@ -136,9 +125,10 @@ class Aggregate extends _aggregateBase.AggregateBase {
136
125
  */
137
126
  if (this.interactionInProgress?.isActiveDuring(timestamp)) return this.interactionInProgress;
138
127
  let saveIxn;
139
- for (let idx = this.interactionsToHarvest.buffer.length - 1; idx >= 0; idx--) {
128
+ const interactionsBuffer = this.interactionsToHarvest.get();
129
+ for (let idx = interactionsBuffer.length - 1; idx >= 0; idx--) {
140
130
  // reverse search for the latest completed interaction for efficiency
141
- const finishedInteraction = this.interactionsToHarvest.buffer[idx];
131
+ const finishedInteraction = interactionsBuffer[idx];
142
132
  if (finishedInteraction.isActiveDuring(timestamp)) {
143
133
  if (finishedInteraction.trigger !== 'initialPageLoad') return finishedInteraction;
144
134
  // It's possible that a complete interaction occurs before page is fully loaded, so we need to consider if a route-change ixn may have overlapped this iPL
@@ -9,7 +9,6 @@ var _runtime = require("../../../common/constants/runtime");
9
9
  var _handle = require("../../../common/event-emitter/handle");
10
10
  var _eventListenerOpts = require("../../../common/event-listener/event-listener-opts");
11
11
  var _invoke = require("../../../common/util/invoke");
12
- var _wrapEvents = require("../../../common/wrap/wrap-events");
13
12
  var _wrapHistory = require("../../../common/wrap/wrap-history");
14
13
  var _instrumentBase = require("../../utils/instrument-base");
15
14
  var _constants = require("../constants");
@@ -28,7 +27,11 @@ class Instrument extends _instrumentBase.InstrumentBase {
28
27
  if (!_runtime.isBrowserScope || !(0, _nreum.gosNREUMOriginals)().o.MO) return; // soft navigations is not supported outside web env or browsers without the mutation observer API
29
28
 
30
29
  const historyEE = (0, _wrapHistory.wrapHistory)(this.ee);
31
- const eventsEE = (0, _wrapEvents.wrapEvents)(this.ee);
30
+ _constants.INTERACTION_TRIGGERS.forEach(trigger => {
31
+ (0, _eventListenerOpts.windowAddEventListener)(trigger, evt => {
32
+ processUserInteraction(evt);
33
+ }, true);
34
+ });
32
35
  const trackURLChange = () => (0, _handle.handle)('newURL', [(0, _now.now)(), '' + window.location], undefined, this.featureName, this.ee);
33
36
  historyEE.on('pushState-end', trackURLChange);
34
37
  historyEE.on('replaceState-end', trackURLChange);
@@ -58,13 +61,6 @@ class Instrument extends _instrumentBase.InstrumentBase {
58
61
  }, UI_WAIT_INTERVAL, {
59
62
  leading: true
60
63
  });
61
- eventsEE.on('fn-start', ([evt]) => {
62
- // set up a new user ixn before the callback for the triggering event executes
63
- if (_constants.INTERACTION_TRIGGERS.includes(evt?.type)) {
64
- processUserInteraction(evt);
65
- }
66
- });
67
- for (let eventType of _constants.INTERACTION_TRIGGERS) document.addEventListener(eventType, () => {/* no-op, this ensures the UI events are monitored by our callback above */});
68
64
  this.abortHandler = abort;
69
65
  this.importAggregator(agentRef, {
70
66
  domObserver
@@ -24,7 +24,6 @@ var _runtime = require("../../../common/constants/runtime");
24
24
  var _handle = require("../../../common/event-emitter/handle");
25
25
  var _constants2 = require("../../metrics/constants");
26
26
  var _console = require("../../../common/util/console");
27
- var _eventBuffer = require("../../utils/event-buffer");
28
27
  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); }
29
28
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
30
29
  /*
@@ -55,7 +54,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
55
54
  static featureName = FEATURE_NAME;
56
55
  constructor(agentRef) {
57
56
  super(agentRef, FEATURE_NAME);
58
- this.state = {
57
+ const state = this.state = {
59
58
  initialPageURL: _runtime.initialLocation,
60
59
  lastSeenUrl: _runtime.initialLocation,
61
60
  lastSeenRouteName: null,
@@ -69,16 +68,12 @@ class Aggregate extends _aggregateBase.AggregateBase {
69
68
  childTime: 0,
70
69
  depth: 0,
71
70
  harvestTimeSeconds: agentRef.init.spa.harvestTimeSeconds || 10,
72
- interactionsToHarvest: new _eventBuffer.EventBuffer(),
73
71
  // The below feature flag is used to disable the SPA ajax fix for specific customers, see https://new-relic.atlassian.net/browse/NR-172169
74
72
  disableSpaFix: (agentRef.init.feature_flags || []).indexOf('disable-spa-fix') > -1
75
73
  };
74
+ this.spaSerializerClass = new _serializer.Serializer(this);
75
+ const classThis = this;
76
76
  let scheduler;
77
- this.serializer = new _serializer.Serializer(this);
78
- const {
79
- state,
80
- serializer
81
- } = this;
82
77
  const baseEE = _contextualEe.ee.get(agentRef.agentIdentifier); // <-- parent baseEE
83
78
  const mutationEE = baseEE.get('mutation');
84
79
  const promiseEE = baseEE.get('promise');
@@ -124,11 +119,11 @@ class Aggregate extends _aggregateBase.AggregateBase {
124
119
 
125
120
  this.waitForFlags(['spa']).then(([spaFlag]) => {
126
121
  if (spaFlag) {
127
- scheduler = new _harvestScheduler.HarvestScheduler('events', {
128
- onFinished: onHarvestFinished,
122
+ scheduler = new _harvestScheduler.HarvestScheduler(_features.FEATURE_TO_ENDPOINT[this.featureName], {
123
+ onFinished: result => this.postHarvestCleanup(result.sent && result.retry),
124
+ getPayload: options => this.makeHarvestPayload(options.retry),
129
125
  retryDelay: state.harvestTimeSeconds
130
126
  }, this);
131
- scheduler.harvest.on('events', onHarvestStarted);
132
127
  this.drain();
133
128
  } else {
134
129
  this.blocked = true;
@@ -619,20 +614,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
619
614
  });
620
615
  setCurrentNode(null);
621
616
  }
622
- const classThis = this;
623
- function onHarvestStarted(options) {
624
- if (!state.interactionsToHarvest.hasData || classThis.blocked) return {};
625
- var payload = serializer.serializeMultiple(state.interactionsToHarvest.buffer, 0, _navTiming.navTimingValues);
626
- if (options.retry) state.interactionsToHarvest.hold();else state.interactionsToHarvest.clear();
627
- return {
628
- body: {
629
- e: payload
630
- }
631
- };
632
- }
633
- function onHarvestFinished(result) {
634
- if (result.sent && result.retry && state.interactionsToHarvest.held.hasData) state.interactionsToHarvest.unhold();else state.interactionsToHarvest.held.clear();
635
- }
636
617
  baseEE.on('spa-jserror', function (type, name, params, metrics) {
637
618
  if (!state.currentNode) return;
638
619
  params._interactionId = state.currentNode.interaction.id;
@@ -679,7 +660,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
679
660
  interaction.root.attrs.firstContentfulPaint = _firstContentfulPaint.firstContentfulPaint.current.value;
680
661
  }
681
662
  baseEE.emit('interactionDone', [interaction, true]);
682
- state.interactionsToHarvest.add(interaction);
663
+ classThis.events.add(interaction);
683
664
  let smCategory;
684
665
  if (interaction.root?.attrs?.trigger === 'initialPageLoad') smCategory = 'InitialPageLoad';else if (interaction.routeChange) smCategory = 'RouteChange';else smCategory = 'Custom';
685
666
  (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ["Spa/Interaction/".concat(smCategory, "/Duration/Ms"), Math.max((interaction.root?.end || 0) - (interaction.root?.start || 0), 0)], undefined, _features.FEATURE_NAMES.metrics, baseEE);
@@ -687,5 +668,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
687
668
  if (!scheduler) (0, _console.warn)(19);
688
669
  }
689
670
  }
671
+ serializer(eventBuffer) {
672
+ return this.spaSerializerClass.serializeMultiple(eventBuffer, 0, _navTiming.navTimingValues);
673
+ }
690
674
  }
691
675
  exports.Aggregate = Aggregate;
@@ -11,10 +11,16 @@ var _nreum = require("../../common/window/nreum");
11
11
  var _drain = require("../../common/drain/drain");
12
12
  var _featureFlags = require("../../common/util/feature-flags");
13
13
  var _obfuscate = require("../../common/util/obfuscate");
14
+ var _eventBuffer = require("./event-buffer");
15
+ var _features = require("../../loaders/features/features");
14
16
  class AggregateBase extends _featureBase.FeatureBase {
15
17
  constructor(agentRef, featureName) {
16
18
  super(agentRef.agentIdentifier, featureName);
17
19
  this.agentRef = agentRef;
20
+ // Jserror and Metric features uses a singleton EventAggregator instead of a regular EventBuffer.
21
+ if ([_features.FEATURE_NAMES.jserrors, _features.FEATURE_NAMES.metrics].includes(this.featureName)) this.events = agentRef.sharedAggregator;
22
+ // PVE has no need for eventBuffer, and SessionTrace has its own storage mechanism.
23
+ else if (![_features.FEATURE_NAMES.pageViewEvent, _features.FEATURE_NAMES.sessionTrace].includes(this.featureName)) this.events = new _eventBuffer.EventBuffer();
18
24
  this.checkConfiguration(agentRef);
19
25
  this.obfuscator = agentRef.runtime.obfuscator;
20
26
  }
@@ -51,6 +57,37 @@ class AggregateBase extends _featureBase.FeatureBase {
51
57
  this.drained = true;
52
58
  }
53
59
 
60
+ /**
61
+ * Return harvest payload. A "serializer" function can be defined on a derived class to format the payload.
62
+ * @param {Boolean} shouldRetryOnFail - harvester flag to backup payload for retry later if harvest request fails; this should be moved to harvester logic
63
+ * @returns final payload, or undefined if there are no pending events
64
+ */
65
+ makeHarvestPayload(shouldRetryOnFail = false, opts = {}) {
66
+ if (this.events.isEmpty(opts)) return;
67
+ // Other conditions and things to do when preparing harvest that is required.
68
+ if (this.preHarvestChecks && !this.preHarvestChecks()) return;
69
+ if (shouldRetryOnFail) this.events.save(opts);
70
+ const returnedData = this.events.get(opts);
71
+ // A serializer or formatter assists in creating the payload `body` from stored events on harvest when defined by derived feature class.
72
+ const body = this.serializer ? this.serializer(returnedData) : returnedData;
73
+ this.events.clear(opts);
74
+ const payload = {
75
+ body
76
+ };
77
+ // Constructs the payload `qs` for relevant features on harvest.
78
+ if (this.queryStringsBuilder) payload.qs = this.queryStringsBuilder(returnedData);
79
+ return payload;
80
+ }
81
+
82
+ /**
83
+ * Cleanup task after a harvest.
84
+ * @param {Boolean} harvestFailed - harvester flag to restore events in main buffer for retry later if request failed
85
+ */
86
+ postHarvestCleanup(harvestFailed = false, opts = {}) {
87
+ if (harvestFailed) this.events.reloadSave(opts);
88
+ this.events.clearSave(opts);
89
+ }
90
+
54
91
  /**
55
92
  * Checks for additional `jsAttributes` items to support backward compatibility with implementations of the agent where
56
93
  * loader configurations may appear after the loader code is executed.