@newrelic/browser-agent 1.276.0 → 1.278.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 (166) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/cjs/common/aggregate/event-aggregator.js +1 -1
  3. package/dist/cjs/common/config/init.js +1 -10
  4. package/dist/cjs/common/config/runtime.js +2 -1
  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/harvester.js +255 -0
  8. package/dist/cjs/common/harvest/types.js +5 -21
  9. package/dist/cjs/features/ajax/aggregate/index.js +2 -11
  10. package/dist/cjs/features/generic_events/aggregate/index.js +17 -13
  11. package/dist/cjs/features/generic_events/constants.js +2 -1
  12. package/dist/cjs/features/jserrors/aggregate/index.js +3 -14
  13. package/dist/cjs/features/logging/aggregate/index.js +4 -12
  14. package/dist/cjs/features/metrics/aggregate/index.js +7 -15
  15. package/dist/cjs/features/page_view_event/aggregate/index.js +46 -48
  16. package/dist/cjs/features/page_view_timing/aggregate/index.js +0 -9
  17. package/dist/cjs/features/session_replay/aggregate/index.js +21 -43
  18. package/dist/cjs/features/session_replay/instrument/index.js +2 -1
  19. package/dist/cjs/features/session_replay/shared/recorder.js +6 -6
  20. package/dist/cjs/features/session_trace/aggregate/index.js +9 -24
  21. package/dist/cjs/features/session_trace/aggregate/trace/storage.js +8 -2
  22. package/dist/cjs/features/soft_navigations/aggregate/index.js +31 -21
  23. package/dist/cjs/features/soft_navigations/aggregate/initial-page-load-interaction.js +2 -1
  24. package/dist/cjs/features/soft_navigations/aggregate/interaction.js +12 -12
  25. package/dist/cjs/features/soft_navigations/constants.js +5 -2
  26. package/dist/cjs/features/spa/aggregate/index.js +7 -10
  27. package/dist/cjs/features/utils/aggregate-base.js +66 -27
  28. package/dist/cjs/features/utils/event-buffer.js +0 -1
  29. package/dist/cjs/features/utils/event-store-manager.js +109 -0
  30. package/dist/cjs/features/utils/instrument-base.js +1 -10
  31. package/dist/cjs/loaders/api/api-methods.js +1 -1
  32. package/dist/cjs/loaders/api/api.js +2 -1
  33. package/dist/cjs/loaders/features/features.js +16 -10
  34. package/dist/cjs/loaders/micro-agent-base.js +10 -0
  35. package/dist/cjs/loaders/micro-agent.js +1 -0
  36. package/dist/esm/common/aggregate/event-aggregator.js +1 -1
  37. package/dist/esm/common/config/init.js +1 -10
  38. package/dist/esm/common/config/runtime.js +2 -1
  39. package/dist/esm/common/constants/env.cdn.js +1 -1
  40. package/dist/esm/common/constants/env.npm.js +1 -1
  41. package/dist/esm/common/harvest/harvester.js +249 -0
  42. package/dist/esm/common/harvest/types.js +5 -21
  43. package/dist/esm/features/ajax/aggregate/index.js +3 -12
  44. package/dist/esm/features/generic_events/aggregate/index.js +18 -14
  45. package/dist/esm/features/generic_events/constants.js +1 -0
  46. package/dist/esm/features/jserrors/aggregate/index.js +4 -15
  47. package/dist/esm/features/logging/aggregate/index.js +4 -12
  48. package/dist/esm/features/metrics/aggregate/index.js +7 -15
  49. package/dist/esm/features/page_view_event/aggregate/index.js +46 -48
  50. package/dist/esm/features/page_view_timing/aggregate/index.js +1 -10
  51. package/dist/esm/features/session_replay/aggregate/index.js +22 -44
  52. package/dist/esm/features/session_replay/instrument/index.js +2 -1
  53. package/dist/esm/features/session_replay/shared/recorder.js +6 -6
  54. package/dist/esm/features/session_trace/aggregate/index.js +9 -24
  55. package/dist/esm/features/session_trace/aggregate/trace/storage.js +8 -2
  56. package/dist/esm/features/soft_navigations/aggregate/index.js +33 -23
  57. package/dist/esm/features/soft_navigations/aggregate/initial-page-load-interaction.js +2 -1
  58. package/dist/esm/features/soft_navigations/aggregate/interaction.js +13 -13
  59. package/dist/esm/features/soft_navigations/constants.js +4 -1
  60. package/dist/esm/features/spa/aggregate/index.js +8 -11
  61. package/dist/esm/features/utils/aggregate-base.js +66 -27
  62. package/dist/esm/features/utils/event-buffer.js +0 -1
  63. package/dist/esm/features/utils/event-store-manager.js +103 -0
  64. package/dist/esm/features/utils/instrument-base.js +1 -10
  65. package/dist/esm/loaders/api/api-methods.js +1 -1
  66. package/dist/esm/loaders/api/api.js +2 -1
  67. package/dist/esm/loaders/features/features.js +15 -9
  68. package/dist/esm/loaders/micro-agent-base.js +10 -0
  69. package/dist/esm/loaders/micro-agent.js +1 -0
  70. package/dist/types/common/aggregate/event-aggregator.d.ts +1 -1
  71. package/dist/types/common/aggregate/event-aggregator.d.ts.map +1 -1
  72. package/dist/types/common/config/init.d.ts.map +1 -1
  73. package/dist/types/common/config/runtime.d.ts.map +1 -1
  74. package/dist/types/common/harvest/harvester.d.ts +16 -0
  75. package/dist/types/common/harvest/harvester.d.ts.map +1 -0
  76. package/dist/types/common/harvest/types.d.ts +8 -45
  77. package/dist/types/common/harvest/types.d.ts.map +1 -1
  78. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  79. package/dist/types/features/generic_events/aggregate/index.d.ts +1 -3
  80. package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
  81. package/dist/types/features/generic_events/constants.d.ts +1 -0
  82. package/dist/types/features/generic_events/constants.d.ts.map +1 -1
  83. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  84. package/dist/types/features/logging/aggregate/index.d.ts +0 -3
  85. package/dist/types/features/logging/aggregate/index.d.ts.map +1 -1
  86. package/dist/types/features/metrics/aggregate/index.d.ts +1 -1
  87. package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
  88. package/dist/types/features/page_view_event/aggregate/index.d.ts +6 -2
  89. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  90. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  91. package/dist/types/features/session_replay/aggregate/index.d.ts +12 -15
  92. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  93. package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
  94. package/dist/types/features/session_trace/aggregate/index.d.ts +0 -5
  95. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  96. package/dist/types/features/session_trace/aggregate/trace/storage.d.ts +8 -5
  97. package/dist/types/features/session_trace/aggregate/trace/storage.d.ts.map +1 -1
  98. package/dist/types/features/soft_navigations/aggregate/index.d.ts +1 -0
  99. package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -1
  100. package/dist/types/features/soft_navigations/aggregate/initial-page-load-interaction.d.ts.map +1 -1
  101. package/dist/types/features/soft_navigations/aggregate/interaction.d.ts +3 -3
  102. package/dist/types/features/soft_navigations/aggregate/interaction.d.ts.map +1 -1
  103. package/dist/types/features/soft_navigations/constants.d.ts +1 -0
  104. package/dist/types/features/soft_navigations/constants.d.ts.map +1 -1
  105. package/dist/types/features/spa/aggregate/index.d.ts +0 -1
  106. package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
  107. package/dist/types/features/utils/aggregate-base.d.ts +12 -7
  108. package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
  109. package/dist/types/features/utils/event-buffer.d.ts +1 -2
  110. package/dist/types/features/utils/event-buffer.d.ts.map +1 -1
  111. package/dist/types/features/utils/event-store-manager.d.ts +43 -0
  112. package/dist/types/features/utils/event-store-manager.d.ts.map +1 -0
  113. package/dist/types/features/utils/instrument-base.d.ts +0 -1
  114. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  115. package/dist/types/loaders/api/api.d.ts +1 -0
  116. package/dist/types/loaders/api/api.d.ts.map +1 -1
  117. package/dist/types/loaders/features/features.d.ts +15 -12
  118. package/dist/types/loaders/features/features.d.ts.map +1 -1
  119. package/dist/types/loaders/micro-agent-base.d.ts +7 -0
  120. package/dist/types/loaders/micro-agent-base.d.ts.map +1 -1
  121. package/dist/types/loaders/micro-agent.d.ts.map +1 -1
  122. package/package.json +6 -6
  123. package/src/common/aggregate/event-aggregator.js +1 -1
  124. package/src/common/config/init.js +9 -10
  125. package/src/common/config/runtime.js +2 -1
  126. package/src/common/harvest/__mocks__/harvester.js +6 -0
  127. package/src/common/harvest/harvester.js +230 -0
  128. package/src/common/harvest/types.js +5 -21
  129. package/src/features/ajax/aggregate/index.js +3 -14
  130. package/src/features/generic_events/aggregate/index.js +20 -17
  131. package/src/features/generic_events/constants.js +2 -0
  132. package/src/features/jserrors/aggregate/index.js +4 -11
  133. package/src/features/logging/aggregate/index.js +4 -12
  134. package/src/features/metrics/aggregate/index.js +5 -12
  135. package/src/features/page_view_event/aggregate/index.js +38 -38
  136. package/src/features/page_view_timing/aggregate/index.js +1 -12
  137. package/src/features/session_replay/aggregate/index.js +19 -42
  138. package/src/features/session_replay/instrument/index.js +1 -1
  139. package/src/features/session_replay/shared/recorder.js +6 -6
  140. package/src/features/session_trace/aggregate/index.js +8 -25
  141. package/src/features/session_trace/aggregate/trace/storage.js +5 -2
  142. package/src/features/soft_navigations/aggregate/index.js +26 -23
  143. package/src/features/soft_navigations/aggregate/initial-page-load-interaction.js +2 -1
  144. package/src/features/soft_navigations/aggregate/interaction.js +13 -12
  145. package/src/features/soft_navigations/constants.js +3 -1
  146. package/src/features/spa/aggregate/index.js +8 -11
  147. package/src/features/utils/aggregate-base.js +59 -27
  148. package/src/features/utils/event-buffer.js +0 -1
  149. package/src/features/utils/event-store-manager.js +101 -0
  150. package/src/features/utils/instrument-base.js +2 -8
  151. package/src/loaders/api/api-methods.js +1 -1
  152. package/src/loaders/api/api.js +3 -1
  153. package/src/loaders/features/features.js +16 -9
  154. package/src/loaders/micro-agent-base.js +10 -0
  155. package/src/loaders/micro-agent.js +1 -0
  156. package/dist/cjs/common/harvest/harvest-scheduler.js +0 -168
  157. package/dist/cjs/common/harvest/harvest.js +0 -295
  158. package/dist/esm/common/harvest/harvest-scheduler.js +0 -160
  159. package/dist/esm/common/harvest/harvest.js +0 -286
  160. package/dist/types/common/harvest/harvest-scheduler.d.ts +0 -50
  161. package/dist/types/common/harvest/harvest-scheduler.d.ts.map +0 -1
  162. package/dist/types/common/harvest/harvest.d.ts +0 -65
  163. package/dist/types/common/harvest/harvest.d.ts.map +0 -1
  164. package/src/common/harvest/__mocks__/harvest.js +0 -13
  165. package/src/common/harvest/harvest-scheduler.js +0 -166
  166. package/src/common/harvest/harvest.js +0 -282
package/CHANGELOG.md CHANGED
@@ -3,6 +3,25 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [1.278.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.277.0...v1.278.0) (2025-01-07)
7
+
8
+
9
+ ### Features
10
+
11
+ * Centralized harvesting ([#1298](https://github.com/newrelic/newrelic-browser-agent/issues/1298)) ([32c0e3f](https://github.com/newrelic/newrelic-browser-agent/commit/32c0e3f7008423a3e327d17b926d1702d08ae21f))
12
+
13
+ ## [1.277.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.276.0...v1.277.0) (2024-12-18)
14
+
15
+
16
+ ### Features
17
+
18
+ * Add custom events API ([#1263](https://github.com/newrelic/newrelic-browser-agent/issues/1263)) ([9395415](https://github.com/newrelic/newrelic-browser-agent/commit/9395415d942b55e88e89438aa203c6a1642d9e6b))
19
+
20
+
21
+ ### Bug Fixes
22
+
23
+ * Soft navigation bug fixes and new soft navigation tests ([#1268](https://github.com/newrelic/newrelic-browser-agent/issues/1268)) ([7624928](https://github.com/newrelic/newrelic-browser-agent/commit/762492896a7b96564269aab1aadeb6e44a4da242))
24
+
6
25
  ## [1.276.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.275.0...v1.276.0) (2024-12-16)
7
26
 
8
27
 
@@ -18,7 +18,7 @@ class EventAggregator {
18
18
  if (!aggregatorTypes) return Object.keys(this.#aggregator.aggregatedData).length === 0;
19
19
  return aggregatorTypes.every(type => !this.#aggregator.aggregatedData[type]); // no bucket exist for any of the types we're looking for
20
20
  }
21
- add(type, name, params, newMetrics, customParams) {
21
+ add([type, name, params, newMetrics, customParams]) {
22
22
  // Do we need to track byte size here like EventBuffer?
23
23
  this.#aggregator.store(type, name, params, newMetrics, customParams);
24
24
  return true;
@@ -49,7 +49,6 @@ const model = () => {
49
49
  deny_list: undefined,
50
50
  block_internal: true,
51
51
  enabled: true,
52
- harvestTimeSeconds: 10,
53
52
  autoStart: true
54
53
  },
55
54
  distributed_tracing: {
@@ -67,20 +66,17 @@ const model = () => {
67
66
  },
68
67
  generic_events: {
69
68
  enabled: true,
70
- harvestTimeSeconds: 30,
71
69
  autoStart: true
72
70
  },
73
71
  harvest: {
74
- tooManyRequestsDelay: 60
72
+ interval: 30
75
73
  },
76
74
  jserrors: {
77
75
  enabled: true,
78
- harvestTimeSeconds: 10,
79
76
  autoStart: true
80
77
  },
81
78
  logging: {
82
79
  enabled: true,
83
- harvestTimeSeconds: 10,
84
80
  autoStart: true,
85
81
  level: _constants2.LOG_LEVELS.INFO
86
82
  },
@@ -98,7 +94,6 @@ const model = () => {
98
94
  },
99
95
  page_view_timing: {
100
96
  enabled: true,
101
- harvestTimeSeconds: 30,
102
97
  autoStart: true
103
98
  },
104
99
  performance: {
@@ -146,7 +141,6 @@ const model = () => {
146
141
  // feature settings
147
142
  autoStart: true,
148
143
  enabled: false,
149
- harvestTimeSeconds: 60,
150
144
  preload: false,
151
145
  // if true, enables the agent to load rrweb immediately instead of waiting to do so after the window.load event
152
146
  sampling_rate: 10,
@@ -199,17 +193,14 @@ const model = () => {
199
193
  },
200
194
  session_trace: {
201
195
  enabled: true,
202
- harvestTimeSeconds: 10,
203
196
  autoStart: true
204
197
  },
205
198
  soft_navigations: {
206
199
  enabled: true,
207
- harvestTimeSeconds: 10,
208
200
  autoStart: true
209
201
  },
210
202
  spa: {
211
203
  enabled: true,
212
- harvestTimeSeconds: 10,
213
204
  autoStart: true
214
205
  },
215
206
  ssl: undefined,
@@ -34,7 +34,8 @@ const model = {
34
34
  session: undefined,
35
35
  denyList: undefined,
36
36
  timeKeeper: undefined,
37
- obfuscator: undefined
37
+ obfuscator: undefined,
38
+ harvester: undefined
38
39
  };
39
40
  const _cache = {};
40
41
  function getRuntime(id) {
@@ -12,7 +12,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
12
12
  /**
13
13
  * Exposes the version of the agent
14
14
  */
15
- const VERSION = exports.VERSION = "1.276.0";
15
+ const VERSION = exports.VERSION = "1.278.0";
16
16
 
17
17
  /**
18
18
  * Exposes the build type of the agent
@@ -12,7 +12,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
12
12
  /**
13
13
  * Exposes the version of the agent
14
14
  */
15
- const VERSION = exports.VERSION = "1.276.0";
15
+ const VERSION = exports.VERSION = "1.278.0";
16
16
 
17
17
  /**
18
18
  * Exposes the build type of the agent
@@ -0,0 +1,255 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.Harvester = void 0;
7
+ var _features = require("../../loaders/features/features");
8
+ var _env = require("../constants/env.npm");
9
+ var _runtime = require("../constants/runtime");
10
+ var _eventListenerOpts = require("../event-listener/event-listener-opts");
11
+ var _constants = require("../session/constants");
12
+ var _now = require("../timing/now");
13
+ var _eol = require("../unload/eol");
14
+ var _cleanUrl = require("../url/clean-url");
15
+ var _encode = require("../url/encode");
16
+ var _console = require("../util/console");
17
+ var _stringify = require("../util/stringify");
18
+ var _submitData = require("../util/submit-data");
19
+ class Harvester {
20
+ #started = false;
21
+ initializedAggregates = [];
22
+ constructor(agentRef) {
23
+ this.agentRef = agentRef;
24
+ (0, _eol.subscribeToEOL)(() => {
25
+ // do one last harvest round or check
26
+ this.initializedAggregates.forEach(aggregateInst => {
27
+ // let all features wrap up things needed to do before ANY harvest in case there's last minute cross-feature data dependencies
28
+ if (typeof aggregateInst.harvestOpts.beforeUnload === 'function') aggregateInst.harvestOpts.beforeUnload();
29
+ });
30
+ this.initializedAggregates.forEach(aggregateInst => this.triggerHarvestFor(aggregateInst, {
31
+ isFinalHarvest: true
32
+ }));
33
+ /* This callback should run in bubble phase, so that that CWV api, like "onLCP", is called before the final harvest so that emitted timings are part of last outgoing. */
34
+ }, false);
35
+
36
+ /* Flush all buffered data if session resets and give up retries. This should be synchronous to ensure that the correct `session` value is sent.
37
+ Since session-reset generates a new session ID and the ID is grabbed at send-time, any delays or retries would cause the payload to be sent under the wrong session ID. */
38
+ agentRef.ee.on(_constants.SESSION_EVENTS.RESET, () => this.initializedAggregates.forEach(aggregateInst => this.triggerHarvestFor(aggregateInst, {
39
+ forceNoRetry: true
40
+ })));
41
+ }
42
+ startTimer(harvestInterval = this.agentRef.init.harvest.interval) {
43
+ if (this.#started) return;
44
+ this.#started = true;
45
+ const onHarvestInterval = () => {
46
+ this.initializedAggregates.forEach(aggregateInst => this.triggerHarvestFor(aggregateInst));
47
+ setTimeout(onHarvestInterval, harvestInterval * 1000); // repeat in X seconds
48
+ };
49
+ setTimeout(onHarvestInterval, harvestInterval * 1000);
50
+ }
51
+
52
+ /**
53
+ * Given a feature (aggregate), execute a harvest on-demand.
54
+ * @param {object} aggregateInst
55
+ * @param {object} localOpts
56
+ * @returns {boolean} True if 1+ network call was made. Note that this does not mean or guarantee that it was successful (or that all were in the case of more than 1).
57
+ */
58
+ triggerHarvestFor(aggregateInst, localOpts = {}) {
59
+ if (aggregateInst.blocked) return false;
60
+ const submitMethod = (0, _submitData.getSubmitMethod)(localOpts);
61
+ if (!submitMethod) return false;
62
+ const shouldRetryOnFail = !localOpts.isFinalHarvest && submitMethod === _submitData.xhr; // always retry all features harvests except for final
63
+ let dataToSendArr;
64
+ let ranSend = false;
65
+ if (!localOpts.directSend) {
66
+ // primarily used by rum call to bypass makeHarvestPayload by providing payload directly
67
+ dataToSendArr = aggregateInst.makeHarvestPayload(shouldRetryOnFail); // be sure the 'this' of makeHarvestPayload is the aggregate w/ access to its harvestOpts
68
+ if (!dataToSendArr) return false; // can be undefined if storage is empty or preharvest checks failed
69
+ } else dataToSendArr = [localOpts.directSend];
70
+ dataToSendArr.forEach(({
71
+ targetApp,
72
+ payload
73
+ }) => {
74
+ if (!payload) return;
75
+ send(this.agentRef, {
76
+ endpoint: _features.FEATURE_TO_ENDPOINT[aggregateInst.featureName],
77
+ targetApp,
78
+ payload,
79
+ localOpts,
80
+ submitMethod,
81
+ cbFinished,
82
+ raw: aggregateInst.harvestOpts.raw
83
+ });
84
+ ranSend = true;
85
+ });
86
+ return ranSend;
87
+
88
+ /**
89
+ * This is executed immediately after harvest sends the data via XHR, or if there's nothing to send. Note that this excludes on unloading / sendBeacon.
90
+ * @param {Object} result - information regarding the result of the harvest attempt
91
+ */
92
+ function cbFinished(result) {
93
+ if (localOpts.forceNoRetry) result.retry = false; // discard unsent data rather than re-queuing for next harvest attempt
94
+ aggregateInst.postHarvestCleanup(result);
95
+ }
96
+ }
97
+ }
98
+
99
+ /**
100
+ * @typedef {import('./types.js').NetworkSendSpec} NetworkSendSpec
101
+ */
102
+ exports.Harvester = Harvester;
103
+ const warnings = {};
104
+ /**
105
+ * Initiate a harvest call.
106
+ * @param {NetworkSendSpec} param0 Specification for sending data
107
+ * @returns {boolean} True if a network call was made. Note that this does not mean or guarantee that it was successful.
108
+ */
109
+ function send(agentRef, {
110
+ endpoint,
111
+ targetApp,
112
+ payload,
113
+ localOpts = {},
114
+ submitMethod,
115
+ cbFinished,
116
+ raw
117
+ }) {
118
+ if (!agentRef.info.errorBeacon) return false;
119
+ let {
120
+ body,
121
+ qs
122
+ } = cleanPayload(payload);
123
+ if (Object.keys(body).length === 0 && !localOpts.sendEmptyBody) {
124
+ // if there's no body to send, just run onfinish stuff and return
125
+ if (cbFinished) cbFinished({
126
+ sent: false,
127
+ targetApp
128
+ });
129
+ return false;
130
+ }
131
+ const protocol = agentRef.init.ssl === false ? 'http' : 'https';
132
+ const perceivedBeacon = agentRef.init.proxy.beacon || agentRef.info.errorBeacon;
133
+ const url = raw ? "".concat(protocol, "://").concat(perceivedBeacon, "/").concat(endpoint) : "".concat(protocol, "://").concat(perceivedBeacon).concat(endpoint !== _features.RUM ? '/' + endpoint : '', "/1/").concat(targetApp.licenseKey);
134
+ const baseParams = !raw ? baseQueryString(agentRef, qs, endpoint, targetApp.appId) : '';
135
+ let payloadParams = (0, _encode.obj)(qs, agentRef.runtime.maxBytes);
136
+ if (baseParams === '' && payloadParams.startsWith('&')) {
137
+ payloadParams = payloadParams.substring(1);
138
+ }
139
+ const fullUrl = "".concat(url, "?").concat(baseParams).concat(payloadParams);
140
+ const gzip = !!qs?.attributes?.includes('gzip');
141
+ if (!gzip) {
142
+ if (endpoint !== _features.EVENTS) body = (0, _stringify.stringify)(body); // all features going to 'events' endpoint should already be serialized & stringified
143
+ // Warn--once per endpoint--if the agent tries to send large payloads
144
+ if (body.length > 750000 && (warnings[endpoint] = (warnings[endpoint] || 0) + 1) === 1) (0, _console.warn)(28, endpoint);
145
+ }
146
+
147
+ // If body is null, undefined, or an empty object or array after stringifying, send an empty string instead.
148
+ if (!body || body.length === 0 || body === '{}' || body === '[]') body = '';
149
+ const headers = [{
150
+ key: 'content-type',
151
+ value: 'text/plain'
152
+ }];
153
+
154
+ /* Since workers don't support sendBeacon right now, they can only use XHR method.
155
+ Because they still do permit synch XHR, the idea is that at final harvest time (worker is closing),
156
+ we just make a BLOCKING request--trivial impact--with the remaining data as a temp fill-in for sendBeacon.
157
+ Following the removal of img-element method. */
158
+ let result = submitMethod({
159
+ url: fullUrl,
160
+ body,
161
+ sync: localOpts.isFinalHarvest && _runtime.isWorkerScope,
162
+ headers
163
+ });
164
+ if (!localOpts.isFinalHarvest && cbFinished) {
165
+ // final harvests don't hold onto buffer data (shouldRetryOnFail is false), so cleanup isn't needed
166
+ if (submitMethod === _submitData.xhr) {
167
+ result.addEventListener('loadend', function () {
168
+ // `this` here in block refers to the XHR object in this scope, do not change the anon function to an arrow function
169
+ // status 0 refers to a local error, such as CORS or network failure, or a blocked request by the browser (e.g. adblocker)
170
+ const cbResult = {
171
+ sent: this.status !== 0,
172
+ status: this.status,
173
+ retry: shouldRetry(this.status),
174
+ xhr: this,
175
+ fullUrl,
176
+ targetApp
177
+ };
178
+ if (localOpts.needResponse) cbResult.responseText = this.responseText;
179
+ cbFinished(cbResult);
180
+ }, (0, _eventListenerOpts.eventListenerOpts)(false));
181
+ } else if (submitMethod === _submitData.xhrFetch) {
182
+ result.then(async function (response) {
183
+ const status = response.status;
184
+ const cbResult = {
185
+ sent: true,
186
+ status,
187
+ retry: shouldRetry(status),
188
+ fullUrl,
189
+ fetchResponse: response,
190
+ targetApp
191
+ };
192
+ if (localOpts.needResponse) cbResult.responseText = await response.text();
193
+ cbFinished(cbResult);
194
+ });
195
+ }
196
+ }
197
+ return true;
198
+ function shouldRetry(status) {
199
+ switch (status) {
200
+ case 429:
201
+ case 408:
202
+ case 500:
203
+ case 503:
204
+ return true;
205
+ default:
206
+ return false;
207
+ }
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Cleans and returns a payload object containing a body and qs
213
+ * object with key/value pairs. KV pairs where the value is null,
214
+ * undefined, or an empty string are removed to save on transmission
215
+ * size.
216
+ * @param {HarvestPayload} payload Payload to be sent to the endpoint.
217
+ * @returns {HarvestPayload} Cleaned payload payload to be sent to the endpoint.
218
+ */
219
+ function cleanPayload(payload = {}) {
220
+ const clean = input => {
221
+ if (typeof Uint8Array !== 'undefined' && input instanceof Uint8Array || Array.isArray(input)) return input;
222
+ if (typeof input === 'string') return input.length > 0 ? input : null;
223
+ return Object.entries(input || {}).reduce((accumulator, [key, value]) => {
224
+ if (typeof value === 'number' || typeof value === 'string' && value.length > 0 || typeof value === 'object' && Object.keys(value || {}).length > 0) {
225
+ accumulator[key] = value;
226
+ }
227
+ return accumulator;
228
+ }, {});
229
+ };
230
+ return {
231
+ body: clean(payload.body),
232
+ qs: clean(payload.qs)
233
+ };
234
+ }
235
+
236
+ // The stuff that gets sent every time.
237
+ function baseQueryString(agentRef, qs, endpoint, applicationID) {
238
+ const ref = agentRef.runtime.obfuscator.obfuscateString((0, _cleanUrl.cleanURL)('' + _runtime.globalScope.location));
239
+ const hr = agentRef.runtime.session?.state.sessionReplayMode === 1 && endpoint !== _features.JSERRORS;
240
+ const qps = ['a=' + applicationID, (0, _encode.param)('sa', agentRef.info.sa ? '' + agentRef.info.sa : ''), (0, _encode.param)('v', _env.VERSION), transactionNameParam(), (0, _encode.param)('ct', agentRef.runtime.customTransaction), '&rst=' + (0, _now.now)(), '&ck=0',
241
+ // ck param DEPRECATED - still expected by backend
242
+ '&s=' + (agentRef.runtime.session?.state.value || '0'),
243
+ // the 0 id encaps all untrackable and default traffic
244
+ (0, _encode.param)('ref', ref), (0, _encode.param)('ptid', agentRef.runtime.ptid ? '' + agentRef.runtime.ptid : '')];
245
+ if (hr) qps.push((0, _encode.param)('hr', '1', qs));
246
+ return qps.join('');
247
+
248
+ // Constructs the transaction name param for the beacon URL.
249
+ // Prefers the obfuscated transaction name over the plain text.
250
+ // Falls back to making up a name.
251
+ function transactionNameParam() {
252
+ if (agentRef.info.transactionName) return (0, _encode.param)('to', agentRef.info.transactionName);
253
+ return (0, _encode.param)('t', agentRef.info.tNamePlain || 'Unnamed Transaction');
254
+ }
255
+ }
@@ -20,32 +20,16 @@ exports.unused = void 0;
20
20
  * @property {object} body Map of values that should be sent as the body of the request.
21
21
  */
22
22
 
23
- /**
24
- * @typedef {object} FeatureHarvestCallbackOptions Options for aggregating data for harvesting.
25
- * @property {boolean} options.retry Indicates if the feature should store the aggregated
26
- * data in anticipation of a possible need to retry the transmission.
27
- */
28
-
29
- /**
30
- * @callback FeatureHarvestCallback
31
- * @param {FeatureHarvestCallbackOptions} options Options for aggregating data for harvesting.
32
- * @returns {HarvestPayload} Payload of data to transmit to bam endpoint.
33
- */
34
-
35
23
  /**
36
24
  * @typedef {object} NetworkSendSpec
37
25
  * @property {HarvestEndpointIdentifier} endpoint The endpoint to use (jserrors, events, resources etc.)
38
26
  * @property {HarvestPayload} payload Object representing payload.
39
- * @property {object} opts Additional options for sending data
40
- * @property {boolean} opts.needResponse Specify whether the caller expects a response data.
41
- * @property {boolean} opts.unload Specify whether the call is a final harvest during page unload.
42
- * @property {boolean} opts.sendEmptyBody Specify whether the call should be made even if the body is empty. Useful for rum calls.
43
- * @property {boolean} opts.retry Indicates if the feature should store the aggregated data in anticipation of a possible need to
44
- * retry the transmission.
27
+ * @property {object} localOpts Additional options for sending data
28
+ * @property {boolean} localOpts.needResponse Specify whether the caller expects a response data.
29
+ * @property {boolean} localOpts.isFinalHarvest Specify whether the call is a final harvest during page unload.
30
+ * @property {boolean} localOpts.sendEmptyBody Specify whether the call should be made even if the body is empty. Useful for rum calls.
31
+ * @property {boolean} localOpts.forceNoRetry Don't save the buffered data in the case of a need to retry the transmission.
45
32
  * @property {import('../util/submit-data.js').NetworkMethods} submitMethod The network method to use {@link ../util/submit-data.js}
46
- * @property {string} customUrl Override the beacon url the data is sent to; must include protocol if defined
47
- * @property {boolean} raw If true, disables adding the license key to the url
48
- * @property {boolean} includeBaseParams Enables the use of base query parameters in the beacon url
49
33
  */
50
34
 
51
35
  /* istanbul ignore next */
@@ -7,7 +7,6 @@ exports.Aggregate = void 0;
7
7
  var _registerHandler = require("../../../common/event-emitter/register-handler");
8
8
  var _stringify = require("../../../common/util/stringify");
9
9
  var _handle = require("../../../common/event-emitter/handle");
10
- var _harvestScheduler = require("../../../common/harvest/harvest-scheduler");
11
10
  var _denyList = require("../../../common/deny-list/deny-list");
12
11
  var _constants = require("../constants");
13
12
  var _features = require("../../../loaders/features/features");
@@ -24,7 +23,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
24
23
  static featureName = _constants.FEATURE_NAME;
25
24
  constructor(agentRef) {
26
25
  super(agentRef, _constants.FEATURE_NAME);
27
- const harvestTimeSeconds = agentRef.init.ajax.harvestTimeSeconds || 10;
28
26
  (0, _denyList.setDenyList)(agentRef.runtime.denyList);
29
27
  this.underSpaEvents = {};
30
28
  const classThis = this;
@@ -46,14 +44,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
46
44
  // 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.
47
45
  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
48
46
  }, this.featureName, this.ee);
49
- this.waitForFlags([]).then(() => {
50
- const scheduler = new _harvestScheduler.HarvestScheduler(_features.FEATURE_TO_ENDPOINT[this.featureName], {
51
- onFinished: result => this.postHarvestCleanup(result.sent && result.retry),
52
- getPayload: options => this.makeHarvestPayload(options.retry)
53
- }, this);
54
- scheduler.startTimer(harvestTimeSeconds);
55
- this.drain();
56
- });
47
+ this.waitForFlags([]).then(() => this.drain());
57
48
  }
58
49
  storeXhr(params, metrics, startTime, endTime, type, ctx) {
59
50
  metrics.time = startTime;
@@ -71,7 +62,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
71
62
 
72
63
  // Report ajax timeslice metric (to be harvested by jserrors feature, but only if it's running).
73
64
  if (jserrorsInUse && (shouldCollect || !shouldOmitAjaxMetrics)) {
74
- this.agentRef.sharedAggregator.add('xhr', hash, params, metrics);
65
+ this.agentRef.sharedAggregator.add(['xhr', hash, params, metrics]);
75
66
  }
76
67
  if (!shouldCollect) {
77
68
  if (params.hostname === this.agentRef.info.errorBeacon || this.agentRef.init.proxy?.beacon && params.hostname === this.agentRef.init.proxy.beacon) {
@@ -5,7 +5,6 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.Aggregate = void 0;
7
7
  var _stringify = require("../../../common/util/stringify");
8
- var _harvestScheduler = require("../../../common/harvest/harvest-scheduler");
9
8
  var _cleanUrl = require("../../../common/url/clean-url");
10
9
  var _constants = require("../constants");
11
10
  var _runtime = require("../../../common/constants/runtime");
@@ -15,7 +14,6 @@ var _now = require("../../../common/timing/now");
15
14
  var _registerHandler = require("../../../common/event-emitter/register-handler");
16
15
  var _constants2 = require("../../metrics/constants");
17
16
  var _traverse = require("../../../common/util/traverse");
18
- var _features = require("../../../loaders/features/features");
19
17
  var _userActionsAggregator = require("./user-actions/user-actions-aggregator");
20
18
  var _iframe = require("../../../common/dom/iframe");
21
19
  var _handle = require("../../../common/event-emitter/handle");
@@ -29,7 +27,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
29
27
  constructor(agentRef) {
30
28
  super(agentRef, _constants.FEATURE_NAME);
31
29
  this.eventsPerHarvest = 1000;
32
- this.harvestTimeSeconds = agentRef.init.generic_events.harvestTimeSeconds;
33
30
  this.referrerUrl = _runtime.isBrowserScope && document.referrer ? (0, _cleanUrl.cleanURL)(document.referrer) : undefined;
34
31
  this.waitForFlags(['ins']).then(([ins]) => {
35
32
  if (!ins) {
@@ -38,12 +35,20 @@ class Aggregate extends _aggregateBase.AggregateBase {
38
35
  return;
39
36
  }
40
37
  this.trackSupportabilityMetrics();
38
+ (0, _registerHandler.registerHandler)('api-recordCustomEvent', (timestamp, eventType, attributes) => {
39
+ if (_constants.RESERVED_EVENT_TYPES.includes(eventType)) return (0, _console.warn)(46);
40
+ this.addEvent({
41
+ eventType,
42
+ timestamp: this.toEpoch(timestamp),
43
+ ...attributes
44
+ });
45
+ }, this.featureName, this.ee);
41
46
  if (agentRef.init.page_action.enabled) {
42
47
  (0, _registerHandler.registerHandler)('api-addPageAction', (timestamp, name, attributes) => {
43
48
  this.addEvent({
44
49
  ...attributes,
45
50
  eventType: 'PageAction',
46
- timestamp: Math.floor(this.agentRef.runtime.timeKeeper.correctRelativeTimestamp(timestamp)),
51
+ timestamp: this.toEpoch(timestamp),
47
52
  timeSinceLoad: timestamp / 1000,
48
53
  actionName: name,
49
54
  referrerUrl: this.referrerUrl,
@@ -57,6 +62,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
57
62
  let addUserAction;
58
63
  if (_runtime.isBrowserScope && agentRef.init.user_actions.enabled) {
59
64
  this.userActionAggregator = new _userActionsAggregator.UserActionsAggregator();
65
+ this.harvestOpts.beforeUnload = () => addUserAction?.(this.userActionAggregator.aggregationEvent);
60
66
  addUserAction = aggregatedUserAction => {
61
67
  try {
62
68
  /** The aggregator process only returns an event when it is "done" aggregating -
@@ -69,7 +75,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
69
75
  } = aggregatedUserAction.event;
70
76
  this.addEvent({
71
77
  eventType: 'UserAction',
72
- timestamp: Math.floor(this.agentRef.runtime.timeKeeper.correctRelativeTimestamp(timeStamp)),
78
+ timestamp: this.toEpoch(timeStamp),
73
79
  action: type,
74
80
  actionCount: aggregatedUserAction.count,
75
81
  actionDuration: aggregatedUserAction.relativeMs[aggregatedUserAction.relativeMs.length - 1],
@@ -130,7 +136,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
130
136
  (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Generic/Performance/' + type + '/Seen']);
131
137
  this.addEvent({
132
138
  eventType: 'BrowserPerformance',
133
- timestamp: Math.floor(agentRef.runtime.timeKeeper.correctRelativeTimestamp(entry.startTime)),
139
+ timestamp: this.toEpoch(entry.startTime),
134
140
  entryName: (0, _cleanUrl.cleanURL)(entry.name),
135
141
  entryDuration: entry.duration,
136
142
  entryType: type,
@@ -192,12 +198,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
192
198
  }
193
199
  }, this.featureName, this.ee);
194
200
  }
195
- this.harvestScheduler = new _harvestScheduler.HarvestScheduler(_features.FEATURE_TO_ENDPOINT[this.featureName], {
196
- onFinished: result => this.postHarvestCleanup(result.sent && result.retry),
197
- onUnload: () => addUserAction?.(this.userActionAggregator.aggregationEvent)
198
- }, this);
199
- this.harvestScheduler.harvest.on(_features.FEATURE_TO_ENDPOINT[this.featureName], options => this.makeHarvestPayload(options.retry));
200
- this.harvestScheduler.startTimer(this.harvestTimeSeconds, 0);
201
+ agentRef.runtime.harvester.triggerHarvestFor(this);
201
202
  this.drain();
202
203
  });
203
204
  }
@@ -247,7 +248,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
247
248
  * if it fails again, we do nothing
248
249
  */
249
250
  this.ee.emit(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['GenericEvents/Harvest/Max/Seen']);
250
- this.harvestScheduler.runHarvest();
251
+ this.agentRef.runtime.harvester.triggerHarvestFor(this);
251
252
  this.events.add(eventAttributes);
252
253
  }
253
254
  }
@@ -262,6 +263,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
262
263
  at: this.agentRef.info.atts
263
264
  };
264
265
  }
266
+ toEpoch(timestamp) {
267
+ return Math.floor(this.agentRef.runtime.timeKeeper.correctRelativeTimestamp(timestamp));
268
+ }
265
269
  trackSupportabilityMetrics() {
266
270
  /** track usage SMs to improve these experimental features */
267
271
  const configPerfTag = 'Config/Performance/';
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.RAGE_CLICK_THRESHOLD_MS = exports.RAGE_CLICK_THRESHOLD_EVENTS = exports.OBSERVED_WINDOW_EVENTS = exports.OBSERVED_EVENTS = exports.MAX_PAYLOAD_SIZE = exports.IDEAL_PAYLOAD_SIZE = exports.FEATURE_NAME = exports.FEATURE_FLAGS = void 0;
6
+ exports.RESERVED_EVENT_TYPES = exports.RAGE_CLICK_THRESHOLD_MS = exports.RAGE_CLICK_THRESHOLD_EVENTS = exports.OBSERVED_WINDOW_EVENTS = exports.OBSERVED_EVENTS = exports.MAX_PAYLOAD_SIZE = exports.IDEAL_PAYLOAD_SIZE = exports.FEATURE_NAME = exports.FEATURE_FLAGS = void 0;
7
7
  var _features = require("../../loaders/features/features");
8
8
  const FEATURE_NAME = exports.FEATURE_NAME = _features.FEATURE_NAMES.genericEvents;
9
9
  const IDEAL_PAYLOAD_SIZE = exports.IDEAL_PAYLOAD_SIZE = 64000;
@@ -12,6 +12,7 @@ const OBSERVED_EVENTS = exports.OBSERVED_EVENTS = ['auxclick', 'click', 'copy',
12
12
  const OBSERVED_WINDOW_EVENTS = exports.OBSERVED_WINDOW_EVENTS = ['focus', 'blur'];
13
13
  const RAGE_CLICK_THRESHOLD_EVENTS = exports.RAGE_CLICK_THRESHOLD_EVENTS = 4;
14
14
  const RAGE_CLICK_THRESHOLD_MS = exports.RAGE_CLICK_THRESHOLD_MS = 1000;
15
+ const RESERVED_EVENT_TYPES = exports.RESERVED_EVENT_TYPES = ['PageAction', 'UserAction', 'BrowserPerformance'];
15
16
  const FEATURE_FLAGS = exports.FEATURE_FLAGS = {
16
17
  MARKS: 'experimental.marks',
17
18
  MEASURES: 'experimental.measures',
@@ -9,7 +9,6 @@ var _computeStackTrace = require("./compute-stack-trace");
9
9
  var _stringHashCode = require("./string-hash-code");
10
10
  var _formatStackTrace = require("./format-stack-trace");
11
11
  var _registerHandler = require("../../../common/event-emitter/register-handler");
12
- var _harvestScheduler = require("../../../common/harvest/harvest-scheduler");
13
12
  var _stringify = require("../../../common/util/stringify");
14
13
  var _handle = require("../../../common/event-emitter/handle");
15
14
  var _runtime = require("../../../common/constants/runtime");
@@ -45,21 +44,11 @@ class Aggregate extends _aggregateBase.AggregateBase {
45
44
  (0, _registerHandler.registerHandler)('ierr', (...args) => this.storeError(...args), this.featureName, this.ee);
46
45
  (0, _registerHandler.registerHandler)('softNavFlush', (interactionId, wasFinished, softNavAttrs) => this.onSoftNavNotification(interactionId, wasFinished, softNavAttrs), this.featureName, this.ee); // when an ixn is done or cancelled
47
46
 
48
- const harvestTimeSeconds = agentRef.init.jserrors.harvestTimeSeconds || 10;
49
- const aggregatorTypes = ['err', 'ierr', 'xhr']; // the types in EventAggregator this feature cares about
47
+ this.harvestOpts.aggregatorTypes = ['err', 'ierr', 'xhr']; // the types in EventAggregator this feature cares about
50
48
 
51
49
  // 0 == off, 1 == on
52
50
  this.waitForFlags(['err']).then(([errFlag]) => {
53
51
  if (errFlag) {
54
- const scheduler = new _harvestScheduler.HarvestScheduler(_features.FEATURE_TO_ENDPOINT[this.featureName], {
55
- onFinished: result => this.postHarvestCleanup(result.sent && result.retry, {
56
- aggregatorTypes
57
- })
58
- }, this);
59
- scheduler.harvest.on(_features.FEATURE_TO_ENDPOINT[this.featureName], options => this.makeHarvestPayload(options.retry, {
60
- aggregatorTypes
61
- }));
62
- scheduler.startTimer(harvestTimeSeconds);
63
52
  this.drain();
64
53
  } else {
65
54
  this.blocked = true; // if rum response determines that customer lacks entitlements for spa endpoint, this feature shouldn't harvest
@@ -225,7 +214,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
225
214
 
226
215
  const jsAttributesHash = (0, _stringHashCode.stringHashCode)((0, _stringify.stringify)(allCustomAttrs));
227
216
  const aggregateHash = bucketHash + ':' + jsAttributesHash;
228
- this.events.add(type, aggregateHash, params, newMetrics, allCustomAttrs);
217
+ this.events.add([type, aggregateHash, params, newMetrics, allCustomAttrs]);
229
218
  function setCustom(key, val) {
230
219
  allCustomAttrs[key] = val && typeof val === 'object' ? (0, _stringify.stringify)(val) : val;
231
220
  }
@@ -249,7 +238,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
249
238
  var hash = wasSaved ? item[1] + interaction.root.attrs.id : item[1];
250
239
  var jsAttributesHash = (0, _stringHashCode.stringHashCode)((0, _stringify.stringify)(allCustomAttrs));
251
240
  var aggregateHash = hash + ':' + jsAttributesHash;
252
- this.events.add(item[0], aggregateHash, params, item[3], allCustomAttrs);
241
+ this.events.add([item[0], aggregateHash, params, item[3], allCustomAttrs]);
253
242
  function setCustom([key, val]) {
254
243
  allCustomAttrs[key] = val && typeof val === 'object' ? (0, _stringify.stringify)(val) : val;
255
244
  }