@newrelic/browser-agent 1.316.0 → 1.317.0-rc.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 (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
@@ -10,10 +10,11 @@ import { AggregateBase } from '../../utils/aggregate-base'
10
10
  import { warn } from '../../../common/util/console'
11
11
  import { now } from '../../../common/timing/now'
12
12
  import { registerHandler } from '../../../common/event-emitter/register-handler'
13
- import { applyFnToProps } from '../../../common/util/traverse'
13
+ import { Obfuscator } from '../../../common/util/obfuscate'
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 { EVENT_TYPES } from '../../../common/constants/events'
17
18
  import { getVersion2Attributes, getVersion2DuplicationAttributes, shouldDuplicate } from '../../../common/v2/utils'
18
19
 
19
20
  export class Aggregate extends AggregateBase {
@@ -24,6 +25,10 @@ export class Aggregate extends AggregateBase {
24
25
  super(agentRef, FEATURE_NAME)
25
26
  this.referrerUrl = (isBrowserScope && document.referrer) ? cleanURL(document.referrer) : undefined
26
27
 
28
+ // Create generic obfuscator (no specific event types) since this feature handles multiple event types
29
+ // Will check each event's eventType property at runtime against obfuscation rules
30
+ this.obfuscator = new Obfuscator(agentRef)
31
+
27
32
  this.waitForFlags(['ins']).then(([ins]) => {
28
33
  if (!ins) {
29
34
  this.blocked = true
@@ -46,7 +51,7 @@ export class Aggregate extends AggregateBase {
46
51
  registerHandler('api-addPageAction', (timestamp, name, attributes, target) => {
47
52
  this.addEvent({
48
53
  ...attributes,
49
- eventType: 'PageAction',
54
+ eventType: EVENT_TYPES.PA,
50
55
  timestamp: this.#toEpoch(timestamp),
51
56
  timeSinceLoad: timestamp / 1000,
52
57
  actionName: name,
@@ -73,7 +78,7 @@ export class Aggregate extends AggregateBase {
73
78
 
74
79
  aggregatedUserAction.targets.forEach(mfeTarget => {
75
80
  const userActionEvent = {
76
- eventType: 'UserAction',
81
+ eventType: EVENT_TYPES.UA,
77
82
  timestamp: this.#toEpoch(timeStamp),
78
83
  action: type,
79
84
  actionCount: aggregatedUserAction.count,
@@ -152,7 +157,7 @@ export class Aggregate extends AggregateBase {
152
157
  const detailObj = agentRef.init.performance.capture_detail ? createDetailAttrs(entry.detail) : {}
153
158
  this.addEvent({
154
159
  ...detailObj,
155
- eventType: 'BrowserPerformance',
160
+ eventType: EVENT_TYPES.BP,
156
161
  timestamp: this.#toEpoch(entry.startTime),
157
162
  entryName: entry.name,
158
163
  entryDuration: entry.duration,
@@ -217,7 +222,7 @@ export class Aggregate extends AggregateBase {
217
222
  this.reportSupportabilityMetric('Generic/Performance/Resource/Seen')
218
223
  const event = {
219
224
  ...entryObject,
220
- eventType: 'BrowserPerformance',
225
+ eventType: EVENT_TYPES.BP,
221
226
  timestamp: this.#toEpoch(entryObject.startTime),
222
227
  entryName: cleanURL(name),
223
228
  entryDuration: duration,
@@ -236,7 +241,7 @@ export class Aggregate extends AggregateBase {
236
241
 
237
242
  const event = {
238
243
  ...customAttributes,
239
- eventType: 'BrowserPerformance',
244
+ eventType: EVENT_TYPES.BP,
240
245
  timestamp: this.#toEpoch(start),
241
246
  entryName: n,
242
247
  entryDuration: duration,
@@ -250,7 +255,7 @@ export class Aggregate extends AggregateBase {
250
255
  registerHandler('ws-complete', (nrData) => {
251
256
  const event = {
252
257
  ...nrData,
253
- eventType: 'WebSocket',
258
+ eventType: EVENT_TYPES.WS,
254
259
  timestamp: this.#toEpoch(nrData.timestamp),
255
260
  openedAt: this.#toEpoch(nrData.openedAt),
256
261
  closedAt: this.#toEpoch(nrData.closedAt)
@@ -266,7 +271,7 @@ export class Aggregate extends AggregateBase {
266
271
  if (!agentRef.init.feature_flags.includes('no_spv')) {
267
272
  registerHandler('spv', (evt) => {
268
273
  this.addEvent({
269
- eventType: 'SecurityPolicyViolation',
274
+ eventType: EVENT_TYPES.SPV,
270
275
  timestamp: this.#toEpoch(evt.timeStamp),
271
276
  blockedUri: evt.blockedURI,
272
277
  documentUri: evt.documentURI,
@@ -335,7 +340,7 @@ export class Aggregate extends AggregateBase {
335
340
  }
336
341
 
337
342
  serializer (eventBuffer) {
338
- return applyFnToProps({ ins: eventBuffer }, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string')
343
+ return this.obfuscator.traverseAndObfuscateEvents({ ins: eventBuffer })
339
344
  }
340
345
 
341
346
  queryStringsBuilder () {
@@ -1,7 +1,8 @@
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
+ import { EVENT_TYPES } from '../../common/constants/events'
5
6
  import { FEATURE_NAMES } from '../../loaders/features/features'
6
7
 
7
8
  export const FEATURE_NAME = FEATURE_NAMES.genericEvents
@@ -14,7 +15,7 @@ export const RAGE_CLICK_THRESHOLD_MS = 1000
14
15
 
15
16
  export const FRUSTRATION_TIMEOUT_MS = 2000
16
17
 
17
- export const RESERVED_EVENT_TYPES = ['PageAction', 'UserAction', 'BrowserPerformance']
18
+ export const RESERVED_EVENT_TYPES = [EVENT_TYPES.PA, EVENT_TYPES.UA, EVENT_TYPES.BP]
18
19
 
19
20
  export const FEATURE_FLAGS = {
20
21
  RESOURCES: 'experimental.resources',
@@ -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
 
@@ -10,7 +10,7 @@ export function formatStackTrace (stackLines) {
10
10
  return truncateStackLines(stackLines).replace(stripNewlinesRegex, '')
11
11
  }
12
12
 
13
- // takes array of stack lines and returns string with top 50 and buttom 50 lines
13
+ // takes array of stack lines and returns string with top 50 and bottom 50 lines
14
14
  function truncateStackLines (stackLines) {
15
15
  var stackString
16
16
  if (stackLines.length > 100) {
@@ -26,5 +26,5 @@ function truncateStackLines (stackLines) {
26
26
 
27
27
  // truncates stack string to limit what is sent to backend
28
28
  export function truncateSize (stackString) {
29
- return (stackString.length > MAX_STACK_TRACE_LENGTH) ? stackString.substr(0, MAX_STACK_TRACE_LENGTH) : stackString
29
+ return stackString.length > MAX_STACK_TRACE_LENGTH ? stackString.substr(0, MAX_STACK_TRACE_LENGTH) : stackString
30
30
  }
@@ -17,11 +17,12 @@ import { FEATURE_NAME } from '../constants'
17
17
  import { FEATURE_NAMES } from '../../../loaders/features/features'
18
18
  import { AggregateBase } from '../../utils/aggregate-base'
19
19
  import { now } from '../../../common/timing/now'
20
- import { applyFnToProps } from '../../../common/util/traverse'
20
+ import { Obfuscator } from '../../../common/util/obfuscate'
21
21
  import { evaluateInternalError } from './internal-errors'
22
22
  import { getRegisteredTargetsFromFilename, getVersion2Attributes, getVersion2DuplicationAttributes, shouldDuplicate } from '../../../common/v2/utils'
23
23
  import { buildCauseString } from './cause-string'
24
24
  import { ShortCircuit } from '../../../common/util/short-circuit'
25
+ import { EVENT_TYPES } from '../../../common/constants/events'
25
26
 
26
27
  /**
27
28
  * @typedef {import('./compute-stack-trace.js').StackInfo} StackInfo
@@ -39,6 +40,10 @@ export class Aggregate extends AggregateBase {
39
40
  this.pageviewReported = {}
40
41
  this.errorOnPage = false
41
42
 
43
+ // Create obfuscators for each event type this feature handles
44
+ this.errorObfuscator = new Obfuscator(agentRef, EVENT_TYPES.JSE)
45
+ this.xhrObfuscator = new Obfuscator(agentRef, EVENT_TYPES.AJAX)
46
+
42
47
  register('err', this.processError.bind(this), this.featureName, this.ee)
43
48
  register('ierr', this.processError.bind(this), this.featureName, this.ee)
44
49
  register('returnJserror', (jsErrorEvent, softNavAttrs) => this.#storeJserrorForHarvest(jsErrorEvent, softNavAttrs), this.featureName, this.ee)
@@ -55,7 +60,11 @@ export class Aggregate extends AggregateBase {
55
60
  }
56
61
 
57
62
  serializer (aggregatorTypeToBucketsMap) {
58
- return applyFnToProps(aggregatorTypeToBucketsMap, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string')
63
+ return {
64
+ err: this.errorObfuscator.traverseAndObfuscateEvents(aggregatorTypeToBucketsMap.err),
65
+ ierr: this.errorObfuscator.traverseAndObfuscateEvents(aggregatorTypeToBucketsMap.ierr),
66
+ xhr: this.xhrObfuscator.traverseAndObfuscateEvents(aggregatorTypeToBucketsMap.xhr)
67
+ }
59
68
  }
60
69
 
61
70
  queryStringsBuilder (aggregatorTakeReturnedData) {
@@ -258,7 +267,7 @@ export class Aggregate extends AggregateBase {
258
267
  params.stack_trace = truncateSize(stackInfo.stackString)
259
268
  this.observedAt[bucketHash] = Math.floor(this.agentRef.runtime.timeKeeper.correctRelativeTimestamp(time))
260
269
  } else {
261
- params.browser_stack_hash = stringHashCode(stackInfo.stackString)
270
+ params.browser_stack_hash = stringHashCode(truncateSize(stackInfo.stackString))
262
271
  }
263
272
  params.releaseIds = stringify(this.agentRef.runtime.releaseIds)
264
273
 
@@ -9,11 +9,12 @@ import { AggregateBase } from '../../utils/aggregate-base'
9
9
  import { FEATURE_NAME, LOGGING_EVENT_EMITTER_CHANNEL, LOG_LEVELS, LOGGING_MODE } from '../constants'
10
10
  import { Log } from '../shared/log'
11
11
  import { isValidLogLevel } from '../shared/utils'
12
- import { applyFnToProps } from '../../../common/util/traverse'
12
+ import { Obfuscator } from '../../../common/util/obfuscate'
13
13
  import { SESSION_EVENT_TYPES, SESSION_EVENTS } from '../../../common/session/constants'
14
14
  import { ABORT_REASONS } from '../../session_replay/constants'
15
15
  import { canEnableSessionTracking } from '../../utils/feature-gates'
16
16
  import { getVersion2Attributes, getVersion2DuplicationAttributes, shouldDuplicate } from '../../../common/v2/utils'
17
+ import { EVENT_TYPES } from '../../../common/constants/events'
17
18
 
18
19
  const LOGGING_EVENT = 'Logging/Event/'
19
20
 
@@ -31,6 +32,9 @@ export class Aggregate extends AggregateBase {
31
32
  this.harvestOpts.raw = true
32
33
  super.customAttributesAreSeparate = true
33
34
 
35
+ // Create obfuscator for log entries
36
+ this.obfuscator = new Obfuscator(agentRef, EVENT_TYPES.LOG)
37
+
34
38
  // 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.
35
39
  this.ee.on(SESSION_EVENTS.RESET, () => {
36
40
  this.abort(ABORT_REASONS.RESET)
@@ -48,7 +52,7 @@ export class Aggregate extends AggregateBase {
48
52
  this.loggingMode ??= { auto, api } // likewise, don't want to overwrite the mode if it was set already
49
53
  const session = this.agentRef.runtime.session
50
54
  if (canEnableSessionTracking(agentRef.init) && session) {
51
- if (session.isNew) this.#syncWithSessionManager()
55
+ if (session.state.loggingMode === null || session.state.logApiMode === null) this.#writeToStorage(this.loggingMode)
52
56
  else updateLocalLoggingMode(session.state.loggingMode, session.state.logApiMode)
53
57
  }
54
58
  if (this.loggingMode.auto === LOGGING_MODE.OFF && this.loggingMode.api === LOGGING_MODE.OFF) {
@@ -119,7 +123,7 @@ export class Aggregate extends AggregateBase {
119
123
  common: {
120
124
  /** Attributes in the `common` section are added to `all` logs generated in the payload */
121
125
  attributes: {
122
- ...(applyFnToProps(this.agentRef.info.jsAttributes, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string')),
126
+ ...(this.obfuscator.traverseAndObfuscateEvents(this.agentRef.info.jsAttributes)),
123
127
  ...(this.harvestEndpointVersion === 1 && {
124
128
  'entity.guid': this.agentRef.runtime.appMetadata.agents[0].entityGuid,
125
129
  appId: this.agentRef.info.applicationID
@@ -139,10 +143,7 @@ export class Aggregate extends AggregateBase {
139
143
  }
140
144
  },
141
145
  /** logs section contains individual unique log entries */
142
- logs: applyFnToProps(
143
- eventBuffer,
144
- this.obfuscator.obfuscateString.bind(this.obfuscator), 'string'
145
- )
146
+ logs: this.obfuscator.traverseAndObfuscateEvents(eventBuffer)
146
147
  }]
147
148
  }
148
149
 
@@ -162,14 +163,14 @@ export class Aggregate extends AggregateBase {
162
163
  auto: LOGGING_MODE.OFF,
163
164
  api: LOGGING_MODE.OFF
164
165
  }
165
- this.#syncWithSessionManager()
166
+ this.#writeToStorage(this.loggingMode)
166
167
  this.deregisterDrain()
167
168
  }
168
169
 
169
- #syncWithSessionManager () {
170
+ #writeToStorage (logModes) {
170
171
  this.agentRef.runtime.session?.write({
171
- loggingMode: this.loggingMode.auto,
172
- logApiMode: this.loggingMode.api
172
+ loggingMode: logModes.auto,
173
+ logApiMode: logModes.api
173
174
  })
174
175
  }
175
176
  }
@@ -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 { registerHandler } from '../../../common/event-emitter/register-handler'
@@ -12,6 +12,7 @@ import { isBrowserScope, isWorkerScope } from '../../../common/constants/runtime
12
12
  import { AggregateBase } from '../../utils/aggregate-base'
13
13
  import { isIFrameWindow } from '../../../common/dom/iframe'
14
14
  import { evaluateHarvestMetadata } from './harvest-metadata'
15
+ import { Obfuscator } from '../../../common/util/obfuscate'
15
16
  // import { WEBSOCKET_TAG } from '../../../common/wrap/wrap-websocket'
16
17
  // import { handleWebsocketEvents } from './websocket-detection'
17
18
 
@@ -21,6 +22,9 @@ export class Aggregate extends AggregateBase {
21
22
  super(agentRef, FEATURE_NAME)
22
23
  this.harvestOpts.aggregatorTypes = ['cm', 'sm'] // the types in EventAggregator this feature cares about
23
24
 
25
+ // Create a generic obfuscator for checking if rules are configured
26
+ this.obfuscator = new Obfuscator(agentRef)
27
+
24
28
  /** all the harvest metadata metrics need to be evaluated simulataneously at unload time so just temporarily buffer them and dont make SMs immediately from the data */
25
29
  this.harvestMetadata = {}
26
30
  this.harvestOpts.beforeUnload = () => {
@@ -15,11 +15,13 @@ import { firstPaint } from '../../../common/vitals/first-paint'
15
15
  import { timeToFirstByte } from '../../../common/vitals/time-to-first-byte'
16
16
  import { now } from '../../../common/timing/now'
17
17
  import { TimeKeeper } from '../../../common/timing/time-keeper'
18
- import { applyFnToProps } from '../../../common/util/traverse'
19
- import { send } from '../../../common/harvest/harvester'
18
+ import { send } from '../../../common/harvest/send'
19
+ import { canEnableSessionTracking } from '../../utils/feature-gates'
20
+ import { Obfuscator } from '../../../common/util/obfuscate'
20
21
  import { FEATURE_NAMES, FEATURE_TO_ENDPOINT } from '../../../loaders/features/features'
21
22
  import { getSubmitMethod } from '../../../common/util/submit-data'
22
23
  import { webdriverDetected } from '../../../common/util/webdriver-detection'
24
+ import { EVENT_TYPES } from '../../../common/constants/events'
23
25
 
24
26
  export class Aggregate extends AggregateBase {
25
27
  static featureName = CONSTANTS.FEATURE_NAME
@@ -27,6 +29,7 @@ export class Aggregate extends AggregateBase {
27
29
  constructor (agentRef) {
28
30
  super(agentRef, CONSTANTS.FEATURE_NAME)
29
31
 
32
+ this.isSessionTrackingEnabled = canEnableSessionTracking(agentRef.init) && !!agentRef.runtime.session
30
33
  this.sentRum = false // flag to facilitate calling sendRum() once externally (by the consent API in agent-session.js)
31
34
 
32
35
  this.timeToFirstByte = 0
@@ -34,9 +37,23 @@ export class Aggregate extends AggregateBase {
34
37
  this.firstByteToDomContent = 0 // our "dom processing" duration
35
38
  this.retries = 0
36
39
 
40
+ // Create obfuscator for page view events
41
+ this.obfuscator = new Obfuscator(agentRef, EVENT_TYPES.PVE)
42
+
37
43
  agentRef.runtime.timeKeeper = new TimeKeeper(agentRef.runtime.session)
38
44
 
39
45
  if (isBrowserScope) {
46
+ const cached = this.isSessionTrackingEnabled && agentRef.runtime.session?.state.cachedRumResponse
47
+ if (cached) {
48
+ const { app: cachedApp, ...cachedFlags } = cached
49
+
50
+ // set the agent runtime objects that require the rum response or entity guid
51
+ if (!Object.keys(this.agentRef.runtime.appMetadata).length) agentRef.runtime.appMetadata = cachedApp
52
+ this.drain()
53
+ agentRef.runtime.harvester.startTimer()
54
+ activateFeatures(cachedFlags, agentRef)
55
+ }
56
+
40
57
  timeToFirstByte.subscribe(({ value, attrs }) => {
41
58
  const navEntry = attrs.navigationEntry
42
59
  this.timeToFirstByte = Math.max(value, this.timeToFirstByte)
@@ -81,9 +98,9 @@ export class Aggregate extends AggregateBase {
81
98
  at: info.atts
82
99
  }
83
100
 
84
- if (this.agentRef.runtime.session) queryParameters.fsh = Number(this.agentRef.runtime.session.isNew) // "first session harvest" aka RUM request or PageView event of a session
101
+ if (this.agentRef.runtime.session) queryParameters.fsh = Number(!this.agentRef.runtime.session.state.cachedRumResponse)
85
102
 
86
- let body = applyFnToProps({ ja: { ...customAttributes, webdriverDetected } }, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string')
103
+ let body = this.obfuscator.traverseAndObfuscateEvents({ ja: { ...customAttributes, webdriverDetected } })
87
104
 
88
105
  if (globalScope.performance) {
89
106
  const navTimingEntry = getNavigationEntry()
@@ -127,6 +144,7 @@ export class Aggregate extends AggregateBase {
127
144
  postHarvestCleanup ({ sent, status, responseText, xhr, retry }) {
128
145
  const rumEndTime = now()
129
146
  let app, flags
147
+ const hasCachedRumResponse = !!this.agentRef.runtime.session?.state.cachedRumResponse
130
148
  try {
131
149
  ({ app, ...flags } = JSON.parse(responseText))
132
150
  } catch (error) {
@@ -134,16 +152,24 @@ export class Aggregate extends AggregateBase {
134
152
  warn(53, error)
135
153
  }
136
154
 
137
- super.postHarvestCleanup({ sent, retry }) // this will set isRetrying & re-buffer the body if request is to be retried
155
+ const shouldCacheResponse = this.isSessionTrackingEnabled && !hasCachedRumResponse && !!app
156
+ if (hasCachedRumResponse) {
157
+ let { app: cachedApp, ...cachedFlags } = this.agentRef.runtime.session.state.cachedRumResponse
158
+ app ??= cachedApp
159
+ flags = cachedFlags
160
+ }
161
+
162
+ super.postHarvestCleanup({ sent, retry: retry && !hasCachedRumResponse }) // this will set isRetrying & re-buffer the body if request is to be retried
163
+
138
164
  if (this.isRetrying && this.retries++ < 1) { // Only retry once
139
165
  setTimeout(() => this.agentRef.runtime.harvester.triggerHarvestFor(this, {
140
166
  sendEmptyBody: true
141
167
  }), 5000) // Retry sending the RUM event after 5 seconds
142
168
  return
143
169
  }
170
+
144
171
  if (status >= 400 || status === 0) {
145
172
  warn(18, status)
146
- this.blocked = true
147
173
 
148
174
  // Get estimated payload size of our backlog
149
175
  const textEncoder = new TextEncoder()
@@ -155,16 +181,24 @@ export class Aggregate extends AggregateBase {
155
181
  }, 0)
156
182
  const BCSError = 'BCS/Error/'
157
183
  // Send SMs about failed RUM request
158
- const body = {
159
- sm: [{
160
- params: {
161
- name: BCSError + status
162
- },
163
- stats: {
164
- c: 1
165
- }
184
+ const sm = [{
185
+ params: {
186
+ name: BCSError + status
166
187
  },
167
- {
188
+ stats: {
189
+ c: 1
190
+ }
191
+ }, {
192
+ params: {
193
+ name: BCSError + 'Duration/Ms'
194
+ },
195
+ stats: {
196
+ c: 1,
197
+ t: rumEndTime - this.rumStartTime
198
+ }
199
+ }]
200
+ if (!hasCachedRumResponse) {
201
+ sm.push({
168
202
  params: {
169
203
  name: BCSError + 'Dropped/Bytes'
170
204
  },
@@ -172,28 +206,23 @@ export class Aggregate extends AggregateBase {
172
206
  c: 1,
173
207
  t: payloadSize
174
208
  }
175
- },
176
- {
177
- params: {
178
- name: BCSError + 'Duration/Ms'
179
- },
180
- stats: {
181
- c: 1,
182
- t: rumEndTime - this.rumStartTime
183
- }
184
- }]
209
+ })
185
210
  }
186
211
 
187
212
  send(this.agentRef, {
188
213
  endpoint: FEATURE_TO_ENDPOINT[FEATURE_NAMES.metrics],
189
- payload: { body },
214
+ payload: { body: { sm } },
190
215
  submitMethod: getSubmitMethod(),
191
216
  featureName: FEATURE_NAMES.metrics
192
217
  })
193
218
 
194
- // Adding retry logic for the rum call will be a separate change; this.blocked will need to be changed since that prevents another triggerHarvestFor()
195
- this.ee.abort()
196
- return
219
+ if (!hasCachedRumResponse) {
220
+ this.blocked = true
221
+ this.ee.abort()
222
+ return
223
+ }
224
+ } else if (shouldCacheResponse) {
225
+ this.agentRef.runtime.session.write({ cachedRumResponse: { app, ...flags } })
197
226
  }
198
227
 
199
228
  try {
@@ -9,6 +9,7 @@ import { handle } from '../../../common/event-emitter/handle'
9
9
  import { FEATURE_NAME } from '../constants'
10
10
  import { FEATURE_NAMES } from '../../../loaders/features/features'
11
11
  import { AggregateBase } from '../../utils/aggregate-base'
12
+ import { Obfuscator } from '../../../common/util/obfuscate'
12
13
  import { cumulativeLayoutShift } from '../../../common/vitals/cumulative-layout-shift'
13
14
  import { firstContentfulPaint } from '../../../common/vitals/first-contentful-paint'
14
15
  import { firstPaint } from '../../../common/vitals/first-paint'
@@ -21,6 +22,7 @@ import { eventOrigin } from '../../../common/util/event-origin'
21
22
  import { loadTime } from '../../../common/vitals/load-time'
22
23
  import { webdriverDetected } from '../../../common/util/webdriver-detection'
23
24
  import { cleanURL } from '../../../common/url/clean-url'
25
+ import { EVENT_TYPES } from '../../../common/constants/events'
24
26
 
25
27
  export class Aggregate extends AggregateBase {
26
28
  static featureName = FEATURE_NAME
@@ -36,6 +38,9 @@ export class Aggregate extends AggregateBase {
36
38
 
37
39
  super.customAttributesAreSeparate = true
38
40
 
41
+ // Create obfuscator for page view timing events
42
+ this.obfuscator = new Obfuscator(agentRef, EVENT_TYPES.PVT)
43
+
39
44
  registerHandler('docHidden', msTimestamp => this.endCurrentSession(msTimestamp), this.featureName, this.ee)
40
45
  // Add the time of _window pagehide event_ firing to the next PVT harvest == NRDB windowUnload attr:
41
46
  registerHandler('winPagehide', msTimestamp => this.addTiming('unload', msTimestamp, null), this.featureName, this.ee)
@@ -141,7 +146,7 @@ export class Aggregate extends AggregateBase {
141
146
  // serialize array of timing data
142
147
  serializer (eventBuffer) {
143
148
  if (!eventBuffer?.length) return ''
144
- var addString = getAddStringContext(this.agentRef.runtime.obfuscator)
149
+ var addString = getAddStringContext(this.obfuscator)
145
150
 
146
151
  var payload = 'bel.6;'
147
152
 
@@ -19,17 +19,23 @@ import { stringify } from '../../../common/util/stringify'
19
19
  import { stylesheetEvaluator } from '../shared/stylesheet-evaluator'
20
20
  import { now } from '../../../common/timing/now'
21
21
  import { MAX_PAYLOAD_SIZE } from '../../../common/constants/agent-constants'
22
+ import { EVENT_TYPES } from '../../../common/constants/events'
22
23
  import { cleanURL } from '../../../common/url/clean-url'
23
24
  import { canEnableSessionTracking } from '../../utils/feature-gates'
24
25
  import { PAUSE_REPLAY } from '../../../loaders/api/constants'
26
+ import { Obfuscator } from '../../../common/util/obfuscate'
25
27
 
26
28
  export class Aggregate extends AggregateBase {
27
29
  static featureName = FEATURE_NAME
28
- mode = MODE.OFF
30
+ mode = null
29
31
 
30
32
  // pass the recorder into the aggregator
31
33
  constructor (agentRef, args) {
32
34
  super(agentRef, FEATURE_NAME)
35
+
36
+ // Create obfuscator for session replay query params
37
+ this.obfuscator = new Obfuscator(agentRef, EVENT_TYPES.SR)
38
+
33
39
  /** Set once the recorder has fully initialized after flag checks and sampling */
34
40
  this.initialized = false
35
41
  /** Set once the feature has been "aborted" to prevent other side-effects from continuing */
@@ -68,7 +74,7 @@ export class Aggregate extends AggregateBase {
68
74
  if (!this.recorder) return
69
75
  // if the mode changed on a different tab, it needs to update this instance to match
70
76
  this.mode = agentRef.runtime.session.state.sessionReplayMode
71
- if (!this.initialized || this.mode === MODE.OFF) return
77
+ if (!this.initialized || this.mode === MODE.OFF || !this.mode) return
72
78
  this.recorder?.startRecording(TRIGGERS.RESUME, this.mode)
73
79
  })
74
80
 
@@ -91,6 +97,8 @@ export class Aggregate extends AggregateBase {
91
97
  this.waitForFlags(['srs', 'sr']).then(([srMode, entitled]) => {
92
98
  this.entitled = !!entitled
93
99
  if (!this.entitled) {
100
+ this.mode = MODE.OFF
101
+ this.#writeToStorage({ sessionReplayMode: this.mode })
94
102
  this.deregisterDrain()
95
103
  if (this.agentRef.runtime.isRecording) {
96
104
  this.abort(ABORT_REASONS.ENTITLEMENTS)
@@ -100,7 +108,7 @@ export class Aggregate extends AggregateBase {
100
108
  }
101
109
  this.initializeRecording(srMode).then(() => { this.drain() })
102
110
  }).then(() => {
103
- if (this.mode === MODE.OFF) {
111
+ if (!this.mode) {
104
112
  this.recorder?.stopRecording() // stop any conservative preload recording launched by instrument
105
113
  while (this.recorder?.getEvents().events.length) this.recorder?.clearBuffer?.()
106
114
  }
@@ -137,7 +145,7 @@ export class Aggregate extends AggregateBase {
137
145
  // if the error was noticed AFTER the recorder was already imported....
138
146
  if (this.recorder && this.initialized) {
139
147
  if (!this.agentRef.runtime.isRecording) this.recorder.startRecording(TRIGGERS.SWITCH_TO_FULL, this.mode) // off --> full
140
- this.syncWithSessionManager({ sessionReplayMode: this.mode })
148
+ this.#writeToStorage({ sessionReplayMode: this.mode })
141
149
  } else {
142
150
  this.initializeRecording(MODE.FULL, true, TRIGGERS.SWITCH_TO_FULL)
143
151
  }
@@ -165,35 +173,36 @@ export class Aggregate extends AggregateBase {
165
173
 
166
174
  if (this.recorder?.trigger === TRIGGERS.API && this.agentRef.runtime.isRecording) {
167
175
  this.mode = MODE.FULL
168
- } else if (!session.isNew && !ignoreSession) { // inherit the mode of the existing session
176
+ } else if (session.state.sessionReplayMode !== null && !ignoreSession) { // inherit the mode of the existing session
169
177
  this.mode = session.state.sessionReplayMode
170
178
  } else {
171
179
  // The session is new... determine the mode the new session should start in
172
180
  this.mode = srMode
173
181
  }
174
- // If off, then don't record (early return)
175
- if (this.mode === MODE.OFF) return
176
182
 
177
- try {
178
- /** will return a recorder instance if already imported, otherwise, will fetch the recorder and initialize it */
179
- this.recorder ??= await this.instrumentClass.importRecorder()
180
- } catch (err) {
181
- /** if the recorder fails to import, abort the feature */
182
- return this.abort(ABORT_REASONS.IMPORT, err)
183
- }
184
-
185
- // If an error was noticed before the mode could be set (like in the early lifecycle of the page), immediately set to FULL mode
186
- if (this.mode === MODE.ERROR && this.instrumentClass.errorNoticed) { this.mode = MODE.FULL }
183
+ if (this.mode !== MODE.OFF) {
184
+ try {
185
+ /** will return a recorder instance if already imported, otherwise, will fetch the recorder and initialize it */
186
+ this.recorder ??= await this.instrumentClass.importRecorder()
187
+ } catch (err) {
188
+ /** if the recorder fails to import, abort the feature */
189
+ return this.abort(ABORT_REASONS.IMPORT, err)
190
+ }
187
191
 
188
- // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
189
- // ERROR mode will do this until an error is thrown, and then switch into FULL mode.
190
- // The makeHarvestPayload should ensure that no payload is returned if we're not in FULL mode...
192
+ // If an error was noticed before the mode could be set (like in the early lifecycle of the page), immediately set to FULL mode
193
+ if (this.mode === MODE.ERROR && this.instrumentClass.errorNoticed) {
194
+ this.mode = MODE.FULL
195
+ }
191
196
 
192
- await this.prepUtils()
197
+ // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
198
+ // ERROR mode will do this until an error is thrown, and then switch into FULL mode.
199
+ // The makeHarvestPayload should ensure that no payload is returned if we're not in FULL mode...
193
200
 
194
- if (!this.agentRef.runtime.isRecording) this.recorder.startRecording(trigger, this.mode)
201
+ await this.prepUtils()
195
202
 
196
- this.syncWithSessionManager({ sessionReplayMode: this.mode })
203
+ if (!this.agentRef.runtime.isRecording) this.recorder.startRecording(trigger, this.mode)
204
+ }
205
+ this.#writeToStorage({ sessionReplayMode: this.mode })
197
206
  }
198
207
 
199
208
  async prepUtils () {
@@ -240,7 +249,7 @@ export class Aggregate extends AggregateBase {
240
249
  return
241
250
  }
242
251
 
243
- if (!this.agentRef.runtime.session.state.sessionReplaySentFirstChunk) this.syncWithSessionManager({ sessionReplaySentFirstChunk: true })
252
+ if (!this.agentRef.runtime.session.state.sessionReplaySentFirstChunk) this.#writeToStorage({ sessionReplaySentFirstChunk: true })
244
253
  this.recorder.clearBuffer()
245
254
 
246
255
  if (!this.agentRef.runtime.session.state.traceHarvestStarted) {
@@ -303,7 +312,7 @@ export class Aggregate extends AggregateBase {
303
312
  return {
304
313
  qs: {
305
314
  browser_monitoring_key: this.agentRef.info.licenseKey,
306
- type: 'SessionReplay',
315
+ type: EVENT_TYPES.SR,
307
316
  app_id: this.agentRef.info.applicationID,
308
317
  protocol_version: '0',
309
318
  timestamp: firstTimestamp,
@@ -364,7 +373,7 @@ export class Aggregate extends AggregateBase {
364
373
  if (forceHarvest) this.agentRef.runtime.harvester.triggerHarvestFor(this)
365
374
  this.mode = MODE.OFF
366
375
  this.recorder?.stopRecording?.()
367
- this.syncWithSessionManager({ sessionReplayMode: this.mode })
376
+ this.#writeToStorage({ sessionReplayMode: this.mode })
368
377
  }
369
378
 
370
379
  /** Abort the feature, once aborted it will not resume */
@@ -374,12 +383,12 @@ export class Aggregate extends AggregateBase {
374
383
  this.blocked = true
375
384
  this.mode = MODE.OFF
376
385
  this.recorder?.stopRecording?.()
377
- this.syncWithSessionManager({ sessionReplayMode: this.mode })
386
+ this.#writeToStorage({ sessionReplayMode: this.mode })
378
387
  this.recorder?.clearTimestamps?.()
379
388
  while (this.recorder?.getEvents().events.length) this.recorder?.clearBuffer?.()
380
389
  }
381
390
 
382
- syncWithSessionManager (state = {}) {
391
+ #writeToStorage (state = {}) {
383
392
  if (this.isSessionTrackingEnabled) {
384
393
  this.agentRef.runtime.session.write(state)
385
394
  }