@newrelic/browser-agent 1.313.1 → 1.314.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 (83) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/cjs/common/constants/agent-constants.js +2 -1
  3. package/dist/cjs/common/constants/env.cdn.js +1 -1
  4. package/dist/cjs/common/constants/env.npm.js +1 -1
  5. package/dist/cjs/common/dom/selector-path.js +12 -3
  6. package/dist/cjs/common/timing/time-keeper.js +18 -6
  7. package/dist/cjs/common/vitals/cumulative-layout-shift.js +3 -2
  8. package/dist/cjs/common/vitals/interaction-to-next-paint.js +3 -2
  9. package/dist/cjs/common/vitals/largest-contentful-paint.js +2 -1
  10. package/dist/cjs/common/vitals/load-time.js +5 -2
  11. package/dist/cjs/common/vitals/vital-metric.js +7 -4
  12. package/dist/cjs/features/ajax/aggregate/index.js +6 -2
  13. package/dist/cjs/features/ajax/constants.js +4 -3
  14. package/dist/cjs/features/generic_events/aggregate/index.js +60 -53
  15. package/dist/cjs/features/generic_events/aggregate/user-actions/aggregated-user-action.js +2 -1
  16. package/dist/cjs/features/generic_events/aggregate/user-actions/user-actions-aggregator.js +4 -3
  17. package/dist/cjs/features/page_view_timing/aggregate/index.js +27 -6
  18. package/dist/cjs/features/session_replay/aggregate/index.js +15 -6
  19. package/dist/cjs/features/session_replay/constants.js +1 -1
  20. package/dist/cjs/features/session_replay/shared/recorder.js +3 -1
  21. package/dist/cjs/features/soft_navigations/aggregate/ajax-node.js +7 -3
  22. package/dist/esm/common/constants/agent-constants.js +2 -1
  23. package/dist/esm/common/constants/env.cdn.js +1 -1
  24. package/dist/esm/common/constants/env.npm.js +1 -1
  25. package/dist/esm/common/dom/selector-path.js +13 -3
  26. package/dist/esm/common/timing/time-keeper.js +18 -6
  27. package/dist/esm/common/vitals/cumulative-layout-shift.js +3 -2
  28. package/dist/esm/common/vitals/interaction-to-next-paint.js +3 -2
  29. package/dist/esm/common/vitals/largest-contentful-paint.js +2 -1
  30. package/dist/esm/common/vitals/load-time.js +5 -2
  31. package/dist/esm/common/vitals/vital-metric.js +7 -4
  32. package/dist/esm/features/ajax/aggregate/index.js +7 -3
  33. package/dist/esm/features/ajax/constants.js +3 -2
  34. package/dist/esm/features/generic_events/aggregate/index.js +61 -54
  35. package/dist/esm/features/generic_events/aggregate/user-actions/aggregated-user-action.js +2 -1
  36. package/dist/esm/features/generic_events/aggregate/user-actions/user-actions-aggregator.js +4 -3
  37. package/dist/esm/features/page_view_timing/aggregate/index.js +27 -6
  38. package/dist/esm/features/session_replay/aggregate/index.js +15 -6
  39. package/dist/esm/features/session_replay/constants.js +1 -1
  40. package/dist/esm/features/session_replay/shared/recorder.js +3 -1
  41. package/dist/esm/features/soft_navigations/aggregate/ajax-node.js +5 -1
  42. package/dist/types/common/constants/agent-constants.d.ts +1 -0
  43. package/dist/types/common/constants/agent-constants.d.ts.map +1 -1
  44. package/dist/types/common/dom/selector-path.d.ts +2 -1
  45. package/dist/types/common/dom/selector-path.d.ts.map +1 -1
  46. package/dist/types/common/timing/time-keeper.d.ts.map +1 -1
  47. package/dist/types/common/vitals/vital-metric.d.ts +3 -2
  48. package/dist/types/common/vitals/vital-metric.d.ts.map +1 -1
  49. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  50. package/dist/types/features/ajax/constants.d.ts +1 -0
  51. package/dist/types/features/ajax/constants.d.ts.map +1 -1
  52. package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
  53. package/dist/types/features/generic_events/aggregate/user-actions/aggregated-user-action.d.ts +1 -0
  54. package/dist/types/features/generic_events/aggregate/user-actions/aggregated-user-action.d.ts.map +1 -1
  55. package/dist/types/features/generic_events/aggregate/user-actions/user-actions-aggregator.d.ts +2 -0
  56. package/dist/types/features/generic_events/aggregate/user-actions/user-actions-aggregator.d.ts.map +1 -1
  57. package/dist/types/features/page_view_timing/aggregate/index.d.ts +1 -1
  58. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  59. package/dist/types/features/session_replay/aggregate/index.d.ts +1 -11
  60. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  61. package/dist/types/features/session_replay/shared/recorder.d.ts +2 -0
  62. package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
  63. package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts +1 -0
  64. package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts.map +1 -1
  65. package/package.json +2 -2
  66. package/src/common/constants/agent-constants.js +2 -1
  67. package/src/common/dom/selector-path.js +13 -4
  68. package/src/common/timing/time-keeper.js +17 -6
  69. package/src/common/vitals/cumulative-layout-shift.js +2 -2
  70. package/src/common/vitals/interaction-to-next-paint.js +2 -2
  71. package/src/common/vitals/largest-contentful-paint.js +1 -1
  72. package/src/common/vitals/load-time.js +5 -2
  73. package/src/common/vitals/vital-metric.js +6 -4
  74. package/src/features/ajax/aggregate/index.js +6 -3
  75. package/src/features/ajax/constants.js +3 -1
  76. package/src/features/generic_events/aggregate/index.js +42 -39
  77. package/src/features/generic_events/aggregate/user-actions/aggregated-user-action.js +2 -1
  78. package/src/features/generic_events/aggregate/user-actions/user-actions-aggregator.js +4 -3
  79. package/src/features/page_view_timing/aggregate/index.js +14 -6
  80. package/src/features/session_replay/aggregate/index.js +16 -5
  81. package/src/features/session_replay/constants.js +1 -1
  82. package/src/features/session_replay/shared/recorder.js +3 -1
  83. package/src/features/soft_navigations/aggregate/ajax-node.js +4 -1
@@ -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 { RAGE_CLICK_THRESHOLD_EVENTS, RAGE_CLICK_THRESHOLD_MS } from '../../constants'
@@ -17,6 +17,7 @@ export class AggregatedUserAction {
17
17
  this.currentUrl = cleanURL('' + location)
18
18
  this.deadClick = false
19
19
  this.errorClick = false
20
+ this.targets = selectorInfo.targets
20
21
  }
21
22
 
22
23
  /**
@@ -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 { analyzeElemPath } from '../../../../common/dom/selector-path'
@@ -16,7 +16,8 @@ export class UserActionsAggregator {
16
16
  #domObserver = undefined
17
17
  #errorClickTimer = undefined
18
18
 
19
- constructor () {
19
+ constructor (agentRef) {
20
+ this.agentRef = agentRef
20
21
  if (gosNREUMOriginals().o.MO) {
21
22
  this.#domObserver = new MutationObserver(this.isLiveClick.bind(this))
22
23
  }
@@ -40,7 +41,7 @@ export class UserActionsAggregator {
40
41
  process (evt, targetFields) {
41
42
  if (!evt) return
42
43
  const targetElem = OBSERVED_WINDOW_EVENTS.includes(evt.type) ? window : evt.target
43
- const selectorInfo = analyzeElemPath(targetElem, targetFields)
44
+ const selectorInfo = analyzeElemPath(targetElem, targetFields, this.agentRef)
44
45
 
45
46
  // if selectorInfo.path is undefined, aggregation will be skipped for this event
46
47
  const aggregationKey = getAggregationKey(evt, selectorInfo.path)
@@ -20,13 +20,15 @@ import { initiallyHidden, getNavigationEntry, initialLocation } from '../../../c
20
20
  import { eventOrigin } from '../../../common/util/event-origin'
21
21
  import { loadTime } from '../../../common/vitals/load-time'
22
22
  import { webdriverDetected } from '../../../common/util/webdriver-detection'
23
+ import { analyzeElemPath } from '../../../common/dom/selector-path'
24
+ import { getVersion2Attributes, getVersion2DuplicationAttributes, shouldDuplicate } from '../../../common/v2/utils'
23
25
  import { cleanURL } from '../../../common/url/clean-url'
24
26
 
25
27
  export class Aggregate extends AggregateBase {
26
28
  static featureName = FEATURE_NAME
27
29
 
28
- #handleVitalMetric = ({ name, value, attrs }) => {
29
- this.addTiming(name, value, attrs)
30
+ #handleVitalMetric = ({ name, value, attrs, element }) => {
31
+ this.addTiming(name, value, attrs, element)
30
32
  }
31
33
 
32
34
  constructor (agentRef) {
@@ -50,9 +52,9 @@ export class Aggregate extends AggregateBase {
50
52
  /* Downstream, the event consumer interprets all timing node value as ms-unit and converts it to seconds via division by 1000. CLS is unitless so this normally is a problem.
51
53
  bel.6 schema also doesn't support decimal values, of which cls within [0,1). However, the two nicely cancels out, and we can multiply cls by 1000 to both negate the division
52
54
  and send an integer > 1. We effectively lose some precision down to 3 decimal places for this workaround. E.g. (real) 0.749132... -> 749.132...-> 749 -> 0.749 (final) */
53
- const { name, value, attrs } = cumulativeLayoutShift.current
55
+ const { name, value, attrs, element } = cumulativeLayoutShift.current
54
56
  if (value === undefined) return
55
- this.addTiming(name, value * 1000, attrs)
57
+ this.addTiming(name, value * 1000, attrs, element)
56
58
  }, true, true) // CLS node should only reports on vis change rather than on every change
57
59
 
58
60
  this.drain()
@@ -70,7 +72,7 @@ export class Aggregate extends AggregateBase {
70
72
  }
71
73
  }
72
74
 
73
- addTiming (name, value, attrs) {
75
+ addTiming (name, value, attrs, element) {
74
76
  attrs = attrs || {}
75
77
  attrs.pageUrl = cleanURL(getNavigationEntry()?.name || initialLocation)
76
78
 
@@ -93,7 +95,13 @@ export class Aggregate extends AggregateBase {
93
95
  value,
94
96
  attrs
95
97
  }
96
- this.events.add(timing)
98
+
99
+ const targets = analyzeElemPath(element, [], this.agentRef).targets
100
+ if (!targets.length) targets.push(undefined)
101
+ targets.forEach(target => {
102
+ this.events.add({ ...timing, attrs: { ...attrs, ...getVersion2Attributes(target, this) } })
103
+ if (shouldDuplicate(target, this.agentRef)) this.events.add({ ...timing, attrs: { ...attrs, ...getVersion2DuplicationAttributes(target, this) } })
104
+ })
97
105
 
98
106
  handle('pvtAdded', [name, value, attrs], undefined, FEATURE_NAMES.sessionTrace, this.ee)
99
107
 
@@ -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
  /**
@@ -209,6 +209,7 @@ export class Aggregate extends AggregateBase {
209
209
  }
210
210
 
211
211
  makeHarvestPayload () {
212
+ if (this.isRetrying) return this.recorder.retryPayload
212
213
  if (this.mode !== MODE.FULL || this.blocked) return // harvests should only be made in FULL mode, and not if the feature is blocked
213
214
  if (this.shouldCompress && !this.gzipper) return // if compression is enabled, but the libraries have not loaded, wait for them to load
214
215
  if (!this.recorder || !this.timeKeeper?.ready || !(this.recorder.hasSeenSnapshot && this.recorder.hasSeenMeta)) return // if the recorder or the timekeeper is not ready, or the recorder has not yet seen a snapshot, do not harvest
@@ -239,7 +240,6 @@ export class Aggregate extends AggregateBase {
239
240
  return
240
241
  }
241
242
 
242
- // TODO -- Gracefully handle the buffer for retries.
243
243
  if (!this.agentRef.runtime.session.state.sessionReplaySentFirstChunk) this.syncWithSessionManager({ sessionReplaySentFirstChunk: true })
244
244
  this.recorder.clearBuffer()
245
245
 
@@ -247,6 +247,8 @@ export class Aggregate extends AggregateBase {
247
247
  warn(59, JSON.stringify(this.agentRef.runtime.session.state))
248
248
  }
249
249
 
250
+ this.recorder.retryPayload = payload
251
+
250
252
  return payload
251
253
  }
252
254
 
@@ -338,9 +340,18 @@ export class Aggregate extends AggregateBase {
338
340
  }
339
341
 
340
342
  postHarvestCleanup (result) {
341
- // The mutual decision for now is to stop recording and clear buffers if ingest is experiencing 429 rate limiting
342
- if (result.status === 429) {
343
- this.abort(ABORT_REASONS.TOO_MANY)
343
+ if (result.sent) {
344
+ if (result.retry) {
345
+ warn(70)
346
+ this.isRetrying = true
347
+ this.forceStop()
348
+ } else {
349
+ this.recorder.retryPayload = undefined
350
+ if (this.isRetrying) {
351
+ this.isRetrying = false
352
+ this.switchToFull()
353
+ }
354
+ }
344
355
  }
345
356
  }
346
357
 
@@ -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 { MODE } from '../../common/session/constants'
@@ -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 { record as recorder } from '@newrelic/rrweb'
@@ -45,6 +45,8 @@ export class Recorder {
45
45
  this.events = new RecorderEvents(this.shouldFix)
46
46
  /** Backlog used for a 2-part sliding window to guarantee a 15-30s buffer window */
47
47
  this.backloggedEvents = new RecorderEvents(this.shouldFix)
48
+ /** Used to hold the harvest contents to facilitate retrying */
49
+ this.retryPayload = undefined
48
50
  /** Only set to true once a snapshot node has been processed. Used to block harvests from sending before we know we have a snapshot */
49
51
  this.hasSeenSnapshot = false
50
52
  this.hasSeenMeta = false
@@ -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 { AJAX_ID } from '../../ajax/constants'
6
7
  import { NODE_TYPE } from '../constants'
7
8
  import { BelNode } from './bel-node'
8
9
 
@@ -22,6 +23,7 @@ export class AjaxNode extends BelNode {
22
23
  this.spanTimestamp = ajaxEvent.spanTimestamp
23
24
  this.gql = ajaxEvent.gql
24
25
  this.targetAttributes = ajaxEvent.targetAttributes
26
+ this[AJAX_ID] = ajaxEvent[AJAX_ID] // all AjaxRequest events should have a unique identifier to allow for easier grouping and analysis in the UI
25
27
 
26
28
  this.start = ajaxEvent.startTime
27
29
  this.end = ajaxEvent.endTime
@@ -55,7 +57,8 @@ export class AjaxNode extends BelNode {
55
57
  ]
56
58
  let allAttachedNodes = addCustomAttributes({
57
59
  ...(this.gql || {}),
58
- ...(this.targetAttributes || {})
60
+ ...(this.targetAttributes || {}),
61
+ [AJAX_ID]: this[AJAX_ID]
59
62
  }, addString)
60
63
  this.children.forEach(node => allAttachedNodes.push(node.serialize())) // no children is expected under ajax nodes at this time
61
64