@newrelic/browser-agent 1.320.1-rc.0 → 1.320.1-rc.2

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 (157) hide show
  1. package/dist/cjs/common/config/runtime.js +3 -0
  2. package/dist/cjs/common/constants/env.cdn.js +1 -1
  3. package/dist/cjs/common/constants/env.npm.js +1 -1
  4. package/dist/cjs/common/dom/selector-path.js +1 -1
  5. package/dist/cjs/common/event-listener/event-listener-opts.js +1 -1
  6. package/dist/cjs/common/harvest/connector.js +200 -0
  7. package/dist/cjs/common/harvest/harvester.js +5 -8
  8. package/dist/cjs/common/harvest/send.js +8 -7
  9. package/dist/cjs/common/session/session-entity.js +1 -1
  10. package/dist/cjs/common/timer/interaction-timer.js +2 -2
  11. package/dist/cjs/common/unload/eol.js +3 -3
  12. package/dist/cjs/common/util/console.js +2 -1
  13. package/dist/cjs/common/util/feature-flags.js +2 -1
  14. package/dist/cjs/common/v2/constants.js +21 -0
  15. package/dist/cjs/common/v2/utils.js +25 -23
  16. package/dist/cjs/common/window/page-visibility.js +1 -1
  17. package/dist/cjs/common/wrap/wrap-events.js +4 -3
  18. package/dist/cjs/common/wrap/wrap-fetch.js +2 -2
  19. package/dist/cjs/common/wrap/wrap-function.js +1 -1
  20. package/dist/cjs/common/wrap/wrap-history.js +3 -2
  21. package/dist/cjs/common/wrap/wrap-xhr.js +2 -2
  22. package/dist/cjs/features/ajax/aggregate/index.js +1 -1
  23. package/dist/cjs/features/generic_events/aggregate/index.js +1 -1
  24. package/dist/cjs/features/generic_events/instrument/index.js +1 -1
  25. package/dist/cjs/features/jserrors/aggregate/index.js +5 -5
  26. package/dist/cjs/features/logging/instrument/index.js +1 -1
  27. package/dist/cjs/features/page_view_event/aggregate/index.js +2 -6
  28. package/dist/cjs/features/page_view_event/aggregate_v2/index.js +109 -0
  29. package/dist/cjs/features/page_view_event/instrument/index.js +6 -1
  30. package/dist/cjs/features/page_view_timing/aggregate/index.js +5 -1
  31. package/dist/cjs/features/session_replay/instrument/index.js +9 -0
  32. package/dist/cjs/features/session_replay/shared/recorder.js +2 -1
  33. package/dist/cjs/features/session_trace/instrument/index.js +3 -3
  34. package/dist/cjs/features/soft_navigations/instrument/index.js +2 -2
  35. package/dist/cjs/features/utils/aggregate-base.js +0 -12
  36. package/dist/cjs/features/utils/instrument-base.js +72 -29
  37. package/dist/cjs/loaders/agent.js +2 -1
  38. package/dist/cjs/loaders/api/consent.js +2 -2
  39. package/dist/cjs/loaders/api/register.js +8 -8
  40. package/dist/cjs/loaders/configure/configure.js +14 -0
  41. package/dist/cjs/loaders/features/features.js +2 -1
  42. package/dist/esm/common/config/runtime.js +3 -0
  43. package/dist/esm/common/constants/env.cdn.js +1 -1
  44. package/dist/esm/common/constants/env.npm.js +1 -1
  45. package/dist/esm/common/dom/selector-path.js +1 -1
  46. package/dist/esm/common/event-listener/event-listener-opts.js +1 -1
  47. package/dist/esm/common/harvest/connector.js +193 -0
  48. package/dist/esm/common/harvest/harvester.js +5 -8
  49. package/dist/esm/common/harvest/send.js +10 -9
  50. package/dist/esm/common/session/session-entity.js +1 -1
  51. package/dist/esm/common/timer/interaction-timer.js +2 -2
  52. package/dist/esm/common/unload/eol.js +3 -3
  53. package/dist/esm/common/util/console.js +2 -1
  54. package/dist/esm/common/util/feature-flags.js +2 -1
  55. package/dist/esm/common/v2/constants.js +15 -0
  56. package/dist/esm/common/v2/utils.js +24 -22
  57. package/dist/esm/common/window/page-visibility.js +1 -1
  58. package/dist/esm/common/wrap/wrap-events.js +4 -3
  59. package/dist/esm/common/wrap/wrap-fetch.js +2 -2
  60. package/dist/esm/common/wrap/wrap-function.js +1 -1
  61. package/dist/esm/common/wrap/wrap-history.js +3 -2
  62. package/dist/esm/common/wrap/wrap-xhr.js +2 -2
  63. package/dist/esm/features/ajax/aggregate/index.js +2 -2
  64. package/dist/esm/features/generic_events/aggregate/index.js +1 -1
  65. package/dist/esm/features/generic_events/instrument/index.js +1 -1
  66. package/dist/esm/features/jserrors/aggregate/index.js +6 -6
  67. package/dist/esm/features/logging/instrument/index.js +1 -1
  68. package/dist/esm/features/page_view_event/aggregate/index.js +2 -6
  69. package/dist/esm/features/page_view_event/aggregate_v2/index.js +100 -0
  70. package/dist/esm/features/page_view_event/instrument/index.js +6 -1
  71. package/dist/esm/features/page_view_timing/aggregate/index.js +5 -1
  72. package/dist/esm/features/session_replay/instrument/index.js +9 -0
  73. package/dist/esm/features/session_replay/shared/recorder.js +2 -1
  74. package/dist/esm/features/session_trace/instrument/index.js +3 -3
  75. package/dist/esm/features/soft_navigations/instrument/index.js +2 -2
  76. package/dist/esm/features/utils/aggregate-base.js +0 -12
  77. package/dist/esm/features/utils/instrument-base.js +71 -28
  78. package/dist/esm/loaders/agent.js +2 -1
  79. package/dist/esm/loaders/api/consent.js +2 -2
  80. package/dist/esm/loaders/api/register.js +1 -1
  81. package/dist/esm/loaders/configure/configure.js +14 -0
  82. package/dist/esm/loaders/features/features.js +1 -0
  83. package/dist/tsconfig.tsbuildinfo +1 -1
  84. package/dist/types/common/config/runtime.d.ts.map +1 -1
  85. package/dist/types/common/event-listener/event-listener-opts.d.ts +1 -1
  86. package/dist/types/common/harvest/connector.d.ts +6 -0
  87. package/dist/types/common/harvest/connector.d.ts.map +1 -0
  88. package/dist/types/common/harvest/harvester.d.ts.map +1 -1
  89. package/dist/types/common/harvest/send.d.ts.map +1 -1
  90. package/dist/types/common/unload/eol.d.ts +1 -1
  91. package/dist/types/common/unload/eol.d.ts.map +1 -1
  92. package/dist/types/common/util/console.d.ts +2 -1
  93. package/dist/types/common/util/console.d.ts.map +1 -1
  94. package/dist/types/common/v2/constants.d.ts +9 -0
  95. package/dist/types/common/v2/constants.d.ts.map +1 -0
  96. package/dist/types/common/v2/utils.d.ts +36 -11
  97. package/dist/types/common/v2/utils.d.ts.map +1 -1
  98. package/dist/types/common/wrap/wrap-events.d.ts +2 -1
  99. package/dist/types/common/wrap/wrap-events.d.ts.map +1 -1
  100. package/dist/types/common/wrap/wrap-history.d.ts +2 -1
  101. package/dist/types/common/wrap/wrap-history.d.ts.map +1 -1
  102. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  103. package/dist/types/features/page_view_event/aggregate_v2/index.d.ts +20 -0
  104. package/dist/types/features/page_view_event/aggregate_v2/index.d.ts.map +1 -0
  105. package/dist/types/features/page_view_event/instrument/index.d.ts.map +1 -1
  106. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  107. package/dist/types/features/session_replay/instrument/index.d.ts +1 -0
  108. package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -1
  109. package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
  110. package/dist/types/features/session_trace/instrument/index.d.ts.map +1 -1
  111. package/dist/types/features/utils/aggregate-base.d.ts +0 -5
  112. package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
  113. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  114. package/dist/types/loaders/agent.d.ts.map +1 -1
  115. package/dist/types/loaders/configure/configure.d.ts.map +1 -1
  116. package/dist/types/loaders/features/features.d.ts +1 -0
  117. package/dist/types/loaders/features/features.d.ts.map +1 -1
  118. package/package.json +1 -1
  119. package/src/common/config/runtime.js +3 -0
  120. package/src/common/dom/selector-path.js +1 -1
  121. package/src/common/event-listener/event-listener-opts.js +1 -1
  122. package/src/common/harvest/connector.js +168 -0
  123. package/src/common/harvest/harvester.js +7 -9
  124. package/src/common/harvest/send.js +9 -7
  125. package/src/common/session/session-entity.js +1 -1
  126. package/src/common/timer/interaction-timer.js +2 -2
  127. package/src/common/unload/eol.js +3 -3
  128. package/src/common/util/console.js +2 -1
  129. package/src/common/util/feature-flags.js +1 -1
  130. package/src/common/v2/constants.js +15 -0
  131. package/src/common/v2/utils.js +23 -21
  132. package/src/common/window/page-visibility.js +1 -1
  133. package/src/common/wrap/wrap-events.js +4 -3
  134. package/src/common/wrap/wrap-fetch.js +2 -2
  135. package/src/common/wrap/wrap-function.js +1 -1
  136. package/src/common/wrap/wrap-history.js +3 -2
  137. package/src/common/wrap/wrap-xhr.js +2 -2
  138. package/src/features/ajax/aggregate/index.js +2 -2
  139. package/src/features/generic_events/aggregate/index.js +1 -1
  140. package/src/features/generic_events/instrument/index.js +1 -1
  141. package/src/features/jserrors/aggregate/index.js +6 -6
  142. package/src/features/logging/instrument/index.js +1 -1
  143. package/src/features/page_view_event/aggregate/index.js +2 -6
  144. package/src/features/page_view_event/aggregate_v2/index.js +97 -0
  145. package/src/features/page_view_event/instrument/index.js +6 -1
  146. package/src/features/page_view_timing/aggregate/index.js +5 -1
  147. package/src/features/session_replay/instrument/index.js +9 -0
  148. package/src/features/session_replay/shared/recorder.js +2 -1
  149. package/src/features/session_trace/instrument/index.js +3 -3
  150. package/src/features/soft_navigations/instrument/index.js +2 -2
  151. package/src/features/utils/aggregate-base.js +0 -12
  152. package/src/features/utils/instrument-base.js +71 -25
  153. package/src/loaders/agent.js +2 -1
  154. package/src/loaders/api/consent.js +2 -2
  155. package/src/loaders/api/register.js +1 -1
  156. package/src/loaders/configure/configure.js +12 -0
  157. package/src/loaders/features/features.js +1 -0
@@ -11,7 +11,7 @@
11
11
  /**
12
12
  * Exposes the version of the agent
13
13
  */
14
- export const VERSION = "1.320.1-rc.0";
14
+ export const VERSION = "1.320.1-rc.2";
15
15
 
16
16
  /**
17
17
  * Exposes the build type of the agent
@@ -11,7 +11,7 @@
11
11
  /**
12
12
  * Exposes the version of the agent
13
13
  */
14
- export const VERSION = "1.320.1-rc.0";
14
+ export const VERSION = "1.320.1-rc.2";
15
15
 
16
16
  /**
17
17
  * Exposes the build type of the agent
@@ -22,7 +22,7 @@ export const analyzeElemPath = (elem, targetFields = [], agentRef) => {
22
22
  path: undefined,
23
23
  nearestFields: {},
24
24
  get targets() {
25
- return targets.length ? targets : [undefined];
25
+ return targets.length ? targets : [agentRef.runtime.v2Target];
26
26
  },
27
27
  hasButton: false,
28
28
  hasLink: false
@@ -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 function eventListenerOpts(useCapture, abortSignal) {
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ import { isBrowserScope } from '../constants/runtime';
7
+ import { send } from './send';
8
+ import { TimeKeeper } from '../timing/time-keeper';
9
+ import { activateFeatures } from '../util/feature-flags';
10
+ import { now } from '../timing/now';
11
+ import { warn } from '../util/console';
12
+ import { VERSION } from "../constants/env.npm";
13
+ import { CONNECT, FEATURE_NAMES, FEATURE_TO_ENDPOINT } from '../../loaders/features/features';
14
+ import { handle } from '../event-emitter/handle';
15
+ import { SUPPORTABILITY_METRIC_CHANNEL } from '../../features/metrics/constants';
16
+ const MAX_CONNECT_ATTEMPTS = 3;
17
+ const CONNECT_RETRY_BASE_MS = 3000; // affects each retry window proportionally (attempt 2: 0-3s, 3: 0-6s, 4: 0-12s, etc)
18
+
19
+ export class Connector {
20
+ #agentRef;
21
+ #connectStartTime;
22
+ #numOfAttempts = 0;
23
+ constructor(agentRef) {
24
+ if (!agentRef.init.feature_flags.includes('rum_v2')) return; // the Connector does not run in v1; legacy PVE has its own BCS handling
25
+ this.#agentRef = agentRef;
26
+ agentRef.runtime.timeKeeper = new TimeKeeper(agentRef.runtime.session);
27
+ if (isBrowserScope) {
28
+ const cached = agentRef.runtime.session && agentRef.runtime.session.state.cachedRumResponse;
29
+ if (cached) {
30
+ // `cachedRumResponse` is stored flat (`{ app, ...flags }`, matching the old v1 RUM response), so re-nest it before applying.
31
+ const {
32
+ app,
33
+ ...config
34
+ } = cached;
35
+ this.#applyConnectResponse({
36
+ app,
37
+ config
38
+ });
39
+ return;
40
+ }
41
+ }
42
+ this.makeConnectRequest();
43
+ }
44
+ makeConnectRequest(retryHeaders) {
45
+ this.#connectStartTime = now();
46
+ return send(this.#agentRef, {
47
+ endpoint: "browser/connect/2/".concat(this.#agentRef.info.licenseKey),
48
+ payload: {
49
+ body: {},
50
+ qs: {
51
+ a: this.#agentRef.info.applicationID,
52
+ v: VERSION,
53
+ s: this.#agentRef.runtime.session?.state.value || '0'
54
+ }
55
+ },
56
+ localOpts: {
57
+ sendEmptyBody: true,
58
+ headers: retryHeaders
59
+ },
60
+ raw: true,
61
+ featureName: CONNECT,
62
+ cbFinished: this.#processConnectResponse.bind(this)
63
+ });
64
+ }
65
+ #applyConnectResponse({
66
+ app,
67
+ config
68
+ }) {
69
+ if (!Object.keys(this.#agentRef.runtime.appMetadata).length) this.#agentRef.runtime.appMetadata = app;
70
+ activateFeatures(config, this.#agentRef);
71
+ }
72
+ #processConnectResponse({
73
+ sent,
74
+ status,
75
+ retry,
76
+ xhr,
77
+ responseText
78
+ }) {
79
+ const connectEndTime = now();
80
+ const session = this.#agentRef.runtime.session;
81
+ if (session) {
82
+ // Double check that there isn't a response saved (e.g. from another agent/browser tab) btwn the async time of the request and now in a race-condition.
83
+ const cachedResp = session.state.cachedRumResponse;
84
+ if (cachedResp) {
85
+ // `cachedRumResponse` is stored flat (`{ app, ...flags }`, matching the old v1 RUM response), so re-nest it before applying.
86
+ const {
87
+ app,
88
+ ...config
89
+ } = cachedResp;
90
+ this.#applyConnectResponse({
91
+ app,
92
+ config
93
+ });
94
+ return;
95
+ }
96
+ }
97
+ const shouldRetry = sent && retry; // important to note: no retry if HTTP status is 0
98
+ if (shouldRetry && ++this.#numOfAttempts < MAX_CONNECT_ATTEMPTS) {
99
+ // We use an exponential backoff calculation for the retry to avoid thundering herd, skipping any further processing.
100
+ const delay = Math.floor(Math.random() * CONNECT_RETRY_BASE_MS * 2 ** (this.#numOfAttempts - 1));
101
+ const retryHeaders = [{
102
+ key: 'X-Retry-Count',
103
+ value: this.#numOfAttempts
104
+ }, {
105
+ key: 'X-Previous-Status',
106
+ value: status
107
+ }];
108
+ setTimeout(() => this.makeConnectRequest(retryHeaders), delay);
109
+ return;
110
+ }
111
+ if (status >= 400 || status === 0) {
112
+ warn(18, status);
113
+
114
+ // Get estimated payload size of our backlog.
115
+ const textEncoder = new TextEncoder();
116
+ const payloadSize = Object.values(newrelic.ee.backlog).reduce((acc, value) => {
117
+ if (!value) return acc;
118
+ const encoded = textEncoder.encode(value);
119
+ return acc + encoded.byteLength;
120
+ }, 0);
121
+
122
+ // Send SMs about the (last) failed connect request, then abort agent.
123
+ const BCSError = 'BCS/Error/';
124
+ const sm = [{
125
+ params: {
126
+ name: BCSError + status
127
+ },
128
+ stats: {
129
+ c: 1
130
+ }
131
+ }, {
132
+ params: {
133
+ name: BCSError + 'Duration/Ms'
134
+ },
135
+ stats: {
136
+ c: 1,
137
+ t: connectEndTime - this.#connectStartTime
138
+ }
139
+ }, {
140
+ params: {
141
+ name: BCSError + 'Dropped/Bytes'
142
+ },
143
+ stats: {
144
+ c: 1,
145
+ t: payloadSize
146
+ }
147
+ }];
148
+ send(this.#agentRef, {
149
+ // note we have to send it this way because the Metrics feature will never be harvested under this branch of code (BCS failure)
150
+ endpoint: FEATURE_TO_ENDPOINT[FEATURE_NAMES.metrics],
151
+ payload: {
152
+ body: {
153
+ sm
154
+ }
155
+ },
156
+ featureName: FEATURE_NAMES.metrics
157
+ });
158
+ this.#agentRef.ee.abort();
159
+ return;
160
+ }
161
+ let resp;
162
+ try {
163
+ resp = JSON.parse(responseText);
164
+ } catch (error) {
165
+ warn(53, error);
166
+ this.#agentRef.ee.abort();
167
+ return;
168
+ }
169
+ // store the response for other pages later in same session or in parallel tabs; flatten `.config` into the old v1 RUM response shape
170
+ if (session) session.write({
171
+ cachedRumResponse: {
172
+ app: resp.app,
173
+ ...resp.config
174
+ }
175
+ });
176
+ try {
177
+ const wasReady = this.#agentRef.runtime.timeKeeper.ready;
178
+ this.#agentRef.runtime.timeKeeper.processRumRequest(xhr, this.#connectStartTime, connectEndTime, resp.app.nrServerTime);
179
+ if (!this.#agentRef.runtime.timeKeeper.ready) throw new Error('TimeKeeper not ready');
180
+
181
+ // If timeKeeper's origin time is ahead of nrServerTime, then the timestamp is invalid. Report a supportability metric.
182
+ const timeDiff = this.#agentRef.runtime.timeKeeper.correctedOriginTime - resp.app.nrServerTime;
183
+ if (wasReady && timeDiff > 0) {
184
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['Generic/TimeKeeper/InvalidTimestamp/Seen', timeDiff], undefined, FEATURE_NAMES.metrics, this.#agentRef.ee);
185
+ }
186
+ } catch (error) {
187
+ warn(17, error);
188
+ this.#agentRef.ee.abort();
189
+ return;
190
+ }
191
+ this.#applyConnectResponse(resp);
192
+ }
193
+ }
@@ -19,11 +19,13 @@ export class Harvester {
19
19
  constructor(agentRef) {
20
20
  this.agentRef = agentRef;
21
21
 
22
- // Create obfuscator for harvest metadata (referrer URL, etc.)
23
22
  // No event type specified - applies to all harvests regardless of feature
24
23
  this.obfuscator = new Obfuscator(agentRef);
24
+ }
25
+ startTimer(harvestInterval = this.agentRef.init.harvest.interval) {
26
+ if (this.#started) return;
27
+ this.#started = true;
25
28
  subscribeToEOL(() => {
26
- // do one last harvest round or check
27
29
  this.initializedAggregates.forEach(aggregateInst => {
28
30
  // let all features wrap up things needed to do before ANY harvest in case there's last minute cross-feature data dependencies
29
31
  if (typeof aggregateInst.harvestOpts.beforeUnload === 'function') aggregateInst.harvestOpts.beforeUnload();
@@ -31,12 +33,7 @@ export class Harvester {
31
33
  this.initializedAggregates.forEach(aggregateInst => this.triggerHarvestFor(aggregateInst, {
32
34
  isFinalHarvest: true
33
35
  }));
34
- /* This callback should run in bubble phase, so that CWV api, like "onLCP", is called before the final harvest so that emitted timings are part of last outgoing. */
35
- }, false);
36
- }
37
- startTimer(harvestInterval = this.agentRef.init.harvest.interval) {
38
- if (this.#started) return;
39
- this.#started = true;
36
+ });
40
37
  const onHarvestInterval = () => {
41
38
  this.initializedAggregates.forEach(aggregateInst => this.triggerHarvestFor(aggregateInst));
42
39
  setTimeout(onHarvestInterval, harvestInterval * 1000); // repeat in X seconds
@@ -2,7 +2,7 @@
2
2
  * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
- import { JSERRORS, RUM, EVENTS, FEATURE_NAMES, BLOBS, LOGS } from '../../loaders/features/features';
5
+ import { JSERRORS, RUM, EVENTS, FEATURE_NAMES, BLOBS, LOGS, CONNECT } from '../../loaders/features/features';
6
6
  import { VERSION } from "../constants/env.npm";
7
7
  import { globalScope, isWorkerScope } from '../constants/runtime';
8
8
  import { handle } from '../event-emitter/handle';
@@ -12,7 +12,7 @@ import { cleanURL } from '../url/clean-url';
12
12
  import { obj, param } from '../url/encode';
13
13
  import { warn } from '../util/console';
14
14
  import { stringify } from '../util/stringify';
15
- import { xhr as xhrMethod, xhrFetch as fetchMethod } from '../util/submit-data';
15
+ import { xhr as xhrMethod, xhrFetch as fetchMethod, getSubmitMethod } from '../util/submit-data';
16
16
  import { dispatchGlobalEvent } from '../dispatch/global-event';
17
17
 
18
18
  /**
@@ -31,7 +31,7 @@ export function send(agentRef, {
31
31
  endpoint,
32
32
  payload,
33
33
  localOpts = {},
34
- submitMethod,
34
+ submitMethod = getSubmitMethod(),
35
35
  cbFinished,
36
36
  raw,
37
37
  featureName,
@@ -52,13 +52,15 @@ export function send(agentRef, {
52
52
  }
53
53
  const protocol = agentRef.init.ssl === false ? 'http' : 'https';
54
54
  const perceivedBeacon = agentRef.init.proxy.beacon || agentRef.info.errorBeacon;
55
- const url = raw ? "".concat(protocol, "://").concat(perceivedBeacon, "/").concat(endpoint) : "".concat(protocol, "://").concat(perceivedBeacon).concat(endpoint !== RUM ? '/' + endpoint : '', "/").concat(endpointVersion, "/").concat(agentRef.info.licenseKey);
55
+ const includeEndpointName = endpoint !== RUM || endpointVersion !== 1;
56
+ const url = raw ? "".concat(protocol, "://").concat(perceivedBeacon, "/").concat(endpoint) : "".concat(protocol, "://").concat(perceivedBeacon).concat(includeEndpointName ? '/' + endpoint : '', "/").concat(endpointVersion, "/").concat(agentRef.info.licenseKey);
56
57
  const baseParams = !raw ? baseQueryString(agentRef, qs, endpoint, harvesterObfuscator) : '';
57
58
  let payloadParams = obj(qs, agentRef.runtime.maxBytes);
58
59
  if (baseParams === '' && payloadParams.startsWith('&')) {
59
60
  payloadParams = payloadParams.substring(1);
60
61
  }
61
- const fullUrl = "".concat(url, "?").concat(baseParams).concat(payloadParams);
62
+ const queryString = "".concat(baseParams).concat(payloadParams);
63
+ const fullUrl = queryString ? "".concat(url, "?").concat(queryString) : url; // omit the '?' if there are no query params
62
64
  const gzip = !!qs?.attributes?.includes('gzip');
63
65
 
64
66
  // all gzipped data is already in the correct format and needs no transformation
@@ -73,7 +75,7 @@ export function send(agentRef, {
73
75
  const headers = [{
74
76
  key: 'content-type',
75
77
  value: 'text/plain'
76
- }];
78
+ }, ...(localOpts.headers || [])];
77
79
 
78
80
  /* Since workers don't support sendBeacon right now, they can only use XHR method.
79
81
  Because they still do permit synch XHR, the idea is that at final harvest time (worker is closing),
@@ -122,6 +124,7 @@ export function send(agentRef, {
122
124
  }
123
125
  function trackHarvestMetadata() {
124
126
  try {
127
+ if (featureName === CONNECT) return;
125
128
  if (featureName === FEATURE_NAMES.jserrors && !body?.err) return;
126
129
  const hasReplay = baseParams.includes('hr=1');
127
130
  const hasTrace = baseParams.includes('ht=1');
@@ -207,9 +210,7 @@ function baseQueryString(agentRef, qs, endpoint, harvesterObfuscator) {
207
210
  const session = agentRef.runtime.session;
208
211
  const hr = !!session?.state.sessionReplaySentFirstChunk && session?.state.sessionReplayMode === 1 && endpoint !== JSERRORS;
209
212
  const ht = !!session?.state.traceHarvestStarted && session?.state.sessionTraceMode === 1 && ![LOGS, BLOBS].includes(endpoint);
210
- const qps = ['a=' + agentRef.info.applicationID, param('sa', agentRef.info.sa ? '' + agentRef.info.sa : ''), param('v', VERSION), transactionNameParam(), param('ct', agentRef.runtime.customTransaction), '&rst=' + now(), '&ck=0',
211
- // ck param DEPRECATED - still expected by backend
212
- '&s=' + (session?.state.value || '0'),
213
+ const qps = ['a=' + agentRef.info.applicationID, param('sa', agentRef.info.sa ? '' + agentRef.info.sa : ''), param('v', VERSION), transactionNameParam(), param('ct', agentRef.runtime.customTransaction), '&rst=' + now(), '&s=' + (session?.state.value || '0'),
213
214
  // the 0 id encaps all untrackable and default traffic
214
215
  param('ref', ref), param('ptid', agentRef.runtime.ptid ? '' + agentRef.runtime.ptid : '')];
215
216
  if (hr) qps.push(param('hr', '1', qs));
@@ -60,7 +60,7 @@ export class SessionEntity {
60
60
  // key is intended to act as the k=v pair
61
61
  this.key = key;
62
62
  this.ee = agentRef.ee;
63
- wrapEvents(this.ee);
63
+ wrapEvents(this.ee, agentRef);
64
64
  this.setup(opts);
65
65
 
66
66
  /**
@@ -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 { Timer } from './timer';
@@ -52,7 +52,7 @@ export class InteractionTimer extends Timer {
52
52
  if (state === 'hidden') this.pause();
53
53
  // vis change --> visible is treated like a new interaction with the page
54
54
  else this.resume();
55
- }, false, false, this.abortController?.signal);
55
+ }, false, this.abortController?.signal);
56
56
  }
57
57
  }
58
58
  abort() {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { globalScope, isWorkerScope, isBrowserScope } from '../constants/runtime';
@@ -22,9 +22,9 @@ if (isWorkerScope) {
22
22
  * This is used, for example, to submit a final harvest and send all remaining data on best-effort.
23
23
  * @param {function} cb - func to run before or during the last reliable event or time of an env's life span
24
24
  */
25
- export function subscribeToEOL(cb, capturePhase) {
25
+ export function subscribeToEOL(cb) {
26
26
  if (isBrowserScope) {
27
- subscribeToVisibilityChange(cb, true, capturePhase); // when user switches tab or hides window, esp. mobile scenario
27
+ subscribeToVisibilityChange(cb, true); // when user switches tab or hides window, esp. mobile scenario
28
28
  } else if (isWorkerScope) {
29
29
  globalScope.cleanupTasks.push(cb); // close() should run these tasks before quitting thread
30
30
  }
@@ -68,7 +68,7 @@ import { dispatchGlobalEvent } from '../dispatch/global-event';
68
68
  * | 50 | Failed to connect. Cannot allow registered API. |
69
69
  * | 51 | Container agent is not available to register with. Can not connect. |
70
70
  * | 52 | Unexpected problem encountered. There should be at least one app for harvest! |
71
- * | 53 | Did not receive a valid entityGuid from connection response. |
71
+ * | 53 | Failed to parse connect response. |
72
72
  * | 54 | An experimental feature is being used. Support can not be offered for issues. |
73
73
  * | 55 | Register API has been disabled on the container agent. |
74
74
  * | 56 | Could not find a matching entity to store data. |
@@ -94,6 +94,7 @@ import { dispatchGlobalEvent } from '../dispatch/global-event';
94
94
  * | 76 | Agent rejected post message, could not match with existing entity. |
95
95
  * | 77 | Agent rejected post message, could not validate origin. |
96
96
  * | 78 | RegisteredIframeEntity could not determine parent origin and will not register, to avoid trusting messages from any origin. |
97
+ * | 79 | Unable to initialize Connector and/or Harvester. |
97
98
  *
98
99
  * @param {number} code The warning code to emit, which will be used to link to the warning code documentation
99
100
  * @param {*} [secondary] Secondary data to include, usually an extra message, error or object
@@ -19,7 +19,8 @@ export function activateFeatures(flags, agentRef) {
19
19
  // let any window level subscribers know that the agent is running, per install docs
20
20
  dispatchGlobalEvent({
21
21
  loaded: true,
22
- drained: true,
22
+ drained: !agentRef.init.feature_flags.includes('rum_v2'),
23
+ // v2 RUM always call activateFeatures before any group is drained (in waitForFlags)
23
24
  type: 'lifecycle',
24
25
  name: 'load',
25
26
  feature: undefined,
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * @enum {string}
8
+ * @readonly
9
+ */
10
+ export const V2_TYPES = {
11
+ /** Micro Frontend */
12
+ MFE: 'MFE',
13
+ /** Browser Application */
14
+ BA: 'BA'
15
+ };
@@ -4,17 +4,24 @@
4
4
  */
5
5
 
6
6
  import { extractUrlsFromStack, getDeepStackTrace } from './script-tracker';
7
+ import { V2_TYPES } from './constants';
7
8
 
8
9
  /**
9
- * @enum {string}
10
- * @readonly
10
+ * @typedef {Object} V2Target
11
+ * @property {'BA'} type always V2_TYPES.BA -- identifies this as the container (main) agent, not a registered MFE
12
+ * @property {string} instance the container agent's agentIdentifier
13
+ * @property {string} [id] the container agent's entity guid, once known (getter, resolved asynchronously)
14
+ * @property {{[key:string]: any}} attributes the V2 payload attributes for the container agent (getter)
11
15
  */
12
- export const V2_TYPES = {
13
- /** Micro Frontend */
14
- MFE: 'MFE',
15
- /** Browser Application */
16
- BA: 'BA'
17
- };
16
+
17
+ /**
18
+ * Determines if a given target represents a registered MFE (as opposed to the container agent).
19
+ * @param {*} target the target to check
20
+ * @returns {boolean} true if the target is a registered MFE target
21
+ */
22
+ export function isMfeTarget(target) {
23
+ return target?.type === V2_TYPES.MFE;
24
+ }
18
25
 
19
26
  /**
20
27
  * Returns a single registered entity associated with a given iframe interface ID. Returns undefined if no entity is found.
@@ -110,14 +117,8 @@ export function dedupeRegisteredEntitiesByAsset(entities) {
110
117
  */
111
118
  export function getVersion2Attributes(target, aggregateInstance) {
112
119
  if (!supportsV2(aggregateInstance)) return {};
113
- const containerAgentEntityGuid = aggregateInstance.agentRef.runtime.appMetadata.agents[0].entityGuid;
114
- /** if there's no target, but we are in v2 mode, this means the data belongs to the container agent */
115
- if (!target) {
116
- return {
117
- 'entity.guid': containerAgentEntityGuid,
118
- appId: aggregateInstance.agentRef.info.applicationID
119
- };
120
- }
120
+ /** if the target isn't a registered MFE, but we are in v2 mode, this means the data belongs to the container agent */
121
+ if (!isMfeTarget(target)) return aggregateInstance.agentRef.runtime.v2Target.attributes;
121
122
  /** otherwise, the data belongs to the target (MFE) and should be attributed as such */
122
123
  return target.attributes;
123
124
  }
@@ -147,16 +148,17 @@ export function getVersion2DuplicationAttributes(target, aggregateInstance) {
147
148
  * @returns {boolean} returns true if the event should be duplicated for the target, false otherwise
148
149
  */
149
150
  export function shouldDuplicate(target, aggregateInstance) {
150
- return !!target && !!supportsV2(aggregateInstance) && aggregateInstance.agentRef.init.api.register.duplicate_data_to_container;
151
+ return isMfeTarget(target) && !!supportsV2(aggregateInstance) && aggregateInstance.agentRef.init.api.register.duplicate_data_to_container;
151
152
  }
152
153
 
153
154
  /**
154
155
  * Finds the registered targets from the stack trace for a given agent reference.
155
156
  * @param {*} agentRef The agent reference to use for finding targets.
156
- * @returns {Array} An array of targets found from the stack trace. If no targets are found or allowed, returns an array with undefined.
157
+ * @returns {Array} An array of targets found from the stack trace. If no targets are found or allowed, returns an array containing the container target.
157
158
  */
158
159
  export function findTargetsFromStackTrace(agentRef) {
159
- if (!isValid(true, agentRef) || !agentRef?.runtime?.registeredEntities?.length) return [undefined];
160
+ if (!agentRef?.runtime) return [];
161
+ if (!isValid(true, agentRef) || !agentRef.runtime.registeredEntities?.length) return [agentRef.runtime.v2Target];
160
162
  const targets = [];
161
163
  try {
162
164
  var urls = extractUrlsFromStack(getDeepStackTrace());
@@ -168,15 +170,15 @@ export function findTargetsFromStackTrace(agentRef) {
168
170
  // Silent catch to prevent errors from propagating
169
171
  }
170
172
  const deduped = dedupeTargetsByInstance(targets);
171
- if (!deduped.length) deduped.push(undefined); // if we can't find any targets from the stack trace, return an array with undefined to signify the container agent is the target
173
+ if (!deduped.length) deduped.push(agentRef.runtime.v2Target); // if we can't find any targets from the stack trace, the container agent is the target
172
174
  return deduped;
173
175
  }
174
176
 
175
177
  /**
176
178
  * Removes duplicate targets from an array, keyed by `target.instance`. This guards against the same canonical
177
179
  * target re-entering the array via multiple matched stack-frame URLs (e.g. a recursive call whose stack contains the
178
- * same file at multiple depths). Entries with no `instance` (i.e. `undefined`, meaning "the container agent")
179
- * naturally collapse to a single entry too, which is the desired behavior.
180
+ * same file at multiple depths). Multiple container-target entries (whose `instance` is the container agent's stable
181
+ * `agentIdentifier`) naturally collapse to a single entry too, which is the desired behavior.
180
182
  * @param {Array} targets
181
183
  * @returns {Array} deduped list of targets, preserving relative order of first occurrence
182
184
  */
@@ -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
 
@@ -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
 
@@ -23,16 +23,17 @@ const flag = "nr@wrapped:".concat(contextId);
23
23
  * `document` (if in a browser scope). Adds custom events in context of a new emitter scoped only to these methods.
24
24
  * @param {Object} sharedEE - The shared event emitter on which a new scoped
25
25
  * event emitter will be based.
26
+ * @param {*} [agentRef] - The agent reference, used to attribute wrapped calls to the correct v2 target.
26
27
  * @returns {Object} Scoped event emitter with a debug ID of `events`.
27
28
  */
28
- export function wrapEvents(sharedEE) {
29
+ export function wrapEvents(sharedEE, agentRef) {
29
30
  var ee = scopedEE(sharedEE);
30
31
 
31
32
  // Notice if our wrapping never ran yet, the falsy NaN will not early return; but if it has,
32
33
  // then we increment the count to track # of feats using this at runtime.
33
34
  if (wrapped[ee.debugId]++) return ee;
34
35
  wrapped[ee.debugId] = 1; // otherwise, first feature to wrap events
35
- var wrapFn = wfn(ee, true);
36
+ var wrapFn = wfn(ee, true, agentRef);
36
37
 
37
38
  // Guard against instrumenting environments w/o necessary features
38
39
  if ('getPrototypeOf' in Object) {
@@ -44,8 +44,8 @@ export function wrapFetch(sharedEE, agentRef) {
44
44
  wrapPromiseMethod(globalScope, 'fetch', prefix);
45
45
  ee.on(prefix + 'end', function (err, res, targets) {
46
46
  var ctx = this;
47
- // undefined target reports to container
48
- ctx.targets = targets || [undefined];
47
+ // default target reports to container
48
+ ctx.targets = targets || [agentRef.runtime.v2Target];
49
49
  if (res) {
50
50
  var size = res.headers.get('content-length');
51
51
  if (size !== null) {
@@ -82,7 +82,7 @@ export function createWrapperWithEmitter(emitter, always, agentRef) {
82
82
 
83
83
  // certain wrappers can inform the function wrapper to evaluate the stack of the executed wrapped function to find targets of the execution
84
84
  // (e.g. wrap-logger can inform this method to find try to find the MFE source of a console.log)
85
- targets = evaluateStack ? findTargetsFromStackTrace(agentRef) : [undefined]; // undefined target always maps to the container agent
85
+ targets = evaluateStack ? findTargetsFromStackTrace(agentRef) : [agentRef?.runtime?.v2Target]; // default target always maps to the container agent
86
86
 
87
87
  if (typeof getContext === 'function') {
88
88
  ctx = getContext(args, originalThis);
@@ -17,9 +17,10 @@ const HISTORY_FNS = ['pushState', 'replaceState'];
17
17
  * Wraps the `pushState` and `replaceState` methods of `window.history` and returns a corresponding event emitter
18
18
  * scoped to the history object.
19
19
  * @param {Object} sharedEE - The shared event emitter on which a new scoped event emitter will be based.
20
+ * @param {*} [agentRef] - The agent reference, used to attribute wrapped calls to the correct v2 target.
20
21
  * @returns {Object} Scoped event emitter with a debug ID of `history`.
21
22
  */
22
- export function wrapHistory(sharedEE) {
23
+ export function wrapHistory(sharedEE, agentRef) {
23
24
  const ee = scopedEE(sharedEE);
24
25
 
25
26
  // Notice if our wrapping never ran yet, the falsy NaN will not early return; but if it has,
@@ -28,7 +29,7 @@ export function wrapHistory(sharedEE) {
28
29
  if (!isBrowserScope || wrapped[ee.debugId]++) return ee;
29
30
  wrapped[ee.debugId] = 1; // otherwise, first feature to wrap history
30
31
 
31
- var wrapFn = wfn(ee);
32
+ var wrapFn = wfn(ee, undefined, agentRef);
32
33
  /*
33
34
  * For objects that will be instantiated more than once, we wrap the object's prototype methods. The history object
34
35
  * is instantiated only once, so we can wrap its methods directly--and we must wrap the history methods directly as
@@ -35,8 +35,8 @@ export function wrapXhr(sharedEE, agentRef) {
35
35
  if (wrapped[ee.debugId]++) return ee;
36
36
  wrapped[ee.debugId] = 1; // otherwise, first feature to wrap XHR
37
37
 
38
- wrapEvents(baseEE); // wrap-events patches XMLHttpRequest.prototype.addEventListener for us
39
- var wrapFn = wfn(ee);
38
+ wrapEvents(baseEE, agentRef); // wrap-events patches XMLHttpRequest.prototype.addEventListener for us
39
+ var wrapFn = wfn(ee, undefined, agentRef);
40
40
  var OrigXHR = globalScope.XMLHttpRequest;
41
41
  var MutationObserver = globalScope.MutationObserver;
42
42
  var Promise = globalScope.Promise;
@@ -14,7 +14,7 @@ import { gosNREUMOriginals } from '../../../common/window/nreum';
14
14
  import { hasGQLErrors, parseGQL } from './gql';
15
15
  import { canCapturePayload, isLikelyHumanReadable, parseQueryString, createStringAdders } from '../../../common/payloads/payloads';
16
16
  import { Obfuscator } from '../../../common/util/obfuscate';
17
- import { getVersion2Attributes, getVersion2DuplicationAttributes, shouldDuplicate } from '../../../common/v2/utils';
17
+ import { getVersion2Attributes, getVersion2DuplicationAttributes, shouldDuplicate, isMfeTarget } from '../../../common/v2/utils';
18
18
  import { EVENT_TYPES } from '../../../common/constants/events';
19
19
  import { generateUuid } from '../../../common/ids/unique-id';
20
20
  export class Aggregate extends AggregateBase {
@@ -119,7 +119,7 @@ export class Aggregate extends AggregateBase {
119
119
  if (event.gql) this.reportSupportabilityMetric('Ajax/Events/GraphQL/Bytes-Added', stringify(event.gql).length);
120
120
 
121
121
  /** make a copy of the event for the MFE target if it exists */
122
- if (target) {
122
+ if (isMfeTarget(target)) {
123
123
  this.events.add({
124
124
  ...event,
125
125
  targetAttributes: getVersion2Attributes(target, this)
@@ -255,7 +255,7 @@ export class Aggregate extends AggregateBase {
255
255
  this.addEvent(event, target);
256
256
  }, this.featureName, this.ee);
257
257
  if (agentRef.init.feature_flags.includes('websockets') || agentRef.init.web_sockets?.enabled) {
258
- registerHandler('ws-complete', (nrData, targets = [undefined]) => {
258
+ registerHandler('ws-complete', (nrData, targets = [agentRef.runtime.v2Target]) => {
259
259
  targets.forEach(target => {
260
260
  const event = {
261
261
  ...nrData,
@@ -58,7 +58,7 @@ export class Instrument extends InstrumentBase {
58
58
  if (isBrowserScope) {
59
59
  wrapFetch(this.ee, agentRef);
60
60
  wrapXhr(this.ee, agentRef);
61
- historyEE = wrapHistory(this.ee);
61
+ historyEE = wrapHistory(this.ee, agentRef);
62
62
  if (agentRef.init.user_actions.enabled) {
63
63
  OBSERVED_EVENTS.forEach(eventType => windowAddEventListener(eventType, evt => handle('ua', [evt], undefined, this.featureName, this.ee), true));
64
64
  OBSERVED_WINDOW_EVENTS.forEach(eventType => {