@newrelic/browser-agent 1.317.0 → 1.318.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/cjs/common/config/configurable.js +1 -1
  3. package/dist/cjs/common/config/init-types.js +2 -0
  4. package/dist/cjs/common/config/init.js +3 -0
  5. package/dist/cjs/common/constants/env.cdn.js +1 -1
  6. package/dist/cjs/common/constants/env.npm.js +1 -1
  7. package/dist/cjs/common/harvest/harvester.js +3 -219
  8. package/dist/cjs/common/harvest/send.js +232 -0
  9. package/dist/cjs/common/harvest/types.js +9 -5
  10. package/dist/cjs/common/session/session-entity.js +8 -8
  11. package/dist/cjs/common/v2/mfe-vitals.js +303 -0
  12. package/dist/cjs/features/generic_events/aggregate/index.js +1 -1
  13. package/dist/cjs/features/generic_events/instrument/index.js +1 -1
  14. package/dist/cjs/features/logging/aggregate/index.js +5 -5
  15. package/dist/cjs/features/page_view_event/aggregate/index.js +65 -30
  16. package/dist/cjs/features/session_replay/aggregate/index.js +32 -28
  17. package/dist/cjs/features/session_trace/aggregate/index.js +29 -27
  18. package/dist/cjs/features/session_trace/constants.js +17 -3
  19. package/dist/cjs/features/utils/aggregate-base.js +2 -0
  20. package/dist/cjs/loaders/api/register.js +27 -4
  21. package/dist/esm/common/config/configurable.js +1 -1
  22. package/dist/esm/common/config/init-types.js +2 -0
  23. package/dist/esm/common/config/init.js +3 -0
  24. package/dist/esm/common/constants/env.cdn.js +1 -1
  25. package/dist/esm/common/constants/env.npm.js +1 -1
  26. package/dist/esm/common/harvest/harvester.js +3 -219
  27. package/dist/esm/common/harvest/send.js +226 -0
  28. package/dist/esm/common/harvest/types.js +9 -5
  29. package/dist/esm/common/session/session-entity.js +9 -9
  30. package/dist/esm/common/v2/mfe-vitals.js +298 -0
  31. package/dist/esm/features/generic_events/aggregate/index.js +1 -1
  32. package/dist/esm/features/generic_events/instrument/index.js +1 -1
  33. package/dist/esm/features/logging/aggregate/index.js +5 -5
  34. package/dist/esm/features/page_view_event/aggregate/index.js +64 -29
  35. package/dist/esm/features/session_replay/aggregate/index.js +32 -28
  36. package/dist/esm/features/session_trace/aggregate/index.js +30 -28
  37. package/dist/esm/features/session_trace/constants.js +16 -2
  38. package/dist/esm/features/utils/aggregate-base.js +2 -0
  39. package/dist/esm/loaders/api/register.js +27 -4
  40. package/dist/tsconfig.tsbuildinfo +1 -1
  41. package/dist/types/common/config/init-types.d.ts +6 -0
  42. package/dist/types/common/config/init.d.ts.map +1 -1
  43. package/dist/types/common/harvest/harvester.d.ts +0 -17
  44. package/dist/types/common/harvest/harvester.d.ts.map +1 -1
  45. package/dist/types/common/harvest/send.d.ts +9 -0
  46. package/dist/types/common/harvest/send.d.ts.map +1 -0
  47. package/dist/types/common/harvest/types.d.ts +39 -10
  48. package/dist/types/common/harvest/types.d.ts.map +1 -1
  49. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  50. package/dist/types/common/v2/mfe-vitals.d.ts +14 -0
  51. package/dist/types/common/v2/mfe-vitals.d.ts.map +1 -0
  52. package/dist/types/features/page_view_event/aggregate/index.d.ts +1 -0
  53. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  54. package/dist/types/features/session_replay/aggregate/index.d.ts +2 -2
  55. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  56. package/dist/types/features/session_trace/aggregate/index.d.ts +1 -1
  57. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  58. package/dist/types/features/session_trace/constants.d.ts +17 -0
  59. package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
  60. package/dist/types/loaders/api/register.d.ts.map +1 -1
  61. package/package.json +2 -2
  62. package/src/common/config/configurable.js +1 -1
  63. package/src/common/config/init-types.js +2 -0
  64. package/src/common/config/init.js +2 -1
  65. package/src/common/harvest/harvester.js +3 -202
  66. package/src/common/harvest/send.js +209 -0
  67. package/src/common/harvest/types.js +9 -5
  68. package/src/common/session/session-entity.js +8 -9
  69. package/src/common/v2/mfe-vitals.js +285 -0
  70. package/src/features/generic_events/aggregate/index.js +1 -1
  71. package/src/features/generic_events/instrument/index.js +1 -1
  72. package/src/features/logging/aggregate/index.js +5 -5
  73. package/src/features/page_view_event/aggregate/index.js +52 -27
  74. package/src/features/session_replay/aggregate/index.js +30 -27
  75. package/src/features/session_trace/aggregate/index.js +31 -28
  76. package/src/features/session_trace/constants.js +7 -1
  77. package/src/features/utils/aggregate-base.js +2 -0
  78. package/src/loaders/api/register.js +25 -4
@@ -47,8 +47,10 @@ class Aggregate extends _aggregateBase.AggregateBase {
47
47
  /** Sets up event listeners, and initializes this module to run in the correct "mode". Can be triggered from a few places, but makes an effort to only set up listeners once */
48
48
  initialize(stMode, stEntitled, ignoreSession) {
49
49
  this.entitled ??= stEntitled;
50
- if (!this.entitled) this.blocked = true;
51
- if (this.blocked) return this.deregisterDrain();
50
+ if (!this.entitled) {
51
+ this.abort(_constants.ABORT_REASONS.ENTITLEMENTS);
52
+ return this.deregisterDrain();
53
+ }
52
54
  this.timeKeeper ??= this.agentRef.runtime.timeKeeper;
53
55
  if (!this.initialized) {
54
56
  this.initialized = true;
@@ -58,7 +60,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
58
60
  // The SessionEntity class can emit a message indicating the session was cleared and reset (expiry, inactivity). This feature must abort and never resume if that occurs.
59
61
  this.ee.on(_constants2.SESSION_EVENTS.RESET, () => {
60
62
  if (this.blocked) return;
61
- this.abort(1);
63
+ this.abort(_constants.ABORT_REASONS.RESET);
62
64
  });
63
65
  // The SessionEntity can have updates (locally or across tabs for SR mode changes), (across tabs for ST mode changes).
64
66
  // Those updates should be sync'd here to ensure this page also honors the mode after initialization
@@ -67,7 +69,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
67
69
  // this will only have an effect if ST is NOT already in full mode
68
70
  if (this.mode !== _constants2.MODE.FULL && (sessionState.sessionReplayMode === _constants2.MODE.FULL || sessionState.sessionTraceMode === _constants2.MODE.FULL)) this.switchToFull();
69
71
  // if another page's session entity has expired, or another page has transitioned to off and this one hasn't... we can just abort straight away here
70
- if (this.sessionId !== sessionState.value || eventType === 'cross-tab' && sessionState.sessionTraceMode === _constants2.MODE.OFF) this.abort(2);
72
+ if (this.sessionId !== sessionState.value || eventType === 'cross-tab' && sessionState.sessionTraceMode === _constants2.MODE.OFF) this.abort(_constants.ABORT_REASONS.CROSS_TAB);
71
73
  });
72
74
  if (typeof PerformanceNavigationTiming !== 'undefined' && _runtime.globalScope.performance?.getEntriesByType('navigation')?.length > 0) {
73
75
  this.traceStorage.storeTiming(_runtime.globalScope.performance.getEntriesByType('navigation')[0]);
@@ -78,30 +80,30 @@ class Aggregate extends _aggregateBase.AggregateBase {
78
80
 
79
81
  /** ST/SR sampling flow in BCS - https://drive.google.com/file/d/19hwt2oft-8Hh4RrjpLqEXfpP_9wYBLcq/view?usp=sharing */
80
82
  /** ST will run in the mode provided by BCS if the session IS NEW. If not... it will use the state of the session entity to determine what mode to run in */
81
- if (!this.agentRef.runtime.session.isNew && !ignoreSession) this.mode = this.agentRef.runtime.session.state.sessionTraceMode;else this.mode = stMode;
82
-
83
- /** If the mode is off, we do not want to hold up draining for other features, so we deregister the feature for now.
84
- * If it drains later (due to a mode change), data and handlers will instantly drain instead of waiting for the registry. */
85
- if (this.mode === _constants2.MODE.OFF) return this.deregisterDrain();
86
-
87
- /** The handlers set up by the Inst file */
88
- (0, _registerHandler.registerHandler)('bst', (...args) => this.traceStorage.storeEvent(...args), this.featureName, this.ee);
89
- (0, _registerHandler.registerHandler)('bstResource', (...args) => this.traceStorage.storeResources(...args), this.featureName, this.ee);
90
- (0, _registerHandler.registerHandler)('bstHist', (...args) => this.traceStorage.storeHist(...args), this.featureName, this.ee);
91
- (0, _registerHandler.registerHandler)('bstXhrAgg', (...args) => this.traceStorage.storeXhrAgg(...args), this.featureName, this.ee);
92
- (0, _registerHandler.registerHandler)('bstApi', (...args) => this.traceStorage.storeNode(...args), this.featureName, this.ee);
93
- (0, _registerHandler.registerHandler)('trace-jserror', (...args) => this.traceStorage.storeErrorAgg(...args), this.featureName, this.ee);
94
- (0, _registerHandler.registerHandler)('pvtAdded', (...args) => this.traceStorage.processPVT(...args), this.featureName, this.ee);
95
- if (this.mode !== _constants2.MODE.FULL) {
96
- /** A separate handler for noticing errors, and switching to "full" mode if running in "error" mode */
97
- (0, _registerHandler.registerHandler)('trace-jserror', () => {
98
- if (this.mode === _constants2.MODE.ERROR) this.switchToFull();
99
- }, this.featureName, this.ee);
83
+ if (this.agentRef.runtime.session.state.sessionTraceMode !== null && !ignoreSession) this.mode = this.agentRef.runtime.session.state.sessionTraceMode;else this.mode = stMode;
84
+ if (this.mode !== _constants2.MODE.OFF) {
85
+ /** The handlers set up by the Inst file */
86
+ (0, _registerHandler.registerHandler)('bst', (...args) => this.traceStorage.storeEvent(...args), this.featureName, this.ee);
87
+ (0, _registerHandler.registerHandler)('bstResource', (...args) => this.traceStorage.storeResources(...args), this.featureName, this.ee);
88
+ (0, _registerHandler.registerHandler)('bstHist', (...args) => this.traceStorage.storeHist(...args), this.featureName, this.ee);
89
+ (0, _registerHandler.registerHandler)('bstXhrAgg', (...args) => this.traceStorage.storeXhrAgg(...args), this.featureName, this.ee);
90
+ (0, _registerHandler.registerHandler)('bstApi', (...args) => this.traceStorage.storeNode(...args), this.featureName, this.ee);
91
+ (0, _registerHandler.registerHandler)('trace-jserror', (...args) => this.traceStorage.storeErrorAgg(...args), this.featureName, this.ee);
92
+ (0, _registerHandler.registerHandler)('pvtAdded', (...args) => this.traceStorage.processPVT(...args), this.featureName, this.ee);
93
+ if (this.mode === _constants2.MODE.ERROR) {
94
+ /** A separate handler for noticing errors, and switching to "full" mode if running in "error" mode */
95
+ (0, _registerHandler.registerHandler)('trace-jserror', () => {
96
+ if (this.mode === _constants2.MODE.ERROR) this.switchToFull();
97
+ }, this.featureName, this.ee);
98
+ }
100
99
  }
101
100
  this.agentRef.runtime.session.write({
102
101
  sessionTraceMode: this.mode
103
102
  });
104
- this.drain();
103
+
104
+ /** If the mode is off, we do not want to hold up draining for other features, so we deregister the feature for now.
105
+ * If it drains later (due to a mode change), data and handlers will instantly drain instead of waiting for the registry. */
106
+ this.mode === _constants2.MODE.OFF ? this.deregisterDrain() : this.drain();
105
107
  }
106
108
  preHarvestChecks() {
107
109
  if (this.blocked || this.mode !== _constants2.MODE.FULL) return; // only allow harvest if running in full mode
@@ -109,7 +111,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
109
111
  if (!this.agentRef.runtime.session) return; // session entity is required for trace to run and continue running
110
112
  if (this.sessionId !== this.agentRef.runtime.session.state.value || this.ptid !== this.agentRef.runtime.ptid) {
111
113
  // If something unexpected happened and we somehow still got to harvesting after a session identifier changed, we should force-exit instead of harvesting:
112
- this.abort(3);
114
+ this.abort(_constants.ABORT_REASONS.SESSION_CHANGED);
113
115
  return;
114
116
  }
115
117
  return true;
@@ -194,8 +196,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
194
196
  }
195
197
 
196
198
  /** Stop running for the remainder of the page lifecycle */
197
- abort(code) {
198
- (0, _console.warn)(60, code);
199
+ abort(reason = {}) {
200
+ (0, _console.warn)(60, reason.message);
199
201
  this.blocked = true;
200
202
  this.mode = _constants2.MODE.OFF;
201
203
  this.agentRef.runtime.session.write({
@@ -3,10 +3,10 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.START = exports.RESOURCE = exports.PUSH_STATE = exports.MAX_NODES_PER_HARVEST = exports.FN_START = exports.FN_END = exports.FEATURE_NAME = exports.ERROR_MODE_SECONDS_WINDOW = exports.END = exports.BST_RESOURCE = void 0;
6
+ exports.START = exports.RESOURCE = exports.PUSH_STATE = exports.MAX_NODES_PER_HARVEST = exports.FN_START = exports.FN_END = exports.FEATURE_NAME = exports.ERROR_MODE_SECONDS_WINDOW = exports.END = exports.BST_RESOURCE = exports.ABORT_REASONS = void 0;
7
7
  var _features = require("../../loaders/features/features");
8
8
  /**
9
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
9
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
10
10
  * SPDX-License-Identifier: Apache-2.0
11
11
  */
12
12
 
@@ -19,4 +19,18 @@ const FN_START = exports.FN_START = 'fn' + START;
19
19
  const FN_END = exports.FN_END = 'fn' + END;
20
20
  const PUSH_STATE = exports.PUSH_STATE = 'pushState';
21
21
  const MAX_NODES_PER_HARVEST = exports.MAX_NODES_PER_HARVEST = 1000;
22
- const ERROR_MODE_SECONDS_WINDOW = exports.ERROR_MODE_SECONDS_WINDOW = 30 * 1000; // sliding window of nodes to track when simply monitoring (but not harvesting) in error mode
22
+ const ERROR_MODE_SECONDS_WINDOW = exports.ERROR_MODE_SECONDS_WINDOW = 30 * 1000; // sliding window of nodes to track when simply monitoring (but not harvesting) in error mode
23
+ const ABORT_REASONS = exports.ABORT_REASONS = {
24
+ CROSS_TAB: {
25
+ message: 'Session Entity was set to OFF on another tab'
26
+ },
27
+ ENTITLEMENTS: {
28
+ message: 'Session Trace is not allowed and will not be started'
29
+ },
30
+ RESET: {
31
+ message: 'Session was reset'
32
+ },
33
+ SESSION_CHANGED: {
34
+ message: 'Session identifier changed unexpectedly'
35
+ }
36
+ };
@@ -13,6 +13,7 @@ var _handle = require("../../common/event-emitter/handle");
13
13
  var _constants = require("../metrics/constants");
14
14
  var _eventAggregator = require("../../common/aggregate/event-aggregator");
15
15
  var _agentConstants = require("../../common/constants/agent-constants");
16
+ var _console = require("../../common/util/console");
16
17
  /**
17
18
  * Copyright 2020-2026 New Relic, Inc. All rights reserved.
18
19
  * SPDX-License-Identifier: Apache-2.0
@@ -117,6 +118,7 @@ class AggregateBase extends _featureBase.FeatureBase {
117
118
  }
118
119
  function buildOutput(ref) {
119
120
  return flagNames.map(flag => {
121
+ if (ref[flag] === undefined || ref[flag] === null) (0, _console.warn)(71, flag);
120
122
  if (!ref[flag]) return 0;
121
123
  return ref[flag];
122
124
  });
@@ -21,6 +21,7 @@ var _measure = require("./measure");
21
21
  var _recordCustomEvent = require("./recordCustomEvent");
22
22
  var _pageVisibility = require("../../common/window/page-visibility");
23
23
  var _scriptTracker = require("../../common/v2/script-tracker");
24
+ var _mfeVitals = require("../../common/v2/mfe-vitals");
24
25
  var _uniqueId = require("../../common/ids/unique-id");
25
26
  /**
26
27
  * Copyright 2020-2026 New Relic, Inc. All rights reserved.
@@ -84,6 +85,9 @@ function register(agentRef, target) {
84
85
  type: _utils.V2_TYPES.BA
85
86
  };
86
87
  const timings = (0, _scriptTracker.findScriptTimings)();
88
+
89
+ // Track MFE vitals for this entity
90
+ const vitals = (0, _mfeVitals.trackMFEVitals)(target.id, timings);
87
91
  const attrs = {};
88
92
 
89
93
  // Only define attributes getter if it doesn't already exist
@@ -172,7 +176,8 @@ function register(agentRef, target) {
172
176
  return attrs;
173
177
  },
174
178
  target,
175
- timings
179
+ timings,
180
+ vitals
176
181
  }
177
182
  };
178
183
 
@@ -200,9 +205,17 @@ function register(agentRef, target) {
200
205
  // only ever report the timings the first time this is called
201
206
  if (timings.reportedAt) return;
202
207
  timings.reportedAt = (0, _now.now)();
208
+
209
+ // Disconnect observers and capture current values, store in timings obj for visibility
210
+ vitals.disconnect();
211
+ timings.fcp = vitals.fcp;
212
+ timings.lcp = vitals.lcp;
213
+ timings.cls = vitals.cls;
214
+ timings.inp = vitals.inp;
203
215
  const timeToFetch = timings.fetchEnd - timings.fetchStart; // fetchStart to fetchEnd
204
216
  const timeToExecute = timings.scriptEnd - timings.scriptStart; // scriptStart to scriptEnd
205
- api.recordCustomEvent('MicroFrontEndTiming', {
217
+
218
+ const eventData = {
206
219
  assetUrl: timings.asset,
207
220
  // the url of the script that was registered, or undefined if it could not be determined (inline or no match)
208
221
  assetType: timings.type,
@@ -217,8 +230,18 @@ function register(agentRef, target) {
217
230
  // fetchStart to fetchEnd
218
231
  timeToLoad: timeToFetch + timeToExecute,
219
232
  // fetch time and script time together
220
- timeToRegister: timings.registeredAt // timestamp when register() was called
221
- });
233
+ timeToRegister: timings.registeredAt,
234
+ // timestamp when register() was called
235
+ // leave room to extend these with more data keys as needed
236
+ 'nr.vitals.fcp.value': vitals.fcp?.value ?? null,
237
+ // FCP vital object with value and metadata
238
+ 'nr.vitals.lcp.value': vitals.lcp?.value ?? null,
239
+ // LCP vital object with value and metadata
240
+ 'nr.vitals.cls.value': vitals.cls?.value ?? null,
241
+ // CLS vital object with value and metadata
242
+ 'nr.vitals.inp.value': vitals.inp?.value ?? null // INP vital object with value and metadata
243
+ };
244
+ api.recordCustomEvent('MicroFrontEndTiming', eventData);
222
245
  }
223
246
 
224
247
  /**
@@ -17,7 +17,7 @@ export function getModeledObject(obj, model) {
17
17
  output[key] = null;
18
18
  continue;
19
19
  }
20
- if (Array.isArray(obj[key]) && Array.isArray(model[key])) output[key] = Array.from(new Set([...obj[key], ...model[key]]));else if (obj[key] instanceof Map || obj[key] instanceof Set || obj[key] instanceof Date || obj[key] instanceof RegExp) output[key] = obj[key];else if (typeof obj[key] === 'object' && typeof model[key] === 'object') output[key] = getModeledObject(obj[key], model[key]);else output[key] = obj[key];
20
+ if (Array.isArray(obj[key]) && Array.isArray(model[key])) output[key] = Array.from(new Set([...obj[key], ...model[key]]));else if (obj[key] instanceof Map || obj[key] instanceof Set || obj[key] instanceof Date || obj[key] instanceof RegExp) output[key] = obj[key];else if (typeof obj[key] === 'object' && model[key] !== null && typeof model[key] === 'object') output[key] = getModeledObject(obj[key], model[key]);else output[key] = obj[key];
21
21
  } catch (e) {
22
22
  if (!output[key]) warn(1, e);
23
23
  }
@@ -88,6 +88,8 @@
88
88
  * @property {Object} [user_actions]
89
89
  * @property {boolean} [user_actions.enabled] - Must be true to allow UserAction events to be captured.
90
90
  * @property {Array<string>} [user_actions.elementAttributes] - List of HTML Element properties to be captured with UserAction events' target elements. This may help to identify the source element being interacted with in the UI.
91
+ * @property {Object} [web_sockets]
92
+ * @property {boolean} [web_sockets.enabled] - Turn on/off the web sockets feature (off by default).
91
93
  */
92
94
 
93
95
  export default {};
@@ -198,6 +198,9 @@ const InitModelFn = () => {
198
198
  user_actions: {
199
199
  enabled: true,
200
200
  elementAttributes: ['id', 'className', 'tagName', 'type']
201
+ },
202
+ web_sockets: {
203
+ enabled: false
201
204
  }
202
205
  };
203
206
  };
@@ -11,7 +11,7 @@
11
11
  /**
12
12
  * Exposes the version of the agent
13
13
  */
14
- export const VERSION = "1.317.0";
14
+ export const VERSION = "1.318.0-rc.0";
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.317.0";
14
+ export const VERSION = "1.318.0-rc.0";
15
15
 
16
16
  /**
17
17
  * Exposes the build type of the agent
@@ -3,19 +3,11 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { SUPPORTABILITY_METRIC_CHANNEL } from '../../features/metrics/constants';
6
- import { FEATURE_TO_ENDPOINT, JSERRORS, RUM, EVENTS, FEATURE_NAMES, BLOBS, LOGS } from '../../loaders/features/features';
7
- import { VERSION } from "../constants/env.npm";
8
- import { globalScope, isWorkerScope } from '../constants/runtime';
6
+ import { FEATURE_TO_ENDPOINT, FEATURE_NAMES } from '../../loaders/features/features';
9
7
  import { handle } from '../event-emitter/handle';
10
- import { eventListenerOpts } from '../event-listener/event-listener-opts';
11
- import { now } from '../timing/now';
12
8
  import { subscribeToEOL } from '../unload/eol';
13
- import { cleanURL } from '../url/clean-url';
14
- import { obj, param } from '../url/encode';
15
- import { warn } from '../util/console';
16
- import { stringify } from '../util/stringify';
17
- import { getSubmitMethod, xhr as xhrMethod, xhrFetch as fetchMethod } from '../util/submit-data';
18
- import { dispatchGlobalEvent } from '../dispatch/global-event';
9
+ import { getSubmitMethod, xhr as xhrMethod } from '../util/submit-data';
10
+ import { send } from './send';
19
11
  import { Obfuscator } from '../util/obfuscate';
20
12
  const RETRY = 'Harvester/Retry/';
21
13
  const RETRY_ATTEMPTED = RETRY + 'Attempted/';
@@ -105,212 +97,4 @@ export class Harvester {
105
97
  aggregateInst.postHarvestCleanup(result);
106
98
  }
107
99
  }
108
- }
109
-
110
- /**
111
- * @typedef {import('./types.js').NetworkSendSpec} NetworkSendSpec
112
- */
113
-
114
- const warnings = {};
115
- /**
116
- * Initiate a harvest call.
117
- * @param {NetworkSendSpec} param0 Specification for sending data
118
- * @returns {boolean} True if a network call was made. Note that this does not mean or guarantee that it was successful.
119
- */
120
- export function send(agentRef, {
121
- endpoint,
122
- payload,
123
- localOpts = {},
124
- submitMethod,
125
- cbFinished,
126
- raw,
127
- featureName,
128
- endpointVersion = 1,
129
- harvesterObfuscator
130
- }) {
131
- if (!agentRef.info.errorBeacon) return false;
132
- let {
133
- body,
134
- qs
135
- } = cleanPayload(payload);
136
- if (Object.keys(body).length === 0 && !localOpts.sendEmptyBody) {
137
- // if there's no body to send, just run onfinish stuff and return
138
- if (cbFinished) cbFinished({
139
- sent: false
140
- });
141
- return false;
142
- }
143
- const protocol = agentRef.init.ssl === false ? 'http' : 'https';
144
- const perceivedBeacon = agentRef.init.proxy.beacon || agentRef.info.errorBeacon;
145
- const url = raw ? "".concat(protocol, "://").concat(perceivedBeacon, "/").concat(endpoint) : "".concat(protocol, "://").concat(perceivedBeacon).concat(endpoint !== RUM ? '/' + endpoint : '', "/").concat(endpointVersion, "/").concat(agentRef.info.licenseKey);
146
- const baseParams = !raw ? baseQueryString(agentRef, qs, endpoint, harvesterObfuscator) : '';
147
- let payloadParams = obj(qs, agentRef.runtime.maxBytes);
148
- if (baseParams === '' && payloadParams.startsWith('&')) {
149
- payloadParams = payloadParams.substring(1);
150
- }
151
- const fullUrl = "".concat(url, "?").concat(baseParams).concat(payloadParams);
152
- const gzip = !!qs?.attributes?.includes('gzip');
153
-
154
- // all gzipped data is already in the correct format and needs no transformation
155
- // all features going to 'events' endpoint should already be serialized & stringified
156
- let stringBody = gzip || endpoint === EVENTS ? body : stringify(body);
157
-
158
- // If body is null, undefined, or an empty object or array after stringifying, send an empty string instead.
159
- if (!stringBody || stringBody.length === 0 || stringBody === '{}' || stringBody === '[]') stringBody = '';
160
-
161
- // Warn--once per endpoint--if the agent tries to send large payloads
162
- if (endpoint !== BLOBS && stringBody.length > 750000 && (warnings[endpoint] = (warnings[endpoint] || 0) + 1) === 1) warn(28, endpoint);
163
- const headers = [{
164
- key: 'content-type',
165
- value: 'text/plain'
166
- }];
167
-
168
- /* Since workers don't support sendBeacon right now, they can only use XHR method.
169
- Because they still do permit synch XHR, the idea is that at final harvest time (worker is closing),
170
- we just make a BLOCKING request--trivial impact--with the remaining data as a temp fill-in for sendBeacon.
171
- Following the removal of img-element method. */
172
- let result = submitMethod({
173
- url: fullUrl,
174
- body: stringBody,
175
- sync: localOpts.isFinalHarvest && isWorkerScope,
176
- headers
177
- });
178
- if (!localOpts.isFinalHarvest && cbFinished) {
179
- // final harvests don't hold onto buffer data (shouldRetryOnFail is false), so cleanup isn't needed
180
- if (submitMethod === xhrMethod) {
181
- result.addEventListener('loadend', function () {
182
- // `this` here in block refers to the XHR object in this scope, do not change the anon function to an arrow function
183
- // status 0 refers to a local error, such as CORS or network failure, or a blocked request by the browser (e.g. adblocker)
184
- const cbResult = {
185
- sent: this.status !== 0,
186
- status: this.status,
187
- retry: shouldRetry(this.status),
188
- fullUrl,
189
- xhr: this,
190
- responseText: this.responseText
191
- };
192
- cbFinished(cbResult);
193
-
194
- /** temporary audit of consistency of harvest metadata flags */
195
- if (!shouldRetry(this.status)) trackHarvestMetadata();
196
- }, eventListenerOpts(false));
197
- } else if (submitMethod === fetchMethod) {
198
- result.then(async function (response) {
199
- const status = response.status;
200
- const cbResult = {
201
- sent: true,
202
- status,
203
- retry: shouldRetry(status),
204
- fullUrl,
205
- fetchResponse: response,
206
- responseText: await response.text()
207
- };
208
- cbFinished(cbResult);
209
- /** temporary audit of consistency of harvest metadata flags */
210
- if (!shouldRetry(status)) trackHarvestMetadata();
211
- });
212
- }
213
- function trackHarvestMetadata() {
214
- try {
215
- if (featureName === FEATURE_NAMES.jserrors && !body?.err) return;
216
- const hasReplay = baseParams.includes('hr=1');
217
- const hasTrace = baseParams.includes('ht=1');
218
- const hasError = qs?.attributes?.includes('hasError=true');
219
- handle('harvest-metadata', [{
220
- [featureName]: {
221
- ...(hasReplay && {
222
- hasReplay
223
- }),
224
- ...(hasTrace && {
225
- hasTrace
226
- }),
227
- ...(hasError && {
228
- hasError
229
- })
230
- }
231
- }], undefined, FEATURE_NAMES.metrics, agentRef.ee);
232
- } catch (err) {
233
- // do nothing
234
- }
235
- }
236
- }
237
- dispatchGlobalEvent({
238
- drained: !!agentRef.runtime?.activatedFeatures,
239
- type: 'data',
240
- name: 'harvest',
241
- feature: featureName,
242
- data: {
243
- endpoint,
244
- headers,
245
- payload,
246
- submitMethod: getSubmitMethodName(),
247
- raw,
248
- synchronousXhr: !!(localOpts.isFinalHarvest && isWorkerScope)
249
- }
250
- });
251
- return true;
252
- function shouldRetry(status) {
253
- switch (status) {
254
- case 408:
255
- case 429:
256
- case 500:
257
- return true;
258
- }
259
- return status >= 502 && status <= 504 || status >= 512 && status <= 530;
260
- }
261
- function getSubmitMethodName() {
262
- if (submitMethod === xhrMethod) return 'xhr';
263
- if (submitMethod === fetchMethod) return 'fetch';
264
- return 'beacon';
265
- }
266
- }
267
-
268
- /**
269
- * Cleans and returns a payload object containing a body and qs
270
- * object with key/value pairs. KV pairs where the value is null,
271
- * undefined, or an empty string are removed to save on transmission
272
- * size.
273
- * @param {HarvestPayload} payload Payload to be sent to the endpoint.
274
- * @returns {HarvestPayload} Cleaned payload payload to be sent to the endpoint.
275
- */
276
- function cleanPayload(payload = {}) {
277
- const clean = input => {
278
- if (typeof Uint8Array !== 'undefined' && input instanceof Uint8Array || Array.isArray(input)) return input;
279
- if (typeof input === 'string') return input;
280
- return Object.entries(input || {}).reduce((accumulator, [key, value]) => {
281
- if (typeof value === 'number' || typeof value === 'string' && value.length > 0 || typeof value === 'object' && Object.keys(value || {}).length > 0) {
282
- accumulator[key] = value;
283
- }
284
- return accumulator;
285
- }, {});
286
- };
287
- return {
288
- body: clean(payload.body),
289
- qs: clean(payload.qs)
290
- };
291
- }
292
-
293
- // The stuff that gets sent every time.
294
- function baseQueryString(agentRef, qs, endpoint, harvesterObfuscator) {
295
- const cleanedURL = cleanURL('' + globalScope.location);
296
- const ref = harvesterObfuscator?.obfuscateString(cleanedURL) ?? cleanedURL;
297
- const session = agentRef.runtime.session;
298
- const hr = !!session?.state.sessionReplaySentFirstChunk && session?.state.sessionReplayMode === 1 && endpoint !== JSERRORS;
299
- const ht = !!session?.state.traceHarvestStarted && session?.state.sessionTraceMode === 1 && ![LOGS, BLOBS].includes(endpoint);
300
- 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',
301
- // ck param DEPRECATED - still expected by backend
302
- '&s=' + (session?.state.value || '0'),
303
- // the 0 id encaps all untrackable and default traffic
304
- param('ref', ref), param('ptid', agentRef.runtime.ptid ? '' + agentRef.runtime.ptid : '')];
305
- if (hr) qps.push(param('hr', '1', qs));
306
- if (ht) qps.push(param('ht', '1', qs));
307
- return qps.join('');
308
-
309
- // Constructs the transaction name param for the beacon URL.
310
- // Prefers the obfuscated transaction name over the plain text.
311
- // Falls back to making up a name.
312
- function transactionNameParam() {
313
- if (agentRef.info.transactionName) return param('to', agentRef.info.transactionName);
314
- return param('t', agentRef.info.tNamePlain || 'Unnamed Transaction');
315
- }
316
100
  }