@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
@@ -10,11 +10,10 @@ import { navTimingValues as navTiming } from '../../../common/timing/nav-timing'
10
10
  import { generateUuid } from '../../../common/ids/unique-id'
11
11
  import { Interaction } from './interaction'
12
12
  import { eventListenerOpts } from '../../../common/event-listener/event-listener-opts'
13
- import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler'
14
13
  import { Serializer } from './serializer'
15
14
  import { ee } from '../../../common/event-emitter/contextual-ee'
16
15
  import * as CONSTANTS from '../constants'
17
- import { FEATURE_NAMES, FEATURE_TO_ENDPOINT } from '../../../loaders/features/features'
16
+ import { FEATURE_NAMES } from '../../../loaders/features/features'
18
17
  import { AggregateBase } from '../../utils/aggregate-base'
19
18
  import { firstContentfulPaint } from '../../../common/vitals/first-contentful-paint'
20
19
  import { firstPaint } from '../../../common/vitals/first-paint'
@@ -46,14 +45,12 @@ export class Aggregate extends AggregateBase {
46
45
  pageLoaded: false,
47
46
  childTime: 0,
48
47
  depth: 0,
49
- harvestTimeSeconds: agentRef.init.spa.harvestTimeSeconds || 10,
50
48
  // The below feature flag is used to disable the SPA ajax fix for specific customers, see https://new-relic.atlassian.net/browse/NR-172169
51
49
  disableSpaFix: (agentRef.init.feature_flags || []).indexOf('disable-spa-fix') > -1
52
50
  }
53
51
  this.spaSerializerClass = new Serializer(this)
54
52
 
55
53
  const classThis = this
56
- let scheduler
57
54
 
58
55
  const baseEE = ee.get(agentRef.agentIdentifier) // <-- parent baseEE
59
56
  const mutationEE = baseEE.get('mutation')
@@ -98,13 +95,10 @@ export class Aggregate extends AggregateBase {
98
95
  // | click ending: | 65 | 50 | | | |
99
96
  // click fn-end | 70 | 0 | 0 | 70 | 20 |
100
97
 
98
+ let harvester
101
99
  this.waitForFlags((['spa'])).then(([spaFlag]) => {
102
100
  if (spaFlag) {
103
- scheduler = new HarvestScheduler(FEATURE_TO_ENDPOINT[this.featureName], {
104
- onFinished: (result) => this.postHarvestCleanup(result.sent && result.retry),
105
- getPayload: (options) => this.makeHarvestPayload(options.retry),
106
- retryDelay: state.harvestTimeSeconds
107
- }, this)
101
+ harvester = agentRef.runtime.harvester // since this is after RUM call, PVE would've initialized harvester by now
108
102
  this.drain()
109
103
  } else {
110
104
  this.blocked = true
@@ -725,8 +719,11 @@ export class Aggregate extends AggregateBase {
725
719
  else smCategory = 'Custom'
726
720
  handle(SUPPORTABILITY_METRIC_CHANNEL, [`Spa/Interaction/${smCategory}/Duration/Ms`, Math.max((interaction.root?.end || 0) - (interaction.root?.start || 0), 0)], undefined, FEATURE_NAMES.metrics, baseEE)
727
721
 
728
- scheduler?.scheduleHarvest(0)
729
- if (!scheduler) warn(19)
722
+ if (!harvester) {
723
+ warn(19)
724
+ return
725
+ }
726
+ harvester.triggerHarvestFor(classThis)
730
727
  }
731
728
  }
732
729
 
@@ -5,19 +5,34 @@ import { gosCDN } from '../../common/window/nreum'
5
5
  import { drain } from '../../common/drain/drain'
6
6
  import { activatedFeatures } from '../../common/util/feature-flags'
7
7
  import { Obfuscator } from '../../common/util/obfuscate'
8
- import { EventBuffer } from './event-buffer'
9
8
  import { FEATURE_NAMES } from '../../loaders/features/features'
9
+ import { EventStoreManager } from './event-store-manager'
10
+ import { Harvester } from '../../common/harvest/harvester'
10
11
 
11
12
  export class AggregateBase extends FeatureBase {
12
13
  constructor (agentRef, featureName) {
13
14
  super(agentRef.agentIdentifier, featureName)
14
15
  this.agentRef = agentRef
15
- // Jserror and Metric features uses a singleton EventAggregator instead of a regular EventBuffer.
16
- if ([FEATURE_NAMES.jserrors, FEATURE_NAMES.metrics].includes(this.featureName)) this.events = agentRef.sharedAggregator
17
- // PVE has no need for eventBuffer, and SessionTrace has its own storage mechanism.
18
- else if (![FEATURE_NAMES.pageViewEvent, FEATURE_NAMES.sessionTrace].includes(this.featureName)) this.events = new EventBuffer()
19
16
  this.checkConfiguration(agentRef)
20
- this.obfuscator = agentRef.runtime.obfuscator
17
+ this.doOnceForAllAggregate(agentRef)
18
+
19
+ // This switch needs to be after doOnceForAllAggregate which may new sharedAggregator and reset mainAppKey.
20
+ switch (this.featureName) {
21
+ // PVE has no need for eventBuffer, and SessionTrace + Replay have their own storage mechanisms.
22
+ case FEATURE_NAMES.pageViewEvent:
23
+ case FEATURE_NAMES.sessionTrace:
24
+ case FEATURE_NAMES.sessionReplay:
25
+ break
26
+ // Jserror and Metric features uses a singleton EventAggregator instead of a regular EventBuffer.
27
+ case FEATURE_NAMES.jserrors:
28
+ case FEATURE_NAMES.metrics:
29
+ this.events = agentRef.sharedAggregator
30
+ break
31
+ default:
32
+ this.events = new EventStoreManager(agentRef.mainAppKey, 1)
33
+ break
34
+ }
35
+ this.harvestOpts = {} // features aggregate classes can define custom opts for when their harvest is called
21
36
  }
22
37
 
23
38
  /**
@@ -56,34 +71,40 @@ export class AggregateBase extends FeatureBase {
56
71
  /**
57
72
  * Return harvest payload. A "serializer" function can be defined on a derived class to format the payload.
58
73
  * @param {Boolean} shouldRetryOnFail - harvester flag to backup payload for retry later if harvest request fails; this should be moved to harvester logic
59
- * @returns final payload, or undefined if there are no pending events
74
+ * @param {object|undefined} target - the target app passed onto the event store manager to determine which app's data to return; if none provided, all apps data will be returned
75
+ * @returns {Array} Final payload tagged with their targeting browser app. The value of `payload` can be undefined if there are no pending events for an app. This should be a minimum length of 1.
60
76
  */
61
- makeHarvestPayload (shouldRetryOnFail = false, opts = {}) {
62
- if (this.events.isEmpty(opts)) return
77
+ makeHarvestPayload (shouldRetryOnFail = false, target) {
78
+ if (this.events.isEmpty(this.harvestOpts, target)) return
63
79
  // Other conditions and things to do when preparing harvest that is required.
64
80
  if (this.preHarvestChecks && !this.preHarvestChecks()) return
65
81
 
66
- if (shouldRetryOnFail) this.events.save(opts)
67
- const returnedData = this.events.get(opts)
68
- // A serializer or formatter assists in creating the payload `body` from stored events on harvest when defined by derived feature class.
69
- const body = this.serializer ? this.serializer(returnedData) : returnedData
70
- this.events.clear(opts)
82
+ if (shouldRetryOnFail) this.events.save(this.harvestOpts, target)
83
+ const returnedDataArr = this.events.get(this.harvestOpts, target)
84
+ if (!returnedDataArr.length) throw new Error('Unexpected problem encountered. There should be at least one app for harvest!')
85
+ this.events.clear(this.harvestOpts, target)
71
86
 
72
- const payload = {
73
- body
74
- }
75
- // Constructs the payload `qs` for relevant features on harvest.
76
- if (this.queryStringsBuilder) payload.qs = this.queryStringsBuilder(returnedData)
77
- return payload
87
+ return returnedDataArr.map(({ targetApp, data }) => {
88
+ // A serializer or formatter assists in creating the payload `body` from stored events on harvest when defined by derived feature class.
89
+ const body = this.serializer ? this.serializer(data) : data
90
+ const payload = {
91
+ body
92
+ }
93
+ // Constructs the payload `qs` for relevant features on harvest.
94
+ if (this.queryStringsBuilder) payload.qs = this.queryStringsBuilder(data)
95
+
96
+ return { targetApp, payload }
97
+ })
78
98
  }
79
99
 
80
100
  /**
81
101
  * Cleanup task after a harvest.
82
- * @param {Boolean} harvestFailed - harvester flag to restore events in main buffer for retry later if request failed
102
+ * @param {object} result - the cbResult object from the harvester's send method
83
103
  */
84
- postHarvestCleanup (harvestFailed = false, opts = {}) {
85
- if (harvestFailed) this.events.reloadSave(opts)
86
- this.events.clearSave(opts)
104
+ postHarvestCleanup (result = {}) {
105
+ const harvestFailed = result.sent && result.retry
106
+ if (harvestFailed) this.events.reloadSave(this.harvestOpts, result.targetApp)
107
+ this.events.clearSave(this.harvestOpts, result.targetApp)
87
108
  }
88
109
 
89
110
  /**
@@ -112,9 +133,20 @@ export class AggregateBase extends FeatureBase {
112
133
  runtime: existingAgent.runtime
113
134
  })
114
135
  }
136
+ }
115
137
 
116
- if (!existingAgent.runtime.obfuscator) {
117
- existingAgent.runtime.obfuscator = new Obfuscator(this.agentIdentifier)
118
- }
138
+ /**
139
+ * These are actions related to shared resources that should be initialized once by whichever feature Aggregate subclass loads first.
140
+ * This method should run after checkConfiguration, which may reset the agent's info/runtime object that is used here.
141
+ */
142
+ doOnceForAllAggregate (agentRef) {
143
+ if (!agentRef.runtime.obfuscator) agentRef.runtime.obfuscator = new Obfuscator(this.agentIdentifier)
144
+ this.obfuscator = agentRef.runtime.obfuscator
145
+
146
+ if (!agentRef.mainAppKey) agentRef.mainAppKey = { licenseKey: agentRef.info.licenseKey, appId: agentRef.info.applicationID }
147
+ // Create a single Aggregator for this agent if DNE yet; to be used by jserror endpoint features.
148
+ if (!agentRef.sharedAggregator) agentRef.sharedAggregator = new EventStoreManager(agentRef.mainAppKey, 2)
149
+
150
+ if (!agentRef.runtime.harvester) agentRef.runtime.harvester = new Harvester(agentRef)
119
151
  }
120
152
  }
@@ -53,7 +53,6 @@ export class EventBuffer {
53
53
 
54
54
  /**
55
55
  * Backup the buffered data and clear the main buffer
56
- * @returns {Array} the events being backed up
57
56
  */
58
57
  save () {
59
58
  this.#bufferBackup = this.#buffer
@@ -0,0 +1,101 @@
1
+ import { EventAggregator } from '../../common/aggregate/event-aggregator'
2
+ import { EventBuffer } from './event-buffer'
3
+
4
+ /**
5
+ * This layer allows multiple browser entity apps, or "target", to each have their own segregated storage instance.
6
+ * The purpose is so the harvester can send data to different apps within the same agent. Each feature should have a manager if it needs this capability.
7
+ */
8
+ export class EventStoreManager {
9
+ /**
10
+ * @param {object} defaultTarget - should contain licenseKey and appId of the main app from NREUM.info at startup
11
+ * @param {1|2} storageChoice - the type of storage to use in this manager; 'EventBuffer' (1), 'EventAggregator' (2)
12
+ */
13
+ constructor (defaultTarget, storageChoice) {
14
+ this.mainApp = defaultTarget
15
+ this.StorageClass = storageChoice === 1 ? EventBuffer : EventAggregator
16
+ this.appStorageMap = new Map()
17
+ this.appStorageMap.set(defaultTarget, new this.StorageClass())
18
+ }
19
+
20
+ // This class must contain an union of all methods from all supported storage classes and conceptualize away the target app argument.
21
+
22
+ /**
23
+ * @param {object} optsIfPresent - exists if called during harvest interval, @see AggregateBase.makeHarvestPayload
24
+ * @param {object} target - specific app's storage to check; if not provided, this method takes into account all apps recorded by this manager
25
+ * @returns {boolean} True if the target's storage is empty, or target does not exist in map (defaults to all storages)
26
+ */
27
+ isEmpty (optsIfPresent, target) {
28
+ if (target) {
29
+ if (!this.appStorageMap.has(target)) return true
30
+ else return this.appStorageMap.get(target).isEmpty(optsIfPresent)
31
+ }
32
+ for (const eventStore of this.appStorageMap.values()) {
33
+ if (!eventStore.isEmpty(optsIfPresent)) return false
34
+ }
35
+ return true
36
+ }
37
+
38
+ /**
39
+ * @param {string} event - the event element to store
40
+ * @param {object} target - the app to store event under; if not provided, this method adds to the main app from NREUM.info
41
+ * @returns {boolean} True if the event was successfully added
42
+ */
43
+ add (event, target) {
44
+ if (target && !this.appStorageMap.has(target)) this.appStorageMap.set(target, new this.StorageClass())
45
+ return this.appStorageMap.get(target || this.mainApp).add(event)
46
+ }
47
+
48
+ /** This is only used by the Metrics feature which has no need to add metric under a different app atm. */
49
+ addMetric (type, name, params, value) {
50
+ return this.appStorageMap.get(this.mainApp).addMetric(type, name, params, value)
51
+ }
52
+
53
+ /**
54
+ * @param {object} optsIfPresent - exists if called during harvest interval, @see AggregateBase.makeHarvestPayload
55
+ * @param {object} target - specific app to fetch; if not provided, this method fetches from all apps
56
+ * @returns {Array} Objects of `data` labeled with their respective `target` app to be sent to
57
+ */
58
+ get (optsIfPresent, target) {
59
+ if (target) return [{ targetApp: target, data: this.appStorageMap.get(target)?.get(optsIfPresent) }]
60
+ const allPayloads = []
61
+ this.appStorageMap.forEach((eventStore, recordedTarget) => {
62
+ allPayloads.push({ targetApp: recordedTarget, data: eventStore.get(optsIfPresent) })
63
+ })
64
+ return allPayloads
65
+ }
66
+
67
+ byteSize (target) {
68
+ return this.appStorageMap.get(target || this.mainApp).byteSize()
69
+ }
70
+
71
+ wouldExceedMaxSize (incomingSize, target) {
72
+ return this.appStorageMap.get(target || this.mainApp).wouldExceedMaxSize(incomingSize)
73
+ }
74
+
75
+ save (optsIfPresent, target) {
76
+ if (target) return this.appStorageMap.get(target)?.save(optsIfPresent)
77
+ this.appStorageMap.forEach((eventStore) => eventStore.save(optsIfPresent))
78
+ }
79
+
80
+ clear (optsIfPresent, target) {
81
+ if (target) return this.appStorageMap.get(target)?.clear(optsIfPresent)
82
+ this.appStorageMap.forEach((eventStore) => eventStore.clear(optsIfPresent))
83
+ }
84
+
85
+ // Unlike the methods above, the following will have a target as they are called by AggregateBase.postHarvestCleanup callback on harvest finish after getting & sending the data.
86
+ reloadSave (optsIfPresent, target) {
87
+ if (!target) { // -- remove this block once the old harvest.js & harvest-schedule.js are deleted!
88
+ this.appStorageMap.forEach((eventStore) => eventStore.reloadSave(optsIfPresent))
89
+ return
90
+ }
91
+ return this.appStorageMap.get(target)?.reloadSave(optsIfPresent)
92
+ }
93
+
94
+ clearSave (optsIfPresent, target) {
95
+ if (!target) { // -- remove this block once the old harvest.js & harvest-schedule.js are deleted!
96
+ this.appStorageMap.forEach((eventStore) => eventStore.clearSave(optsIfPresent))
97
+ return
98
+ }
99
+ return this.appStorageMap.get(target)?.clearSave(optsIfPresent)
100
+ }
101
+ }
@@ -43,7 +43,6 @@ export class InstrumentBase extends FeatureBase {
43
43
  /**
44
44
  * @type {Promise} Assigned immediately after @see importAggregator runs. Serves as a signal for when the inner async fn finishes execution. Useful for features to await
45
45
  * one another if there are inter-features dependencies.
46
- * TODO: This is only used for the SPA feature component tests and should be refactored out.
47
46
  */
48
47
  this.onAggregateImported = undefined
49
48
 
@@ -95,13 +94,6 @@ export class InstrumentBase extends FeatureBase {
95
94
  * it's only responsible for aborting its one specific feature, rather than all.
96
95
  */
97
96
  try {
98
- // Create a single Aggregator for this agent if DNE yet; to be used by jserror endpoint features.
99
- if (!agentRef.sharedAggregator) {
100
- agentRef.sharedAggregator = import(/* webpackChunkName: "shared-aggregator" */ '../../common/aggregate/event-aggregator')
101
- const { EventAggregator } = await agentRef.sharedAggregator
102
- agentRef.sharedAggregator = new EventAggregator()
103
- } else await agentRef.sharedAggregator // if another feature is already importing the aggregator, wait for it to finish
104
-
105
97
  if (!this.#shouldImportAgg(this.featureName, session)) {
106
98
  drain(this.agentIdentifier, this.featureName)
107
99
  loadedSuccessfully(false) // aggregate module isn't loaded at all
@@ -110,6 +102,8 @@ export class InstrumentBase extends FeatureBase {
110
102
  const { lazyFeatureLoader } = await import(/* webpackChunkName: "lazy-feature-loader" */ './lazy-feature-loader')
111
103
  const { Aggregate } = await lazyFeatureLoader(this.featureName, 'aggregate')
112
104
  this.featAggregate = new Aggregate(agentRef, argsObjFromInstrument)
105
+
106
+ agentRef.runtime.harvester.initializedAggregates.push(this.featAggregate) // "subscribe" the feature to future harvest intervals (PVE will start the timer)
113
107
  loadedSuccessfully(true)
114
108
  } catch (e) {
115
109
  warn(34, e)
@@ -1,7 +1,7 @@
1
1
  import { SR_EVENT_EMITTER_TYPES } from '../../features/session_replay/constants'
2
2
 
3
3
  export const apiMethods = [
4
- 'setErrorHandler', 'finished', 'addToTrace', 'addRelease',
4
+ 'setErrorHandler', 'finished', 'addToTrace', 'addRelease', 'recordCustomEvent',
5
5
  'addPageAction', 'setCurrentRouteName', 'setPageViewName', 'setCustomAttribute',
6
6
  'interaction', 'noticeError', 'setUserId', 'setApplicationVersion', 'start',
7
7
  SR_EVENT_EMITTER_TYPES.RECORD, SR_EVENT_EMITTER_TYPES.PAUSE, 'log', 'wrapLogger'
@@ -72,6 +72,8 @@ export function setAPI (agentIdentifier, forceDrain, runSoftNavOverSpa = false)
72
72
 
73
73
  apiInterface.addPageAction = apiCall(prefix, 'addPageAction', true, FEATURE_NAMES.genericEvents)
74
74
 
75
+ apiInterface.recordCustomEvent = apiCall(prefix, 'recordCustomEvent', true, FEATURE_NAMES.genericEvents)
76
+
75
77
  apiInterface.setPageViewName = function (name, host) {
76
78
  if (typeof name !== 'string') return
77
79
  if (name.charAt(0) !== '/') name = '/' + name
@@ -192,7 +194,7 @@ export function setAPI (agentIdentifier, forceDrain, runSoftNavOverSpa = false)
192
194
  function apiCall (prefix, name, notSpa, bufferGroup) {
193
195
  return function () {
194
196
  handle(SUPPORTABILITY_METRIC_CHANNEL, ['API/' + name + '/called'], undefined, FEATURE_NAMES.metrics, instanceEE)
195
- if (bufferGroup) handle(prefix + name, [now(), ...arguments], notSpa ? null : this, bufferGroup, instanceEE) // no bufferGroup means only the SM is emitted
197
+ if (bufferGroup) handle(prefix + name, [notSpa ? now() : performance.now(), ...arguments], notSpa ? null : this, bufferGroup, instanceEE) // no bufferGroup means only the SM is emitted
196
198
  return notSpa ? undefined : this // returns the InteractionHandle which allows these methods to be chained
197
199
  }
198
200
  }
@@ -1,7 +1,13 @@
1
+ // To reduce build size a bit:
2
+ export const EVENTS = 'events'
3
+ export const JSERRORS = 'jserrors'
4
+ const BLOBS = 'browser/blobs'
5
+ export const RUM = 'rum'
6
+
1
7
  export const FEATURE_NAMES = {
2
8
  ajax: 'ajax',
3
9
  genericEvents: 'generic_events',
4
- jserrors: 'jserrors',
10
+ jserrors: JSERRORS,
5
11
  logging: 'logging',
6
12
  metrics: 'metrics',
7
13
  /**
@@ -35,14 +41,15 @@ export const featurePriority = {
35
41
  }
36
42
 
37
43
  export const FEATURE_TO_ENDPOINT = {
38
- [FEATURE_NAMES.pageViewTiming]: 'events',
39
- [FEATURE_NAMES.ajax]: 'events',
40
- [FEATURE_NAMES.spa]: 'events',
41
- [FEATURE_NAMES.softNav]: 'events',
42
- [FEATURE_NAMES.metrics]: 'jserrors',
43
- [FEATURE_NAMES.jserrors]: 'jserrors',
44
- [FEATURE_NAMES.sessionTrace]: 'browser/blobs',
45
- [FEATURE_NAMES.sessionReplay]: 'browser/blobs',
44
+ [FEATURE_NAMES.pageViewEvent]: RUM,
45
+ [FEATURE_NAMES.pageViewTiming]: EVENTS,
46
+ [FEATURE_NAMES.ajax]: EVENTS,
47
+ [FEATURE_NAMES.spa]: EVENTS,
48
+ [FEATURE_NAMES.softNav]: EVENTS,
49
+ [FEATURE_NAMES.metrics]: JSERRORS,
50
+ [FEATURE_NAMES.jserrors]: JSERRORS,
51
+ [FEATURE_NAMES.sessionTrace]: BLOBS,
52
+ [FEATURE_NAMES.sessionReplay]: BLOBS,
46
53
  [FEATURE_NAMES.logging]: 'browser/logs',
47
54
  [FEATURE_NAMES.genericEvents]: 'ins'
48
55
  }
@@ -30,6 +30,16 @@ export class MicroAgentBase {
30
30
  return this.#callMethod('addPageAction', name, attributes)
31
31
  }
32
32
 
33
+ /**
34
+ * Records a custom event with a specified eventType and attributes.
35
+ * {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/recordCustomEvent/}
36
+ * @param {string} eventType The eventType to store the event as.
37
+ * @param {object} [attributes] JSON object with one or more key/value pairs. For example: {key:"value"}.
38
+ */
39
+ recordCustomEvent (eventType, attributes) {
40
+ return this.#callMethod('recordCustomEvent', eventType, attributes)
41
+ }
42
+
33
43
  /**
34
44
  * Groups page views to help URL structure or to capture the URL's routing information.
35
45
  * {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/setpageviewname/}
@@ -64,6 +64,7 @@ export class MicroAgent extends MicroAgentBase {
64
64
  return lazyFeatureLoader(f, 'aggregate')
65
65
  }).then(({ Aggregate }) => {
66
66
  this.features[f] = new Aggregate(this)
67
+ this.runtime.harvester.initializedAggregates.push(this.features[f]) // so that harvester will poll this feature agg on interval
67
68
  }).catch(err => warn(25, err))
68
69
  }
69
70
  })
@@ -1,168 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.HarvestScheduler = void 0;
7
- var submitData = _interopRequireWildcard(require("../util/submit-data"));
8
- var _sharedContext = require("../context/shared-context");
9
- var _harvest = require("./harvest");
10
- var _eol = require("../unload/eol");
11
- var _constants = require("../session/constants");
12
- function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
13
- function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
14
- /*
15
- * Copyright 2020 New Relic Corporation. All rights reserved.
16
- * SPDX-License-Identifier: Apache-2.0
17
- */
18
-
19
- /**
20
- * Periodically invokes harvest calls and handles retries
21
- */
22
- class HarvestScheduler extends _sharedContext.SharedContext {
23
- /**
24
- * Create a HarvestScheduler
25
- * @param {string} endpoint - The base BAM endpoint name -- ex. 'events'
26
- * @param {object} opts - The options used to configure the HarvestScheduler
27
- * @param {Function} opts.onFinished - The callback to be fired when a harvest has finished
28
- * @param {Function} opts.getPayload - A callback which can be triggered to return a payload for harvesting
29
- * @param {number} opts.retryDelay - The number of seconds to wait before retrying after a network failure
30
- * @param {boolean} opts.raw - Use a prefabricated payload shape as the harvest payload without the need for formatting
31
- * @param {string} opts.customUrl - A custom url that falls outside of the shape of the standard BAM harvester url pattern. Will use directly instead of concatenating various pieces
32
- * @param {*} parent - The parent object, whose state can be passed into SharedContext
33
- */
34
- constructor(endpoint, opts, parent) {
35
- super(parent); // gets any allowed properties from the parent and stores them in `sharedContext`
36
- this.endpoint = endpoint;
37
- this.opts = opts || {};
38
- this.started = false;
39
- this.timeoutHandle = null;
40
- this.aborted = false; // this controls the per-interval and final harvests for the scheduler (currently per feature specific!)
41
- this.harvesting = false;
42
- this.harvest = new _harvest.Harvest(this.sharedContext);
43
-
44
- // If a feature specifies stuff to be done on page unload, those are frontrunned (via capture phase) before ANY feature final harvests.
45
- if (typeof this.opts.onUnload === 'function') (0, _eol.subscribeToEOL)(this.opts.onUnload, true);
46
- (0, _eol.subscribeToEOL)(this.unload.bind(this)); // this should consist only of sending final harvest
47
-
48
- /* Flush all buffered data if session resets and give up retries. This should be synchronous to ensure that the correct `session` value is sent.
49
- 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
50
- the wrong session ID. */
51
- this.sharedContext?.ee.on(_constants.SESSION_EVENTS.RESET, () => this.runHarvest({
52
- forceNoRetry: true
53
- }));
54
- }
55
-
56
- /**
57
- * This function is only meant for the last outgoing harvest cycle of a page. It trickles down to using sendBeacon, which should not be used
58
- * to send payloads while the page is still active, due to limitations on how much data can be buffered in the API at any one time.
59
- */
60
- unload() {
61
- if (this.aborted) return;
62
- this.runHarvest({
63
- unload: true
64
- });
65
- }
66
- startTimer(interval, initialDelay) {
67
- this.interval = interval;
68
- this.started = true;
69
- this.scheduleHarvest(initialDelay != null ? initialDelay : this.interval);
70
- }
71
- stopTimer(permanently = false) {
72
- this.aborted = permanently; // stopping permanently is same as aborting, but this function also cleans up the setTimeout loop
73
- this.started = false;
74
- if (this.timeoutHandle) {
75
- clearTimeout(this.timeoutHandle);
76
- }
77
- }
78
- scheduleHarvest(delay, opts) {
79
- if (this.timeoutHandle) return;
80
- if (delay == null) {
81
- delay = this.interval;
82
- }
83
- this.timeoutHandle = setTimeout(() => {
84
- this.timeoutHandle = null;
85
- this.runHarvest(opts);
86
- }, delay * 1000);
87
- }
88
- runHarvest(opts) {
89
- if (this.aborted) return;
90
- this.harvesting = true;
91
-
92
- /**
93
- * 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.
94
- * @param {Object} result
95
- */
96
- const cbRanAfterSend = result => {
97
- this.harvesting = false;
98
- if (opts?.forceNoRetry) result.retry = false; // discard unsent data rather than re-queuing for next harvest attempt
99
- this.onHarvestFinished(opts, result);
100
- };
101
- let harvests = [];
102
- let submitMethod;
103
- let payload;
104
- if (this.opts.getPayload) {
105
- // Ajax, PVT, Softnav, Logging, SR & ST features provide a single callback function to get data for harvesting
106
- submitMethod = submitData.getSubmitMethod({
107
- isFinalHarvest: opts?.unload
108
- });
109
- if (!submitMethod) return false;
110
- const retry = !opts?.unload && submitMethod === submitData.xhr;
111
- payload = this.opts.getPayload({
112
- retry,
113
- ...opts
114
- });
115
- if (!payload) {
116
- if (this.started) {
117
- this.scheduleHarvest();
118
- }
119
- return;
120
- }
121
- payload = Object.prototype.toString.call(payload) === '[object Array]' ? payload : [payload];
122
- harvests.push(...payload);
123
- }
124
-
125
- /** sendX is used for features that do not supply a preformatted payload via "getPayload" */
126
- let send = args => this.harvest.sendX(args);
127
- if (harvests.length) {
128
- /** _send is the underlying method for sending in the harvest, if sending raw we can bypass the other helpers completely which format the payloads */
129
- if (this.opts.raw) send = args => this.harvest._send(args);
130
- /** send is used to formated the payloads from "getPayload" and obfuscate before sending */else send = args => this.harvest.send(args);
131
- } else {
132
- // force it to run at least once in sendX mode
133
- harvests.push(undefined);
134
- }
135
- harvests.forEach(payload => {
136
- send({
137
- endpoint: this.endpoint,
138
- payload,
139
- opts,
140
- submitMethod,
141
- cbFinished: cbRanAfterSend,
142
- customUrl: this.opts.customUrl,
143
- raw: this.opts.raw
144
- });
145
- });
146
- if (this.started) {
147
- this.scheduleHarvest();
148
- }
149
- }
150
- onHarvestFinished(opts, result) {
151
- if (this.opts.onFinished) {
152
- this.opts.onFinished(result);
153
- }
154
- if (result.sent && result.retry) {
155
- const delay = result.delay || this.opts.retryDelay;
156
- // reschedule next harvest if should be delayed longer
157
- if (this.started && delay) {
158
- clearTimeout(this.timeoutHandle);
159
- this.timeoutHandle = null;
160
- this.scheduleHarvest(delay, opts);
161
- } else if (!this.started && delay) {
162
- // if not running on a timer, schedule a single retry
163
- this.scheduleHarvest(delay, opts);
164
- }
165
- }
166
- }
167
- }
168
- exports.HarvestScheduler = HarvestScheduler;