@newrelic/browser-agent 1.316.0 → 1.317.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 (140) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/cjs/common/config/init-types.js +1 -0
  3. package/dist/cjs/common/config/init.js +6 -4
  4. package/dist/cjs/common/constants/env.cdn.js +2 -2
  5. package/dist/cjs/common/constants/env.npm.js +2 -2
  6. package/dist/cjs/common/constants/events.js +26 -0
  7. package/dist/cjs/common/harvest/harvester.js +12 -4
  8. package/dist/cjs/common/payloads/payloads.js +127 -0
  9. package/dist/cjs/common/serialize/bel-serializer.js +4 -3
  10. package/dist/cjs/common/util/data-size.js +2 -2
  11. package/dist/cjs/common/util/obfuscate.js +147 -3
  12. package/dist/cjs/common/wrap/wrap-websocket.js +2 -1
  13. package/dist/cjs/common/wrap/wrap-xhr.js +1 -1
  14. package/dist/cjs/features/ajax/aggregate/gql.js +40 -1
  15. package/dist/cjs/features/ajax/aggregate/index.js +51 -13
  16. package/dist/cjs/features/ajax/constants.js +6 -1
  17. package/dist/cjs/features/ajax/instrument/index.js +105 -17
  18. package/dist/cjs/features/generic_events/aggregate/index.js +15 -10
  19. package/dist/cjs/features/generic_events/constants.js +3 -2
  20. package/dist/cjs/features/jserrors/aggregate/format-stack-trace.js +2 -2
  21. package/dist/cjs/features/jserrors/aggregate/index.js +12 -3
  22. package/dist/cjs/features/logging/aggregate/index.js +7 -3
  23. package/dist/cjs/features/metrics/aggregate/index.js +5 -1
  24. package/dist/cjs/features/page_view_event/aggregate/index.js +7 -3
  25. package/dist/cjs/features/page_view_timing/aggregate/index.js +6 -1
  26. package/dist/cjs/features/session_replay/aggregate/index.js +7 -1
  27. package/dist/cjs/features/session_trace/aggregate/index.js +6 -2
  28. package/dist/cjs/features/soft_navigations/aggregate/ajax-node.js +36 -5
  29. package/dist/cjs/features/soft_navigations/aggregate/index.js +7 -1
  30. package/dist/cjs/features/soft_navigations/aggregate/initial-page-load-interaction.js +1 -1
  31. package/dist/cjs/features/soft_navigations/aggregate/interaction.js +6 -4
  32. package/dist/cjs/features/utils/aggregate-base.js +2 -3
  33. package/dist/esm/common/config/init-types.js +1 -0
  34. package/dist/esm/common/config/init.js +3 -1
  35. package/dist/esm/common/constants/env.cdn.js +2 -2
  36. package/dist/esm/common/constants/env.npm.js +2 -2
  37. package/dist/esm/common/constants/events.js +20 -0
  38. package/dist/esm/common/harvest/harvester.js +12 -4
  39. package/dist/esm/common/payloads/payloads.js +118 -0
  40. package/dist/esm/common/serialize/bel-serializer.js +4 -3
  41. package/dist/esm/common/util/data-size.js +2 -2
  42. package/dist/esm/common/util/obfuscate.js +147 -3
  43. package/dist/esm/common/wrap/wrap-websocket.js +2 -1
  44. package/dist/esm/common/wrap/wrap-xhr.js +1 -1
  45. package/dist/esm/features/ajax/aggregate/gql.js +39 -1
  46. package/dist/esm/features/ajax/aggregate/index.js +52 -14
  47. package/dist/esm/features/ajax/constants.js +5 -0
  48. package/dist/esm/features/ajax/instrument/index.js +106 -18
  49. package/dist/esm/features/generic_events/aggregate/index.js +15 -10
  50. package/dist/esm/features/generic_events/constants.js +3 -2
  51. package/dist/esm/features/jserrors/aggregate/format-stack-trace.js +2 -2
  52. package/dist/esm/features/jserrors/aggregate/index.js +12 -3
  53. package/dist/esm/features/logging/aggregate/index.js +7 -3
  54. package/dist/esm/features/metrics/aggregate/index.js +5 -1
  55. package/dist/esm/features/page_view_event/aggregate/index.js +7 -3
  56. package/dist/esm/features/page_view_timing/aggregate/index.js +6 -1
  57. package/dist/esm/features/session_replay/aggregate/index.js +7 -1
  58. package/dist/esm/features/session_trace/aggregate/index.js +6 -2
  59. package/dist/esm/features/soft_navigations/aggregate/ajax-node.js +36 -5
  60. package/dist/esm/features/soft_navigations/aggregate/index.js +7 -1
  61. package/dist/esm/features/soft_navigations/aggregate/initial-page-load-interaction.js +1 -1
  62. package/dist/esm/features/soft_navigations/aggregate/interaction.js +6 -4
  63. package/dist/esm/features/utils/aggregate-base.js +2 -3
  64. package/dist/tsconfig.tsbuildinfo +1 -1
  65. package/dist/types/common/config/init-types.d.ts +4 -0
  66. package/dist/types/common/config/init-types.d.ts.map +1 -1
  67. package/dist/types/common/config/init.d.ts.map +1 -1
  68. package/dist/types/common/constants/events.d.ts +16 -0
  69. package/dist/types/common/constants/events.d.ts.map +1 -0
  70. package/dist/types/common/harvest/harvester.d.ts +4 -1
  71. package/dist/types/common/harvest/harvester.d.ts.map +1 -1
  72. package/dist/types/common/payloads/payloads.d.ts +42 -0
  73. package/dist/types/common/payloads/payloads.d.ts.map +1 -0
  74. package/dist/types/common/serialize/bel-serializer.d.ts +1 -1
  75. package/dist/types/common/serialize/bel-serializer.d.ts.map +1 -1
  76. package/dist/types/common/util/obfuscate.d.ts +23 -2
  77. package/dist/types/common/util/obfuscate.d.ts.map +1 -1
  78. package/dist/types/common/wrap/wrap-websocket.d.ts.map +1 -1
  79. package/dist/types/features/ajax/aggregate/gql.d.ts +8 -0
  80. package/dist/types/features/ajax/aggregate/gql.d.ts.map +1 -1
  81. package/dist/types/features/ajax/aggregate/index.d.ts +2 -0
  82. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  83. package/dist/types/features/ajax/constants.d.ts +5 -0
  84. package/dist/types/features/ajax/constants.d.ts.map +1 -1
  85. package/dist/types/features/ajax/instrument/index.d.ts.map +1 -1
  86. package/dist/types/features/generic_events/aggregate/index.d.ts +3 -1
  87. package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
  88. package/dist/types/features/generic_events/constants.d.ts.map +1 -1
  89. package/dist/types/features/jserrors/aggregate/index.d.ts +8 -1
  90. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  91. package/dist/types/features/logging/aggregate/index.d.ts +3 -1
  92. package/dist/types/features/logging/aggregate/index.d.ts.map +1 -1
  93. package/dist/types/features/metrics/aggregate/index.d.ts +2 -0
  94. package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
  95. package/dist/types/features/page_view_event/aggregate/index.d.ts +2 -0
  96. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  97. package/dist/types/features/page_view_timing/aggregate/index.d.ts +2 -0
  98. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  99. package/dist/types/features/session_replay/aggregate/index.d.ts +2 -0
  100. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  101. package/dist/types/features/session_trace/aggregate/index.d.ts +2 -0
  102. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  103. package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts +6 -1
  104. package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts.map +1 -1
  105. package/dist/types/features/soft_navigations/aggregate/index.d.ts +3 -0
  106. package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -1
  107. package/dist/types/features/soft_navigations/aggregate/interaction.d.ts +3 -1
  108. package/dist/types/features/soft_navigations/aggregate/interaction.d.ts.map +1 -1
  109. package/dist/types/features/utils/aggregate-base.d.ts +0 -1
  110. package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
  111. package/package.json +2 -2
  112. package/src/common/config/init-types.js +1 -0
  113. package/src/common/config/init.js +2 -1
  114. package/src/common/constants/events.js +20 -0
  115. package/src/common/harvest/harvester.js +11 -4
  116. package/src/common/payloads/payloads.js +135 -0
  117. package/src/common/serialize/bel-serializer.js +4 -3
  118. package/src/common/util/data-size.js +2 -2
  119. package/src/common/util/obfuscate.js +154 -3
  120. package/src/common/wrap/wrap-websocket.js +2 -1
  121. package/src/common/wrap/wrap-xhr.js +1 -1
  122. package/src/features/ajax/aggregate/gql.js +42 -1
  123. package/src/features/ajax/aggregate/index.js +43 -13
  124. package/src/features/ajax/constants.js +5 -1
  125. package/src/features/ajax/instrument/index.js +114 -18
  126. package/src/features/generic_events/aggregate/index.js +14 -9
  127. package/src/features/generic_events/constants.js +3 -2
  128. package/src/features/jserrors/aggregate/format-stack-trace.js +3 -3
  129. package/src/features/jserrors/aggregate/index.js +12 -3
  130. package/src/features/logging/aggregate/index.js +7 -6
  131. package/src/features/metrics/aggregate/index.js +5 -1
  132. package/src/features/page_view_event/aggregate/index.js +6 -2
  133. package/src/features/page_view_timing/aggregate/index.js +6 -1
  134. package/src/features/session_replay/aggregate/index.js +7 -1
  135. package/src/features/session_trace/aggregate/index.js +6 -2
  136. package/src/features/soft_navigations/aggregate/ajax-node.js +26 -5
  137. package/src/features/soft_navigations/aggregate/index.js +7 -1
  138. package/src/features/soft_navigations/aggregate/initial-page-load-interaction.js +1 -1
  139. package/src/features/soft_navigations/aggregate/interaction.js +6 -4
  140. package/src/features/utils/aggregate-base.js +1 -3
@@ -14,7 +14,7 @@ import { parseUrl } from '../../../common/url/parse-url'
14
14
  import { DT } from './distributed-tracing'
15
15
  import { responseSizeFromXhr } from './response-size'
16
16
  import { InstrumentBase } from '../../utils/instrument-base'
17
- import { FEATURE_NAME } from '../constants'
17
+ import { CAPTURE_PAYLOAD_SETTINGS, FEATURE_NAME } from '../constants'
18
18
  import { FEATURE_NAMES } from '../../../loaders/features/features'
19
19
  import { SUPPORTABILITY_METRIC } from '../../metrics/constants'
20
20
  import { now } from '../../../common/timing/now'
@@ -27,6 +27,7 @@ var handlersLen = handlers.length
27
27
  var origRequest = gosNREUMOriginals().o.REQ
28
28
  var origXHR = gosNREUMOriginals().o.XHR
29
29
  const NR_CAT_HEADER = 'X-NewRelic-App-Data'
30
+ const INTERNAL_ERROR = 'internal-error'
30
31
 
31
32
  export class Instrument extends InstrumentBase {
32
33
  static featureName = FEATURE_NAME
@@ -62,16 +63,18 @@ export class Instrument extends InstrumentBase {
62
63
  wrapFetch(this.ee, agentRef)
63
64
  wrapXhr(this.ee, agentRef)
64
65
  subscribeToEvents(agentRef, this.ee, this.handler, this.dt)
65
-
66
66
  this.importAggregator(agentRef, () => import(/* webpackChunkName: "ajax-aggregate" */ '../aggregate/index.js'))
67
67
  }
68
68
  }
69
69
 
70
70
  function subscribeToEvents (agentRef, ee, handler, dt) {
71
+ const shouldInterceptPayloads = [CAPTURE_PAYLOAD_SETTINGS.ALL, CAPTURE_PAYLOAD_SETTINGS.FAILURES].includes(agentRef.init.ajax?.capture_payloads)
72
+
71
73
  ee.on('new-xhr', onNewXhr)
72
74
  ee.on('open-xhr-start', onOpenXhrStart)
73
75
  ee.on('open-xhr-end', onOpenXhrEnd)
74
76
  ee.on('send-xhr-start', onSendXhrStart)
77
+ ee.on('setRequestHeader-xhr-start', onSetRequestHeader)
75
78
  ee.on('xhr-cb-time', onXhrCbTime)
76
79
  ee.on('xhr-load-added', onXhrLoadAdded)
77
80
  ee.on('xhr-load-removed', onXhrLoadRemoved)
@@ -147,6 +150,14 @@ function subscribeToEvents (agentRef, ee, handler, dt) {
147
150
  }
148
151
  }
149
152
 
153
+ function onSetRequestHeader (args, xhr) {
154
+ // args[0] = header name, args[1] = header value
155
+ if (shouldInterceptPayloads && args.length >= 2) {
156
+ this.requestHeaders ??= {}
157
+ this.requestHeaders[args[0].toLowerCase()] = args[1]
158
+ }
159
+ }
160
+
150
161
  function onSendXhrStart (args, xhr) {
151
162
  var metrics = this.metrics
152
163
  var data = args[0]
@@ -159,7 +170,7 @@ function subscribeToEvents (agentRef, ee, handler, dt) {
159
170
 
160
171
  this.startTime = now()
161
172
 
162
- this.body = data
173
+ this.requestBody = data
163
174
 
164
175
  this.listener = function (evt) {
165
176
  try {
@@ -169,7 +180,7 @@ function subscribeToEvents (agentRef, ee, handler, dt) {
169
180
  if (evt.type !== 'load' || ((context.called === context.totalCbs) && (context.onloadCalled || typeof (xhr.onload) !== 'function') && typeof context.end === 'function')) context.end(xhr)
170
181
  } catch (e) {
171
182
  try {
172
- ee.emit('internal-error', [e])
183
+ ee.emit(INTERNAL_ERROR, [e])
173
184
  } catch (err) {
174
185
  // do nothing
175
186
  }
@@ -321,9 +332,29 @@ function subscribeToEvents (agentRef, ee, handler, dt) {
321
332
  const method = ('' + ((target && target instanceof origRequest && target.method) ||
322
333
  opts.method || 'GET')).toUpperCase()
323
334
  this.params.method = method
324
- this.body = opts.body
325
335
 
326
- this.txSize = dataSize(opts.body) || 0
336
+ this.txSize = dataSize(opts.body || (target?.body)) || 0
337
+
338
+ // Capture request headers
339
+ try {
340
+ var headers = opts.headers || (target?.headers)
341
+ if (shouldInterceptPayloads && headers) {
342
+ this.requestHeaders ??= {}
343
+ if (headers instanceof Headers) {
344
+ headers.forEach(function (value, key) {
345
+ this.requestHeaders[key.toLowerCase()] = value
346
+ }.bind(this))
347
+ } else if (typeof headers === 'object') {
348
+ for (var key in headers) {
349
+ this.requestHeaders[key.toLowerCase()] = headers[key]
350
+ }
351
+ }
352
+ }
353
+ } catch (e) {
354
+ // Silently fail if we can't access headers
355
+ }
356
+
357
+ this.requestBody = opts.body || (target?.body)
327
358
  }
328
359
 
329
360
  // we capture failed call as status 0, the actual error is ignored
@@ -335,20 +366,47 @@ function subscribeToEvents (agentRef, ee, handler, dt) {
335
366
 
336
367
  this.params.status = res ? res.status : 0
337
368
 
338
- // convert rxSize to a number
339
- let responseSize
340
- if (typeof this.rxSize === 'string' && this.rxSize.length > 0) {
341
- responseSize = +this.rxSize
369
+ const finishAndReport = () => {
370
+ // convert rxSize to a number - handle both string (from content-length header) and number (from fallback)
371
+ const num = +this.rxSize
372
+ const responseSize = this.rxSize != null && !isNaN(num) ? num : undefined
373
+
374
+ const metrics = {
375
+ txSize: this.txSize,
376
+ rxSize: responseSize,
377
+ duration: this.endTime - this.startTime
378
+ }
379
+
380
+ const payload = [this.params, metrics, this.startTime, this.endTime, 'fetch']
381
+ this.targets.forEach(target => reportToAgg(payload, this, target))
342
382
  }
343
383
 
344
- const metrics = {
345
- txSize: this.txSize,
346
- rxSize: responseSize,
347
- duration: now() - this.startTime
384
+ /** Since accessing fetch bodies is an async process, these are
385
+ * reasonable conditions to check to not do needless extra work */
386
+ if (!res || !shouldInterceptPayloads) {
387
+ finishAndReport()
388
+ return
348
389
  }
349
390
 
350
- const payload = [this.params, metrics, this.startTime, this.endTime, 'fetch']
351
- this.targets.forEach(target => reportToAgg(payload, this, target))
391
+ // Clone the response to read the body without consuming the original
392
+ res.clone().text().then((text) => {
393
+ this.responseBody = text
394
+ // Use captured payload size as fallback if content-length header was missing or is 0 with a body
395
+ // Only apply fallback for non-network-error responses (status !== 0)
396
+ if ((!this.rxSize || this.rxSize === '0' || this.rxSize === 0) && text !== undefined && this.params.status !== 0) {
397
+ this.rxSize = dataSize(text)
398
+ }
399
+ if (res?.headers) {
400
+ this.responseHeaders = {}
401
+ res.headers.forEach(function (value, key) {
402
+ this.responseHeaders[key.toLowerCase()] = value
403
+ }.bind(this))
404
+ }
405
+ }).catch((err) => {
406
+ ee.emit(INTERNAL_ERROR, [err])
407
+ }).finally(() => {
408
+ finishAndReport()
409
+ })
352
410
  }
353
411
 
354
412
  // Create report for XHR request that has finished
@@ -365,7 +423,7 @@ function subscribeToEvents (agentRef, ee, handler, dt) {
365
423
  if (params.aborted) return
366
424
  if (hasUndefinedHostname(params)) return // don't bother with XHR of url with no hostname
367
425
 
368
- metrics.duration = now() - this.startTime
426
+ metrics.duration = this.endTime - this.startTime
369
427
  if (!this.loadCaptureCalled && xhr.readyState === 4) {
370
428
  captureXhrData(this, xhr)
371
429
  } else if (params.status == null) {
@@ -375,6 +433,27 @@ function subscribeToEvents (agentRef, ee, handler, dt) {
375
433
  // Always send cbTime, even if no noticeable time was taken.
376
434
  metrics.cbTime = this.cbTime
377
435
 
436
+ if (shouldInterceptPayloads) {
437
+ try {
438
+ this.responseBody = xhr.responseText
439
+ } catch (e) {
440
+ this.responseBody = xhr.response
441
+ }
442
+
443
+ // Use captured payload size as fallback if not already determined or is 0 with a body
444
+ // Only apply fallback for non-network-error responses (status !== 0)
445
+ if ((!metrics.rxSize || metrics.rxSize === 0) && this.responseBody !== undefined && params.status !== 0) {
446
+ const size = dataSize(this.responseBody)
447
+ if (size !== undefined) metrics.rxSize = size
448
+ }
449
+
450
+ try {
451
+ this.responseHeaders = parseResponseHeaders(xhr.getAllResponseHeaders())
452
+ } catch (err) {
453
+ ee.emit(INTERNAL_ERROR, [err])
454
+ }
455
+ }
456
+
378
457
  const payload = [params, metrics, this.startTime, this.endTime, 'xhr']
379
458
  this.targets.forEach(target => reportToAgg(payload, this, target))
380
459
  }
@@ -387,7 +466,8 @@ function subscribeToEvents (agentRef, ee, handler, dt) {
387
466
  ctx.params.status = xhr.status
388
467
 
389
468
  var size = responseSizeFromXhr(xhr, ctx.lastSize)
390
- if (size) ctx.metrics.rxSize = size
469
+ // Don't set rxSize for network errors (status 0)
470
+ if (size !== undefined && xhr.status !== 0) ctx.metrics.rxSize = size
391
471
 
392
472
  if (ctx.sameOrigin && xhr.getAllResponseHeaders().indexOf(NR_CAT_HEADER) >= 0) {
393
473
  var header = xhr.getResponseHeader(NR_CAT_HEADER)
@@ -414,4 +494,20 @@ function addUrl (ctx, url) {
414
494
  ctx.sameOrigin = parsed.sameOrigin
415
495
  }
416
496
 
497
+ function parseResponseHeaders (headerStr) {
498
+ const headers = {}
499
+ if (!headerStr) return headers
500
+
501
+ headerStr.split('\r\n').forEach(function (line) {
502
+ const separatorIndex = line.indexOf(': ')
503
+ if (separatorIndex > 0) {
504
+ const name = line.substring(0, separatorIndex)
505
+ const value = line.substring(separatorIndex + 2)
506
+ headers[name.toLowerCase()] = value
507
+ }
508
+ })
509
+
510
+ return headers
511
+ }
512
+
417
513
  export const Ajax = Instrument
@@ -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)
@@ -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
 
@@ -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,12 @@ 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'
18
+ import { Obfuscator } from '../../../common/util/obfuscate'
19
19
  import { send } from '../../../common/harvest/harvester'
20
20
  import { FEATURE_NAMES, FEATURE_TO_ENDPOINT } from '../../../loaders/features/features'
21
21
  import { getSubmitMethod } from '../../../common/util/submit-data'
22
22
  import { webdriverDetected } from '../../../common/util/webdriver-detection'
23
+ import { EVENT_TYPES } from '../../../common/constants/events'
23
24
 
24
25
  export class Aggregate extends AggregateBase {
25
26
  static featureName = CONSTANTS.FEATURE_NAME
@@ -34,6 +35,9 @@ export class Aggregate extends AggregateBase {
34
35
  this.firstByteToDomContent = 0 // our "dom processing" duration
35
36
  this.retries = 0
36
37
 
38
+ // Create obfuscator for page view events
39
+ this.obfuscator = new Obfuscator(agentRef, EVENT_TYPES.PVE)
40
+
37
41
  agentRef.runtime.timeKeeper = new TimeKeeper(agentRef.runtime.session)
38
42
 
39
43
  if (isBrowserScope) {
@@ -83,7 +87,7 @@ export class Aggregate extends AggregateBase {
83
87
 
84
88
  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
85
89
 
86
- let body = applyFnToProps({ ja: { ...customAttributes, webdriverDetected } }, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string')
90
+ let body = this.obfuscator.traverseAndObfuscateEvents({ ja: { ...customAttributes, webdriverDetected } })
87
91
 
88
92
  if (globalScope.performance) {
89
93
  const navTimingEntry = getNavigationEntry()
@@ -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,9 +19,11 @@ 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
@@ -30,6 +32,10 @@ export class Aggregate extends AggregateBase {
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 */
@@ -303,7 +309,7 @@ export class Aggregate extends AggregateBase {
303
309
  return {
304
310
  qs: {
305
311
  browser_monitoring_key: this.agentRef.info.licenseKey,
306
- type: 'SessionReplay',
312
+ type: EVENT_TYPES.SR,
307
313
  app_id: this.agentRef.info.applicationID,
308
314
  protocol_version: '0',
309
315
  timestamp: firstTimestamp,
@@ -9,9 +9,10 @@ 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 */
@@ -114,7 +118,7 @@ export class Aggregate extends AggregateBase {
114
118
  serializer (stns) {
115
119
  if (!stns.length) return // there are no processed nodes
116
120
  this.everHarvested = true
117
- return applyFnToProps(stns, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string')
121
+ return this.obfuscator.traverseAndObfuscateEvents(stns)
118
122
  }
119
123
 
120
124
  queryStringsBuilder (stns) {