@newrelic/browser-agent 1.277.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 (133) hide show
  1. package/CHANGELOG.md +7 -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 +3 -10
  11. package/dist/cjs/features/jserrors/aggregate/index.js +3 -14
  12. package/dist/cjs/features/logging/aggregate/index.js +4 -12
  13. package/dist/cjs/features/metrics/aggregate/index.js +7 -15
  14. package/dist/cjs/features/page_view_event/aggregate/index.js +46 -48
  15. package/dist/cjs/features/page_view_timing/aggregate/index.js +0 -9
  16. package/dist/cjs/features/session_replay/aggregate/index.js +21 -43
  17. package/dist/cjs/features/session_replay/instrument/index.js +2 -1
  18. package/dist/cjs/features/session_replay/shared/recorder.js +6 -6
  19. package/dist/cjs/features/session_trace/aggregate/index.js +9 -24
  20. package/dist/cjs/features/session_trace/aggregate/trace/storage.js +8 -2
  21. package/dist/cjs/features/soft_navigations/aggregate/index.js +4 -11
  22. package/dist/cjs/features/spa/aggregate/index.js +7 -10
  23. package/dist/cjs/features/utils/aggregate-base.js +66 -27
  24. package/dist/cjs/features/utils/event-buffer.js +0 -1
  25. package/dist/cjs/features/utils/event-store-manager.js +109 -0
  26. package/dist/cjs/features/utils/instrument-base.js +1 -10
  27. package/dist/cjs/loaders/features/features.js +16 -10
  28. package/dist/cjs/loaders/micro-agent.js +1 -0
  29. package/dist/esm/common/aggregate/event-aggregator.js +1 -1
  30. package/dist/esm/common/config/init.js +1 -10
  31. package/dist/esm/common/config/runtime.js +2 -1
  32. package/dist/esm/common/constants/env.cdn.js +1 -1
  33. package/dist/esm/common/constants/env.npm.js +1 -1
  34. package/dist/esm/common/harvest/harvester.js +249 -0
  35. package/dist/esm/common/harvest/types.js +5 -21
  36. package/dist/esm/features/ajax/aggregate/index.js +3 -12
  37. package/dist/esm/features/generic_events/aggregate/index.js +3 -10
  38. package/dist/esm/features/jserrors/aggregate/index.js +4 -15
  39. package/dist/esm/features/logging/aggregate/index.js +4 -12
  40. package/dist/esm/features/metrics/aggregate/index.js +7 -15
  41. package/dist/esm/features/page_view_event/aggregate/index.js +46 -48
  42. package/dist/esm/features/page_view_timing/aggregate/index.js +1 -10
  43. package/dist/esm/features/session_replay/aggregate/index.js +22 -44
  44. package/dist/esm/features/session_replay/instrument/index.js +2 -1
  45. package/dist/esm/features/session_replay/shared/recorder.js +6 -6
  46. package/dist/esm/features/session_trace/aggregate/index.js +9 -24
  47. package/dist/esm/features/session_trace/aggregate/trace/storage.js +8 -2
  48. package/dist/esm/features/soft_navigations/aggregate/index.js +5 -12
  49. package/dist/esm/features/spa/aggregate/index.js +8 -11
  50. package/dist/esm/features/utils/aggregate-base.js +66 -27
  51. package/dist/esm/features/utils/event-buffer.js +0 -1
  52. package/dist/esm/features/utils/event-store-manager.js +103 -0
  53. package/dist/esm/features/utils/instrument-base.js +1 -10
  54. package/dist/esm/loaders/features/features.js +15 -9
  55. package/dist/esm/loaders/micro-agent.js +1 -0
  56. package/dist/types/common/aggregate/event-aggregator.d.ts +1 -1
  57. package/dist/types/common/aggregate/event-aggregator.d.ts.map +1 -1
  58. package/dist/types/common/config/init.d.ts.map +1 -1
  59. package/dist/types/common/config/runtime.d.ts.map +1 -1
  60. package/dist/types/common/harvest/harvester.d.ts +16 -0
  61. package/dist/types/common/harvest/harvester.d.ts.map +1 -0
  62. package/dist/types/common/harvest/types.d.ts +8 -45
  63. package/dist/types/common/harvest/types.d.ts.map +1 -1
  64. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  65. package/dist/types/features/generic_events/aggregate/index.d.ts +0 -3
  66. package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
  67. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  68. package/dist/types/features/logging/aggregate/index.d.ts +0 -3
  69. package/dist/types/features/logging/aggregate/index.d.ts.map +1 -1
  70. package/dist/types/features/metrics/aggregate/index.d.ts +1 -1
  71. package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
  72. package/dist/types/features/page_view_event/aggregate/index.d.ts +6 -2
  73. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  74. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  75. package/dist/types/features/session_replay/aggregate/index.d.ts +12 -15
  76. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  77. package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
  78. package/dist/types/features/session_trace/aggregate/index.d.ts +0 -5
  79. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  80. package/dist/types/features/session_trace/aggregate/trace/storage.d.ts +8 -5
  81. package/dist/types/features/session_trace/aggregate/trace/storage.d.ts.map +1 -1
  82. package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -1
  83. package/dist/types/features/spa/aggregate/index.d.ts +0 -1
  84. package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
  85. package/dist/types/features/utils/aggregate-base.d.ts +12 -7
  86. package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
  87. package/dist/types/features/utils/event-buffer.d.ts +1 -2
  88. package/dist/types/features/utils/event-buffer.d.ts.map +1 -1
  89. package/dist/types/features/utils/event-store-manager.d.ts +43 -0
  90. package/dist/types/features/utils/event-store-manager.d.ts.map +1 -0
  91. package/dist/types/features/utils/instrument-base.d.ts +0 -1
  92. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  93. package/dist/types/loaders/features/features.d.ts +15 -12
  94. package/dist/types/loaders/features/features.d.ts.map +1 -1
  95. package/dist/types/loaders/micro-agent.d.ts.map +1 -1
  96. package/package.json +6 -6
  97. package/src/common/aggregate/event-aggregator.js +1 -1
  98. package/src/common/config/init.js +9 -10
  99. package/src/common/config/runtime.js +2 -1
  100. package/src/common/harvest/__mocks__/harvester.js +6 -0
  101. package/src/common/harvest/harvester.js +230 -0
  102. package/src/common/harvest/types.js +5 -21
  103. package/src/features/ajax/aggregate/index.js +3 -14
  104. package/src/features/generic_events/aggregate/index.js +3 -13
  105. package/src/features/jserrors/aggregate/index.js +4 -11
  106. package/src/features/logging/aggregate/index.js +4 -12
  107. package/src/features/metrics/aggregate/index.js +5 -12
  108. package/src/features/page_view_event/aggregate/index.js +38 -38
  109. package/src/features/page_view_timing/aggregate/index.js +1 -12
  110. package/src/features/session_replay/aggregate/index.js +19 -42
  111. package/src/features/session_replay/instrument/index.js +1 -1
  112. package/src/features/session_replay/shared/recorder.js +6 -6
  113. package/src/features/session_trace/aggregate/index.js +8 -25
  114. package/src/features/session_trace/aggregate/trace/storage.js +5 -2
  115. package/src/features/soft_navigations/aggregate/index.js +4 -12
  116. package/src/features/spa/aggregate/index.js +8 -11
  117. package/src/features/utils/aggregate-base.js +59 -27
  118. package/src/features/utils/event-buffer.js +0 -1
  119. package/src/features/utils/event-store-manager.js +101 -0
  120. package/src/features/utils/instrument-base.js +2 -8
  121. package/src/loaders/features/features.js +16 -9
  122. package/src/loaders/micro-agent.js +1 -0
  123. package/dist/cjs/common/harvest/harvest-scheduler.js +0 -168
  124. package/dist/cjs/common/harvest/harvest.js +0 -295
  125. package/dist/esm/common/harvest/harvest-scheduler.js +0 -160
  126. package/dist/esm/common/harvest/harvest.js +0 -286
  127. package/dist/types/common/harvest/harvest-scheduler.d.ts +0 -50
  128. package/dist/types/common/harvest/harvest-scheduler.d.ts.map +0 -1
  129. package/dist/types/common/harvest/harvest.d.ts +0 -65
  130. package/dist/types/common/harvest/harvest.d.ts.map +0 -1
  131. package/src/common/harvest/__mocks__/harvest.js +0 -13
  132. package/src/common/harvest/harvest-scheduler.js +0 -166
  133. package/src/common/harvest/harvest.js +0 -282
@@ -14,32 +14,16 @@
14
14
  * @property {object} body Map of values that should be sent as the body of the request.
15
15
  */
16
16
 
17
- /**
18
- * @typedef {object} FeatureHarvestCallbackOptions Options for aggregating data for harvesting.
19
- * @property {boolean} options.retry Indicates if the feature should store the aggregated
20
- * data in anticipation of a possible need to retry the transmission.
21
- */
22
-
23
- /**
24
- * @callback FeatureHarvestCallback
25
- * @param {FeatureHarvestCallbackOptions} options Options for aggregating data for harvesting.
26
- * @returns {HarvestPayload} Payload of data to transmit to bam endpoint.
27
- */
28
-
29
17
  /**
30
18
  * @typedef {object} NetworkSendSpec
31
19
  * @property {HarvestEndpointIdentifier} endpoint The endpoint to use (jserrors, events, resources etc.)
32
20
  * @property {HarvestPayload} payload Object representing payload.
33
- * @property {object} opts Additional options for sending data
34
- * @property {boolean} opts.needResponse Specify whether the caller expects a response data.
35
- * @property {boolean} opts.unload Specify whether the call is a final harvest during page unload.
36
- * @property {boolean} opts.sendEmptyBody Specify whether the call should be made even if the body is empty. Useful for rum calls.
37
- * @property {boolean} opts.retry Indicates if the feature should store the aggregated data in anticipation of a possible need to
38
- * retry the transmission.
21
+ * @property {object} localOpts Additional options for sending data
22
+ * @property {boolean} localOpts.needResponse Specify whether the caller expects a response data.
23
+ * @property {boolean} localOpts.isFinalHarvest Specify whether the call is a final harvest during page unload.
24
+ * @property {boolean} localOpts.sendEmptyBody Specify whether the call should be made even if the body is empty. Useful for rum calls.
25
+ * @property {boolean} localOpts.forceNoRetry Don't save the buffered data in the case of a need to retry the transmission.
39
26
  * @property {import('../util/submit-data.js').NetworkMethods} submitMethod The network method to use {@link ../util/submit-data.js}
40
- * @property {string} customUrl Override the beacon url the data is sent to; must include protocol if defined
41
- * @property {boolean} raw If true, disables adding the license key to the url
42
- * @property {boolean} includeBaseParams Enables the use of base query parameters in the beacon url
43
27
  */
44
28
 
45
29
  /* istanbul ignore next */
@@ -5,10 +5,9 @@
5
5
  import { registerHandler } from '../../../common/event-emitter/register-handler'
6
6
  import { stringify } from '../../../common/util/stringify'
7
7
  import { handle } from '../../../common/event-emitter/handle'
8
- import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler'
9
8
  import { setDenyList, shouldCollectEvent } from '../../../common/deny-list/deny-list'
10
9
  import { FEATURE_NAME } from '../constants'
11
- import { FEATURE_NAMES, FEATURE_TO_ENDPOINT } from '../../../loaders/features/features'
10
+ import { FEATURE_NAMES } from '../../../loaders/features/features'
12
11
  import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants'
13
12
  import { AggregateBase } from '../../utils/aggregate-base'
14
13
  import { parseGQL } from './gql'
@@ -19,10 +18,7 @@ export class Aggregate extends AggregateBase {
19
18
 
20
19
  constructor (agentRef) {
21
20
  super(agentRef, FEATURE_NAME)
22
-
23
- const harvestTimeSeconds = agentRef.init.ajax.harvestTimeSeconds || 10
24
21
  setDenyList(agentRef.runtime.denyList)
25
-
26
22
  this.underSpaEvents = {}
27
23
  const classThis = this
28
24
 
@@ -43,14 +39,7 @@ export class Aggregate extends AggregateBase {
43
39
  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
40
  }, this.featureName, this.ee)
45
41
 
46
- this.waitForFlags(([])).then(() => {
47
- const scheduler = new HarvestScheduler(FEATURE_TO_ENDPOINT[this.featureName], {
48
- onFinished: (result) => this.postHarvestCleanup(result.sent && result.retry),
49
- getPayload: (options) => this.makeHarvestPayload(options.retry)
50
- }, this)
51
- scheduler.startTimer(harvestTimeSeconds)
52
- this.drain()
53
- })
42
+ this.waitForFlags(([])).then(() => this.drain())
54
43
  }
55
44
 
56
45
  storeXhr (params, metrics, startTime, endTime, type, ctx) {
@@ -70,7 +59,7 @@ export class Aggregate extends AggregateBase {
70
59
 
71
60
  // Report ajax timeslice metric (to be harvested by jserrors feature, but only if it's running).
72
61
  if (jserrorsInUse && (shouldCollect || !shouldOmitAjaxMetrics)) {
73
- this.agentRef.sharedAggregator.add('xhr', hash, params, metrics)
62
+ this.agentRef.sharedAggregator.add(['xhr', hash, params, metrics])
74
63
  }
75
64
 
76
65
  if (!shouldCollect) {
@@ -3,7 +3,6 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { stringify } from '../../../common/util/stringify'
6
- import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler'
7
6
  import { cleanURL } from '../../../common/url/clean-url'
8
7
  import { FEATURE_NAME, RESERVED_EVENT_TYPES } from '../constants'
9
8
  import { globalScope, initialLocation, isBrowserScope } from '../../../common/constants/runtime'
@@ -13,7 +12,6 @@ import { now } from '../../../common/timing/now'
13
12
  import { registerHandler } from '../../../common/event-emitter/register-handler'
14
13
  import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants'
15
14
  import { applyFnToProps } from '../../../common/util/traverse'
16
- import { FEATURE_TO_ENDPOINT } from '../../../loaders/features/features'
17
15
  import { UserActionsAggregator } from './user-actions/user-actions-aggregator'
18
16
  import { isIFrameWindow } from '../../../common/dom/iframe'
19
17
  import { handle } from '../../../common/event-emitter/handle'
@@ -22,10 +20,7 @@ export class Aggregate extends AggregateBase {
22
20
  static featureName = FEATURE_NAME
23
21
  constructor (agentRef) {
24
22
  super(agentRef, FEATURE_NAME)
25
-
26
23
  this.eventsPerHarvest = 1000
27
- this.harvestTimeSeconds = agentRef.init.generic_events.harvestTimeSeconds
28
-
29
24
  this.referrerUrl = (isBrowserScope && document.referrer) ? cleanURL(document.referrer) : undefined
30
25
 
31
26
  this.waitForFlags(['ins']).then(([ins]) => {
@@ -66,6 +61,7 @@ export class Aggregate extends AggregateBase {
66
61
  let addUserAction
67
62
  if (isBrowserScope && agentRef.init.user_actions.enabled) {
68
63
  this.userActionAggregator = new UserActionsAggregator()
64
+ this.harvestOpts.beforeUnload = () => addUserAction?.(this.userActionAggregator.aggregationEvent)
69
65
 
70
66
  addUserAction = (aggregatedUserAction) => {
71
67
  try {
@@ -186,13 +182,7 @@ export class Aggregate extends AggregateBase {
186
182
  }, this.featureName, this.ee)
187
183
  }
188
184
 
189
- this.harvestScheduler = new HarvestScheduler(FEATURE_TO_ENDPOINT[this.featureName], {
190
- onFinished: (result) => this.postHarvestCleanup(result.sent && result.retry),
191
- onUnload: () => addUserAction?.(this.userActionAggregator.aggregationEvent)
192
- }, this)
193
- this.harvestScheduler.harvest.on(FEATURE_TO_ENDPOINT[this.featureName], (options) => this.makeHarvestPayload(options.retry))
194
- this.harvestScheduler.startTimer(this.harvestTimeSeconds, 0)
195
-
185
+ agentRef.runtime.harvester.triggerHarvestFor(this)
196
186
  this.drain()
197
187
  })
198
188
  }
@@ -246,7 +236,7 @@ export class Aggregate extends AggregateBase {
246
236
  * if it fails again, we do nothing
247
237
  */
248
238
  this.ee.emit(SUPPORTABILITY_METRIC_CHANNEL, ['GenericEvents/Harvest/Max/Seen'])
249
- this.harvestScheduler.runHarvest()
239
+ this.agentRef.runtime.harvester.triggerHarvestFor(this)
250
240
  this.events.add(eventAttributes)
251
241
  }
252
242
  }
@@ -9,13 +9,12 @@ import { stringHashCode } from './string-hash-code'
9
9
  import { truncateSize } from './format-stack-trace'
10
10
 
11
11
  import { registerHandler as register } from '../../../common/event-emitter/register-handler'
12
- import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler'
13
12
  import { stringify } from '../../../common/util/stringify'
14
13
  import { handle } from '../../../common/event-emitter/handle'
15
14
  import { globalScope } from '../../../common/constants/runtime'
16
15
 
17
16
  import { FEATURE_NAME } from '../constants'
18
- import { FEATURE_NAMES, FEATURE_TO_ENDPOINT } from '../../../loaders/features/features'
17
+ import { FEATURE_NAMES } from '../../../loaders/features/features'
19
18
  import { AggregateBase } from '../../utils/aggregate-base'
20
19
  import { now } from '../../../common/timing/now'
21
20
  import { applyFnToProps } from '../../../common/util/traverse'
@@ -45,17 +44,11 @@ export class Aggregate extends AggregateBase {
45
44
  register('softNavFlush', (interactionId, wasFinished, softNavAttrs) =>
46
45
  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(FEATURE_TO_ENDPOINT[this.featureName], {
55
- onFinished: (result) => this.postHarvestCleanup(result.sent && result.retry, { aggregatorTypes })
56
- }, this)
57
- scheduler.harvest.on(FEATURE_TO_ENDPOINT[this.featureName], (options) => this.makeHarvestPayload(options.retry, { aggregatorTypes }))
58
- scheduler.startTimer(harvestTimeSeconds)
59
52
  this.drain()
60
53
  } else {
61
54
  this.blocked = true // if rum response determines that customer lacks entitlements for spa endpoint, this feature shouldn't harvest
@@ -235,7 +228,7 @@ export class Aggregate extends AggregateBase {
235
228
 
236
229
  const jsAttributesHash = stringHashCode(stringify(allCustomAttrs))
237
230
  const aggregateHash = bucketHash + ':' + jsAttributesHash
238
- this.events.add(type, aggregateHash, params, newMetrics, allCustomAttrs)
231
+ this.events.add([type, aggregateHash, params, newMetrics, allCustomAttrs])
239
232
 
240
233
  function setCustom (key, val) {
241
234
  allCustomAttrs[key] = (val && typeof val === 'object' ? stringify(val) : val)
@@ -265,7 +258,7 @@ export class Aggregate extends AggregateBase {
265
258
  var jsAttributesHash = stringHashCode(stringify(allCustomAttrs))
266
259
  var aggregateHash = hash + ':' + jsAttributesHash
267
260
 
268
- this.events.add(item[0], aggregateHash, params, item[3], allCustomAttrs)
261
+ this.events.add([item[0], aggregateHash, params, item[3], allCustomAttrs])
269
262
 
270
263
  function setCustom ([key, val]) {
271
264
  allCustomAttrs[key] = (val && typeof val === 'object' ? stringify(val) : val)
@@ -1,6 +1,5 @@
1
1
  import { handle } from '../../../common/event-emitter/handle'
2
2
  import { registerHandler } from '../../../common/event-emitter/register-handler'
3
- import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler'
4
3
  import { warn } from '../../../common/util/console'
5
4
  import { stringify } from '../../../common/util/stringify'
6
5
  import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants'
@@ -10,26 +9,19 @@ import { Log } from '../shared/log'
10
9
  import { isValidLogLevel } from '../shared/utils'
11
10
  import { applyFnToProps } from '../../../common/util/traverse'
12
11
  import { MAX_PAYLOAD_SIZE } from '../../../common/constants/agent-constants'
13
- import { FEATURE_TO_ENDPOINT } from '../../../loaders/features/features'
14
12
 
15
13
  export class Aggregate extends AggregateBase {
16
14
  static featureName = FEATURE_NAME
17
15
  constructor (agentRef) {
18
16
  super(agentRef, FEATURE_NAME)
19
- this.harvestTimeSeconds = agentRef.init.logging.harvestTimeSeconds
17
+ this.harvestOpts.raw = true
20
18
 
21
19
  this.waitForFlags([]).then(() => {
22
- this.scheduler = new HarvestScheduler(FEATURE_TO_ENDPOINT[this.featureName], {
23
- onFinished: (result) => this.postHarvestCleanup(result.sent && result.retry),
24
- retryDelay: this.harvestTimeSeconds,
25
- getPayload: (options) => this.makeHarvestPayload(options.retry),
26
- raw: true
27
- }, this)
28
20
  /** emitted by instrument class (wrapped loggers) or the api methods directly */
29
21
  registerHandler(LOGGING_EVENT_EMITTER_CHANNEL, this.handleLog.bind(this), this.featureName, this.ee)
30
22
  this.drain()
31
23
  /** harvest immediately once started to purge pre-load logs collected */
32
- this.scheduler.startTimer(this.harvestTimeSeconds, 0)
24
+ agentRef.runtime.harvester.triggerHarvestFor(this)
33
25
  })
34
26
  }
35
27
 
@@ -73,8 +65,8 @@ export class Aggregate extends AggregateBase {
73
65
  }
74
66
 
75
67
  if (this.events.wouldExceedMaxSize(logBytes)) {
76
- handle(SUPPORTABILITY_METRIC_CHANNEL, ['Logging/Harvest/Early/Seen', this.events.bytes + logBytes])
77
- this.scheduler.runHarvest() // force a harvest to try adding again
68
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['Logging/Harvest/Early/Seen', this.events.byteSize() + logBytes])
69
+ this.agentRef.runtime.harvester.triggerHarvestFor(this) // force a harvest synchronously to try adding again
78
70
  }
79
71
 
80
72
  if (!this.events.add(log)) { // still failed after a harvest attempt despite not being too large would mean harvest failed with options.retry
@@ -1,5 +1,4 @@
1
1
  import { registerHandler } from '../../../common/event-emitter/register-handler'
2
- import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler'
3
2
  import { FEATURE_NAME, SUPPORTABILITY_METRIC, CUSTOM_METRIC, SUPPORTABILITY_METRIC_CHANNEL, CUSTOM_METRIC_CHANNEL/*, WATCHABLE_WEB_SOCKET_EVENTS */ } from '../constants'
4
3
  import { getFrameworks } from './framework-detection'
5
4
  import { isFileProtocol } from '../../../common/url/protocol'
@@ -7,7 +6,6 @@ import { onDOMContentLoaded } from '../../../common/window/load'
7
6
  import { windowAddEventListener } from '../../../common/event-listener/event-listener-opts'
8
7
  import { isBrowserScope, isWorkerScope } from '../../../common/constants/runtime'
9
8
  import { AggregateBase } from '../../utils/aggregate-base'
10
- import { FEATURE_TO_ENDPOINT } from '../../../loaders/features/features'
11
9
  import { isIFrameWindow } from '../../../common/dom/iframe'
12
10
  // import { WEBSOCKET_TAG } from '../../../common/wrap/wrap-websocket'
13
11
  // import { handleWebsocketEvents } from './websocket-detection'
@@ -16,14 +14,11 @@ export class Aggregate extends AggregateBase {
16
14
  static featureName = FEATURE_NAME
17
15
  constructor (agentRef) {
18
16
  super(agentRef, FEATURE_NAME)
19
- const aggregatorTypes = ['cm', 'sm'] // the types in EventAggregator this feature cares about
17
+ this.harvestOpts.aggregatorTypes = ['cm', 'sm'] // the types in EventAggregator this feature cares about
18
+ // This feature only harvests once per potential EoL of the page, which is handled by the central harvester.
20
19
 
21
20
  this.waitForFlags(['err']).then(([errFlag]) => {
22
21
  if (errFlag) {
23
- // *cli, Mar 23 - Per NR-94597, this feature should only harvest ONCE at the (potential) EoL time of the page.
24
- const scheduler = new HarvestScheduler(FEATURE_TO_ENDPOINT[this.featureName], { onUnload: () => this.unload() }, this)
25
- // this is needed to ensure EoL is "on" and sent
26
- scheduler.harvest.on(FEATURE_TO_ENDPOINT[this.featureName], () => this.makeHarvestPayload(undefined, { aggregatorTypes }))
27
22
  this.drain()
28
23
  } else {
29
24
  this.blocked = true // if rum response determines that customer lacks entitlements for spa endpoint, this feature shouldn't harvest
@@ -39,6 +34,8 @@ export class Aggregate extends AggregateBase {
39
34
  this.eachSessionChecks() // the start of every time user engages with page
40
35
  }
41
36
 
37
+ preHarvestChecks () { return this.drained } // only allow any metrics to be sent if we know for sure it has gotten the go-ahead RUM flag
38
+
42
39
  storeSupportabilityMetrics (name, value) {
43
40
  if (this.blocked) return
44
41
  const type = SUPPORTABILITY_METRIC
@@ -50,7 +47,7 @@ export class Aggregate extends AggregateBase {
50
47
  if (this.blocked) return
51
48
  const type = CUSTOM_METRIC
52
49
  const params = { name }
53
- this.events.add(type, name, params, metrics)
50
+ this.events.add([type, name, params, metrics])
54
51
  }
55
52
 
56
53
  singleChecks () {
@@ -132,8 +129,4 @@ export class Aggregate extends AggregateBase {
132
129
  if (evt?.persisted) { this.storeSupportabilityMetrics('Generic/BFCache/PageRestored') }
133
130
  })
134
131
  }
135
-
136
- unload () {
137
- // do nothing for now, marks and measures and resources stats are now being captured by the ge feature
138
- }
139
132
  }
@@ -2,7 +2,6 @@ import { globalScope, isBrowserScope, originTime } from '../../../common/constan
2
2
  import { addPT, addPN } from '../../../common/timing/nav-timing'
3
3
  import { stringify } from '../../../common/util/stringify'
4
4
  import { isValid } from '../../../common/config/info'
5
- import { Harvest } from '../../../common/harvest/harvest'
6
5
  import * as CONSTANTS from '../constants'
7
6
  import { getActivatedFeaturesFlags } from './initialized-features'
8
7
  import { activateFeatures } from '../../../common/util/feature-flags'
@@ -23,12 +22,12 @@ export class Aggregate extends AggregateBase {
23
22
  this.timeToFirstByte = 0
24
23
  this.firstByteToWindowLoad = 0 // our "frontend" duration
25
24
  this.firstByteToDomContent = 0 // our "dom processing" duration
26
- this.timeKeeper = new TimeKeeper(agentRef.agentIdentifier)
27
25
 
28
26
  if (!isValid(agentRef.agentIdentifier)) {
29
27
  this.ee.abort()
30
28
  return warn(43)
31
29
  }
30
+ agentRef.runtime.timeKeeper = new TimeKeeper(agentRef.agentIdentifier)
32
31
 
33
32
  if (isBrowserScope) {
34
33
  timeToFirstByte.subscribe(({ value, attrs }) => {
@@ -47,7 +46,6 @@ export class Aggregate extends AggregateBase {
47
46
 
48
47
  sendRum () {
49
48
  const info = this.agentRef.info
50
- const harvester = new Harvest(this)
51
49
  const measures = {}
52
50
 
53
51
  if (info.queueTime) measures.qt = info.queueTime
@@ -99,43 +97,45 @@ export class Aggregate extends AggregateBase {
99
97
  queryParameters.fp = firstPaint.current.value
100
98
  queryParameters.fcp = firstContentfulPaint.current.value
101
99
 
102
- if (this.timeKeeper?.ready) {
103
- queryParameters.timestamp = Math.floor(this.timeKeeper.correctRelativeTimestamp(now()))
100
+ const timeKeeper = this.agentRef.runtime.timeKeeper
101
+ if (timeKeeper?.ready) {
102
+ queryParameters.timestamp = Math.floor(timeKeeper.correctRelativeTimestamp(now()))
104
103
  }
105
104
 
106
- const rumStartTime = now()
107
- harvester.send({
108
- endpoint: 'rum',
109
- payload: { qs: queryParameters, body },
110
- opts: { needResponse: true, sendEmptyBody: true },
111
- cbFinished: ({ status, responseText, xhr }) => {
112
- const rumEndTime = now()
113
-
114
- if (status >= 400 || status === 0) {
115
- // Adding retry logic for the rum call will be a separate change
116
- this.ee.abort()
117
- return
118
- }
119
-
120
- try {
121
- const { app, ...flags } = JSON.parse(responseText)
122
- try {
123
- this.timeKeeper.processRumRequest(xhr, rumStartTime, rumEndTime, app.nrServerTime)
124
- if (!this.timeKeeper.ready) throw new Error('TimeKeeper not ready')
125
- this.agentRef.runtime.timeKeeper = this.timeKeeper
126
- } catch (error) {
127
- this.ee.abort()
128
- warn(17, error)
129
- return
130
- }
131
- this.agentRef.runtime.appMetadata = app
132
- activateFeatures(flags, this.agentIdentifier)
133
- this.drain()
134
- } catch (err) {
135
- this.ee.abort()
136
- warn(18, err)
137
- }
138
- }
105
+ this.rumStartTime = now()
106
+ this.agentRef.runtime.harvester.triggerHarvestFor(this, {
107
+ directSend: {
108
+ targetApp: this.agentRef.mainAppKey,
109
+ payload: { qs: queryParameters, body }
110
+ },
111
+ needResponse: true,
112
+ sendEmptyBody: true
139
113
  })
140
114
  }
115
+
116
+ postHarvestCleanup ({ status, responseText, xhr }) {
117
+ const rumEndTime = now()
118
+ this.blocked = true // this prevents harvester from polling this feature's event buffer (DNE) on interval; in other words, harvests will skip PVE
119
+
120
+ if (status >= 400 || status === 0) {
121
+ warn(18, status)
122
+ // Adding retry logic for the rum call will be a separate change; this.blocked will need to be changed since that prevents another triggerHarvestFor()
123
+ this.ee.abort()
124
+ return
125
+ }
126
+
127
+ const { app, ...flags } = JSON.parse(responseText)
128
+ try {
129
+ this.agentRef.runtime.timeKeeper.processRumRequest(xhr, this.rumStartTime, rumEndTime, app.nrServerTime)
130
+ if (!this.agentRef.runtime.timeKeeper.ready) throw new Error('TimeKeeper not ready')
131
+ } catch (error) {
132
+ this.ee.abort()
133
+ warn(17, error)
134
+ return
135
+ }
136
+ this.agentRef.runtime.appMetadata = app
137
+ activateFeatures(flags, this.agentIdentifier)
138
+ this.drain()
139
+ this.agentRef.runtime.harvester.startTimer()
140
+ }
141
141
  }
@@ -4,11 +4,10 @@
4
4
  */
5
5
 
6
6
  import { nullable, numeric, getAddStringContext, addCustomAttributes } from '../../../common/serialize/bel-serializer'
7
- import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler'
8
7
  import { registerHandler } from '../../../common/event-emitter/register-handler'
9
8
  import { handle } from '../../../common/event-emitter/handle'
10
9
  import { FEATURE_NAME } from '../constants'
11
- import { FEATURE_NAMES, FEATURE_TO_ENDPOINT } from '../../../loaders/features/features'
10
+ import { FEATURE_NAMES } from '../../../loaders/features/features'
12
11
  import { AggregateBase } from '../../utils/aggregate-base'
13
12
  import { cumulativeLayoutShift } from '../../../common/vitals/cumulative-layout-shift'
14
13
  import { firstContentfulPaint } from '../../../common/vitals/first-contentful-paint'
@@ -35,11 +34,7 @@ export class Aggregate extends AggregateBase {
35
34
  // Add the time of _window pagehide event_ firing to the next PVT harvest == NRDB windowUnload attr:
36
35
  registerHandler('winPagehide', msTimestamp => this.addTiming('unload', msTimestamp, null), this.featureName, this.ee)
37
36
 
38
- const harvestTimeSeconds = agentRef.init.page_view_timing.harvestTimeSeconds || 30
39
-
40
37
  this.waitForFlags(([])).then(() => {
41
- /* It's important that CWV api, like "onLCP", is called before the **scheduler** is initialized. The reason is because they listen to the same
42
- on vis change or pagehide events, and we'd want ex. onLCP to record the timing (win the race) before we try to send "final harvest". */
43
38
  firstPaint.subscribe(this.#handleVitalMetric)
44
39
  firstContentfulPaint.subscribe(this.#handleVitalMetric)
45
40
  firstInputDelay.subscribe(this.#handleVitalMetric)
@@ -57,12 +52,6 @@ export class Aggregate extends AggregateBase {
57
52
  this.addTiming(name, value * 1000, attrs)
58
53
  }, true) // CLS node should only reports on vis change rather than on every change
59
54
 
60
- const scheduler = new HarvestScheduler(FEATURE_TO_ENDPOINT[this.featureName], {
61
- onFinished: (result) => this.postHarvestCleanup(result.sent && result.retry),
62
- getPayload: (options) => this.makeHarvestPayload(options.retry)
63
- }, this)
64
- scheduler.startTimer(harvestTimeSeconds)
65
-
66
55
  this.drain()
67
56
  })
68
57
  }
@@ -7,7 +7,6 @@
7
7
  */
8
8
 
9
9
  import { registerHandler } from '../../../common/event-emitter/register-handler'
10
- import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler'
11
10
  import { ABORT_REASONS, FEATURE_NAME, QUERY_PARAM_PADDING, RRWEB_EVENT_TYPES, SR_EVENT_EMITTER_TYPES, TRIGGERS } from '../constants'
12
11
  import { AggregateBase } from '../../utils/aggregate-base'
13
12
  import { sharedChannel } from '../../../common/constants/shared-channel'
@@ -16,7 +15,7 @@ import { warn } from '../../../common/util/console'
16
15
  import { globalScope } from '../../../common/constants/runtime'
17
16
  import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants'
18
17
  import { handle } from '../../../common/event-emitter/handle'
19
- import { FEATURE_NAMES, FEATURE_TO_ENDPOINT } from '../../../loaders/features/features'
18
+ import { FEATURE_NAMES } from '../../../loaders/features/features'
20
19
  import { RRWEB_VERSION } from '../../../common/constants/env'
21
20
  import { MODE, SESSION_EVENTS, SESSION_EVENT_TYPES } from '../../../common/session/constants'
22
21
  import { stringify } from '../../../common/util/stringify'
@@ -33,8 +32,6 @@ export class Aggregate extends AggregateBase {
33
32
  // pass the recorder into the aggregator
34
33
  constructor (agentRef, args) {
35
34
  super(agentRef, FEATURE_NAME)
36
- /** The interval to harvest at. This gets overridden if the size of the payload exceeds certain thresholds */
37
- this.harvestTimeSeconds = agentRef.init.session_replay.harvestTimeSeconds || 60
38
35
  /** Set once the recorder has fully initialized after flag checks and sampling */
39
36
  this.initialized = false
40
37
  /** Set once the feature has been "aborted" to prevent other side-effects from continuing */
@@ -51,6 +48,7 @@ export class Aggregate extends AggregateBase {
51
48
 
52
49
  this.recorder = args?.recorder
53
50
  this.errorNoticed = args?.errorNoticed || false
51
+ this.harvestOpts.raw = true
54
52
 
55
53
  handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/Enabled'], undefined, FEATURE_NAMES.metrics, this.ee)
56
54
 
@@ -76,16 +74,8 @@ export class Aggregate extends AggregateBase {
76
74
  this.mode = data.sessionReplay
77
75
  })
78
76
 
79
- // Bespoke logic for blobs endpoint.
80
- this.scheduler = new HarvestScheduler(FEATURE_TO_ENDPOINT[this.featureName], {
81
- onFinished: (result) => this.postHarvestCleanup(result),
82
- retryDelay: this.harvestTimeSeconds,
83
- getPayload: ({ retry, ...opts }) => this.makeHarvestPayload(retry, opts),
84
- raw: true
85
- }, this)
86
-
87
77
  registerHandler(SR_EVENT_EMITTER_TYPES.PAUSE, () => {
88
- this.forceStop(this.mode !== MODE.ERROR)
78
+ this.forceStop(this.mode === MODE.FULL)
89
79
  }, this.featureName, this.ee)
90
80
 
91
81
  registerHandler(SR_EVENT_EMITTER_TYPES.ERROR_DURING_REPLAY, e => {
@@ -127,7 +117,7 @@ export class Aggregate extends AggregateBase {
127
117
  }
128
118
 
129
119
  replayIsActive () {
130
- return Boolean(this.scheduler?.started && this.recorder && this.mode === MODE.FULL && !this.blocked && this.entitled)
120
+ return Boolean(this.recorder && this.mode === MODE.FULL && !this.blocked && this.entitled)
131
121
  }
132
122
 
133
123
  handleError (e) {
@@ -144,18 +134,15 @@ export class Aggregate extends AggregateBase {
144
134
  // if the error was noticed AFTER the recorder was already imported....
145
135
  if (this.recorder && this.initialized) {
146
136
  if (!this.recorder.recording) this.recorder.startRecording()
147
- this.scheduler.startTimer(this.harvestTimeSeconds)
148
137
  this.syncWithSessionManager({ sessionReplayMode: this.mode })
149
138
  } else {
150
- this.initializeRecording(false, true, true)
139
+ this.initializeRecording(MODE.FULL, true)
151
140
  }
152
141
  }
153
142
 
154
143
  /**
155
144
  * Evaluate entitlements and sampling before starting feature mechanics, importing and configuring recording library, and setting storage state
156
145
  * @param {boolean} entitlements - the true/false state of the "sr" flag from RUM response
157
- * @param {boolean} errorSample - the true/false state of the error sampling decision
158
- * @param {boolean} fullSample - the true/false state of the full sampling decision
159
146
  * @param {boolean} ignoreSession - whether to force the method to ignore the session state and use just the sample flags
160
147
  * @returns {void}
161
148
  */
@@ -198,20 +185,13 @@ export class Aggregate extends AggregateBase {
198
185
  // If an error was noticed before the mode could be set (like in the early lifecycle of the page), immediately set to FULL mode
199
186
  if (this.mode === MODE.ERROR && this.errorNoticed) this.mode = MODE.FULL
200
187
 
201
- if (this.mode === MODE.FULL) {
202
- // If theres preloaded events and we are in full mode, just harvest immediately to clear up space and for consistency
203
- if (this.recorder?.getEvents().type === 'preloaded') {
204
- this.prepUtils().then(() => {
205
- this.scheduler.runHarvest()
206
- })
207
- }
208
- // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
209
- // ERROR mode will do this until an error is thrown, and then switch into FULL mode.
210
- // If an error happened in ERROR mode before we've gotten to this stage, it will have already set the mode to FULL
211
- if (!this.scheduler.started) {
212
- // We only report (harvest) in FULL mode
213
- this.scheduler.startTimer(this.harvestTimeSeconds)
214
- }
188
+ // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
189
+ // ERROR mode will do this until an error is thrown, and then switch into FULL mode.
190
+ // The makeHarvestPayload should ensure that no payload is returned if we're not in FULL mode...
191
+
192
+ // If theres preloaded events and we are in full mode, just harvest immediately to clear up space and for consistency
193
+ if (this.mode === MODE.FULL && this.recorder?.getEvents().type === 'preloaded') {
194
+ this.prepUtils().then(() => this.agentRef.runtime.harvester.triggerHarvestFor(this))
215
195
  }
216
196
 
217
197
  await this.prepUtils()
@@ -232,11 +212,13 @@ export class Aggregate extends AggregateBase {
232
212
  }
233
213
  }
234
214
 
235
- makeHarvestPayload (shouldRetryOnFail, opts) {
215
+ makeHarvestPayload (shouldRetryOnFail) {
216
+ if (this.mode !== MODE.FULL || this.blocked) return
236
217
  if (!this.recorder || !this.timeKeeper?.ready || !this.recorder.hasSeenSnapshot) return
218
+
237
219
  const recorderEvents = this.recorder.getEvents()
238
220
  // get the event type and use that to trigger another harvest if needed
239
- if (!recorderEvents.events.length || (this.mode !== MODE.FULL) || this.blocked) return
221
+ if (!recorderEvents.events.length) return
240
222
 
241
223
  const payload = this.getHarvestContents(recorderEvents)
242
224
  if (!payload.body.length) {
@@ -258,7 +240,6 @@ export class Aggregate extends AggregateBase {
258
240
  return stringify(output)
259
241
  }).join(',')}]`))
260
242
  len = payload.body.length
261
- this.scheduler.opts.gzip = true
262
243
  } else {
263
244
  payload.body = payload.body.map(({ __serialized, ...node }) => {
264
245
  if (node.__newrelic) return node
@@ -268,7 +249,6 @@ export class Aggregate extends AggregateBase {
268
249
  return output
269
250
  })
270
251
  len = stringify(payload.body).length
271
- this.scheduler.opts.gzip = false
272
252
  }
273
253
 
274
254
  if (len > MAX_PAYLOAD_SIZE) {
@@ -278,8 +258,8 @@ export class Aggregate extends AggregateBase {
278
258
  // TODO -- Gracefully handle the buffer for retries.
279
259
  if (!this.agentRef.runtime.session.state.sessionReplaySentFirstChunk) this.syncWithSessionManager({ sessionReplaySentFirstChunk: true })
280
260
  this.recorder.clearBuffer()
281
- if (recorderEvents.type === 'preloaded') this.scheduler.runHarvest(opts)
282
- return [payload]
261
+ if (recorderEvents.type === 'preloaded') this.agentRef.runtime.harvester.triggerHarvestFor(this)
262
+ return [{ targetApp: undefined, payload }] // SR doesn't need a targetApp as it only works for the main, but format needs to make AggregateBase
283
263
  }
284
264
 
285
265
  getCorrectedTimestamp (node) {
@@ -366,8 +346,6 @@ export class Aggregate extends AggregateBase {
366
346
  if (result.status === 429) {
367
347
  this.abort(ABORT_REASONS.TOO_MANY)
368
348
  }
369
-
370
- if (this.blocked) this.scheduler.stopTimer(true)
371
349
  }
372
350
 
373
351
  /**
@@ -376,7 +354,7 @@ export class Aggregate extends AggregateBase {
376
354
  * the stopRecording API.
377
355
  */
378
356
  forceStop (forceHarvest) {
379
- if (forceHarvest) this.scheduler.runHarvest()
357
+ if (forceHarvest) this.agentRef.runtime.harvester.triggerHarvestFor(this)
380
358
  this.mode = MODE.OFF
381
359
  this.recorder?.stopRecording?.()
382
360
  this.syncWithSessionManager({ sessionReplayMode: this.mode })
@@ -391,7 +369,6 @@ export class Aggregate extends AggregateBase {
391
369
  this.recorder?.stopRecording?.()
392
370
  this.syncWithSessionManager({ sessionReplayMode: this.mode })
393
371
  this.recorder?.clearTimestamps?.()
394
- this.ee.emit('REPLAY_ABORTED')
395
372
  while (this.recorder?.getEvents().events.length) this.recorder?.clearBuffer?.()
396
373
  }
397
374
 
@@ -77,7 +77,7 @@ export class Instrument extends InstrumentBase {
77
77
 
78
78
  // If startReplay() has been used by this point, we must record in full mode regardless of session preload:
79
79
  // Note: recorder starts here with w/e the mode is at this time, but this may be changed later (see #apiStartOrRestartReplay else-case)
80
- this.recorder ??= new Recorder({ mode: this.#mode, agentIdentifier: this.agentIdentifier, trigger, ee: this.ee })
80
+ this.recorder ??= new Recorder({ mode: this.#mode, agentIdentifier: this.agentIdentifier, trigger, ee: this.ee, agentRef: this.#agentRef })
81
81
  this.recorder.startRecording()
82
82
  this.abortHandler = this.recorder.stopRecording
83
83
  } catch (e) {}