@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.
- package/CHANGELOG.md +9 -0
- package/dist/cjs/common/config/configurable.js +1 -1
- package/dist/cjs/common/config/init-types.js +2 -0
- package/dist/cjs/common/config/init.js +3 -0
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/harvest/harvester.js +3 -219
- package/dist/cjs/common/harvest/send.js +232 -0
- package/dist/cjs/common/harvest/types.js +9 -5
- package/dist/cjs/common/session/session-entity.js +8 -8
- package/dist/cjs/common/v2/mfe-vitals.js +303 -0
- package/dist/cjs/features/generic_events/aggregate/index.js +1 -1
- package/dist/cjs/features/generic_events/instrument/index.js +1 -1
- package/dist/cjs/features/logging/aggregate/index.js +5 -5
- package/dist/cjs/features/page_view_event/aggregate/index.js +65 -30
- package/dist/cjs/features/session_replay/aggregate/index.js +32 -28
- package/dist/cjs/features/session_trace/aggregate/index.js +29 -27
- package/dist/cjs/features/session_trace/constants.js +17 -3
- package/dist/cjs/features/utils/aggregate-base.js +2 -0
- package/dist/cjs/loaders/api/register.js +27 -4
- package/dist/esm/common/config/configurable.js +1 -1
- package/dist/esm/common/config/init-types.js +2 -0
- package/dist/esm/common/config/init.js +3 -0
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/harvest/harvester.js +3 -219
- package/dist/esm/common/harvest/send.js +226 -0
- package/dist/esm/common/harvest/types.js +9 -5
- package/dist/esm/common/session/session-entity.js +9 -9
- package/dist/esm/common/v2/mfe-vitals.js +298 -0
- package/dist/esm/features/generic_events/aggregate/index.js +1 -1
- package/dist/esm/features/generic_events/instrument/index.js +1 -1
- package/dist/esm/features/logging/aggregate/index.js +5 -5
- package/dist/esm/features/page_view_event/aggregate/index.js +64 -29
- package/dist/esm/features/session_replay/aggregate/index.js +32 -28
- package/dist/esm/features/session_trace/aggregate/index.js +30 -28
- package/dist/esm/features/session_trace/constants.js +16 -2
- package/dist/esm/features/utils/aggregate-base.js +2 -0
- package/dist/esm/loaders/api/register.js +27 -4
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/common/config/init-types.d.ts +6 -0
- package/dist/types/common/config/init.d.ts.map +1 -1
- package/dist/types/common/harvest/harvester.d.ts +0 -17
- package/dist/types/common/harvest/harvester.d.ts.map +1 -1
- package/dist/types/common/harvest/send.d.ts +9 -0
- package/dist/types/common/harvest/send.d.ts.map +1 -0
- package/dist/types/common/harvest/types.d.ts +39 -10
- package/dist/types/common/harvest/types.d.ts.map +1 -1
- package/dist/types/common/session/session-entity.d.ts.map +1 -1
- package/dist/types/common/v2/mfe-vitals.d.ts +14 -0
- package/dist/types/common/v2/mfe-vitals.d.ts.map +1 -0
- package/dist/types/features/page_view_event/aggregate/index.d.ts +1 -0
- package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts +2 -2
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/index.d.ts +1 -1
- package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_trace/constants.d.ts +17 -0
- package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
- package/dist/types/loaders/api/register.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/common/config/configurable.js +1 -1
- package/src/common/config/init-types.js +2 -0
- package/src/common/config/init.js +2 -1
- package/src/common/harvest/harvester.js +3 -202
- package/src/common/harvest/send.js +209 -0
- package/src/common/harvest/types.js +9 -5
- package/src/common/session/session-entity.js +8 -9
- package/src/common/v2/mfe-vitals.js +285 -0
- package/src/features/generic_events/aggregate/index.js +1 -1
- package/src/features/generic_events/instrument/index.js +1 -1
- package/src/features/logging/aggregate/index.js +5 -5
- package/src/features/page_view_event/aggregate/index.js +52 -27
- package/src/features/session_replay/aggregate/index.js +30 -27
- package/src/features/session_trace/aggregate/index.js +31 -28
- package/src/features/session_trace/constants.js +7 -1
- package/src/features/utils/aggregate-base.js +2 -0
- 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)
|
|
51
|
-
|
|
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(
|
|
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(
|
|
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 (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
198
|
-
(0, _console.warn)(60,
|
|
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-
|
|
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
|
-
|
|
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
|
|
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 {};
|
|
@@ -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,
|
|
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 {
|
|
14
|
-
import {
|
|
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
|
}
|