@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
@@ -6,128 +6,77 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.EventBuffer = void 0;
7
7
  var _stringify = require("../../common/util/stringify");
8
8
  var _agentConstants = require("../../common/constants/agent-constants");
9
- /**
10
- * A container that keeps an event buffer and size with helper methods
11
- * @typedef {Object} EventBuffer
12
- * @property {number} size
13
- * @property {*[]} buffer
14
- */
15
-
16
- /**
17
- * A container that holds, evaluates, and merges event objects for harvesting
18
- */
19
9
  class EventBuffer {
20
- /** @type {Object[]} */
21
10
  #buffer = [];
22
- /** @type {number} */
23
- #bytes = 0;
24
- /** @type {EventBuffer} */
25
- #held;
11
+ #rawBytes = 0;
12
+ #bufferBackup;
13
+ #rawBytesBackup;
26
14
 
27
15
  /**
28
- *
29
- * @param {number=} maxPayloadSize
16
+ * @param {number} maxPayloadSize
30
17
  */
31
18
  constructor(maxPayloadSize = _agentConstants.MAX_PAYLOAD_SIZE) {
32
19
  this.maxPayloadSize = maxPayloadSize;
33
20
  }
34
-
35
- /**
36
- * buffer is read only, use the helper methods to add or clear buffer data
37
- */
38
- get buffer() {
21
+ isEmpty() {
22
+ return this.#buffer.length === 0;
23
+ }
24
+ get() {
39
25
  return this.#buffer;
40
26
  }
41
-
42
- /**
43
- * bytes is read only, use the helper methods to add or clear buffer data
44
- */
45
- get bytes() {
46
- return this.#bytes;
27
+ byteSize() {
28
+ return this.#rawBytes;
47
29
  }
48
-
49
- /**
50
- * held is another event buffer
51
- */
52
- get held() {
53
- if (!this.#held) this.#held = new EventBuffer(this.maxPayloadSize);
54
- return this.#held;
55
- }
56
-
57
- /**
58
- * Returns a boolean indicating whether the current size and buffer contain valid data
59
- * @returns {boolean}
60
- */
61
- get hasData() {
62
- return this.buffer.length > 0 && this.bytes > 0;
30
+ wouldExceedMaxSize(incomingSize) {
31
+ return this.#rawBytes + incomingSize > this.maxPayloadSize;
63
32
  }
64
33
 
65
34
  /**
66
- * Adds an event object to the buffer while tallying size. Only adds the event if it is valid
67
- * and would not make the event buffer exceed the maxPayloadSize.
68
- * @param {Object} event the event object to add to the buffer
69
- * @returns {EventBuffer} returns the event buffer for chaining
35
+ * Add feature-processed event to our buffer. If this event would cause our total raw size to exceed the set max payload size, it is dropped.
36
+ * @param {any} event - any primitive type or object
37
+ * @returns {Boolean} true if successfully added; false otherwise
70
38
  */
71
39
  add(event) {
72
- const size = (0, _stringify.stringify)(event).length;
73
- if (!this.canMerge(size)) return this;
40
+ const addSize = (0, _stringify.stringify)(event)?.length || 0; // (estimate) # of bytes a directly stringified event it would take to send
41
+ if (this.#rawBytes + addSize > this.maxPayloadSize) return false;
74
42
  this.#buffer.push(event);
75
- this.#bytes += size;
76
- return this;
43
+ this.#rawBytes += addSize;
44
+ return true;
77
45
  }
78
46
 
79
47
  /**
80
- * clear the buffer data
81
- * @returns {EventBuffer}
48
+ * Wipes the main buffer
82
49
  */
83
50
  clear() {
84
- this.#bytes = 0;
85
51
  this.#buffer = [];
86
- return this;
87
- }
88
-
89
- /**
90
- * Hold the buffer data in a new (child) EventBuffer (.held) to unblock the main buffer.
91
- * This action clears the main buffer
92
- * @returns {EventBuffer}
93
- */
94
- hold() {
95
- this.held.merge(this);
96
- this.clear();
97
- return this;
52
+ this.#rawBytes = 0;
98
53
  }
99
54
 
100
55
  /**
101
- * Prepend the held EventBuffer (.held) back into the main buffer
102
- * This action clears the held buffer
103
- * @returns {EventBuffer}
56
+ * Backup the buffered data and clear the main buffer
57
+ * @returns {Array} the events being backed up
104
58
  */
105
- unhold() {
106
- this.merge(this.held, true);
107
- this.held.clear();
108
- return this;
59
+ save() {
60
+ this.#bufferBackup = this.#buffer;
61
+ this.#rawBytesBackup = this.#rawBytes;
109
62
  }
110
63
 
111
64
  /**
112
- * Merges an EventBuffer into this EventBuffer
113
- * @param {EventBuffer} events an EventBuffer intended to merge with this EventBuffer
114
- * @param {boolean} prepend if true, the supplied events will be prepended before the events of this class
115
- * @returns {EventBuffer} returns the event buffer for chaining
65
+ * Wipes the backup buffer
116
66
  */
117
- merge(eventBuffer, prepend = false) {
118
- if (!this.canMerge(eventBuffer.bytes)) return this;
119
- this.#buffer = prepend ? [...eventBuffer.buffer, ...this.#buffer] : [...this.#buffer, ...eventBuffer.buffer];
120
- this.#bytes += eventBuffer.#bytes;
121
- return this;
67
+ clearSave() {
68
+ this.#bufferBackup = undefined;
69
+ this.#rawBytesBackup = undefined;
122
70
  }
123
71
 
124
72
  /**
125
- * Returns a boolean indicating the resulting size of a merge would be valid. Compares against the maxPayloadSize provided at initialization time.
126
- * @param {number} size
127
- * @returns {boolean}
73
+ * Prepend the backup buffer back into the main buffer
128
74
  */
129
- canMerge(size) {
130
- return this.bytes + (size || Infinity) < this.maxPayloadSize;
75
+ reloadSave() {
76
+ if (!this.#bufferBackup) return;
77
+ if (this.#rawBytesBackup + this.#rawBytes > this.maxPayloadSize) return;
78
+ this.#buffer = [...this.#bufferBackup, ...this.#buffer];
79
+ this.#rawBytes = this.#rawBytesBackup + this.#rawBytes;
131
80
  }
132
81
  }
133
82
  exports.EventBuffer = EventBuffer;
@@ -102,11 +102,11 @@ class InstrumentBase extends _featureBase.FeatureBase {
102
102
  try {
103
103
  // Create a single Aggregator for this agent if DNE yet; to be used by jserror endpoint features.
104
104
  if (!agentRef.sharedAggregator) {
105
- agentRef.sharedAggregator = Promise.resolve().then(() => _interopRequireWildcard(require(/* webpackChunkName: "shared-aggregator" */'../../common/aggregate/aggregator')));
105
+ agentRef.sharedAggregator = Promise.resolve().then(() => _interopRequireWildcard(require(/* webpackChunkName: "shared-aggregator" */'../../common/aggregate/event-aggregator')));
106
106
  const {
107
- Aggregator
107
+ EventAggregator
108
108
  } = await agentRef.sharedAggregator;
109
- agentRef.sharedAggregator = new Aggregator();
109
+ agentRef.sharedAggregator = new EventAggregator();
110
110
  } else await agentRef.sharedAggregator; // if another feature is already importing the aggregator, wait for it to finish
111
111
 
112
112
  if (!this.#shouldImportAgg(this.featureName, session)) {
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.featurePriority = exports.FEATURE_NAMES = void 0;
6
+ exports.featurePriority = exports.FEATURE_TO_ENDPOINT = exports.FEATURE_NAMES = void 0;
7
7
  const FEATURE_NAMES = exports.FEATURE_NAMES = {
8
8
  ajax: 'ajax',
9
9
  genericEvents: 'generic_events',
@@ -38,4 +38,16 @@ const featurePriority = exports.featurePriority = {
38
38
  [FEATURE_NAMES.sessionReplay]: 9,
39
39
  [FEATURE_NAMES.logging]: 10,
40
40
  [FEATURE_NAMES.genericEvents]: 11
41
+ };
42
+ const FEATURE_TO_ENDPOINT = exports.FEATURE_TO_ENDPOINT = {
43
+ [FEATURE_NAMES.pageViewTiming]: 'events',
44
+ [FEATURE_NAMES.ajax]: 'events',
45
+ [FEATURE_NAMES.spa]: 'events',
46
+ [FEATURE_NAMES.softNav]: 'events',
47
+ [FEATURE_NAMES.metrics]: 'jserrors',
48
+ [FEATURE_NAMES.jserrors]: 'jserrors',
49
+ [FEATURE_NAMES.sessionTrace]: 'browser/blobs',
50
+ [FEATURE_NAMES.sessionReplay]: 'browser/blobs',
51
+ [FEATURE_NAMES.logging]: 'browser/logs',
52
+ [FEATURE_NAMES.genericEvents]: 'ins'
41
53
  };
@@ -2,11 +2,8 @@
2
2
  * Copyright 2020 New Relic Corporation. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
-
6
- import { SharedContext } from '../context/shared-context';
7
- export class Aggregator extends SharedContext {
8
- constructor(parent) {
9
- super(parent);
5
+ export class Aggregator {
6
+ constructor() {
10
7
  this.aggregatedData = {};
11
8
  }
12
9
 
@@ -15,12 +12,14 @@ export class Aggregator extends SharedContext {
15
12
  // metrics are the numeric values to be aggregated
16
13
 
17
14
  store(type, name, params, newMetrics, customParams) {
18
- var bucket = this.getBucket(type, name, params, customParams);
15
+ var bucket = this.#getBucket(type, name, params, customParams);
19
16
  bucket.metrics = aggregateMetrics(newMetrics, bucket.metrics);
20
17
  return bucket;
21
18
  }
22
- merge(type, name, metrics, params, customParams) {
23
- var bucket = this.getBucket(type, name, params, customParams);
19
+ merge(type, name, metrics, params, customParams, overwriteParams = false) {
20
+ var bucket = this.#getBucket(type, name, params, customParams);
21
+ if (overwriteParams) bucket.params = params; // replace current params with incoming params obj
22
+
24
23
  if (!bucket.metrics) {
25
24
  bucket.metrics = metrics;
26
25
  return;
@@ -45,11 +44,25 @@ export class Aggregator extends SharedContext {
45
44
  });
46
45
  }
47
46
  storeMetric(type, name, params, value) {
48
- var bucket = this.getBucket(type, name, params);
47
+ var bucket = this.#getBucket(type, name, params);
49
48
  bucket.stats = updateMetric(value, bucket.stats);
50
49
  return bucket;
51
50
  }
52
- getBucket(type, name, params, customParams) {
51
+
52
+ // Get all listed types buckets and it deletes the retrieved content from the aggregatedData
53
+ take(types, deleteWhenRetrieved = true) {
54
+ var results = {};
55
+ var type = '';
56
+ var hasData = false;
57
+ for (var i = 0; i < types.length; i++) {
58
+ type = types[i];
59
+ results[type] = Object.values(this.aggregatedData[type] || {});
60
+ if (results[type].length) hasData = true;
61
+ if (deleteWhenRetrieved) delete this.aggregatedData[type];
62
+ }
63
+ return hasData ? results : null;
64
+ }
65
+ #getBucket(type, name, params, customParams) {
53
66
  if (!this.aggregatedData[type]) this.aggregatedData[type] = {};
54
67
  var bucket = this.aggregatedData[type][name];
55
68
  if (!bucket) {
@@ -62,26 +75,6 @@ export class Aggregator extends SharedContext {
62
75
  }
63
76
  return bucket;
64
77
  }
65
- get(type, name) {
66
- // if name is passed, get a single bucket
67
- if (name) return this.aggregatedData[type] && this.aggregatedData[type][name];
68
- // else, get all buckets of that type
69
- return this.aggregatedData[type];
70
- }
71
-
72
- // Like get, but for many types and it deletes the retrieved content from the aggregatedData
73
- take(types) {
74
- var results = {};
75
- var type = '';
76
- var hasData = false;
77
- for (var i = 0; i < types.length; i++) {
78
- type = types[i];
79
- results[type] = Object.values(this.aggregatedData[type] || {});
80
- if (results[type].length) hasData = true;
81
- delete this.aggregatedData[type];
82
- }
83
- return hasData ? results : null;
84
- }
85
78
  }
86
79
  function aggregateMetrics(newMetrics, oldMetrics) {
87
80
  if (!oldMetrics) oldMetrics = {
@@ -0,0 +1,78 @@
1
+ import { Aggregator } from './aggregator';
2
+
3
+ /**
4
+ * An extension of the Aggregator class that provides an interface similar to that of EventBuffer class.
5
+ * This typecasting allow features that uses Aggregator as their event handler to share the same AggregateBase.events utilization by those features.
6
+ */
7
+ export class EventAggregator {
8
+ #aggregator = new Aggregator();
9
+ #savedNamesToBuckets = {};
10
+ isEmpty({
11
+ aggregatorTypes
12
+ }) {
13
+ if (!aggregatorTypes) return Object.keys(this.#aggregator.aggregatedData).length === 0;
14
+ return aggregatorTypes.every(type => !this.#aggregator.aggregatedData[type]); // no bucket exist for any of the types we're looking for
15
+ }
16
+ add(type, name, params, newMetrics, customParams) {
17
+ // Do we need to track byte size here like EventBuffer?
18
+ this.#aggregator.store(type, name, params, newMetrics, customParams);
19
+ return true;
20
+ }
21
+ addMetric(type, name, params, value) {
22
+ this.#aggregator.storeMetric(type, name, params, value);
23
+ return true;
24
+ }
25
+ save({
26
+ aggregatorTypes
27
+ }) {
28
+ const key = aggregatorTypes.toString(); // the stringified types serve as the key to each save call, e.g. ['err', 'ierr', 'xhr'] => 'err,ierr,xhr'
29
+ const backupAggregatedDataSubset = {};
30
+ aggregatorTypes.forEach(type => backupAggregatedDataSubset[type] = this.#aggregator.aggregatedData[type]); // make a subset of the aggregatedData for each of the types we want to save
31
+ this.#savedNamesToBuckets[key] = backupAggregatedDataSubset;
32
+ /*
33
+ { 'err,ierr,xhr': {
34
+ 'err': {
35
+ <aggregateHash>: { metrics: { count: 1, time, ... }, params: {}, custom: {} },
36
+ <otherHashName>: { metrics: { count: 1, ... }, ... }
37
+ },
38
+ 'ierr': { ... },
39
+ 'xhr': { ... }
40
+ }
41
+ }
42
+ */
43
+ }
44
+ get(opts) {
45
+ const aggregatorTypes = Array.isArray(opts) ? opts : opts.aggregatorTypes;
46
+ return this.#aggregator.take(aggregatorTypes, false);
47
+ }
48
+ clear({
49
+ aggregatorTypes
50
+ } = {}) {
51
+ if (!aggregatorTypes) {
52
+ this.#aggregator.aggregatedData = {};
53
+ return;
54
+ }
55
+ aggregatorTypes.forEach(type => delete this.#aggregator.aggregatedData[type]);
56
+ }
57
+ reloadSave({
58
+ aggregatorTypes
59
+ }) {
60
+ const key = aggregatorTypes.toString();
61
+ const backupAggregatedDataSubset = this.#savedNamesToBuckets[key];
62
+ // Grabs the previously stored subset and merge it back into aggregatedData.
63
+ aggregatorTypes.forEach(type => {
64
+ Object.keys(backupAggregatedDataSubset[type] || {}).forEach(name => {
65
+ const bucket = backupAggregatedDataSubset[type][name];
66
+ // The older aka saved params take effect over the newer one. This is especially important when merging back for a failed harvest retry if, for example,
67
+ // the first-ever occurrence of an error is in the retry: it contains the params.stack_trace whereas the newer or current bucket.params would not.
68
+ this.#aggregator.merge(type, name, bucket.metrics, bucket.params, bucket.custom, true);
69
+ });
70
+ });
71
+ }
72
+ clearSave({
73
+ aggregatorTypes
74
+ }) {
75
+ const key = aggregatorTypes.toString();
76
+ delete this.#savedNamesToBuckets[key];
77
+ }
78
+ }
@@ -72,9 +72,6 @@ const model = () => {
72
72
  page_action: {
73
73
  enabled: true
74
74
  },
75
- user_actions: {
76
- enabled: true
77
- },
78
75
  page_view_event: {
79
76
  enabled: true,
80
77
  autoStart: true
@@ -84,6 +81,10 @@ const model = () => {
84
81
  harvestTimeSeconds: 30,
85
82
  autoStart: true
86
83
  },
84
+ performance: {
85
+ capture_marks: false,
86
+ capture_measures: false // false by default through experimental phase, but flipped to true once GA'd
87
+ },
87
88
  privacy: {
88
89
  cookies_enabled: true
89
90
  },
@@ -167,7 +168,10 @@ const model = () => {
167
168
  harvestTimeSeconds: 10,
168
169
  autoStart: true
169
170
  },
170
- ssl: undefined
171
+ ssl: undefined,
172
+ user_actions: {
173
+ enabled: true
174
+ }
171
175
  };
172
176
  };
173
177
  const _cache = {};
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Exposes the version of the agent
8
8
  */
9
- export const VERSION = "1.271.0";
9
+ export const VERSION = "1.273.0";
10
10
 
11
11
  /**
12
12
  * Exposes the build type of the agent
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Exposes the version of the agent
8
8
  */
9
- export const VERSION = "1.271.0";
9
+ export const VERSION = "1.273.0";
10
10
 
11
11
  /**
12
12
  * Exposes the build type of the agent
@@ -95,7 +95,7 @@ export class HarvestScheduler extends SharedContext {
95
95
  let submitMethod;
96
96
  let payload;
97
97
  if (this.opts.getPayload) {
98
- // Ajax & PVT & SR features provide a callback function to get data for harvesting
98
+ // Ajax, PVT, Softnav, Logging, SR & ST features provide a single callback function to get data for harvesting
99
99
  submitMethod = submitData.getSubmitMethod({
100
100
  isFinalHarvest: opts?.unload
101
101
  });
@@ -120,11 +120,7 @@ export class Harvest extends SharedContext {
120
120
  const fullUrl = "".concat(url, "?").concat(baseParams).concat(payloadParams);
121
121
  const gzip = !!qs?.attributes?.includes('gzip');
122
122
  if (!gzip) {
123
- if (endpoint === 'events') {
124
- body = body.e;
125
- } else {
126
- body = stringify(body);
127
- }
123
+ if (endpoint !== 'events') body = stringify(body); // all features going to /events/ endpoint should already be serialized & stringified
128
124
  /** Warn --once per endpoint-- if the agent tries to send large payloads */
129
125
  if (body.length > 750000 && (warnings[endpoint] = (warnings?.[endpoint] || 0) + 1) === 1) warn(28, endpoint);
130
126
  }
@@ -12,7 +12,6 @@
12
12
  * @typedef {object} HarvestPayload
13
13
  * @property {object} qs Map of values that should be sent as part of the request query string.
14
14
  * @property {object} body Map of values that should be sent as the body of the request.
15
- * @property {string} body.e Special case of body used for browser interactions.
16
15
  */
17
16
 
18
17
  /**
@@ -8,44 +8,41 @@ import { handle } from '../../../common/event-emitter/handle';
8
8
  import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler';
9
9
  import { setDenyList, shouldCollectEvent } from '../../../common/deny-list/deny-list';
10
10
  import { FEATURE_NAME } from '../constants';
11
- import { FEATURE_NAMES } from '../../../loaders/features/features';
11
+ import { FEATURE_NAMES, FEATURE_TO_ENDPOINT } from '../../../loaders/features/features';
12
12
  import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants';
13
13
  import { AggregateBase } from '../../utils/aggregate-base';
14
14
  import { parseGQL } from './gql';
15
- import { getNREUMInitializedAgent } from '../../../common/window/nreum';
16
- import Chunk from './chunk';
17
- import { EventBuffer } from '../../utils/event-buffer';
15
+ import { nullable, numeric, getAddStringContext, addCustomAttributes } from '../../../common/serialize/bel-serializer';
18
16
  export class Aggregate extends AggregateBase {
19
17
  static featureName = FEATURE_NAME;
20
18
  constructor(agentRef) {
21
19
  super(agentRef, FEATURE_NAME);
22
20
  const harvestTimeSeconds = agentRef.init.ajax.harvestTimeSeconds || 10;
23
21
  setDenyList(agentRef.runtime.denyList);
24
- this.ajaxEvents = new EventBuffer();
25
- this.spaAjaxEvents = {};
22
+ this.underSpaEvents = {};
26
23
  const classThis = this;
27
24
 
28
25
  // --- v Used by old spa feature
29
26
  this.ee.on('interactionDone', (interaction, wasSaved) => {
30
- if (!this.spaAjaxEvents[interaction.id]?.hasData) return;
27
+ if (!this.underSpaEvents[interaction.id]) return;
31
28
  if (!wasSaved) {
32
29
  // if the ixn was saved, then its ajax reqs are part of the payload whereas if it was discarded, it should still be harvested in the ajax feature itself
33
- this.ajaxEvents.merge(this.spaAjaxEvents[interaction.id]);
30
+ this.underSpaEvents[interaction.id].forEach(item => this.events.add(item));
34
31
  }
35
- delete this.spaAjaxEvents[interaction.id];
32
+ delete this.underSpaEvents[interaction.id];
36
33
  });
37
34
  // --- ^
38
35
  // --- v Used by new soft nav
39
- registerHandler('returnAjax', event => this.ajaxEvents.add(event), this.featureName, this.ee);
36
+ registerHandler('returnAjax', event => this.events.add(event), this.featureName, this.ee);
40
37
  // --- ^
41
38
  registerHandler('xhr', function () {
42
39
  // the EE-drain system not only switches "this" but also passes a new EventContext with info. Should consider platform refactor to another system which passes a mutable context around separately and predictably to avoid problems like this.
43
40
  classThis.storeXhr(...arguments, this); // this switches the context back to the class instance while passing the NR context as an argument -- see "ctx" in storeXhr
44
41
  }, this.featureName, this.ee);
45
42
  this.waitForFlags([]).then(() => {
46
- const scheduler = new HarvestScheduler('events', {
47
- onFinished: this.onEventsHarvestFinished.bind(this),
48
- getPayload: this.prepareHarvest.bind(this)
43
+ const scheduler = new HarvestScheduler(FEATURE_TO_ENDPOINT[this.featureName], {
44
+ onFinished: result => this.postHarvestCleanup(result.sent && result.retry),
45
+ getPayload: options => this.makeHarvestPayload(options.retry)
49
46
  }, this);
50
47
  scheduler.startTimer(harvestTimeSeconds);
51
48
  this.drain();
@@ -63,10 +60,11 @@ export class Aggregate extends AggregateBase {
63
60
  }
64
61
  const shouldCollect = shouldCollectEvent(params);
65
62
  const shouldOmitAjaxMetrics = this.agentRef.init.feature_flags?.includes('ajax_metrics_deny_list');
63
+ const jserrorsInUse = Boolean(this.agentRef.features?.[FEATURE_NAMES.jserrors]);
66
64
 
67
- // store for timeslice metric (harvested by jserrors feature)
68
- if (shouldCollect || !shouldOmitAjaxMetrics) {
69
- this.agentRef.sharedAggregator.store('xhr', hash, params, metrics);
65
+ // Report ajax timeslice metric (to be harvested by jserrors feature, but only if it's running).
66
+ if (jserrorsInUse && (shouldCollect || !shouldOmitAjaxMetrics)) {
67
+ this.agentRef.sharedAggregator.add('xhr', hash, params, metrics);
70
68
  }
71
69
  if (!shouldCollect) {
72
70
  if (params.hostname === this.agentRef.info.errorBeacon || this.agentRef.init.proxy?.beacon && params.hostname === this.agentRef.init.proxy.beacon) {
@@ -105,62 +103,55 @@ export class Aggregate extends AggregateBase {
105
103
  query: ctx.parsedOrigin?.search
106
104
  });
107
105
  if (event.gql) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Events/GraphQL/Bytes-Added', stringify(event.gql).length], undefined, FEATURE_NAMES.metrics, this.ee);
108
- const softNavInUse = Boolean(getNREUMInitializedAgent(this.agentIdentifier)?.features?.[FEATURE_NAMES.softNav]);
106
+ const softNavInUse = Boolean(this.agentRef.features?.[FEATURE_NAMES.softNav]);
109
107
  if (softNavInUse) {
110
108
  // For newer soft nav (when running), pass the event to it for evaluation -- either part of an interaction or is given back
111
109
  handle('ajax', [event], undefined, FEATURE_NAMES.softNav, this.ee);
112
110
  } else if (ctx.spaNode) {
113
111
  // For old spa (when running), if the ajax happened inside an interaction, hold it until the interaction finishes
114
112
  const interactionId = ctx.spaNode.interaction.id;
115
- this.spaAjaxEvents[interactionId] ??= new EventBuffer();
116
- this.spaAjaxEvents[interactionId].add(event);
113
+ this.underSpaEvents[interactionId] ??= [];
114
+ this.underSpaEvents[interactionId].push(event);
117
115
  } else {
118
- this.ajaxEvents.add(event);
116
+ this.events.add(event);
119
117
  }
120
118
  }
121
- prepareHarvest(options) {
122
- options = options || {};
123
- if (this.ajaxEvents.buffer.length === 0) return null;
124
- const payload = this.#getPayload(this.ajaxEvents.buffer);
125
- const payloadObjs = [];
126
- for (let i = 0; i < payload.length; i++) payloadObjs.push({
127
- body: {
128
- e: payload[i]
129
- }
130
- });
131
- if (options.retry) this.ajaxEvents.hold();else this.ajaxEvents.clear();
132
- return payloadObjs;
133
- }
134
- onEventsHarvestFinished(result) {
135
- if (result.retry && this.ajaxEvents.held.hasData) this.ajaxEvents.unhold();else this.ajaxEvents.held.clear();
136
- }
137
- #getPayload(events, numberOfChunks) {
138
- numberOfChunks = numberOfChunks || 1;
139
- const payload = [];
140
- const chunkSize = events.length / numberOfChunks;
141
- const eventChunks = splitChunks.call(this, events, chunkSize);
142
- let tooBig = false;
143
- for (let i = 0; i < eventChunks.length; i++) {
144
- const currentChunk = eventChunks[i];
145
- if (currentChunk.tooBig) {
146
- if (currentChunk.events.length > 1) {
147
- tooBig = true;
148
- break; // if the payload is too big BUT is made of more than 1 event, we can split it down again
149
- }
150
- // Otherwise, if it consists of one sole event, we do not send it (discarded) since we cannot break it apart any further.
151
- } else {
152
- payload.push(currentChunk.payload);
153
- }
154
- }
155
- // Check if the current payload string is too big, if so then run getPayload again with more buckets.
156
- return tooBig ? this.#getPayload(events, ++numberOfChunks) : payload;
157
- function splitChunks(arr, chunkSize) {
158
- chunkSize = chunkSize || arr.length;
159
- const chunks = [];
160
- for (let i = 0, len = arr.length; i < len; i += chunkSize) {
161
- chunks.push(new Chunk(arr.slice(i, i + chunkSize), this));
119
+ serializer(eventBuffer) {
120
+ const addString = getAddStringContext(this.agentIdentifier);
121
+ let payload = 'bel.7;';
122
+ for (let i = 0; i < eventBuffer.length; i++) {
123
+ const event = eventBuffer[i];
124
+ const fields = [numeric(event.startTime), numeric(event.endTime - event.startTime), numeric(0),
125
+ // callbackEnd
126
+ numeric(0),
127
+ // no callbackDuration for non-SPA events
128
+ addString(event.method), numeric(event.status), addString(event.domain), addString(event.path), numeric(event.requestSize), numeric(event.responseSize), event.type === 'fetch' ? 1 : '', addString(0),
129
+ // nodeId
130
+ nullable(event.spanId, addString, true) +
131
+ // guid
132
+ nullable(event.traceId, addString, true) +
133
+ // traceId
134
+ nullable(event.spanTimestamp, numeric, false) // timestamp
135
+ ];
136
+ let insert = '2,';
137
+
138
+ // Since configuration objects (like info) are created new each time they are set, we have to grab the current pointer to the attr object here.
139
+ const jsAttributes = this.agentRef.info.jsAttributes;
140
+
141
+ // add custom attributes
142
+ // gql decorators are added as custom attributes to alleviate need for new BEL schema
143
+ const attrParts = addCustomAttributes({
144
+ ...(jsAttributes || {}),
145
+ ...(event.gql || {})
146
+ }, addString);
147
+ fields.unshift(numeric(attrParts.length));
148
+ insert += fields.join(',');
149
+ if (attrParts && attrParts.length > 0) {
150
+ insert += ';' + attrParts.join(';');
162
151
  }
163
- return chunks;
152
+ if (i + 1 < eventBuffer.length) insert += ';';
153
+ payload += insert;
164
154
  }
155
+ return payload;
165
156
  }
166
157
  }