@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 { record as recorder } from '@newrelic/rrweb';
@@ -40,6 +40,8 @@ export class Recorder {
40
40
  this.events = new RecorderEvents(this.shouldFix);
41
41
  /** Backlog used for a 2-part sliding window to guarantee a 15-30s buffer window */
42
42
  this.backloggedEvents = new RecorderEvents(this.shouldFix);
43
+ /** Used to hold the harvest contents to facilitate retrying */
44
+ this.retryPayload = undefined;
43
45
  /** Only set to true once a snapshot node has been processed. Used to block harvests from sending before we know we have a snapshot */
44
46
  this.hasSeenSnapshot = false;
45
47
  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
  export class AjaxNode extends BelNode {
@@ -21,6 +22,8 @@ export class AjaxNode extends BelNode {
21
22
  this.spanTimestamp = ajaxEvent.spanTimestamp;
22
23
  this.gql = ajaxEvent.gql;
23
24
  this.targetAttributes = ajaxEvent.targetAttributes;
25
+ this[AJAX_ID] = ajaxEvent[AJAX_ID]; // all AjaxRequest events should have a unique identifier to allow for easier grouping and analysis in the UI
26
+
24
27
  this.start = ajaxEvent.startTime;
25
28
  this.end = ajaxEvent.endTime;
26
29
  if (ajaxContext?.latestLongtaskEnd) {
@@ -46,7 +49,8 @@ export class AjaxNode extends BelNode {
46
49
  addString(this.method), numeric(this.status), addString(this.domain), addString(this.path), numeric(this.txSize), numeric(this.rxSize), this.requestedWith, addString(this.nodeId), nullable(this.spanId, addString, true) + nullable(this.traceId, addString, true) + nullable(this.spanTimestamp, numeric)];
47
50
  let allAttachedNodes = addCustomAttributes({
48
51
  ...(this.gql || {}),
49
- ...(this.targetAttributes || {})
52
+ ...(this.targetAttributes || {}),
53
+ [AJAX_ID]: this[AJAX_ID]
50
54
  }, addString);
51
55
  this.children.forEach(node => allAttachedNodes.push(node.serialize())); // no children is expected under ajax nodes at this time
52
56
 
@@ -7,6 +7,7 @@ export const SUPPORTS_REGISTERED_ENTITIES: {
7
7
  [FEATURE_NAMES.genericEvents]: boolean;
8
8
  [FEATURE_NAMES.jserrors]: boolean;
9
9
  [FEATURE_NAMES.ajax]: boolean;
10
+ [FEATURE_NAMES.pageViewTiming]: boolean;
10
11
  };
11
12
  import { FEATURE_NAMES } from '../../loaders/features/features';
12
13
  //# sourceMappingURL=agent-constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"agent-constants.d.ts","sourceRoot":"","sources":["../../../../src/common/constants/agent-constants.js"],"names":[],"mappings":"AAMA,iCAAkC,KAAK,CAAA;AACvC,+BAAgC,OAAO,CAAA;AACvC,0BAA2B,oBAAoB,CAAA;AAC/C,4BAA6B,eAAe,CAAA;AAE5C;IACE,CAAC,aAAa,CAAC,OAAO,CAAC,UAAM;IAC7B,CAAC,aAAa,CAAC,aAAa,CAAC,UAAM;IACnC,CAAC,aAAa,CAAC,QAAQ,CAAC,UAAM;IAC9B,CAAC,aAAa,CAAC,IAAI,CAAC,UAAM;EAC3B;8BAZ6B,iCAAiC"}
1
+ {"version":3,"file":"agent-constants.d.ts","sourceRoot":"","sources":["../../../../src/common/constants/agent-constants.js"],"names":[],"mappings":"AAMA,iCAAkC,KAAK,CAAA;AACvC,+BAAgC,OAAO,CAAA;AACvC,0BAA2B,oBAAoB,CAAA;AAC/C,4BAA6B,eAAe,CAAA;AAE5C;IACE,CAAC,aAAa,CAAC,OAAO,CAAC,UAAM;IAC7B,CAAC,aAAa,CAAC,aAAa,CAAC,UAAM;IACnC,CAAC,aAAa,CAAC,QAAQ,CAAC,UAAM;IAC9B,CAAC,aAAa,CAAC,IAAI,CAAC,UAAM;IAC1B,CAAC,aAAa,CAAC,cAAc,CAAC,UAAM;EACrC;8BAb6B,iCAAiC"}
@@ -1,6 +1,7 @@
1
- export function analyzeElemPath(elem: HTMLElement, targetFields?: Array<string>): {
1
+ export function analyzeElemPath(elem: HTMLElement, targetFields?: Array<string>, agentRef: any): {
2
2
  path: (undefined | string);
3
3
  nearestFields: {};
4
+ targets: any[];
4
5
  hasButton: boolean;
5
6
  hasLink: boolean;
6
7
  };
@@ -1 +1 @@
1
- {"version":3,"file":"selector-path.d.ts","sourceRoot":"","sources":["../../../../src/common/dom/selector-path.js"],"names":[],"mappings":"AAgBO,sCAJI,WAAW,iBACX,KAAK,CAAC,MAAM,CAAC,GACX;IAAC,IAAI,EAAE,CAAC,SAAS,GAAC,MAAM,CAAC,CAAC;IAAC,aAAa,EAAE,EAAE,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAC,CA2B/F"}
1
+ {"version":3,"file":"selector-path.d.ts","sourceRoot":"","sources":["../../../../src/common/dom/selector-path.js"],"names":[],"mappings":"AAkBO,sCAJI,WAAW,iBACX,KAAK,CAAC,MAAM,CAAC,kBACX;IAAC,IAAI,EAAE,CAAC,SAAS,GAAC,MAAM,CAAC,CAAC;IAAC,aAAa,EAAE,EAAE,CAAC;IAAC,OAAO,QAAQ;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAC,CAkC/G"}
@@ -1 +1 @@
1
- {"version":3,"file":"time-keeper.d.ts","sourceRoot":"","sources":["../../../../src/common/timing/time-keeper.js"],"names":[],"mappings":"AAUA;;;;GAIG;AACH;IA6BE,6BAIC;IAsBD,qBAEC;IAED,kCAEC;IAED,4BAEC;IAED;;;;;;OAMG;IACH,8BALsB,cAAc,aACf,MAAM,WACR,MAAM,gBACD,MAAM,QAqB7B;IAED;;;;;OAKG;IACH,uCAHwB,MAAM,GACjB,MAAM,CAKlB;IAED;;;;;OAKG;IACH,0CAFa,MAAM,CAKlB;IAED;;;;OAIG;IACH,oCAHqB,MAAM,GACf,MAAM,CAKjB;IAED;;;;OAIG;IACH,uCAHW,mBAAmB,GACjB,MAAM,CAKlB;IAED,+FAA+F;IAC/F,0BASC;;CACF"}
1
+ {"version":3,"file":"time-keeper.d.ts","sourceRoot":"","sources":["../../../../src/common/timing/time-keeper.js"],"names":[],"mappings":"AAUA;;;;GAIG;AACH;IAkCE,6BAIC;IA0BD,qBAEC;IAED,kCAEC;IAED,4BAEC;IAED;;;;;;OAMG;IACH,8BALsB,cAAc,aACf,MAAM,WACR,MAAM,gBACD,MAAM,QAqB7B;IAED;;;;;OAKG;IACH,uCAHwB,MAAM,GACjB,MAAM,CAMlB;IAED;;;;;OAKG;IACH,0CAFa,MAAM,CAMlB;IAED;;;;OAIG;IACH,oCAHqB,MAAM,GACf,MAAM,CAKjB;IAED;;;;OAIG;IACH,uCAHW,mBAAmB,GACjB,MAAM,CAKlB;IAED,+FAA+F;IAC/F,0BASC;;CACF"}
@@ -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
  export class VitalMetric {
@@ -8,9 +8,10 @@ export class VitalMetric {
8
8
  name: any;
9
9
  attrs: {};
10
10
  roundingMethod: any;
11
- update({ value, attrs }: {
11
+ update({ value, attrs, element }: {
12
12
  value: any;
13
13
  attrs?: {} | undefined;
14
+ element: any;
14
15
  }): void;
15
16
  get current(): any;
16
17
  get isValid(): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"vital-metric.d.ts","sourceRoot":"","sources":["../../../../src/common/vitals/vital-metric.js"],"names":[],"mappings":"AAAA;;;GAGG;AACH;IAIE,4CAIC;IAND,eAAY;IAGV,UAAgB;IAChB,UAAe;IACf,oBAAwF;IAG1F;;;aAgBC;IAED,mBAMC;IAED,uBAEC;IAED,uEAMC;;CACF"}
1
+ {"version":3,"file":"vital-metric.d.ts","sourceRoot":"","sources":["../../../../src/common/vitals/vital-metric.js"],"names":[],"mappings":"AAAA;;;GAGG;AACH;IAIE,4CAIC;IAND,eAAY;IAGV,UAAgB;IAChB,UAAe;IACf,oBAAwF;IAG1F;;;;aAiBC;IAED,mBAOC;IAED,uBAEC;IAED,uEAMC;;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/ajax/aggregate/index.js"],"names":[],"mappings":"AAgBA;IACE,2BAAiC;IAEjC,2BA0BC;IAED,0GAuEC;IAED,+CAOC;IAED,iDAwDC;CACF;8BAhL6B,4BAA4B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/ajax/aggregate/index.js"],"names":[],"mappings":"AAiBA;IACE,2BAAiC;IAEjC,2BA0BC;IAED,0GAwEC;IAED,+CAOC;IAED,iDAyDC;CACF;8BAnL6B,4BAA4B"}
@@ -1,2 +1,3 @@
1
1
  export const FEATURE_NAME: string;
2
+ export const AJAX_ID: "ajaxRequest.id";
2
3
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../src/features/ajax/constants.js"],"names":[],"mappings":"AAMA,kCAA8C"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../src/features/ajax/constants.js"],"names":[],"mappings":"AAMA,kCAA8C;AAE9C,sBAAuB,gBAAgB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/generic_events/aggregate/index.js"],"names":[],"mappings":"AAkBA;IACE,2BAAiC;IAGjC,2BAqQC;IAnQC,gCAAkG;IAsQpG;;;;;;;;;;;;OAYG;IACH,eAJW,MAAM,YAAC,WACP,MAAM,YAAC,QAmCjB;IAED,qCAEC;IAED;;;MAEC;;CAsBF;8BAjW6B,4BAA4B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/generic_events/aggregate/index.js"],"names":[],"mappings":"AAkBA;IACE,2BAAiC;IAGjC,2BAyQC;IAvQC,gCAAkG;IA0QpG;;;;;;;;;;;;OAYG;IACH,eAJW,MAAM,YAAC,WACP,MAAM,YAAC,QAkCjB;IAED,qCAEC;IAED;;;MAEC;;CAsBF;8BApW6B,4BAA4B"}
@@ -10,6 +10,7 @@ export class AggregatedUserAction {
10
10
  currentUrl: string;
11
11
  deadClick: boolean;
12
12
  errorClick: boolean;
13
+ targets: any;
13
14
  /**
14
15
  * Aggregates the count and maintains the relative MS array for matching events
15
16
  * Will determine if a rage click was observed as part of the aggregation
@@ -1 +1 @@
1
- {"version":3,"file":"aggregated-user-action.d.ts","sourceRoot":"","sources":["../../../../../../src/features/generic_events/aggregate/user-actions/aggregated-user-action.js"],"names":[],"mappings":"AAOA;IACE,yCAWC;IAVC,WAAgB;IAChB,cAAc;IACd,iBAAyC;IACzC,qBAAqB;IACrB,kBAAqC;IACrC,+BAA0B;IAC1B,yBAAqD;IACrD,mBAAyC;IACzC,mBAAsB;IACtB,oBAAuB;IAGzB;;;;;OAKG;IACH,eAHW,KAAK,GACH,IAAI,CAMhB;IAED;;;OAGG;IACH,eAFa,OAAO,CAKnB;CACF"}
1
+ {"version":3,"file":"aggregated-user-action.d.ts","sourceRoot":"","sources":["../../../../../../src/features/generic_events/aggregate/user-actions/aggregated-user-action.js"],"names":[],"mappings":"AAOA;IACE,yCAYC;IAXC,WAAgB;IAChB,cAAc;IACd,iBAAyC;IACzC,qBAAqB;IACrB,kBAAqC;IACrC,+BAA0B;IAC1B,yBAAqD;IACrD,mBAAyC;IACzC,mBAAsB;IACtB,oBAAuB;IACvB,aAAmC;IAGrC;;;;;OAKG;IACH,eAHW,KAAK,GACH,IAAI,CAMhB;IAED;;;OAGG;IACH,eAFa,OAAO,CAKnB;CACF"}
@@ -1,4 +1,6 @@
1
1
  export class UserActionsAggregator {
2
+ constructor(agentRef: any);
3
+ agentRef: any;
2
4
  get aggregationEvent(): AggregatedUserAction | undefined;
3
5
  /**
4
6
  * Process the event and determine if a new aggregation set should be made or if it should increment the current aggregation
@@ -1 +1 @@
1
- {"version":3,"file":"user-actions-aggregator.d.ts","sourceRoot":"","sources":["../../../../../../src/features/generic_events/aggregate/user-actions/user-actions-aggregator.js"],"names":[],"mappings":"AAUA;IAcE,yDAQC;IAED;;;;OAIG;IACH,aAHW,KAAK,sBACH,oBAAoB,GAAC,SAAS,CA2B1C;IAED,yBAKC;IA0CD,oBAEC;;CACF;qCA9GoC,0BAA0B"}
1
+ {"version":3,"file":"user-actions-aggregator.d.ts","sourceRoot":"","sources":["../../../../../../src/features/generic_events/aggregate/user-actions/user-actions-aggregator.js"],"names":[],"mappings":"AAUA;IAQE,2BAKC;IAJC,cAAwB;IAM1B,yDAQC;IAED;;;;OAIG;IACH,aAHW,KAAK,sBACH,oBAAoB,GAAC,SAAS,CA2B1C;IAED,yBAKC;IA0CD,oBAEC;;CACF;qCA/GoC,0BAA0B"}
@@ -8,7 +8,7 @@ export class Aggregate extends AggregateBase {
8
8
  * @param {number} timestamp
9
9
  */
10
10
  endCurrentSession(timestamp: number): void;
11
- addTiming(name: any, value: any, attrs: any): {
11
+ addTiming(name: any, value: any, attrs: any, element: any): {
12
12
  name: any;
13
13
  value: any;
14
14
  attrs: any;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_timing/aggregate/index.js"],"names":[],"mappings":"AAwBA;IACE,2BAAiC;IAMjC,2BA4BC;IA1BC,4BAA+B;IAC/B,0BAA6B;IA2B/B;;;OAGG;IACH,6BAFW,MAAM,QAOhB;IAED;;;;MA+BC;IAED;;;OAGG;IACH,4BAFa,IAAI,CAahB;IAED,gDAWC;IAED,4BAGC;IAGD,qCAwBC;;CACF;8BA5J6B,4BAA4B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_timing/aggregate/index.js"],"names":[],"mappings":"AA0BA;IACE,2BAAiC;IAMjC,2BA4BC;IA1BC,4BAA+B;IAC/B,0BAA6B;IA2B/B;;;OAGG;IACH,6BAFW,MAAM,QAOhB;IAED;;;;MAqCC;IAED;;;OAGG;IACH,4BAFa,IAAI,CAahB;IAED,gDAWC;IAED,4BAGC;IAGD,qCAwBC;;CACF;8BApK6B,4BAA4B"}
@@ -35,17 +35,7 @@ export class Aggregate extends AggregateBase {
35
35
  PRELOAD: string;
36
36
  }): void;
37
37
  prepUtils(): Promise<void>;
38
- makeHarvestPayload(): {
39
- qs: {
40
- browser_monitoring_key: any;
41
- type: string;
42
- app_id: any;
43
- protocol_version: string;
44
- timestamp: any;
45
- attributes: string;
46
- };
47
- body: any;
48
- } | undefined;
38
+ makeHarvestPayload(): any;
49
39
  /**
50
40
  * returns the timestamps for the earliest and latest nodes in the provided array, even if out of order
51
41
  * @param {Object[]} [nodes] - the nodes to evaluate
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"AAyBA;IACE,2BAAiC;IAIjC,sCAyFC;IA5FD,aAAe;IAKb,iFAAiF;IACjF,qBAAwB;IAGxB,2CAA2C;IAC3C,sDAAwB;IACxB,6CAA6C;IAC7C,gDAAmB;IACnB,+DAA+D;IAC/D,wBAA0B;IAE1B,0BAA0B;IAC1B,kBAAqB;IACrB,6CAA6C;IAC7C,gBAA2B;IAE3B,qBAA2B;IAE3B,cAA8C;IAI9C,kCAAqG;IAmEvG,0BAEC;IAED,0BAMC;IAED,qBAUC;IAED;;;;;;OAMG;IACH,4BALW,OAAO,iBACP,OAAO;;;;;;QAEL,IAAI,CA8ChB;IAED,2BAUC;IAED;;;;;;;;;;kBAwCC;IAED;;;;OAIG;IACH,6BAHW,MAAM,EAAE,GACN;QAAE,UAAU,EAAE,MAAM,GAAC,SAAS,CAAC;QAAC,SAAS,EAAE,MAAM,GAAC,SAAS,CAAA;KAAE,CAUzE;IAED;;;;;;;;;;MAsEC;IAED,sCAKC;IAED;;;;OAIG;IACH,mCAKC;IAED,yDAAyD;IACzD,+CASC;IAED,yCAIC;CACF;8BA7W6B,4BAA4B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"AAyBA;IACE,2BAAiC;IAIjC,sCAyFC;IA5FD,aAAe;IAKb,iFAAiF;IACjF,qBAAwB;IAGxB,2CAA2C;IAC3C,sDAAwB;IACxB,6CAA6C;IAC7C,gDAAmB;IACnB,+DAA+D;IAC/D,wBAA0B;IAE1B,0BAA0B;IAC1B,kBAAqB;IACrB,6CAA6C;IAC7C,gBAA2B;IAE3B,qBAA2B;IAE3B,cAA8C;IAI9C,kCAAqG;IAmEvG,0BAEC;IAED,0BAMC;IAED,qBAUC;IAED;;;;;;OAMG;IACH,4BALW,OAAO,iBACP,OAAO;;;;;;QAEL,IAAI,CA8ChB;IAED,2BAUC;IAED,0BA0CC;IAED;;;;OAIG;IACH,6BAHW,MAAM,EAAE,GACN;QAAE,UAAU,EAAE,MAAM,GAAC,SAAS,CAAC;QAAC,SAAS,EAAE,MAAM,GAAC,SAAS,CAAA;KAAE,CAUzE;IAED;;;;;;;;;;MAsEC;IAED,sCAcC;IAED;;;;OAIG;IACH,mCAKC;IAED,yDAAyD;IACzD,+CASC;IAED,yCAIC;CACF;8BAxX6B,4BAA4B"}
@@ -13,6 +13,8 @@ export class Recorder {
13
13
  events: RecorderEvents;
14
14
  /** Backlog used for a 2-part sliding window to guarantee a 15-30s buffer window */
15
15
  backloggedEvents: RecorderEvents;
16
+ /** Used to hold the harvest contents to facilitate retrying */
17
+ retryPayload: any;
16
18
  /** Only set to true once a snapshot node has been processed. Used to block harvests from sending before we know we have a snapshot */
17
19
  hasSeenSnapshot: boolean;
18
20
  hasSeenMeta: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/recorder.js"],"names":[],"mappings":"AAqBA;IAUE,+BAkCC;IApCD,sBAAmB;IAGjB,iDAAiD;IACjD,kBAAgC;IAEhC,QAAyB;IACzB,mBAA6C;IAC7C,cAAqC;IAErC,qBAAwB;IACxB,0FAA0F;IAC1F,eAAkE;IAElE,iHAAiH;IACjH,uBAAgD;IAChD,mFAAmF;IACnF,iCAA0D;IAC1D,uIAAuI;IACvI,yBAA4B;IAC5B,qBAAwB;IACxB,kIAAkI;IAClI,kBAAqB;IACrB,uIAAuI;IACvI,0BAAwE;IAc1E,mBAEC;IAED;;;;;;;;;MAWC;IAED,kFAAkF;IAClF,oBAGC;IAED,qDAAqD;IACrD,8CA0CC;IAED;;;;;OAKG;IACH,aAHW,GAAC,cACD,GAAC,QAiCX;IAED,yHAAyH;IACzH,yCAiCC;IAED,0HAA0H;IAC1H,yBAOC;IAED,wBAEC;IAED,gCAAgC;IAChC,uCAGC;IAED;;;SAGK;IACL,oCAGC;;CACF;+BAvO8B,mBAAmB"}
1
+ {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/recorder.js"],"names":[],"mappings":"AAqBA;IAUE,+BAoCC;IAtCD,sBAAmB;IAGjB,iDAAiD;IACjD,kBAAgC;IAEhC,QAAyB;IACzB,mBAA6C;IAC7C,cAAqC;IAErC,qBAAwB;IACxB,0FAA0F;IAC1F,eAAkE;IAElE,iHAAiH;IACjH,uBAAgD;IAChD,mFAAmF;IACnF,iCAA0D;IAC1D,+DAA+D;IAC/D,kBAA6B;IAC7B,uIAAuI;IACvI,yBAA4B;IAC5B,qBAAwB;IACxB,kIAAkI;IAClI,kBAAqB;IACrB,uIAAuI;IACvI,0BAAwE;IAc1E,mBAEC;IAED;;;;;;;;;MAWC;IAED,kFAAkF;IAClF,oBAGC;IAED,qDAAqD;IACrD,8CA0CC;IAED;;;;;OAKG;IACH,aAHW,GAAC,cACD,GAAC,QAiCX;IAED,yHAAyH;IACzH,yCAiCC;IAED,0HAA0H;IAC1H,yBAOC;IAED,wBAEC;IAED,gCAAgC;IAChC,uCAGC;IAED;;;SAGK;IACL,oCAGC;;CACF;+BAzO8B,mBAAmB"}
@@ -15,6 +15,7 @@ export class AjaxNode extends BelNode {
15
15
  targetAttributes: any;
16
16
  callbackEnd: any;
17
17
  serialize(parentStartTimestamp: any, agentRef: any): string;
18
+ "ajaxRequest.id": any;
18
19
  }
19
20
  import { BelNode } from './bel-node';
20
21
  //# sourceMappingURL=ajax-node.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ajax-node.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/ajax-node.js"],"names":[],"mappings":"AAQA;IACE,8CAsBC;IApBC,gBAA6B;IAC7B,YAA8B;IAC9B,YAA8B;IAC9B,YAA8B;IAC9B,UAA0B;IAC1B,YAAmC;IACnC,YAAoC;IACpC,+BAAwD;IACxD,YAA8B;IAC9B,aAAgC;IAChC,mBAA4C;IAC5C,SAAwB;IACxB,sBAAkD;IAKhD,iBAAoE;IAKxE,4DAiCC;CACF;wBA7DuB,YAAY"}
1
+ {"version":3,"file":"ajax-node.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/ajax-node.js"],"names":[],"mappings":"AASA;IACE,8CAuBC;IArBC,gBAA6B;IAC7B,YAA8B;IAC9B,YAA8B;IAC9B,YAA8B;IAC9B,UAA0B;IAC1B,YAAmC;IACnC,YAAoC;IACpC,+BAAwD;IACxD,YAA8B;IAC9B,aAAgC;IAChC,mBAA4C;IAC5C,SAAwB;IACxB,sBAAkD;IAMhD,iBAAoE;IAKxE,4DAkCC;IA5CC,sBAAkC;CA6CrC;wBA/DuB,YAAY"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/browser-agent",
3
- "version": "1.313.1",
3
+ "version": "1.314.0-rc.1",
4
4
  "private": false,
5
5
  "author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
6
6
  "description": "New Relic Browser Agent",
@@ -284,4 +284,4 @@
284
284
  "README.md",
285
285
  "CHANGELOG.md"
286
286
  ]
287
- }
287
+ }
@@ -13,5 +13,6 @@ export const SUPPORTS_REGISTERED_ENTITIES = {
13
13
  [FEATURE_NAMES.logging]: true,
14
14
  [FEATURE_NAMES.genericEvents]: true,
15
15
  [FEATURE_NAMES.jserrors]: true,
16
- [FEATURE_NAMES.ajax]: true
16
+ [FEATURE_NAMES.ajax]: true,
17
+ [FEATURE_NAMES.pageViewTiming]: true
17
18
  }
@@ -1,8 +1,10 @@
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
 
6
+ import { getRegisteredTargetsFromId } from '../v2/utils'
7
+
6
8
  /**
7
9
  * Generates a CSS selector path for the given element, if possible.
8
10
  * Also gather metadata about the element's nearest fields, and whether there are any links or buttons in the path.
@@ -12,10 +14,11 @@
12
14
  *
13
15
  * @param {HTMLElement} elem
14
16
  * @param {Array<string>} [targetFields=[]] specifies which fields to gather from the nearest element in the path
15
- * @returns {{path: (undefined|string), nearestFields: {}, hasButton: boolean, hasLink: boolean}}
17
+ * @returns {{path: (undefined|string), nearestFields: {}, targets: Array, hasButton: boolean, hasLink: boolean}}
16
18
  */
17
- export const analyzeElemPath = (elem, targetFields = []) => {
18
- const result = { path: undefined, nearestFields: {}, hasButton: false, hasLink: false }
19
+ export const analyzeElemPath = (elem, targetFields = [], agentRef) => {
20
+ const targets = []
21
+ const result = { path: undefined, nearestFields: {}, get targets () { return targets.length ? targets : [undefined] }, hasButton: false, hasLink: false }
19
22
  if (!elem) return result
20
23
  if (elem === window) { result.path = 'window'; return result }
21
24
  if (elem === document) { result.path = 'document'; return result }
@@ -30,6 +33,12 @@ export const analyzeElemPath = (elem, targetFields = []) => {
30
33
  result.hasButton ||= tagName === 'button' || (tagName === 'input' && elem.type.toLowerCase() === 'button')
31
34
 
32
35
  targetFields.forEach(field => { result.nearestFields[nearestAttrName(field)] ||= (elem[field]?.baseVal || elem[field]) })
36
+
37
+ const dataAttrs = elem?.dataset
38
+ if (dataAttrs.nrMfeId) {
39
+ targets.push(...getRegisteredTargetsFromId(dataAttrs.nrMfeId, agentRef))
40
+ }
41
+
33
42
  pathSelector = buildPathSelector(elem, pathSelector)
34
43
  elem = elem.parentNode
35
44
  }
@@ -40,7 +40,12 @@ export class TimeKeeper {
40
40
  */
41
41
  #ready = false
42
42
 
43
- #reportedDrift = false
43
+ /**
44
+ * The total measured drift in milliseconds. Represents how much performance.now()
45
+ * has fallen behind Date.now(), which is used to correct timestamp conversions.
46
+ * @type {number}
47
+ */
48
+ #measuredDrift = 0
44
49
 
45
50
  constructor (sessionObj) {
46
51
  this.#session = sessionObj
@@ -49,7 +54,6 @@ export class TimeKeeper {
49
54
  }
50
55
 
51
56
  #detectDrift () {
52
- if (this.#reportedDrift) return
53
57
  try {
54
58
  // Drift detection: measures if performance.now() and Date.now() have become desynchronized
55
59
  // This can happen when a machine sleeps and the performance timer freezes while Date continues
@@ -60,8 +64,13 @@ export class TimeKeeper {
60
64
  // Note: localTimeDiff (server time offset) is NOT part of drift - that's a legitimate offset
61
65
  const drift = (Date.now() - originTime) - performance.now()
62
66
  if (drift > 1000) {
63
- this.#reportedDrift = true
64
- handle(SUPPORTABILITY_METRIC_CHANNEL, ['Generic/TimeKeeper/ClockDrift/Detected', drift], undefined, FEATURE_NAMES.metrics, this.#session.agentRef.ee)
67
+ // Check if this is new drift (increase of >1000ms from last measurement)
68
+ const newDrift = drift - this.#measuredDrift
69
+ if (newDrift > 1000) {
70
+ // Update measured drift and report it
71
+ this.#measuredDrift = drift
72
+ if (this.#session) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Generic/TimeKeeper/ClockDrift/Detected', drift], undefined, FEATURE_NAMES.metrics, this.#session.agentRef.ee)
73
+ }
65
74
  }
66
75
  } catch (err) {
67
76
  // Silently ignore drift detection errors to avoid breaking normal operation
@@ -116,7 +125,8 @@ export class TimeKeeper {
116
125
  */
117
126
  convertRelativeTimestamp (relativeTime) {
118
127
  this.#detectDrift()
119
- return originTime + relativeTime
128
+ // Add measured drift to compensate for performance.now() falling behind
129
+ return originTime + relativeTime + this.#measuredDrift
120
130
  }
121
131
 
122
132
  /**
@@ -127,7 +137,8 @@ export class TimeKeeper {
127
137
  */
128
138
  convertAbsoluteTimestamp (timestamp) {
129
139
  this.#detectDrift()
130
- return timestamp - originTime
140
+ // Subtract measured drift since we're converting from absolute to relative
141
+ return timestamp - originTime - this.#measuredDrift
131
142
  }
132
143
 
133
144
  /**
@@ -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 { onCLS } from 'web-vitals/attribution'
@@ -18,6 +18,6 @@ if (isBrowserScope) {
18
18
  largestShiftValue: attribution.largestShiftValue,
19
19
  loadState: attribution.loadState
20
20
  }
21
- cumulativeLayoutShift.update({ value, attrs })
21
+ cumulativeLayoutShift.update({ value, attrs, element: attribution.largestShiftSource?.node })
22
22
  }, { reportAllChanges: true })
23
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 { onINP } from 'web-vitals/attribution'
@@ -25,6 +25,6 @@ if (isBrowserScope) {
25
25
  presentationDelay: attribution.presentationDelay,
26
26
  loadState: attribution.loadState
27
27
  }
28
- interactionToNextPaint.update({ value, attrs })
28
+ interactionToNextPaint.update({ value, attrs, element: attribution.interactionTargetElement })
29
29
  })
30
30
  }
@@ -31,6 +31,6 @@ if (isBrowserScope) {
31
31
  if (attribution.element) attrs.element = attribution.element
32
32
  if (attribution.url) attrs.elUrl = cleanURL(attribution.url)
33
33
 
34
- largestContentfulPaint.update({ value, attrs })
34
+ largestContentfulPaint.update({ value, attrs, element: lcpEntry?.element })
35
35
  })
36
36
  }
@@ -12,12 +12,15 @@ export const loadTime = new VitalMetric(VITAL_NAMES.LOAD_TIME)
12
12
  if (isBrowserScope) {
13
13
  const perf = globalScope.performance
14
14
  const handler = () => {
15
- if (!loadTime.isValid && perf) {
15
+ // setTimeout defers the read until after the load event handler returns,
16
+ // ensuring loadEventEnd is populated (non-zero) — matching the web-vitals onTTFB pattern
17
+ setTimeout(() => {
18
+ if (loadTime.isValid || !perf) return
16
19
  const navEntry = getNavigationEntry()
17
20
  loadTime.update({
18
21
  value: navEntry ? navEntry.loadEventEnd : (perf.timing?.loadEventEnd - originTime)
19
22
  })
20
- }
23
+ }, 0)
21
24
  }
22
25
 
23
26
  onWindowLoad(handler, true)
@@ -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
  export class VitalMetric {
@@ -12,12 +12,13 @@ export class VitalMetric {
12
12
  this.roundingMethod = typeof roundingMethod === 'function' ? roundingMethod : Math.floor
13
13
  }
14
14
 
15
- update ({ value, attrs = {} }) {
15
+ update ({ value, attrs = {}, element }) {
16
16
  if (value === undefined || value === null || value < 0) return
17
17
  const state = {
18
18
  value: this.roundingMethod(value),
19
19
  name: this.name,
20
- attrs
20
+ attrs,
21
+ element
21
22
  }
22
23
 
23
24
  this.history.push(state)
@@ -34,7 +35,8 @@ export class VitalMetric {
34
35
  return this.history[this.history.length - 1] || {
35
36
  value: undefined,
36
37
  name: this.name,
37
- attrs: {}
38
+ attrs: {},
39
+ element: undefined
38
40
  }
39
41
  }
40
42
 
@@ -6,13 +6,14 @@ import { registerHandler } from '../../../common/event-emitter/register-handler'
6
6
  import { stringify } from '../../../common/util/stringify'
7
7
  import { handle } from '../../../common/event-emitter/handle'
8
8
  import { setDenyList, shouldCollectEvent } from '../../../common/deny-list/deny-list'
9
- import { FEATURE_NAME } from '../constants'
9
+ import { AJAX_ID, FEATURE_NAME } from '../constants'
10
10
  import { FEATURE_NAMES } from '../../../loaders/features/features'
11
11
  import { AggregateBase } from '../../utils/aggregate-base'
12
12
  import { parseGQL } from './gql'
13
13
  import { nullable, numeric, getAddStringContext, addCustomAttributes } from '../../../common/serialize/bel-serializer'
14
14
  import { gosNREUMOriginals } from '../../../common/window/nreum'
15
15
  import { getVersion2Attributes, getVersion2DuplicationAttributes, shouldDuplicate } from '../../../common/v2/utils'
16
+ import { generateUuid } from '../../../common/ids/unique-id'
16
17
 
17
18
  export class Aggregate extends AggregateBase {
18
19
  static featureName = FEATURE_NAME
@@ -91,7 +92,8 @@ export class Aggregate extends AggregateBase {
91
92
  type,
92
93
  startTime,
93
94
  endTime,
94
- callbackDuration: metrics.cbTime
95
+ callbackDuration: metrics.cbTime,
96
+ [AJAX_ID]: generateUuid() // all AjaxRequest events should have a unique identifier to allow for easier grouping and analysis in the UI
95
97
  }
96
98
 
97
99
  if (ctx.dt) {
@@ -168,7 +170,8 @@ export class Aggregate extends AggregateBase {
168
170
  const attrParts = addCustomAttributes({
169
171
  ...(jsAttributes || {}),
170
172
  ...(event.gql || {}),
171
- ...(event.targetAttributes || {}) // used to supply the version 2 attributes, either MFE target or duplication attributes for the main agent app
173
+ ...(event.targetAttributes || {}), // used to supply the version 2 attributes, either MFE target or duplication attributes for the main agent app
174
+ [AJAX_ID]: event[AJAX_ID] // all AjaxRequest events should have a unique identifier to allow for easier grouping and analysis in the UI
172
175
  }, addString)
173
176
 
174
177
  fields.unshift(numeric(attrParts.length))
@@ -1,7 +1,9 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { FEATURE_NAMES } from '../../loaders/features/features'
6
6
 
7
7
  export const FEATURE_NAME = FEATURE_NAMES.ajax
8
+
9
+ export const AJAX_ID = 'ajaxRequest.id'
@@ -14,7 +14,7 @@ import { applyFnToProps } from '../../../common/util/traverse'
14
14
  import { UserActionsAggregator } from './user-actions/user-actions-aggregator'
15
15
  import { isIFrameWindow } from '../../../common/dom/iframe'
16
16
  import { isPureObject } from '../../../common/util/type-check'
17
- import { getVersion2Attributes } from '../../../common/v2/utils'
17
+ import { getVersion2Attributes, getVersion2DuplicationAttributes, shouldDuplicate } from '../../../common/v2/utils'
18
18
 
19
19
  export class Aggregate extends AggregateBase {
20
20
  static featureName = FEATURE_NAME
@@ -61,7 +61,7 @@ export class Aggregate extends AggregateBase {
61
61
 
62
62
  let addUserAction = () => { /** no-op */ }
63
63
  if (isBrowserScope && agentRef.init.user_actions.enabled) {
64
- this.#userActionAggregator = new UserActionsAggregator()
64
+ this.#userActionAggregator = new UserActionsAggregator(this.agentRef)
65
65
  this.harvestOpts.beforeUnload = () => addUserAction?.(this.#userActionAggregator.aggregationEvent)
66
66
 
67
67
  addUserAction = (aggregatedUserAction) => {
@@ -70,50 +70,54 @@ export class Aggregate extends AggregateBase {
70
70
  * so we still need to validate that an event was given to this method before we try to add */
71
71
  if (aggregatedUserAction?.event) {
72
72
  const { target, timeStamp, type } = aggregatedUserAction.event
73
- const userActionEvent = {
74
- eventType: 'UserAction',
75
- timestamp: this.#toEpoch(timeStamp),
76
- action: type,
77
- actionCount: aggregatedUserAction.count,
78
- actionDuration: aggregatedUserAction.relativeMs[aggregatedUserAction.relativeMs.length - 1],
79
- actionMs: aggregatedUserAction.relativeMs,
80
- rageClick: aggregatedUserAction.rageClick,
81
- target: aggregatedUserAction.selectorPath,
82
- currentUrl: aggregatedUserAction.currentUrl,
83
- ...(isIFrameWindow(window) && { iframe: true }),
84
- ...(this.agentRef.init.user_actions.elementAttributes.reduce((acc, field) => {
73
+
74
+ aggregatedUserAction.targets.forEach(mfeTarget => {
75
+ const userActionEvent = {
76
+ eventType: 'UserAction',
77
+ timestamp: this.#toEpoch(timeStamp),
78
+ action: type,
79
+ actionCount: aggregatedUserAction.count,
80
+ actionDuration: aggregatedUserAction.relativeMs[aggregatedUserAction.relativeMs.length - 1],
81
+ actionMs: aggregatedUserAction.relativeMs,
82
+ rageClick: aggregatedUserAction.rageClick,
83
+ target: aggregatedUserAction.selectorPath,
84
+ currentUrl: aggregatedUserAction.currentUrl,
85
+ ...(isIFrameWindow(window) && { iframe: true }),
86
+ ...(this.agentRef.init.user_actions.elementAttributes.reduce((acc, field) => {
85
87
  /** prevent us from capturing an obscenely long value */
86
- if (canTrustTargetAttribute(field)) acc[targetAttrName(field)] = String(target[field]).trim().slice(0, 128)
87
- return acc
88
- }, {})),
89
- ...aggregatedUserAction.nearestTargetFields,
90
- ...(aggregatedUserAction.deadClick && { deadClick: true }),
91
- ...(aggregatedUserAction.errorClick && { errorClick: true })
92
- }
93
- this.addEvent(userActionEvent)
94
- this.#trackUserActionSM(userActionEvent)
95
-
96
- /**
88
+ if (canTrustTargetAttribute(field)) acc[targetAttrName(field)] = String(target[field]).trim().slice(0, 128)
89
+ return acc
90
+ }, {})),
91
+ ...aggregatedUserAction.nearestTargetFields,
92
+ ...(aggregatedUserAction.deadClick && { deadClick: true }),
93
+ ...(aggregatedUserAction.errorClick && { errorClick: true })
94
+ }
95
+ this.addEvent(userActionEvent, mfeTarget)
96
+
97
+ this.#trackUserActionSM(userActionEvent)
98
+
99
+ /**
97
100
  * Returns the original target field name with `target` prepended and camelCased
98
101
  * @param {string} originalFieldName
99
102
  * @returns {string} the target field name
100
103
  */
101
- function targetAttrName (originalFieldName) {
104
+ function targetAttrName (originalFieldName) {
102
105
  /** preserve original renaming structure for pre-existing field maps */
103
- if (originalFieldName === 'tagName') originalFieldName = 'tag'
104
- if (originalFieldName === 'className') originalFieldName = 'class'
105
- /** return the original field name, cap'd and prepended with target to match formatting */
106
- return `target${originalFieldName.charAt(0).toUpperCase() + originalFieldName.slice(1)}`
107
- }
106
+ if (originalFieldName === 'tagName') originalFieldName = 'tag'
107
+ if (originalFieldName === 'className') originalFieldName = 'class'
108
+ /** return the original field name, cap'd and prepended with target to match formatting */
109
+ return `target${originalFieldName.charAt(0).toUpperCase() + originalFieldName.slice(1)}`
110
+ }
108
111
 
109
- /**
112
+ /**
110
113
  * Only trust attributes that exist on HTML element targets, which excludes the window and the document targets
111
114
  * @param {string} attribute The attribute to check for on the target element
112
115
  * @returns {boolean} Whether the target element has the attribute and can be trusted
113
116
  */
114
- function canTrustTargetAttribute (attribute) {
115
- return !!(aggregatedUserAction.selectorPath !== 'window' && aggregatedUserAction.selectorPath !== 'document' && target instanceof HTMLElement && target?.[attribute])
116
- }
117
+ function canTrustTargetAttribute (attribute) {
118
+ return !!(aggregatedUserAction.selectorPath !== 'window' && aggregatedUserAction.selectorPath !== 'document' && target instanceof HTMLElement && target?.[attribute])
119
+ }
120
+ })
117
121
  }
118
122
  } catch (e) {
119
123
  // do nothing for now
@@ -314,9 +318,7 @@ export class Aggregate extends AggregateBase {
314
318
  timestamp: this.#toEpoch(now()),
315
319
  /** all generic events require pageUrl(s) */
316
320
  pageUrl: cleanURL('' + initialLocation),
317
- currentUrl: cleanURL('' + location),
318
- /** Specific attributes only supplied if harvesting to endpoint version 2 */
319
- ...(getVersion2Attributes(target, this))
321
+ currentUrl: cleanURL('' + location)
320
322
  }
321
323
 
322
324
  const eventAttributes = {
@@ -328,7 +330,8 @@ export class Aggregate extends AggregateBase {
328
330
  ...obj
329
331
  }
330
332
 
331
- this.events.add(eventAttributes)
333
+ this.events.add({ ...eventAttributes, ...getVersion2Attributes(target, this) })
334
+ if (shouldDuplicate(target, this)) this.addEvent({ ...eventAttributes, ...getVersion2DuplicationAttributes(target, this) })
332
335
  }
333
336
 
334
337
  serializer (eventBuffer) {