@newrelic/browser-agent 1.316.0 → 1.317.0-rc.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 (170) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/cjs/common/config/configurable.js +1 -1
  3. package/dist/cjs/common/config/init-types.js +1 -0
  4. package/dist/cjs/common/config/init.js +6 -4
  5. package/dist/cjs/common/constants/env.cdn.js +2 -2
  6. package/dist/cjs/common/constants/env.npm.js +2 -2
  7. package/dist/cjs/common/constants/events.js +26 -0
  8. package/dist/cjs/common/harvest/harvester.js +9 -217
  9. package/dist/cjs/common/harvest/send.js +232 -0
  10. package/dist/cjs/common/harvest/types.js +9 -5
  11. package/dist/cjs/common/payloads/payloads.js +127 -0
  12. package/dist/cjs/common/serialize/bel-serializer.js +4 -3
  13. package/dist/cjs/common/session/session-entity.js +8 -8
  14. package/dist/cjs/common/util/data-size.js +2 -2
  15. package/dist/cjs/common/util/obfuscate.js +147 -3
  16. package/dist/cjs/common/v2/mfe-vitals.js +286 -0
  17. package/dist/cjs/common/wrap/wrap-websocket.js +2 -1
  18. package/dist/cjs/common/wrap/wrap-xhr.js +1 -1
  19. package/dist/cjs/features/ajax/aggregate/gql.js +40 -1
  20. package/dist/cjs/features/ajax/aggregate/index.js +51 -13
  21. package/dist/cjs/features/ajax/constants.js +6 -1
  22. package/dist/cjs/features/ajax/instrument/index.js +105 -17
  23. package/dist/cjs/features/generic_events/aggregate/index.js +15 -10
  24. package/dist/cjs/features/generic_events/constants.js +3 -2
  25. package/dist/cjs/features/jserrors/aggregate/format-stack-trace.js +2 -2
  26. package/dist/cjs/features/jserrors/aggregate/index.js +12 -3
  27. package/dist/cjs/features/logging/aggregate/index.js +12 -8
  28. package/dist/cjs/features/metrics/aggregate/index.js +5 -1
  29. package/dist/cjs/features/page_view_event/aggregate/index.js +72 -33
  30. package/dist/cjs/features/page_view_timing/aggregate/index.js +6 -1
  31. package/dist/cjs/features/session_replay/aggregate/index.js +39 -29
  32. package/dist/cjs/features/session_trace/aggregate/index.js +35 -29
  33. package/dist/cjs/features/session_trace/constants.js +17 -3
  34. package/dist/cjs/features/soft_navigations/aggregate/ajax-node.js +36 -5
  35. package/dist/cjs/features/soft_navigations/aggregate/index.js +7 -1
  36. package/dist/cjs/features/soft_navigations/aggregate/initial-page-load-interaction.js +1 -1
  37. package/dist/cjs/features/soft_navigations/aggregate/interaction.js +6 -4
  38. package/dist/cjs/features/utils/aggregate-base.js +4 -3
  39. package/dist/cjs/loaders/api/register.js +26 -4
  40. package/dist/esm/common/config/configurable.js +1 -1
  41. package/dist/esm/common/config/init-types.js +1 -0
  42. package/dist/esm/common/config/init.js +3 -1
  43. package/dist/esm/common/constants/env.cdn.js +2 -2
  44. package/dist/esm/common/constants/env.npm.js +2 -2
  45. package/dist/esm/common/constants/events.js +20 -0
  46. package/dist/esm/common/harvest/harvester.js +9 -217
  47. package/dist/esm/common/harvest/send.js +226 -0
  48. package/dist/esm/common/harvest/types.js +9 -5
  49. package/dist/esm/common/payloads/payloads.js +118 -0
  50. package/dist/esm/common/serialize/bel-serializer.js +4 -3
  51. package/dist/esm/common/session/session-entity.js +9 -9
  52. package/dist/esm/common/util/data-size.js +2 -2
  53. package/dist/esm/common/util/obfuscate.js +147 -3
  54. package/dist/esm/common/v2/mfe-vitals.js +281 -0
  55. package/dist/esm/common/wrap/wrap-websocket.js +2 -1
  56. package/dist/esm/common/wrap/wrap-xhr.js +1 -1
  57. package/dist/esm/features/ajax/aggregate/gql.js +39 -1
  58. package/dist/esm/features/ajax/aggregate/index.js +52 -14
  59. package/dist/esm/features/ajax/constants.js +5 -0
  60. package/dist/esm/features/ajax/instrument/index.js +106 -18
  61. package/dist/esm/features/generic_events/aggregate/index.js +15 -10
  62. package/dist/esm/features/generic_events/constants.js +3 -2
  63. package/dist/esm/features/jserrors/aggregate/format-stack-trace.js +2 -2
  64. package/dist/esm/features/jserrors/aggregate/index.js +12 -3
  65. package/dist/esm/features/logging/aggregate/index.js +12 -8
  66. package/dist/esm/features/metrics/aggregate/index.js +5 -1
  67. package/dist/esm/features/page_view_event/aggregate/index.js +71 -32
  68. package/dist/esm/features/page_view_timing/aggregate/index.js +6 -1
  69. package/dist/esm/features/session_replay/aggregate/index.js +39 -29
  70. package/dist/esm/features/session_trace/aggregate/index.js +36 -30
  71. package/dist/esm/features/session_trace/constants.js +16 -2
  72. package/dist/esm/features/soft_navigations/aggregate/ajax-node.js +36 -5
  73. package/dist/esm/features/soft_navigations/aggregate/index.js +7 -1
  74. package/dist/esm/features/soft_navigations/aggregate/initial-page-load-interaction.js +1 -1
  75. package/dist/esm/features/soft_navigations/aggregate/interaction.js +6 -4
  76. package/dist/esm/features/utils/aggregate-base.js +4 -3
  77. package/dist/esm/loaders/api/register.js +26 -4
  78. package/dist/tsconfig.tsbuildinfo +1 -1
  79. package/dist/types/common/config/init-types.d.ts +4 -0
  80. package/dist/types/common/config/init-types.d.ts.map +1 -1
  81. package/dist/types/common/config/init.d.ts.map +1 -1
  82. package/dist/types/common/constants/events.d.ts +16 -0
  83. package/dist/types/common/constants/events.d.ts.map +1 -0
  84. package/dist/types/common/harvest/harvester.d.ts +2 -16
  85. package/dist/types/common/harvest/harvester.d.ts.map +1 -1
  86. package/dist/types/common/harvest/send.d.ts +9 -0
  87. package/dist/types/common/harvest/send.d.ts.map +1 -0
  88. package/dist/types/common/harvest/types.d.ts +39 -10
  89. package/dist/types/common/harvest/types.d.ts.map +1 -1
  90. package/dist/types/common/payloads/payloads.d.ts +42 -0
  91. package/dist/types/common/payloads/payloads.d.ts.map +1 -0
  92. package/dist/types/common/serialize/bel-serializer.d.ts +1 -1
  93. package/dist/types/common/serialize/bel-serializer.d.ts.map +1 -1
  94. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  95. package/dist/types/common/util/obfuscate.d.ts +23 -2
  96. package/dist/types/common/util/obfuscate.d.ts.map +1 -1
  97. package/dist/types/common/v2/mfe-vitals.d.ts +14 -0
  98. package/dist/types/common/v2/mfe-vitals.d.ts.map +1 -0
  99. package/dist/types/common/wrap/wrap-websocket.d.ts.map +1 -1
  100. package/dist/types/features/ajax/aggregate/gql.d.ts +8 -0
  101. package/dist/types/features/ajax/aggregate/gql.d.ts.map +1 -1
  102. package/dist/types/features/ajax/aggregate/index.d.ts +2 -0
  103. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  104. package/dist/types/features/ajax/constants.d.ts +5 -0
  105. package/dist/types/features/ajax/constants.d.ts.map +1 -1
  106. package/dist/types/features/ajax/instrument/index.d.ts.map +1 -1
  107. package/dist/types/features/generic_events/aggregate/index.d.ts +3 -1
  108. package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
  109. package/dist/types/features/generic_events/constants.d.ts.map +1 -1
  110. package/dist/types/features/jserrors/aggregate/index.d.ts +8 -1
  111. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  112. package/dist/types/features/logging/aggregate/index.d.ts +3 -1
  113. package/dist/types/features/logging/aggregate/index.d.ts.map +1 -1
  114. package/dist/types/features/metrics/aggregate/index.d.ts +2 -0
  115. package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
  116. package/dist/types/features/page_view_event/aggregate/index.d.ts +3 -0
  117. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  118. package/dist/types/features/page_view_timing/aggregate/index.d.ts +2 -0
  119. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  120. package/dist/types/features/session_replay/aggregate/index.d.ts +4 -2
  121. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  122. package/dist/types/features/session_trace/aggregate/index.d.ts +3 -1
  123. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  124. package/dist/types/features/session_trace/constants.d.ts +17 -0
  125. package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts +6 -1
  126. package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts.map +1 -1
  127. package/dist/types/features/soft_navigations/aggregate/index.d.ts +3 -0
  128. package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -1
  129. package/dist/types/features/soft_navigations/aggregate/interaction.d.ts +3 -1
  130. package/dist/types/features/soft_navigations/aggregate/interaction.d.ts.map +1 -1
  131. package/dist/types/features/utils/aggregate-base.d.ts +0 -1
  132. package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
  133. package/dist/types/loaders/api/register.d.ts.map +1 -1
  134. package/package.json +3 -3
  135. package/src/common/config/configurable.js +1 -1
  136. package/src/common/config/init-types.js +1 -0
  137. package/src/common/config/init.js +2 -1
  138. package/src/common/constants/events.js +20 -0
  139. package/src/common/harvest/harvester.js +9 -201
  140. package/src/common/harvest/send.js +209 -0
  141. package/src/common/harvest/types.js +9 -5
  142. package/src/common/payloads/payloads.js +135 -0
  143. package/src/common/serialize/bel-serializer.js +4 -3
  144. package/src/common/session/session-entity.js +8 -9
  145. package/src/common/util/data-size.js +2 -2
  146. package/src/common/util/obfuscate.js +154 -3
  147. package/src/common/v2/mfe-vitals.js +260 -0
  148. package/src/common/wrap/wrap-websocket.js +2 -1
  149. package/src/common/wrap/wrap-xhr.js +1 -1
  150. package/src/features/ajax/aggregate/gql.js +42 -1
  151. package/src/features/ajax/aggregate/index.js +43 -13
  152. package/src/features/ajax/constants.js +5 -1
  153. package/src/features/ajax/instrument/index.js +114 -18
  154. package/src/features/generic_events/aggregate/index.js +14 -9
  155. package/src/features/generic_events/constants.js +3 -2
  156. package/src/features/jserrors/aggregate/format-stack-trace.js +3 -3
  157. package/src/features/jserrors/aggregate/index.js +12 -3
  158. package/src/features/logging/aggregate/index.js +12 -11
  159. package/src/features/metrics/aggregate/index.js +5 -1
  160. package/src/features/page_view_event/aggregate/index.js +58 -29
  161. package/src/features/page_view_timing/aggregate/index.js +6 -1
  162. package/src/features/session_replay/aggregate/index.js +37 -28
  163. package/src/features/session_trace/aggregate/index.js +37 -30
  164. package/src/features/session_trace/constants.js +7 -1
  165. package/src/features/soft_navigations/aggregate/ajax-node.js +26 -5
  166. package/src/features/soft_navigations/aggregate/index.js +7 -1
  167. package/src/features/soft_navigations/aggregate/initial-page-load-interaction.js +1 -1
  168. package/src/features/soft_navigations/aggregate/interaction.js +6 -4
  169. package/src/features/utils/aggregate-base.js +3 -3
  170. package/src/loaders/api/register.js +24 -4
@@ -3,15 +3,16 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { registerHandler } from '../../../common/event-emitter/register-handler'
6
- import { FEATURE_NAME } from '../constants'
6
+ import { ABORT_REASONS, FEATURE_NAME } from '../constants'
7
7
  import { AggregateBase } from '../../utils/aggregate-base'
8
8
  import { TraceStorage } from './trace/storage'
9
9
  import { obj as encodeObj } from '../../../common/url/encode'
10
10
  import { globalScope } from '../../../common/constants/runtime'
11
11
  import { MODE, SESSION_EVENTS } from '../../../common/session/constants'
12
- import { applyFnToProps } from '../../../common/util/traverse'
12
+ import { Obfuscator } from '../../../common/util/obfuscate'
13
13
  import { cleanURL } from '../../../common/url/clean-url'
14
14
  import { warn } from '../../../common/util/console'
15
+ import { EVENT_TYPES } from '../../../common/constants/events'
15
16
 
16
17
  /** Reserved room for query param attrs */
17
18
  const QUERY_PARAM_PADDING = 5000
@@ -22,6 +23,9 @@ export class Aggregate extends AggregateBase {
22
23
  super(agentRef, FEATURE_NAME)
23
24
  this.harvestOpts.raw = true
24
25
 
26
+ // Create obfuscator for session trace nodes
27
+ this.obfuscator = new Obfuscator(agentRef, EVENT_TYPES.ST)
28
+
25
29
  /** Tied to the entitlement flag response from BCS. Will short circuit operations of the agg if false */
26
30
  this.entitled = undefined
27
31
  /** A flag used to decide if the 30 node threshold should be ignored on the first harvest to ensure sending on the first payload */
@@ -39,8 +43,10 @@ export class Aggregate extends AggregateBase {
39
43
  /** Sets up event listeners, and initializes this module to run in the correct "mode". Can be triggered from a few places, but makes an effort to only set up listeners once */
40
44
  initialize (stMode, stEntitled, ignoreSession) {
41
45
  this.entitled ??= stEntitled
42
- if (!this.entitled) this.blocked = true
43
- if (this.blocked) return this.deregisterDrain()
46
+ if (!this.entitled) {
47
+ this.abort(ABORT_REASONS.ENTITLEMENTS)
48
+ return this.deregisterDrain()
49
+ }
44
50
  this.timeKeeper ??= this.agentRef.runtime.timeKeeper
45
51
 
46
52
  if (!this.initialized) {
@@ -51,7 +57,7 @@ export class Aggregate extends AggregateBase {
51
57
  // 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.
52
58
  this.ee.on(SESSION_EVENTS.RESET, () => {
53
59
  if (this.blocked) return
54
- this.abort(1)
60
+ this.abort(ABORT_REASONS.RESET)
55
61
  })
56
62
  // The SessionEntity can have updates (locally or across tabs for SR mode changes), (across tabs for ST mode changes).
57
63
  // Those updates should be sync'd here to ensure this page also honors the mode after initialization
@@ -60,7 +66,7 @@ export class Aggregate extends AggregateBase {
60
66
  // this will only have an effect if ST is NOT already in full mode
61
67
  if (this.mode !== MODE.FULL && (sessionState.sessionReplayMode === MODE.FULL || sessionState.sessionTraceMode === MODE.FULL)) this.switchToFull()
62
68
  // if another page's session entity has expired, or another page has transitioned to off and this one hasn't... we can just abort straight away here
63
- if (this.sessionId !== sessionState.value || (eventType === 'cross-tab' && sessionState.sessionTraceMode === MODE.OFF)) this.abort(2)
69
+ if (this.sessionId !== sessionState.value || (eventType === 'cross-tab' && sessionState.sessionTraceMode === MODE.OFF)) this.abort(ABORT_REASONS.CROSS_TAB)
64
70
  })
65
71
 
66
72
  if (typeof PerformanceNavigationTiming !== 'undefined' && globalScope.performance?.getEntriesByType('navigation')?.length > 0) {
@@ -72,30 +78,31 @@ export class Aggregate extends AggregateBase {
72
78
 
73
79
  /** ST/SR sampling flow in BCS - https://drive.google.com/file/d/19hwt2oft-8Hh4RrjpLqEXfpP_9wYBLcq/view?usp=sharing */
74
80
  /** ST will run in the mode provided by BCS if the session IS NEW. If not... it will use the state of the session entity to determine what mode to run in */
75
- if (!this.agentRef.runtime.session.isNew && !ignoreSession) this.mode = this.agentRef.runtime.session.state.sessionTraceMode
81
+ if (this.agentRef.runtime.session.state.sessionTraceMode !== null && !ignoreSession) this.mode = this.agentRef.runtime.session.state.sessionTraceMode
76
82
  else this.mode = stMode
77
83
 
78
- /** If the mode is off, we do not want to hold up draining for other features, so we deregister the feature for now.
79
- * If it drains later (due to a mode change), data and handlers will instantly drain instead of waiting for the registry. */
80
- if (this.mode === MODE.OFF) return this.deregisterDrain()
81
-
82
- /** The handlers set up by the Inst file */
83
- registerHandler('bst', (...args) => this.traceStorage.storeEvent(...args), this.featureName, this.ee)
84
- registerHandler('bstResource', (...args) => this.traceStorage.storeResources(...args), this.featureName, this.ee)
85
- registerHandler('bstHist', (...args) => this.traceStorage.storeHist(...args), this.featureName, this.ee)
86
- registerHandler('bstXhrAgg', (...args) => this.traceStorage.storeXhrAgg(...args), this.featureName, this.ee)
87
- registerHandler('bstApi', (...args) => this.traceStorage.storeNode(...args), this.featureName, this.ee)
88
- registerHandler('trace-jserror', (...args) => this.traceStorage.storeErrorAgg(...args), this.featureName, this.ee)
89
- registerHandler('pvtAdded', (...args) => this.traceStorage.processPVT(...args), this.featureName, this.ee)
90
-
91
- if (this.mode !== MODE.FULL) {
92
- /** A separate handler for noticing errors, and switching to "full" mode if running in "error" mode */
93
- registerHandler('trace-jserror', () => {
94
- if (this.mode === MODE.ERROR) this.switchToFull()
95
- }, this.featureName, this.ee)
84
+ if (this.mode !== MODE.OFF) {
85
+ /** The handlers set up by the Inst file */
86
+ registerHandler('bst', (...args) => this.traceStorage.storeEvent(...args), this.featureName, this.ee)
87
+ registerHandler('bstResource', (...args) => this.traceStorage.storeResources(...args), this.featureName, this.ee)
88
+ registerHandler('bstHist', (...args) => this.traceStorage.storeHist(...args), this.featureName, this.ee)
89
+ registerHandler('bstXhrAgg', (...args) => this.traceStorage.storeXhrAgg(...args), this.featureName, this.ee)
90
+ registerHandler('bstApi', (...args) => this.traceStorage.storeNode(...args), this.featureName, this.ee)
91
+ registerHandler('trace-jserror', (...args) => this.traceStorage.storeErrorAgg(...args), this.featureName, this.ee)
92
+ registerHandler('pvtAdded', (...args) => this.traceStorage.processPVT(...args), this.featureName, this.ee)
93
+
94
+ if (this.mode === MODE.ERROR) {
95
+ /** A separate handler for noticing errors, and switching to "full" mode if running in "error" mode */
96
+ registerHandler('trace-jserror', () => {
97
+ if (this.mode === MODE.ERROR) this.switchToFull()
98
+ }, this.featureName, this.ee)
99
+ }
96
100
  }
97
101
  this.agentRef.runtime.session.write({ sessionTraceMode: this.mode })
98
- this.drain()
102
+
103
+ /** If the mode is off, we do not want to hold up draining for other features, so we deregister the feature for now.
104
+ * If it drains later (due to a mode change), data and handlers will instantly drain instead of waiting for the registry. */
105
+ this.mode === MODE.OFF ? this.deregisterDrain() : this.drain()
99
106
  }
100
107
 
101
108
  preHarvestChecks () {
@@ -104,7 +111,7 @@ export class Aggregate extends AggregateBase {
104
111
  if (!this.agentRef.runtime.session) return // session entity is required for trace to run and continue running
105
112
  if (this.sessionId !== this.agentRef.runtime.session.state.value || this.ptid !== this.agentRef.runtime.ptid) {
106
113
  // If something unexpected happened and we somehow still got to harvesting after a session identifier changed, we should force-exit instead of harvesting:
107
- this.abort(3)
114
+ this.abort(ABORT_REASONS.SESSION_CHANGED)
108
115
  return
109
116
  }
110
117
 
@@ -114,7 +121,7 @@ export class Aggregate extends AggregateBase {
114
121
  serializer (stns) {
115
122
  if (!stns.length) return // there are no processed nodes
116
123
  this.everHarvested = true
117
- return applyFnToProps(stns, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string')
124
+ return this.obfuscator.traverseAndObfuscateEvents(stns)
118
125
  }
119
126
 
120
127
  queryStringsBuilder (stns) {
@@ -181,8 +188,8 @@ export class Aggregate extends AggregateBase {
181
188
  }
182
189
 
183
190
  /** Stop running for the remainder of the page lifecycle */
184
- abort (code) {
185
- warn(60, code)
191
+ abort (reason = {}) {
192
+ warn(60, reason.message)
186
193
  this.blocked = true
187
194
  this.mode = MODE.OFF
188
195
  this.agentRef.runtime.session.write({ sessionTraceMode: this.mode })
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { FEATURE_NAMES } from '../../loaders/features/features'
@@ -14,3 +14,9 @@ export const FN_END = 'fn' + END
14
14
  export const PUSH_STATE = 'pushState'
15
15
  export const MAX_NODES_PER_HARVEST = 1000
16
16
  export const ERROR_MODE_SECONDS_WINDOW = 30 * 1000 // sliding window of nodes to track when simply monitoring (but not harvesting) in error mode
17
+ export const ABORT_REASONS = {
18
+ CROSS_TAB: { message: 'Session Entity was set to OFF on another tab' },
19
+ ENTITLEMENTS: { message: 'Session Trace is not allowed and will not be started' },
20
+ RESET: { message: 'Session was reset' },
21
+ SESSION_CHANGED: { message: 'Session identifier changed unexpectedly' }
22
+ }
@@ -3,6 +3,7 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { addCustomAttributes, getAddStringContext, nullable, numeric } from '../../../common/serialize/bel-serializer'
6
+ import { createStringAdders } from '../../../common/payloads/payloads'
6
7
  import { AJAX_ID } from '../../ajax/constants'
7
8
  import { NODE_TYPE } from '../constants'
8
9
  import { BelNode } from './bel-node'
@@ -25,6 +26,13 @@ export class AjaxNode extends BelNode {
25
26
  this.targetAttributes = ajaxEvent.targetAttributes
26
27
  this[AJAX_ID] = ajaxEvent[AJAX_ID] // all AjaxRequest events should have a unique identifier to allow for easier grouping and analysis in the UI
27
28
 
29
+ // optional payload metadata attributes
30
+ this.requestBody = ajaxEvent.requestBody
31
+ this.requestHeaders = ajaxEvent.requestHeaders
32
+ this.requestQuery = ajaxEvent.requestQuery
33
+ this.responseBody = ajaxEvent.responseBody
34
+ this.responseHeaders = ajaxEvent.responseHeaders
35
+
28
36
  this.start = ajaxEvent.startTime
29
37
  this.end = ajaxEvent.endTime
30
38
  if (ajaxContext?.latestLongtaskEnd) {
@@ -33,8 +41,9 @@ export class AjaxNode extends BelNode {
33
41
  } else this.callbackEnd = this.end // if no long task was observed, callbackEnd is the same as end
34
42
  }
35
43
 
36
- serialize (parentStartTimestamp, agentRef) {
37
- const addString = getAddStringContext(agentRef.runtime.obfuscator)
44
+ serialize (parentStartTimestamp, agentRef, ajaxObfuscator) {
45
+ const { addString, addStringWithTruncation } = createStringAdders(getAddStringContext, ajaxObfuscator)
46
+
38
47
  const nodeList = []
39
48
 
40
49
  // IMPORTANT: The order in which addString is called matters and correlates to the order in which string shows up in the harvest payload. Do not re-order the following code.
@@ -55,11 +64,23 @@ export class AjaxNode extends BelNode {
55
64
  addString(this.nodeId),
56
65
  nullable(this.spanId, addString, true) + nullable(this.traceId, addString, true) + nullable(this.spanTimestamp, numeric)
57
66
  ]
58
- let allAttachedNodes = addCustomAttributes({
59
- ...(this.gql || {}),
67
+ // Regular attributes: obfuscate only
68
+ const regularAttrs = addCustomAttributes({
69
+ [AJAX_ID]: this[AJAX_ID],
60
70
  ...(this.targetAttributes || {}),
61
- [AJAX_ID]: this[AJAX_ID]
71
+ ...(this.gql || {})
62
72
  }, addString)
73
+
74
+ // Payload attributes: obfuscate then truncate
75
+ const payloadAttrs = addCustomAttributes({
76
+ ...(this.requestBody ? { requestBody: this.requestBody } : {}),
77
+ ...(this.requestHeaders ? { requestHeaders: this.requestHeaders } : {}),
78
+ ...(this.requestQuery ? { requestQuery: this.requestQuery } : {}),
79
+ ...(this.responseBody ? { responseBody: this.responseBody } : {}),
80
+ ...(this.responseHeaders ? { responseHeaders: this.responseHeaders } : {})
81
+ }, addStringWithTruncation)
82
+
83
+ let allAttachedNodes = [...regularAttrs, ...payloadAttrs]
63
84
  this.children.forEach(node => allAttachedNodes.push(node.serialize())) // no children is expected under ajax nodes at this time
64
85
 
65
86
  fields[1] = numeric(allAttachedNodes.length)
@@ -8,10 +8,12 @@ import { webdriverDetected } from '../../../common/util/webdriver-detection'
8
8
  import { loadTime } from '../../../common/vitals/load-time'
9
9
  import { FEATURE_NAMES } from '../../../loaders/features/features'
10
10
  import { AggregateBase } from '../../utils/aggregate-base'
11
+ import { Obfuscator } from '../../../common/util/obfuscate'
11
12
  import { API_TRIGGER_NAME, FEATURE_NAME, INTERACTION_STATUS, INTERACTION_TRIGGERS, IPL_TRIGGER_NAME, NO_LONG_TASK_WINDOW, POPSTATE_MERGE_WINDOW, POPSTATE_TRIGGER } from '../constants'
12
13
  import { AjaxNode } from './ajax-node'
13
14
  import { InitialPageLoadInteraction } from './initial-page-load-interaction'
14
15
  import { Interaction } from './interaction'
16
+ import { EVENT_TYPES } from '../../../common/constants/events'
15
17
 
16
18
  export class Aggregate extends AggregateBase {
17
19
  static featureName = FEATURE_NAME
@@ -20,6 +22,10 @@ export class Aggregate extends AggregateBase {
20
22
 
21
23
  super.customAttributesAreSeparate = true
22
24
 
25
+ // Create obfuscators for browser interactions and nested AJAX requests
26
+ this.interactionObfuscator = new Obfuscator(agentRef, EVENT_TYPES.BI)
27
+ this.ajaxObfuscator = new Obfuscator(agentRef, EVENT_TYPES.AJAX)
28
+
23
29
  this.interactionsToHarvest = this.events
24
30
  this.domObserver = domObserver
25
31
 
@@ -86,7 +92,7 @@ export class Aggregate extends AggregateBase {
86
92
  let firstIxnStartTime
87
93
  const serializedIxnList = []
88
94
  for (const interaction of eventBuffer) {
89
- serializedIxnList.push(interaction.serialize(firstIxnStartTime, this.agentRef))
95
+ serializedIxnList.push(interaction.serialize(firstIxnStartTime, this.agentRef, this.interactionObfuscator, this.ajaxObfuscator))
90
96
  if (firstIxnStartTime === undefined) firstIxnStartTime = Math.floor(interaction.start) // careful not to match or overwrite on 0 value!
91
97
  }
92
98
  return `bel.7;${serializedIxnList.join(';')}`
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { navTimingValues } from '../../../common/timing/nav-timing'
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { globalScope, initialLocation } from '../../../common/constants/runtime'
@@ -133,11 +133,13 @@ export class Interaction extends BelNode {
133
133
  * Serializes (BEL) the interaction data for transmission.
134
134
  * @param {Number} firstStartTimeOfPayload timestamp
135
135
  * @param {Agent} agentRef Pass in the agent reference directly so that the event itself doesnt need to store the pointers and ruin the evaluation of the event size by including unused object references.
136
+ * @param {Obfuscator} interactionObfuscator Obfuscator for BrowserInteraction event type
137
+ * @param {Obfuscator} ajaxObfuscator Obfuscator for AjaxRequest event type (passed to child AjaxNodes)
136
138
  * @returns {String} A string that is the serialized representation of this interaction.
137
139
  */
138
- serialize (firstStartTimeOfPayload, agentRef) {
140
+ serialize (firstStartTimeOfPayload, agentRef, interactionObfuscator, ajaxObfuscator) {
139
141
  const isFirstIxnOfPayload = firstStartTimeOfPayload === undefined
140
- const addString = getAddStringContext(agentRef.runtime.obfuscator)
142
+ const addString = getAddStringContext(interactionObfuscator)
141
143
  const nodeList = []
142
144
  let ixnType
143
145
  if (this.trigger === IPL_TRIGGER_NAME) ixnType = INTERACTION_TYPE.INITIAL_PAGE_LOAD
@@ -170,7 +172,7 @@ export class Interaction extends BelNode {
170
172
  /* Querypack encoder+decoder quirkiness:
171
173
  - If first ixn node of payload is being processed, its children's start time must be offset by this node's start. (firstStartTime should be undefined.)
172
174
  - Else for subsequent ixns in the same payload, we go back to using that first ixn node's start to offset their children's start. */
173
- this.children.forEach(node => allAttachedNodes.push(node.serialize(isFirstIxnOfPayload ? this.start : firstStartTimeOfPayload, agentRef))) // recursively add the serialized string of every child of this (ixn) bel node
175
+ this.children.forEach(node => allAttachedNodes.push(node.serialize(isFirstIxnOfPayload ? this.start : firstStartTimeOfPayload, agentRef, ajaxObfuscator))) // recursively add the serialized string of every child of this (ixn) bel node
174
176
 
175
177
  fields[1] = numeric(allAttachedNodes.length)
176
178
  nodeList.push(fields)
@@ -4,7 +4,6 @@
4
4
  */
5
5
  import { FeatureBase } from './feature-base'
6
6
  import { drain } from '../../common/drain/drain'
7
- import { Obfuscator } from '../../common/util/obfuscate'
8
7
  import { FEATURE_NAMES } from '../../loaders/features/features'
9
8
  import { Harvester } from '../../common/harvest/harvester'
10
9
  import { EventBuffer } from './event-buffer'
@@ -12,6 +11,7 @@ import { handle } from '../../common/event-emitter/handle'
12
11
  import { SUPPORTABILITY_METRIC_CHANNEL } from '../metrics/constants'
13
12
  import { EventAggregator } from '../../common/aggregate/event-aggregator'
14
13
  import { MAX_PAYLOAD_SIZE, IDEAL_PAYLOAD_SIZE, SUPPORTS_REGISTERED_ENTITIES } from '../../common/constants/agent-constants'
14
+ import { warn } from '../../common/util/console'
15
15
 
16
16
  export class AggregateBase extends FeatureBase {
17
17
  /**
@@ -115,6 +115,7 @@ export class AggregateBase extends FeatureBase {
115
115
  }
116
116
  function buildOutput (ref) {
117
117
  return flagNames.map(flag => {
118
+ if (ref[flag] === undefined || ref[flag] === null) warn(71, flag)
118
119
  if (!ref[flag]) return 0
119
120
  return ref[flag]
120
121
  })
@@ -182,8 +183,7 @@ export class AggregateBase extends FeatureBase {
182
183
  * This method should run after checkConfiguration, which may reset the agent's info/runtime object that is used here.
183
184
  */
184
185
  doOnceForAllAggregate (agentRef) {
185
- if (!agentRef.runtime.obfuscator) agentRef.runtime.obfuscator = new Obfuscator(agentRef)
186
- this.obfuscator = agentRef.runtime.obfuscator
186
+ // Note: obfuscator is now created per-feature for their specific event types
187
187
 
188
188
  if (!agentRef.runtime.harvester) agentRef.runtime.harvester = new Harvester(agentRef)
189
189
  }
@@ -18,6 +18,7 @@ import { measure } from './measure'
18
18
  import { recordCustomEvent } from './recordCustomEvent'
19
19
  import { subscribeToPageUnload } from '../../common/window/page-visibility'
20
20
  import { findScriptTimings } from '../../common/v2/script-tracker'
21
+ import { trackMFEVitals } from '../../common/v2/mfe-vitals'
21
22
  import { generateRandomHexString } from '../../common/ids/unique-id'
22
23
 
23
24
  /**
@@ -83,6 +84,9 @@ function register (agentRef, target) {
83
84
 
84
85
  const timings = findScriptTimings()
85
86
 
87
+ // Track MFE vitals for this entity
88
+ const vitals = trackMFEVitals(target.id)
89
+
86
90
  const attrs = {}
87
91
 
88
92
  // Only define attributes getter if it doesn't already exist
@@ -149,7 +153,8 @@ function register (agentRef, target) {
149
153
  metadata: {
150
154
  get customAttributes () { return attrs },
151
155
  target,
152
- timings
156
+ timings,
157
+ vitals
153
158
  }
154
159
  }
155
160
 
@@ -177,9 +182,18 @@ function register (agentRef, target) {
177
182
  // only ever report the timings the first time this is called
178
183
  if (timings.reportedAt) return
179
184
  timings.reportedAt = now()
185
+
186
+ // Disconnect observers and capture current values, store in timings obj for visibility
187
+ vitals.disconnect()
188
+ timings.fcp = vitals.fcp
189
+ timings.lcp = vitals.lcp
190
+ timings.cls = vitals.cls
191
+ timings.inp = vitals.inp
192
+
180
193
  const timeToFetch = timings.fetchEnd - timings.fetchStart // fetchStart to fetchEnd
181
194
  const timeToExecute = timings.scriptEnd - timings.scriptStart // scriptStart to scriptEnd
182
- api.recordCustomEvent('MicroFrontEndTiming', {
195
+
196
+ const eventData = {
183
197
  assetUrl: timings.asset, // the url of the script that was registered, or undefined if it could not be determined (inline or no match)
184
198
  assetType: timings.type, // the type of asset that was associated with the timings, one of 'script', 'link' (if preloaded and found in the resource timing buffer), 'preload' (if preloaded but not found in the resource timing buffer), or "unknown" if it could not be determined
185
199
  timeAlive: timings.reportedAt - timings.registeredAt, // registeredAt to reportedAt
@@ -187,8 +201,14 @@ function register (agentRef, target) {
187
201
  timeToExecute, // scriptStart to scriptEnd
188
202
  timeToFetch, // fetchStart to fetchEnd
189
203
  timeToLoad: timeToFetch + timeToExecute, // fetch time and script time together
190
- timeToRegister: timings.registeredAt // timestamp when register() was called
191
- })
204
+ timeToRegister: timings.registeredAt, // timestamp when register() was called
205
+ 'nr.vitals.fcp': vitals.fcp || null, // FCP vital object with value and metadata
206
+ 'nr.vitals.lcp': vitals.lcp || null, // LCP vital object with value and metadata
207
+ 'nr.vitals.cls': vitals.cls || null, // CLS vital object with value and metadata
208
+ 'nr.vitals.inp': vitals.inp || null // INP vital object with value and metadata
209
+ }
210
+
211
+ api.recordCustomEvent('MicroFrontEndTiming', eventData)
192
212
  }
193
213
 
194
214
  /**