@newrelic/browser-agent 1.301.0 → 1.302.0-alpha.1

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 (155) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/cjs/common/config/init-types.js +1 -1
  3. package/dist/cjs/common/config/init.js +7 -1
  4. package/dist/cjs/common/config/runtime.js +1 -1
  5. package/dist/cjs/common/constants/agent-constants.js +11 -2
  6. package/dist/cjs/common/constants/env.cdn.js +1 -1
  7. package/dist/cjs/common/constants/env.npm.js +1 -1
  8. package/dist/cjs/common/drain/drain.js +1 -1
  9. package/dist/cjs/common/harvest/harvester.js +30 -39
  10. package/dist/cjs/common/util/mfe.js +45 -0
  11. package/dist/cjs/common/wrap/wrap-promise.js +10 -5
  12. package/dist/cjs/features/ajax/aggregate/index.js +5 -1
  13. package/dist/cjs/features/generic_events/aggregate/index.js +9 -8
  14. package/dist/cjs/features/generic_events/constants.js +3 -1
  15. package/dist/cjs/features/generic_events/instrument/index.js +38 -32
  16. package/dist/cjs/features/jserrors/aggregate/index.js +18 -17
  17. package/dist/cjs/features/logging/aggregate/index.js +19 -15
  18. package/dist/cjs/features/logging/shared/utils.js +3 -3
  19. package/dist/cjs/features/page_view_event/aggregate/index.js +51 -33
  20. package/dist/cjs/features/session_replay/aggregate/index.js +16 -15
  21. package/dist/cjs/features/session_replay/constants.js +2 -6
  22. package/dist/cjs/features/session_replay/instrument/index.js +3 -2
  23. package/dist/cjs/features/session_replay/shared/recorder.js +3 -2
  24. package/dist/cjs/features/session_trace/aggregate/index.js +0 -2
  25. package/dist/cjs/features/soft_navigations/aggregate/index.js +1 -2
  26. package/dist/cjs/features/utils/aggregate-base.js +45 -47
  27. package/dist/cjs/loaders/api/addPageAction.js +2 -2
  28. package/dist/cjs/loaders/api/log.js +2 -2
  29. package/dist/cjs/loaders/api/noticeError.js +2 -2
  30. package/dist/cjs/loaders/api/register-api-types.js +5 -5
  31. package/dist/cjs/loaders/api/register.js +80 -97
  32. package/dist/esm/common/config/init-types.js +1 -1
  33. package/dist/esm/common/config/init.js +7 -1
  34. package/dist/esm/common/config/runtime.js +1 -1
  35. package/dist/esm/common/constants/agent-constants.js +9 -1
  36. package/dist/esm/common/constants/env.cdn.js +1 -1
  37. package/dist/esm/common/constants/env.npm.js +1 -1
  38. package/dist/esm/common/drain/drain.js +1 -1
  39. package/dist/esm/common/harvest/harvester.js +30 -39
  40. package/dist/esm/common/util/mfe.js +38 -0
  41. package/dist/esm/common/wrap/wrap-promise.js +10 -5
  42. package/dist/esm/features/ajax/aggregate/index.js +5 -1
  43. package/dist/esm/features/generic_events/aggregate/index.js +9 -8
  44. package/dist/esm/features/generic_events/constants.js +3 -1
  45. package/dist/esm/features/generic_events/instrument/index.js +38 -32
  46. package/dist/esm/features/jserrors/aggregate/index.js +18 -17
  47. package/dist/esm/features/logging/aggregate/index.js +19 -15
  48. package/dist/esm/features/logging/shared/utils.js +3 -3
  49. package/dist/esm/features/page_view_event/aggregate/index.js +51 -33
  50. package/dist/esm/features/session_replay/aggregate/index.js +17 -16
  51. package/dist/esm/features/session_replay/constants.js +1 -5
  52. package/dist/esm/features/session_replay/instrument/index.js +4 -3
  53. package/dist/esm/features/session_replay/shared/recorder.js +3 -2
  54. package/dist/esm/features/session_trace/aggregate/index.js +0 -2
  55. package/dist/esm/features/soft_navigations/aggregate/index.js +1 -2
  56. package/dist/esm/features/utils/aggregate-base.js +46 -48
  57. package/dist/esm/loaders/api/addPageAction.js +2 -2
  58. package/dist/esm/loaders/api/log.js +2 -2
  59. package/dist/esm/loaders/api/noticeError.js +2 -2
  60. package/dist/esm/loaders/api/register-api-types.js +5 -5
  61. package/dist/esm/loaders/api/register.js +80 -97
  62. package/dist/tsconfig.tsbuildinfo +1 -1
  63. package/dist/types/common/config/init-types.d.ts +4 -1
  64. package/dist/types/common/config/init-types.d.ts.map +1 -1
  65. package/dist/types/common/config/init.d.ts.map +1 -1
  66. package/dist/types/common/constants/agent-constants.d.ts +7 -4
  67. package/dist/types/common/constants/agent-constants.d.ts.map +1 -1
  68. package/dist/types/common/harvest/harvester.d.ts +2 -2
  69. package/dist/types/common/harvest/harvester.d.ts.map +1 -1
  70. package/dist/types/common/util/mfe.d.ts +20 -0
  71. package/dist/types/common/util/mfe.d.ts.map +1 -0
  72. package/dist/types/common/wrap/wrap-promise.d.ts.map +1 -1
  73. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  74. package/dist/types/features/generic_events/aggregate/index.d.ts +2 -2
  75. package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
  76. package/dist/types/features/generic_events/constants.d.ts +1 -0
  77. package/dist/types/features/generic_events/instrument/index.d.ts.map +1 -1
  78. package/dist/types/features/jserrors/aggregate/index.d.ts +3 -3
  79. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  80. package/dist/types/features/logging/aggregate/index.d.ts +3 -3
  81. package/dist/types/features/logging/aggregate/index.d.ts.map +1 -1
  82. package/dist/types/features/logging/shared/utils.d.ts +2 -2
  83. package/dist/types/features/logging/shared/utils.d.ts.map +1 -1
  84. package/dist/types/features/page_view_event/aggregate/index.d.ts +2 -4
  85. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  86. package/dist/types/features/session_replay/aggregate/index.d.ts +13 -4
  87. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  88. package/dist/types/features/session_replay/constants.d.ts +1 -5
  89. package/dist/types/features/session_replay/constants.d.ts.map +1 -1
  90. package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -1
  91. package/dist/types/features/session_replay/shared/recorder.d.ts +1 -0
  92. package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
  93. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  94. package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -1
  95. package/dist/types/features/utils/aggregate-base.d.ts +13 -5
  96. package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
  97. package/dist/types/loaders/api/addPageAction.d.ts +1 -1
  98. package/dist/types/loaders/api/addPageAction.d.ts.map +1 -1
  99. package/dist/types/loaders/api/log.d.ts +1 -1
  100. package/dist/types/loaders/api/log.d.ts.map +1 -1
  101. package/dist/types/loaders/api/noticeError.d.ts +1 -1
  102. package/dist/types/loaders/api/noticeError.d.ts.map +1 -1
  103. package/dist/types/loaders/api/register-api-types.d.ts +4 -4
  104. package/dist/types/loaders/api/register-api-types.d.ts.map +1 -1
  105. package/dist/types/loaders/api/register.d.ts +8 -1
  106. package/dist/types/loaders/api/register.d.ts.map +1 -1
  107. package/package.json +3 -3
  108. package/src/common/config/init-types.js +1 -1
  109. package/src/common/config/init.js +3 -1
  110. package/src/common/config/runtime.js +1 -1
  111. package/src/common/constants/agent-constants.js +10 -0
  112. package/src/common/drain/drain.js +1 -1
  113. package/src/common/harvest/harvester.js +27 -32
  114. package/src/common/util/mfe.js +35 -0
  115. package/src/common/wrap/wrap-promise.js +16 -6
  116. package/src/features/ajax/aggregate/index.js +7 -1
  117. package/src/features/generic_events/aggregate/index.js +9 -8
  118. package/src/features/generic_events/constants.js +3 -1
  119. package/src/features/generic_events/instrument/index.js +44 -35
  120. package/src/features/jserrors/aggregate/index.js +17 -17
  121. package/src/features/logging/aggregate/index.js +20 -13
  122. package/src/features/logging/shared/utils.js +3 -3
  123. package/src/features/page_view_event/aggregate/index.js +53 -28
  124. package/src/features/session_replay/aggregate/index.js +16 -13
  125. package/src/features/session_replay/constants.js +1 -5
  126. package/src/features/session_replay/instrument/index.js +4 -3
  127. package/src/features/session_replay/shared/recorder.js +3 -2
  128. package/src/features/session_trace/aggregate/index.js +0 -2
  129. package/src/features/soft_navigations/aggregate/index.js +1 -2
  130. package/src/features/utils/aggregate-base.js +47 -42
  131. package/src/loaders/api/addPageAction.js +2 -2
  132. package/src/loaders/api/log.js +2 -2
  133. package/src/loaders/api/noticeError.js +2 -2
  134. package/src/loaders/api/register-api-types.js +5 -5
  135. package/src/loaders/api/register.js +62 -89
  136. package/dist/cjs/common/util/target.js +0 -34
  137. package/dist/cjs/features/utils/entity-manager.js +0 -46
  138. package/dist/cjs/features/utils/event-store-manager.js +0 -174
  139. package/dist/cjs/loaders/api/register-api.js +0 -165
  140. package/dist/esm/common/util/target.js +0 -27
  141. package/dist/esm/features/utils/entity-manager.js +0 -39
  142. package/dist/esm/features/utils/event-store-manager.js +0 -166
  143. package/dist/esm/loaders/api/register-api.js +0 -159
  144. package/dist/types/common/util/target.d.ts +0 -18
  145. package/dist/types/common/util/target.d.ts.map +0 -1
  146. package/dist/types/features/utils/entity-manager.d.ts +0 -11
  147. package/dist/types/features/utils/entity-manager.d.ts.map +0 -1
  148. package/dist/types/features/utils/event-store-manager.d.ts +0 -85
  149. package/dist/types/features/utils/event-store-manager.d.ts.map +0 -1
  150. package/dist/types/loaders/api/register-api.d.ts +0 -14
  151. package/dist/types/loaders/api/register-api.d.ts.map +0 -1
  152. package/src/common/util/target.js +0 -27
  153. package/src/features/utils/entity-manager.js +0 -45
  154. package/src/features/utils/event-store-manager.js +0 -165
  155. package/src/loaders/api/register-api.js +0 -152
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Copyright 2020-2025 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * @param {Object} [target] - the target to be validated
8
+ * @returns {boolean}
9
+ */
10
+ export function isValidMFETarget (target = {}) {
11
+ return !!(target.id && target.name)
12
+ }
13
+
14
+ /**
15
+ * When given a valid target, returns an object with the MFE payload attributes. Returns an empty object otherwise.
16
+ * @param {Object} [target] the registered target
17
+ * @param {AggregateInstance} [aggregateInstance] the aggregate instance calling the method
18
+ * @returns {{'mfe.id': *, 'mfe.name': String}|{}} returns an empty object if args are not supplied or the aggregate instance is not supporting version 2
19
+ */
20
+ export function getVersion2Attributes (target, aggregateInstance) {
21
+ if (aggregateInstance?.harvestEndpointVersion !== 2) return {}
22
+ const containerAgentEntityGuid = aggregateInstance.agentRef.runtime.appMetadata.agents[0].entityGuid
23
+ if (!isValidMFETarget(target)) {
24
+ return {
25
+ 'entity.guid': containerAgentEntityGuid,
26
+ appId: aggregateInstance.agentRef.info.applicationID
27
+ }
28
+ }
29
+ return {
30
+ 'mfe.id': target.id, // these field names may change as the schema is finalized
31
+ 'mfe.name': target.name, // these field names may change as the schema is finalized
32
+ eventSource: 'MicroFrontendBrowserAgent', // these field names may change as the schema is finalized
33
+ 'parent.id': containerAgentEntityGuid
34
+ }
35
+ }
@@ -140,13 +140,23 @@ export function wrapPromise (sharedEE) {
140
140
 
141
141
  promiseEE.on('propagate', function (val, overwrite, trigger) {
142
142
  if (!this.getCtx || overwrite) {
143
- this.getCtx = function () {
144
- // eslint-disable-next-line
145
- if (val instanceof Promise) {
146
- var store = promiseEE.context(val)
143
+ const selfStore = this
144
+ const parentStore =
145
+ val instanceof Promise ? promiseEE.context(val) : null
146
+ let cachedCtx
147
+
148
+ this.getCtx = function getCtx () {
149
+ if (cachedCtx) return cachedCtx
150
+
151
+ if (parentStore && parentStore !== selfStore) {
152
+ cachedCtx =
153
+ typeof parentStore.getCtx === 'function'
154
+ ? parentStore.getCtx()
155
+ : parentStore
156
+ } else {
157
+ cachedCtx = selfStore
147
158
  }
148
-
149
- return store && store.getCtx ? store.getCtx() : this
159
+ return cachedCtx
150
160
  }
151
161
  }
152
162
  })
@@ -137,10 +137,16 @@ export class Aggregate extends AggregateBase {
137
137
  const addString = getAddStringContext(this.agentRef.runtime.obfuscator)
138
138
  let payload = 'bel.7;'
139
139
 
140
+ let firstTimestamp = 0
141
+
140
142
  for (let i = 0; i < eventBuffer.length; i++) {
141
143
  const event = eventBuffer[i]
144
+ // ajax nodes are relative to the first node (or page origin if no previous node), so we need to calculate the relative start time
145
+ const relativeStartTime = event.startTime - firstTimestamp
146
+ if (i === 0) firstTimestamp = event.startTime
147
+
142
148
  const fields = [
143
- numeric(event.startTime),
149
+ numeric(relativeStartTime),
144
150
  numeric(event.endTime - event.startTime),
145
151
  numeric(0), // callbackEnd
146
152
  numeric(0), // no callbackDuration for non-SPA events
@@ -14,6 +14,7 @@ import { applyFnToProps } from '../../../common/util/traverse'
14
14
  import { UserActionsAggregator } from './user-actions/user-actions-aggregator'
15
15
  import { isIFrameWindow } from '../../../common/dom/iframe'
16
16
  import { isPureObject } from '../../../common/util/type-check'
17
+ import { getVersion2Attributes } from '../../../common/util/mfe'
17
18
 
18
19
  export class Aggregate extends AggregateBase {
19
20
  static featureName = FEATURE_NAME
@@ -42,8 +43,7 @@ export class Aggregate extends AggregateBase {
42
43
  }, this.featureName, this.ee)
43
44
 
44
45
  if (agentRef.init.page_action.enabled) {
45
- registerHandler('api-addPageAction', (timestamp, name, attributes, targetEntityGuid) => {
46
- if (!this.agentRef.runtime.entityManager.get(targetEntityGuid)) return warn(56, this.featureName)
46
+ registerHandler('api-addPageAction', (timestamp, name, attributes, target) => {
47
47
  this.addEvent({
48
48
  ...attributes,
49
49
  eventType: 'PageAction',
@@ -55,7 +55,7 @@ export class Aggregate extends AggregateBase {
55
55
  browserWidth: window.document.documentElement?.clientWidth,
56
56
  browserHeight: window.document.documentElement?.clientHeight
57
57
  })
58
- }, targetEntityGuid)
58
+ }, target)
59
59
  }, this.featureName, this.ee)
60
60
  }
61
61
 
@@ -242,7 +242,6 @@ export class Aggregate extends AggregateBase {
242
242
  this.addEvent(event)
243
243
  }, this.featureName, this.ee)
244
244
 
245
- agentRef.runtime.harvester.triggerHarvestFor(this)
246
245
  this.drain()
247
246
  })
248
247
  }
@@ -258,10 +257,10 @@ export class Aggregate extends AggregateBase {
258
257
  * * sessionTraceId: set by the `ptid=` query param
259
258
  * * userAgent*: set by the userAgent header
260
259
  * @param {object=} obj the event object for storing in the event buffer
261
- * @param {string=} targetEntityGuid the target entity guid for the event to scope buffering and harvesting. Defaults to agent config if undefined
260
+ * @param {string=} target the target metadata for the event to scope buffering and harvesting. Defaults to container agent config if undefined
262
261
  * @returns void
263
262
  */
264
- addEvent (obj = {}, targetEntityGuid) {
263
+ addEvent (obj = {}, target) {
265
264
  if (!obj || !Object.keys(obj).length) return
266
265
  if (!obj.eventType) {
267
266
  warn(44)
@@ -278,7 +277,9 @@ export class Aggregate extends AggregateBase {
278
277
  timestamp: Math.floor(this.agentRef.runtime.timeKeeper.correctRelativeTimestamp(now())),
279
278
  /** all generic events require pageUrl(s) */
280
279
  pageUrl: cleanURL('' + initialLocation),
281
- currentUrl: cleanURL('' + location)
280
+ currentUrl: cleanURL('' + location),
281
+ /** Specific attributes only supplied if harvesting to endpoint version 2 */
282
+ ...(getVersion2Attributes(target, this))
282
283
  }
283
284
 
284
285
  const eventAttributes = {
@@ -290,7 +291,7 @@ export class Aggregate extends AggregateBase {
290
291
  ...obj
291
292
  }
292
293
 
293
- this.events.add(eventAttributes, targetEntityGuid)
294
+ this.events.add(eventAttributes)
294
295
  }
295
296
 
296
297
  serializer (eventBuffer) {
@@ -17,5 +17,7 @@ export const FRUSTRATION_TIMEOUT_MS = 2000
17
17
  export const RESERVED_EVENT_TYPES = ['PageAction', 'UserAction', 'BrowserPerformance']
18
18
 
19
19
  export const FEATURE_FLAGS = {
20
- RESOURCES: 'experimental.resources'
20
+ RESOURCES: 'experimental.resources',
21
+ REGISTER: 'register'
22
+ // register.jserrors and register.generic_events are also used, but not referenced directly so no need to represent here
21
23
  }
@@ -41,6 +41,14 @@ export class Instrument extends InstrumentBase {
41
41
  setupRegisterAPI(agentRef)
42
42
  setupMeasureAPI(agentRef)
43
43
 
44
+ const ufEnabled = agentRef.init.feature_flags.includes('user_frustrations')
45
+ let historyEE
46
+ if (isBrowserScope && ufEnabled) {
47
+ wrapFetch(this.ee)
48
+ wrapXhr(this.ee)
49
+ historyEE = wrapHistory(this.ee)
50
+ }
51
+
44
52
  if (isBrowserScope) {
45
53
  if (agentRef.init.user_actions.enabled) {
46
54
  OBSERVED_EVENTS.forEach(eventType =>
@@ -52,7 +60,43 @@ export class Instrument extends InstrumentBase {
52
60
  }
53
61
  // Capture is not used here so that we don't get element focus/blur events, only the window's as they do not bubble. They are also not cancellable, so no worries about being front of line.
54
62
  )
63
+
64
+ if (ufEnabled) {
65
+ globalScope.addEventListener('error', () => {
66
+ handle('uaErr', [], undefined, FEATURE_NAMES.genericEvents, this.ee)
67
+ }, eventListenerOpts(false, this.removeOnAbort?.signal))
68
+
69
+ this.ee.on('open-xhr-start', (args, xhr) => {
70
+ if (!isInternalTraffic(args[1])) {
71
+ xhr.addEventListener('readystatechange', () => {
72
+ if (xhr.readyState === 2) { // HEADERS_RECEIVED
73
+ handle('uaXhr', [], undefined, FEATURE_NAMES.genericEvents, this.ee)
74
+ }
75
+ })
76
+ }
77
+ })
78
+ this.ee.on('fetch-start', (fetchArguments) => {
79
+ if (fetchArguments.length >= 1 && !isInternalTraffic(extractUrl(fetchArguments[0]))) {
80
+ handle('uaXhr', [], undefined, FEATURE_NAMES.genericEvents, this.ee)
81
+ }
82
+ })
83
+
84
+ function isInternalTraffic (url) {
85
+ const parsedUrl = parseUrl(url)
86
+ return agentRef.beacons.includes(parsedUrl.hostname + ':' + parsedUrl.port)
87
+ }
88
+
89
+ historyEE.on('pushState-end', navigationChange)
90
+ historyEE.on('replaceState-end', navigationChange)
91
+ window.addEventListener('hashchange', navigationChange, eventListenerOpts(true, this.removeOnAbort?.signal))
92
+ window.addEventListener('popstate', navigationChange, eventListenerOpts(true, this.removeOnAbort?.signal))
93
+
94
+ function navigationChange () {
95
+ historyEE.emit('navChange')
96
+ }
97
+ }
55
98
  }
99
+
56
100
  if (agentRef.init.performance.resources.enabled && globalScope.PerformanceObserver?.supportedEntryTypes.includes('resource')) {
57
101
  const observer = new PerformanceObserver((list) => {
58
102
  list.getEntries().forEach(entry => {
@@ -61,15 +105,6 @@ export class Instrument extends InstrumentBase {
61
105
  })
62
106
  observer.observe({ type: 'resource', buffered: true })
63
107
  }
64
-
65
- const historyEE = wrapHistory(this.ee)
66
- historyEE.on('pushState-end', navigationChange)
67
- historyEE.on('replaceState-end', navigationChange)
68
- window.addEventListener('hashchange', navigationChange, eventListenerOpts(true, this.removeOnAbort?.signal))
69
- window.addEventListener('popstate', navigationChange, eventListenerOpts(true, this.removeOnAbort?.signal))
70
- function navigationChange () {
71
- historyEE.emit('navChange')
72
- }
73
108
  }
74
109
 
75
110
  try {
@@ -81,32 +116,6 @@ export class Instrument extends InstrumentBase {
81
116
  this.abortHandler = undefined // weakly allow this abort op to run only once
82
117
  }
83
118
 
84
- globalScope.addEventListener('error', () => {
85
- handle('uaErr', [], undefined, FEATURE_NAMES.genericEvents, this.ee)
86
- }, eventListenerOpts(false, this.removeOnAbort?.signal))
87
-
88
- wrapFetch(this.ee)
89
- wrapXhr(this.ee)
90
- this.ee.on('open-xhr-start', (args, xhr) => {
91
- if (!isInternalTraffic(args[1])) {
92
- xhr.addEventListener('readystatechange', () => {
93
- if (xhr.readyState === 2) { // HEADERS_RECEIVED
94
- handle('uaXhr', [], undefined, FEATURE_NAMES.genericEvents, this.ee)
95
- }
96
- })
97
- }
98
- })
99
- this.ee.on('fetch-start', (fetchArguments) => {
100
- if (fetchArguments.length >= 1 && !isInternalTraffic(extractUrl(fetchArguments[0]))) {
101
- handle('uaXhr', [], undefined, FEATURE_NAMES.genericEvents, this.ee)
102
- }
103
- })
104
-
105
- function isInternalTraffic (url) {
106
- const parsedUrl = parseUrl(url)
107
- return agentRef.beacons.includes(parsedUrl.hostname + ':' + parsedUrl.port)
108
- }
109
-
110
119
  /** If any of the sources are active, import the aggregator. otherwise deregister */
111
120
  if (genericEventSourceConfigs.some(x => x)) this.importAggregator(agentRef, () => import(/* webpackChunkName: "generic_events-aggregate" */ '../aggregate'))
112
121
  else this.deregisterDrain()
@@ -19,8 +19,7 @@ import { AggregateBase } from '../../utils/aggregate-base'
19
19
  import { now } from '../../../common/timing/now'
20
20
  import { applyFnToProps } from '../../../common/util/traverse'
21
21
  import { evaluateInternalError } from './internal-errors'
22
- import { isContainerAgentTarget } from '../../../common/util/target'
23
- import { warn } from '../../../common/util/console'
22
+ import { getVersion2Attributes } from '../../../common/util/mfe'
24
23
  import { buildCauseString } from './cause-string'
25
24
 
26
25
  /**
@@ -32,6 +31,8 @@ export class Aggregate extends AggregateBase {
32
31
  constructor (agentRef) {
33
32
  super(agentRef, FEATURE_NAME)
34
33
 
34
+ /** set up agg-level behaviors specific to this feature */
35
+ this.harvestOpts.aggregatorTypes = ['err', 'ierr', 'xhr'] // the types in EventAggregator this feature cares about
35
36
  this.stackReported = {}
36
37
  this.observedAt = {}
37
38
  this.pageviewReported = {}
@@ -46,8 +47,6 @@ export class Aggregate extends AggregateBase {
46
47
  register('softNavFlush', (interactionId, wasFinished, softNavAttrs, interactionEndTime) =>
47
48
  this.onSoftNavNotification(interactionId, wasFinished, softNavAttrs, interactionEndTime), this.featureName, this.ee) // when an ixn is done or cancelled
48
49
 
49
- this.harvestOpts.aggregatorTypes = ['err', 'ierr', 'xhr'] // the types in EventAggregator this feature cares about
50
-
51
50
  // 0 == off, 1 == on
52
51
  this.waitForFlags(['err']).then(([errFlag]) => {
53
52
  if (errFlag) {
@@ -113,10 +112,8 @@ export class Aggregate extends AggregateBase {
113
112
  * @param {object=} target the target to buffer and harvest to, if undefined the default configuration target is used
114
113
  * @returns
115
114
  */
116
- storeError (err, time, internal, customAttributes, hasReplay, swallowReason, targetEntityGuid) {
115
+ storeError (err, time, internal, customAttributes, hasReplay, swallowReason, target) {
117
116
  if (!err) return
118
- const target = this.agentRef.runtime.entityManager.get(targetEntityGuid)
119
- if (!target) return warn(56, this.featureName)
120
117
  // are we in an interaction
121
118
  time = time || now()
122
119
  let filterOutput
@@ -155,7 +152,7 @@ export class Aggregate extends AggregateBase {
155
152
  if (filterOutput?.group) params.errorGroup = filterOutput.group
156
153
 
157
154
  // Should only decorate "hasReplay" for the container agent, so check if the target matches the config
158
- if (hasReplay && isContainerAgentTarget(target, this.agentRef)) params.hasReplay = hasReplay
155
+ if (hasReplay && !target) params.hasReplay = hasReplay
159
156
  /**
160
157
  * The bucketHash is different from the params.stackHash because the params.stackHash is based on the canonicalized
161
158
  * stack trace and is used downstream in NR1 to attempt to group the same errors across different browsers. However,
@@ -191,7 +188,7 @@ export class Aggregate extends AggregateBase {
191
188
 
192
189
  // Trace sends the error in its payload, and both trace & replay simply listens for any error to occur.
193
190
  const jsErrorEvent = [type, bucketHash, params, newMetrics, customAttributes]
194
- if (this.shouldAllowMainAgentToCapture(targetEntityGuid)) handle('trace-jserror', jsErrorEvent, undefined, FEATURE_NAMES.sessionTrace, this.ee)
191
+ if (this.shouldAllowMainAgentToCapture(target)) handle('trace-jserror', jsErrorEvent, undefined, FEATURE_NAMES.sessionTrace, this.ee)
195
192
  // still send EE events for other features such as above, but stop this one from aggregating internal data
196
193
  if (this.blocked) return
197
194
 
@@ -200,7 +197,7 @@ export class Aggregate extends AggregateBase {
200
197
  params._interactionNodeId = err.__newrelic[this.agentIdentifier].interactionNodeId
201
198
  }
202
199
 
203
- if (this.shouldAllowMainAgentToCapture(targetEntityGuid)) {
200
+ if (this.shouldAllowMainAgentToCapture(target)) {
204
201
  const softNavInUse = Boolean(this.agentRef.features?.[FEATURE_NAMES.softNav])
205
202
  // Note: the following are subject to potential race cond wherein if the other feature aren't fully initialized, it'll be treated as there being no associated interaction.
206
203
  // They each will also tack on their respective properties to the params object as part of the decision flow.
@@ -221,12 +218,15 @@ export class Aggregate extends AggregateBase {
221
218
  }
222
219
 
223
220
  // always add directly if scoped to a sub-entity, the other pathways above will be deterministic if the main agent should procede
224
- if (targetEntityGuid) this.#storeJserrorForHarvest([...jsErrorEvent, targetEntityGuid], false, params._softNavAttributes)
221
+ if (target) this.#storeJserrorForHarvest([...jsErrorEvent, target], false, params._softNavAttributes)
225
222
  }
226
223
 
227
224
  #storeJserrorForHarvest (errorInfoArr, softNavOccurredFinished, softNavCustomAttrs = {}) {
228
- let [type, bucketHash, params, newMetrics, localAttrs, targetEntityGuid] = errorInfoArr
229
- const allCustomAttrs = {}
225
+ let [type, bucketHash, params, newMetrics, localAttrs, target] = errorInfoArr
226
+ const allCustomAttrs = {
227
+ /** MFE specific attributes if in "multiple" mode (ie consumer version 2) */
228
+ ...getVersion2Attributes(target, this)
229
+ }
230
230
 
231
231
  if (softNavOccurredFinished) {
232
232
  Object.entries(softNavCustomAttrs).forEach(([k, v]) => setCustom(k, v)) // when an ixn finishes, it'll include stuff in jsAttributes + attrs specific to the ixn
@@ -243,7 +243,7 @@ export class Aggregate extends AggregateBase {
243
243
  const jsAttributesHash = stringHashCode(stringify(allCustomAttrs))
244
244
  const aggregateHash = bucketHash + ':' + jsAttributesHash
245
245
 
246
- this.events.add([type, aggregateHash, params, newMetrics, allCustomAttrs], targetEntityGuid)
246
+ this.events.add([type, aggregateHash, params, newMetrics, allCustomAttrs])
247
247
 
248
248
  function setCustom (key, val) {
249
249
  allCustomAttrs[key] = (val && typeof val === 'object' ? stringify(val) : val)
@@ -253,11 +253,11 @@ export class Aggregate extends AggregateBase {
253
253
  /**
254
254
  * If the event lacks an entityGuid (the default behavior), the main agent should capture the data. If the data is assigned to a sub-entity target
255
255
  * the main agent should not capture events unless it is configured to do so.
256
- * @param {string} entityGuid - the context object for the event
256
+ * @param {string} target - the context object for the event
257
257
  * @returns {boolean} - whether the main agent should capture the event to its internal target
258
258
  */
259
- shouldAllowMainAgentToCapture (entityGuid) {
260
- return (!entityGuid || this.agentRef.init.api.duplicate_registered_data)
259
+ shouldAllowMainAgentToCapture (target) {
260
+ return (!target || this.agentRef.init.api.duplicate_registered_data)
261
261
  }
262
262
 
263
263
  // TO-DO: Remove this function when old spa is taken out. #storeJserrorForHarvest handles the work with the softnav feature.
@@ -10,10 +10,10 @@ import { FEATURE_NAME, LOGGING_EVENT_EMITTER_CHANNEL, LOG_LEVELS, LOGGING_MODE }
10
10
  import { Log } from '../shared/log'
11
11
  import { isValidLogLevel } from '../shared/utils'
12
12
  import { applyFnToProps } from '../../../common/util/traverse'
13
- import { isContainerAgentTarget } from '../../../common/util/target'
14
13
  import { SESSION_EVENT_TYPES, SESSION_EVENTS } from '../../../common/session/constants'
15
14
  import { ABORT_REASONS } from '../../session_replay/constants'
16
15
  import { canEnableSessionTracking } from '../../utils/feature-gates'
16
+ import { getVersion2Attributes } from '../../../common/util/mfe'
17
17
 
18
18
  export class Aggregate extends AggregateBase {
19
19
  static featureName = FEATURE_NAME
@@ -21,6 +21,8 @@ export class Aggregate extends AggregateBase {
21
21
  super(agentRef, FEATURE_NAME)
22
22
  this.isSessionTrackingEnabled = canEnableSessionTracking(agentRef.init) && agentRef.runtime.session
23
23
 
24
+ /** set up agg-level behaviors specific to this feature */
25
+ this.harvestOpts.raw = true
24
26
  super.customAttributesAreSeparate = true
25
27
 
26
28
  // The SessionEntity class can emit a message indicating the session was cleared and reset (expiry, inactivity). This feature must abort and never resume if that occurs.
@@ -34,7 +36,6 @@ export class Aggregate extends AggregateBase {
34
36
  else this.loggingMode = data.loggingMode
35
37
  })
36
38
 
37
- this.harvestOpts.raw = true
38
39
  this.waitForFlags(['log']).then(([loggingMode]) => {
39
40
  const session = this.agentRef.runtime.session ?? {}
40
41
  if (this.loggingMode === LOGGING_MODE.OFF || (session.isNew && loggingMode === LOGGING_MODE.OFF)) {
@@ -63,11 +64,17 @@ export class Aggregate extends AggregateBase {
63
64
  })
64
65
  }
65
66
 
66
- handleLog (timestamp, message, attributes = {}, level = LOG_LEVELS.INFO, targetEntityGuid) {
67
- if (!this.agentRef.runtime.entityManager.get(targetEntityGuid)) return warn(56, this.featureName)
67
+ handleLog (timestamp, message, attributes = {}, level = LOG_LEVELS.INFO, target) {
68
68
  if (this.blocked || !this.loggingMode) return
69
69
 
70
70
  if (!attributes || typeof attributes !== 'object') attributes = {}
71
+
72
+ attributes = {
73
+ ...attributes,
74
+ /** Specific attributes only supplied if harvesting to endpoint version 2 */
75
+ ...(getVersion2Attributes(target, this))
76
+ }
77
+
71
78
  if (typeof level === 'string') level = level.toUpperCase()
72
79
  if (!isValidLogLevel(level)) return warn(30, level)
73
80
  if (this.loggingMode < (LOGGING_MODE[level] || Infinity)) {
@@ -100,25 +107,26 @@ export class Aggregate extends AggregateBase {
100
107
  level
101
108
  )
102
109
 
103
- this.events.add(log, targetEntityGuid)
110
+ this.events.add(log)
104
111
  }
105
112
 
106
- serializer (eventBuffer, targetEntityGuid) {
107
- const target = this.agentRef.runtime.entityManager.get(targetEntityGuid)
113
+ serializer (eventBuffer) {
108
114
  const sessionEntity = this.agentRef.runtime.session
109
115
  return [{
110
116
  common: {
111
117
  /** Attributes in the `common` section are added to `all` logs generated in the payload */
112
118
  attributes: {
113
119
  ...this.agentRef.info.jsAttributes, // user-provided custom attributes
114
- 'entity.guid': target.entityGuid, // browser entity guid as provided API target OR the default from RUM response if not supplied
120
+ ...(this.harvestEndpointVersion === 1 && {
121
+ 'entity.guid': this.agentRef.runtime.appMetadata.agents[0].entityGuid,
122
+ appId: this.agentRef.info.applicationID
123
+ }),
115
124
  ...(sessionEntity && {
116
125
  session: sessionEntity.state.value || '0', // The session ID that we generate and keep across page loads
117
- hasReplay: sessionEntity.state.sessionReplayMode === 1 && isContainerAgentTarget(target, this.agentRef), // True if a session replay recording is running
126
+ hasReplay: sessionEntity.state.sessionReplayMode === 1, // True if a session replay recording is running
118
127
  hasTrace: sessionEntity.state.sessionTraceMode === 1 // True if a session trace recording is running
119
128
  }),
120
129
  ptid: this.agentRef.runtime.ptid, // page trace id
121
- appId: target.applicationID || this.agentRef.info.applicationID, // Application ID from info object,
122
130
  standalone: Boolean(this.agentRef.info.sa), // copy paste (true) vs APM (false)
123
131
  agentVersion: this.agentRef.runtime.version, // browser agent version
124
132
  // The following 3 attributes are evaluated and dropped at ingest processing time and do not get stored on NRDB:
@@ -135,9 +143,8 @@ export class Aggregate extends AggregateBase {
135
143
  }]
136
144
  }
137
145
 
138
- queryStringsBuilder (_, targetEntityGuid) {
139
- const target = this.agentRef.runtime.entityManager.get(targetEntityGuid)
140
- return { browser_monitoring_key: target.licenseKey }
146
+ queryStringsBuilder () {
147
+ return { browser_monitoring_key: this.agentRef.info.licenseKey }
141
148
  }
142
149
 
143
150
  /** Abort the feature, once aborted it will not resume */
@@ -13,11 +13,11 @@ import { LOGGING_EVENT_EMITTER_CHANNEL, LOG_LEVELS } from '../constants'
13
13
  * @param {string} message - the log message string
14
14
  * @param {{[key: string]: *}} customAttributes - The log's custom attributes if any
15
15
  * @param {enum} level - the log level enum
16
- * @param {object=} targetEntityGuid - the optional target entity guid provided by an api call
16
+ * @param {object=} target - the optional target provided by an api call
17
17
  */
18
- export function bufferLog (ee, message, customAttributes = {}, level = LOG_LEVELS.INFO, targetEntityGuid, timestamp = now()) {
18
+ export function bufferLog (ee, message, customAttributes = {}, level = LOG_LEVELS.INFO, target, timestamp = now()) {
19
19
  handle(SUPPORTABILITY_METRIC_CHANNEL, [`API/logging/${level.toLowerCase()}/called`], undefined, FEATURE_NAMES.metrics, ee)
20
- handle(LOGGING_EVENT_EMITTER_CHANNEL, [timestamp, message, customAttributes, level, targetEntityGuid], undefined, FEATURE_NAMES.logging, ee)
20
+ handle(LOGGING_EVENT_EMITTER_CHANNEL, [timestamp, message, customAttributes, level, target], undefined, FEATURE_NAMES.logging, ee)
21
21
  }
22
22
 
23
23
  /**
@@ -17,8 +17,9 @@ import { timeToFirstByte } from '../../../common/vitals/time-to-first-byte'
17
17
  import { now } from '../../../common/timing/now'
18
18
  import { TimeKeeper } from '../../../common/timing/time-keeper'
19
19
  import { applyFnToProps } from '../../../common/util/traverse'
20
- import { registerHandler } from '../../../common/event-emitter/register-handler'
21
- import { isContainerAgentTarget } from '../../../common/util/target'
20
+ import { send } from '../../../common/harvest/harvester'
21
+ import { FEATURE_NAMES, FEATURE_TO_ENDPOINT } from '../../../loaders/features/features'
22
+ import { getSubmitMethod } from '../../../common/util/submit-data'
22
23
 
23
24
  export class Aggregate extends AggregateBase {
24
25
  static featureName = CONSTANTS.FEATURE_NAME
@@ -29,10 +30,6 @@ export class Aggregate extends AggregateBase {
29
30
  this.firstByteToWindowLoad = 0 // our "frontend" duration
30
31
  this.firstByteToDomContent = 0 // our "dom processing" duration
31
32
 
32
- registerHandler('send-rum', (customAttibutes, target) => {
33
- this.sendRum(customAttibutes, target)
34
- }, this.featureName, this.ee)
35
-
36
33
  if (!isValid(agentRef.info)) {
37
34
  this.ee.abort()
38
35
  return warn(43)
@@ -58,7 +55,7 @@ export class Aggregate extends AggregateBase {
58
55
  *
59
56
  * @param {Function} cb A function to run once the RUM call has finished - Defaults to activateFeatures
60
57
  * @param {*} customAttributes custom attributes to attach to the RUM call - Defaults to info.js
61
- * @param {*} target The target to harvest to - Since we will not know the entityGuid before harvesting, this must be an object directly supplied from the info object or API, not an entityGuid string for lookup with the entityManager - Defaults to { licenseKey: this.agentRef.info.licenseKey, applicationID: this.agentRef.info.applicationID }
58
+ * @param {*} target The target to harvest to
62
59
  */
63
60
  sendRum (customAttributes = this.agentRef.info.jsAttributes, target = { licenseKey: this.agentRef.info.licenseKey, applicationID: this.agentRef.info.applicationID }) {
64
61
  const info = this.agentRef.info
@@ -122,7 +119,7 @@ export class Aggregate extends AggregateBase {
122
119
 
123
120
  this.agentRef.runtime.harvester.triggerHarvestFor(this, {
124
121
  directSend: {
125
- targetApp: target,
122
+ target,
126
123
  payload: { qs: queryParameters, body }
127
124
  },
128
125
  needResponse: true,
@@ -130,22 +127,65 @@ export class Aggregate extends AggregateBase {
130
127
  })
131
128
  }
132
129
 
133
- postHarvestCleanup ({ status, responseText, xhr, targetApp }) {
130
+ postHarvestCleanup ({ status, responseText, xhr }) {
134
131
  const rumEndTime = now()
135
132
  let app, flags
136
133
  try {
137
134
  ({ app, ...flags } = JSON.parse(responseText))
138
- this.processEntities(app.agents, targetApp)
139
135
  } catch (error) {
140
136
  // wont set entity stuff here, if main agent will later abort, if registered agent, nothing will happen
141
137
  warn(53, error)
142
138
  }
143
139
 
144
- /** Only run agent-wide side-effects if the harvest was for the main agent */
145
- if (!isContainerAgentTarget(targetApp, this.agentRef)) return
146
-
147
140
  if (status >= 400 || status === 0) {
148
141
  warn(18, status)
142
+
143
+ // Get estimated payload size of our backlog
144
+ const textEncoder = new TextEncoder()
145
+ const payloadSize = Object.values(newrelic.ee.backlog).reduce((acc, value) => {
146
+ if (!value) return acc
147
+
148
+ const encoded = textEncoder.encode(value)
149
+ return acc + encoded.byteLength
150
+ }, 0)
151
+
152
+ // Send SMs about failed RUM request
153
+ const body = {
154
+ sm: [{
155
+ params: {
156
+ name: `Browser/Supportability/BCS/Error/${status}`
157
+ },
158
+ stats: {
159
+ c: 1
160
+ }
161
+ },
162
+ {
163
+ params: {
164
+ name: 'Browser/Supportability/BCS/Error/Dropped/Bytes'
165
+ },
166
+ stats: {
167
+ c: 1,
168
+ t: payloadSize
169
+ }
170
+ },
171
+ {
172
+ params: {
173
+ name: 'Browser/Supportability/BCS/Error/Duration/Ms'
174
+ },
175
+ stats: {
176
+ c: 1,
177
+ t: rumEndTime - this.rumStartTime
178
+ }
179
+ }]
180
+ }
181
+
182
+ send(this.agentRef, {
183
+ endpoint: FEATURE_TO_ENDPOINT[FEATURE_NAMES.metrics],
184
+ payload: { body },
185
+ submitMethod: getSubmitMethod(),
186
+ featureName: FEATURE_NAMES.metrics
187
+ })
188
+
149
189
  // Adding retry logic for the rum call will be a separate change; this.blocked will need to be changed since that prevents another triggerHarvestFor()
150
190
  this.ee.abort()
151
191
  return
@@ -176,19 +216,4 @@ export class Aggregate extends AggregateBase {
176
216
  this.agentRef.runtime.harvester.startTimer()
177
217
  activateFeatures(flags, this.agentRef)
178
218
  }
179
-
180
- processEntities (entities, targetApp) {
181
- if (!entities || !targetApp) return
182
- entities.forEach(agent => {
183
- const entityManager = this.agentRef.runtime.entityManager
184
- const entityGuid = agent.entityGuid
185
- const entity = entityManager.get(entityGuid)
186
- if (entity) return // already processed
187
-
188
- if (isContainerAgentTarget(targetApp, this.agentRef)) {
189
- entityManager.setDefaultEntity({ ...targetApp, entityGuid })
190
- }
191
- entityManager.set(agent.entityGuid, { ...targetApp, entityGuid })
192
- })
193
- }
194
219
  }