@newrelic/browser-agent 1.320.1 → 1.321.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 +15 -0
- package/dist/cjs/common/config/runtime.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/dom/selector-path.js +1 -1
- package/dist/cjs/common/event-listener/event-listener-opts.js +1 -1
- package/dist/cjs/common/harvest/connector.js +202 -0
- package/dist/cjs/common/harvest/harvester.js +5 -8
- package/dist/cjs/common/harvest/send.js +8 -7
- package/dist/cjs/common/session/session-entity.js +1 -1
- package/dist/cjs/common/timer/interaction-timer.js +2 -2
- package/dist/cjs/common/unload/eol.js +3 -3
- package/dist/cjs/common/util/console.js +2 -1
- package/dist/cjs/common/util/feature-flags.js +2 -1
- package/dist/cjs/common/v2/constants.js +21 -0
- package/dist/cjs/common/v2/utils.js +25 -23
- package/dist/cjs/common/window/page-visibility.js +1 -1
- package/dist/cjs/common/wrap/wrap-events.js +4 -3
- package/dist/cjs/common/wrap/wrap-fetch.js +2 -2
- package/dist/cjs/common/wrap/wrap-function.js +1 -1
- package/dist/cjs/common/wrap/wrap-history.js +3 -2
- package/dist/cjs/common/wrap/wrap-xhr.js +2 -2
- package/dist/cjs/features/ajax/aggregate/index.js +1 -1
- 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/jserrors/aggregate/index.js +5 -5
- package/dist/cjs/features/logging/instrument/index.js +1 -1
- package/dist/cjs/features/page_view_event/aggregate/index.js +2 -6
- package/dist/cjs/features/page_view_event/aggregate_v2/index.js +109 -0
- package/dist/cjs/features/page_view_event/instrument/index.js +6 -1
- package/dist/cjs/features/page_view_timing/aggregate/index.js +5 -1
- package/dist/cjs/features/session_replay/instrument/index.js +9 -0
- package/dist/cjs/features/session_replay/shared/recorder.js +2 -1
- package/dist/cjs/features/session_replay/shared/utils.js +3 -5
- package/dist/cjs/features/session_trace/instrument/index.js +3 -3
- package/dist/cjs/features/soft_navigations/instrument/index.js +2 -2
- package/dist/cjs/features/utils/aggregate-base.js +0 -12
- package/dist/cjs/features/utils/instrument-base.js +72 -29
- package/dist/cjs/loaders/agent.js +2 -1
- package/dist/cjs/loaders/api/consent.js +2 -2
- package/dist/cjs/loaders/api/register.js +26 -13
- package/dist/cjs/loaders/configure/configure.js +14 -0
- package/dist/cjs/loaders/features/features.js +2 -1
- package/dist/esm/common/config/runtime.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/dom/selector-path.js +1 -1
- package/dist/esm/common/event-listener/event-listener-opts.js +1 -1
- package/dist/esm/common/harvest/connector.js +195 -0
- package/dist/esm/common/harvest/harvester.js +5 -8
- package/dist/esm/common/harvest/send.js +10 -9
- package/dist/esm/common/session/session-entity.js +1 -1
- package/dist/esm/common/timer/interaction-timer.js +2 -2
- package/dist/esm/common/unload/eol.js +3 -3
- package/dist/esm/common/util/console.js +2 -1
- package/dist/esm/common/util/feature-flags.js +2 -1
- package/dist/esm/common/v2/constants.js +15 -0
- package/dist/esm/common/v2/utils.js +24 -22
- package/dist/esm/common/window/page-visibility.js +1 -1
- package/dist/esm/common/wrap/wrap-events.js +4 -3
- package/dist/esm/common/wrap/wrap-fetch.js +2 -2
- package/dist/esm/common/wrap/wrap-function.js +1 -1
- package/dist/esm/common/wrap/wrap-history.js +3 -2
- package/dist/esm/common/wrap/wrap-xhr.js +2 -2
- package/dist/esm/features/ajax/aggregate/index.js +2 -2
- 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/jserrors/aggregate/index.js +6 -6
- package/dist/esm/features/logging/instrument/index.js +1 -1
- package/dist/esm/features/page_view_event/aggregate/index.js +2 -6
- package/dist/esm/features/page_view_event/aggregate_v2/index.js +100 -0
- package/dist/esm/features/page_view_event/instrument/index.js +6 -1
- package/dist/esm/features/page_view_timing/aggregate/index.js +5 -1
- package/dist/esm/features/session_replay/instrument/index.js +9 -0
- package/dist/esm/features/session_replay/shared/recorder.js +2 -1
- package/dist/esm/features/session_replay/shared/utils.js +3 -5
- package/dist/esm/features/session_trace/instrument/index.js +3 -3
- package/dist/esm/features/soft_navigations/instrument/index.js +2 -2
- package/dist/esm/features/utils/aggregate-base.js +0 -12
- package/dist/esm/features/utils/instrument-base.js +71 -28
- package/dist/esm/loaders/agent.js +2 -1
- package/dist/esm/loaders/api/consent.js +2 -2
- package/dist/esm/loaders/api/register.js +19 -6
- package/dist/esm/loaders/configure/configure.js +14 -0
- package/dist/esm/loaders/features/features.js +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/common/config/runtime.d.ts.map +1 -1
- package/dist/types/common/event-listener/event-listener-opts.d.ts +1 -1
- package/dist/types/common/harvest/connector.d.ts +6 -0
- package/dist/types/common/harvest/connector.d.ts.map +1 -0
- package/dist/types/common/harvest/harvester.d.ts.map +1 -1
- package/dist/types/common/harvest/send.d.ts.map +1 -1
- package/dist/types/common/unload/eol.d.ts +1 -1
- package/dist/types/common/unload/eol.d.ts.map +1 -1
- package/dist/types/common/util/console.d.ts +2 -1
- package/dist/types/common/util/console.d.ts.map +1 -1
- package/dist/types/common/v2/constants.d.ts +9 -0
- package/dist/types/common/v2/constants.d.ts.map +1 -0
- package/dist/types/common/v2/utils.d.ts +36 -11
- package/dist/types/common/v2/utils.d.ts.map +1 -1
- package/dist/types/common/wrap/wrap-events.d.ts +2 -1
- package/dist/types/common/wrap/wrap-events.d.ts.map +1 -1
- package/dist/types/common/wrap/wrap-history.d.ts +2 -1
- package/dist/types/common/wrap/wrap-history.d.ts.map +1 -1
- package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/page_view_event/aggregate_v2/index.d.ts +20 -0
- package/dist/types/features/page_view_event/aggregate_v2/index.d.ts.map +1 -0
- package/dist/types/features/page_view_event/instrument/index.d.ts.map +1 -1
- package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/instrument/index.d.ts +1 -0
- package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/utils.d.ts.map +1 -1
- package/dist/types/features/session_trace/instrument/index.d.ts.map +1 -1
- package/dist/types/features/utils/aggregate-base.d.ts +0 -5
- package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
- package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
- package/dist/types/loaders/agent.d.ts.map +1 -1
- package/dist/types/loaders/api/register.d.ts.map +1 -1
- package/dist/types/loaders/configure/configure.d.ts.map +1 -1
- package/dist/types/loaders/features/features.d.ts +1 -0
- package/dist/types/loaders/features/features.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/common/config/runtime.js +3 -0
- package/src/common/dom/selector-path.js +1 -1
- package/src/common/event-listener/event-listener-opts.js +1 -1
- package/src/common/harvest/connector.js +170 -0
- package/src/common/harvest/harvester.js +7 -9
- package/src/common/harvest/send.js +9 -7
- package/src/common/session/session-entity.js +1 -1
- package/src/common/timer/interaction-timer.js +2 -2
- package/src/common/unload/eol.js +3 -3
- package/src/common/util/console.js +2 -1
- package/src/common/util/feature-flags.js +1 -1
- package/src/common/v2/constants.js +15 -0
- package/src/common/v2/utils.js +23 -21
- package/src/common/window/page-visibility.js +1 -1
- package/src/common/wrap/wrap-events.js +4 -3
- package/src/common/wrap/wrap-fetch.js +2 -2
- package/src/common/wrap/wrap-function.js +1 -1
- package/src/common/wrap/wrap-history.js +3 -2
- package/src/common/wrap/wrap-xhr.js +2 -2
- package/src/features/ajax/aggregate/index.js +2 -2
- package/src/features/generic_events/aggregate/index.js +1 -1
- package/src/features/generic_events/instrument/index.js +1 -1
- package/src/features/jserrors/aggregate/index.js +6 -6
- package/src/features/logging/instrument/index.js +1 -1
- package/src/features/page_view_event/aggregate/index.js +2 -6
- package/src/features/page_view_event/aggregate_v2/index.js +97 -0
- package/src/features/page_view_event/instrument/index.js +6 -1
- package/src/features/page_view_timing/aggregate/index.js +5 -1
- package/src/features/session_replay/instrument/index.js +9 -0
- package/src/features/session_replay/shared/recorder.js +2 -1
- package/src/features/session_replay/shared/utils.js +3 -5
- package/src/features/session_trace/instrument/index.js +3 -3
- package/src/features/soft_navigations/instrument/index.js +2 -2
- package/src/features/utils/aggregate-base.js +0 -12
- package/src/features/utils/instrument-base.js +71 -25
- package/src/loaders/agent.js +2 -1
- package/src/loaders/api/consent.js +2 -2
- package/src/loaders/api/register.js +16 -6
- package/src/loaders/configure/configure.js +12 -0
- package/src/loaders/features/features.js +1 -0
- package/dist/cjs/common/url/location.js +0 -18
- package/dist/esm/common/url/location.js +0 -12
- package/dist/types/common/url/location.d.ts +0 -10
- package/dist/types/common/url/location.d.ts.map +0 -1
- package/src/common/url/__mocks__/location.js +0 -5
- package/src/common/url/location.js +0 -12
|
@@ -7,12 +7,12 @@ exports.setupRegisterAPI = setupRegisterAPI;
|
|
|
7
7
|
exports.warnings = void 0;
|
|
8
8
|
var _handle = require("../../common/event-emitter/handle");
|
|
9
9
|
var _console = require("../../common/util/console");
|
|
10
|
-
var
|
|
10
|
+
var _constants = require("../../common/v2/constants");
|
|
11
11
|
var _features = require("../features/features");
|
|
12
12
|
var _now = require("../../common/timing/now");
|
|
13
|
-
var
|
|
13
|
+
var _constants2 = require("../../features/metrics/constants");
|
|
14
14
|
var _sharedHandlers = require("./sharedHandlers");
|
|
15
|
-
var
|
|
15
|
+
var _constants3 = require("./constants");
|
|
16
16
|
var _log = require("./log");
|
|
17
17
|
var _addPageAction = require("./addPageAction");
|
|
18
18
|
var _noticeError = require("./noticeError");
|
|
@@ -23,6 +23,7 @@ var _pageVisibility = require("../../common/window/page-visibility");
|
|
|
23
23
|
var _scriptTracker = require("../../common/v2/script-tracker");
|
|
24
24
|
var _mfeVitals = require("../../common/v2/mfe-vitals");
|
|
25
25
|
var _uniqueId = require("../../common/ids/unique-id");
|
|
26
|
+
var _cleanUrl = require("../../common/url/clean-url");
|
|
26
27
|
/**
|
|
27
28
|
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
28
29
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -57,7 +58,7 @@ const warnings = exports.warnings = {
|
|
|
57
58
|
* It is not recommended for use in production environments and will not receive support for issues.
|
|
58
59
|
*/
|
|
59
60
|
function setupRegisterAPI(agent) {
|
|
60
|
-
(0, _sharedHandlers.setupAPI)(
|
|
61
|
+
(0, _sharedHandlers.setupAPI)(_constants3.REGISTER, function (target) {
|
|
61
62
|
return register(agent, target);
|
|
62
63
|
}, agent);
|
|
63
64
|
}
|
|
@@ -73,7 +74,7 @@ function register(agentRef, target) {
|
|
|
73
74
|
warnings.experimental();
|
|
74
75
|
target ||= {};
|
|
75
76
|
target.instance = (0, _uniqueId.generateRandomHexString)(8);
|
|
76
|
-
target.type =
|
|
77
|
+
target.type = _constants.V2_TYPES.MFE;
|
|
77
78
|
target.licenseKey ||= agentRef.info.licenseKey; // will inherit the license key from the container agent if not provided for brevity. A future state may dictate that we need different license keys to do different things.
|
|
78
79
|
target.blocked = false;
|
|
79
80
|
if (typeof target.tags !== 'object' || target.tags === null || Array.isArray(target.tags)) target.tags = {};
|
|
@@ -82,12 +83,17 @@ function register(agentRef, target) {
|
|
|
82
83
|
return agentRef.runtime.appMetadata.agents?.[0].entityGuid;
|
|
83
84
|
},
|
|
84
85
|
// getter because this is asynchronously set
|
|
85
|
-
type:
|
|
86
|
+
type: _constants.V2_TYPES.BA
|
|
86
87
|
};
|
|
88
|
+
|
|
89
|
+
// The script timings for this entity, which will be used to populate part of the MicroFrontEndTiming custom event.
|
|
87
90
|
const timings = (0, _scriptTracker.findScriptTimings)(target);
|
|
88
91
|
|
|
89
|
-
// Track MFE vitals for this entity
|
|
92
|
+
// Track MFE vitals for this entity, which will be used to populate part of the MicroFrontEndTiming custom event.
|
|
90
93
|
const vitals = (0, _mfeVitals.trackMFEVitals)(target, timings);
|
|
94
|
+
|
|
95
|
+
// the URL of the page at the time this entity was registered, which will be used to populate part of the MicroFrontEndTiming custom event.
|
|
96
|
+
const registerUrl = (0, _cleanUrl.cleanURL)('' + location);
|
|
91
97
|
const attrs = {};
|
|
92
98
|
|
|
93
99
|
// Only define attributes getter if it doesn't already exist
|
|
@@ -99,7 +105,7 @@ function register(agentRef, target) {
|
|
|
99
105
|
'source.id': target.id,
|
|
100
106
|
'source.name': target.name,
|
|
101
107
|
'source.type': target.type,
|
|
102
|
-
'parent.type': target.parent?.type ||
|
|
108
|
+
'parent.type': target.parent?.type || _constants.V2_TYPES.BA,
|
|
103
109
|
'parent.id': target.parent?.id
|
|
104
110
|
};
|
|
105
111
|
}
|
|
@@ -223,6 +229,13 @@ function register(agentRef, target) {
|
|
|
223
229
|
// the url of the script that was registered, or undefined if it could not be determined (inline or no match)
|
|
224
230
|
assetType: timings.type,
|
|
225
231
|
// the type of asset that was associated with the timings, one of 'script', 'link' (if preloaded and found in the resource timing buffer), 'preload' (if preloaded but not found in the resource timing buffer), or "unknown" if it could not be determined
|
|
232
|
+
registerUrl,
|
|
233
|
+
// the url of the page at the time this entity was registered
|
|
234
|
+
deregisterUrl: (0, _cleanUrl.cleanURL)('' + location),
|
|
235
|
+
// the url of the page at the time this entity was deregistered (or unloaded)
|
|
236
|
+
// generic_events' addEvent() injects pageUrl/currentUrl on every custom event by default; override them away here in favor of registerUrl/deregisterUrl above
|
|
237
|
+
pageUrl: undefined,
|
|
238
|
+
currentUrl: undefined,
|
|
226
239
|
timeAlive: timings.reportedAt - timings.registeredAt,
|
|
227
240
|
// registeredAt to reportedAt
|
|
228
241
|
timeToBeRequested: timings.fetchStart,
|
|
@@ -237,19 +250,19 @@ function register(agentRef, target) {
|
|
|
237
250
|
// timestamp when register() was called
|
|
238
251
|
// leave room to extend these with more data keys as needed
|
|
239
252
|
...(vitals.fcp.value >= 0 && {
|
|
240
|
-
'
|
|
253
|
+
'vitals.fcp.value': vitals.fcp.value
|
|
241
254
|
}),
|
|
242
255
|
// FCP vital object with value and metadata
|
|
243
256
|
...(vitals.lcp.value >= 0 && {
|
|
244
|
-
'
|
|
257
|
+
'vitals.lcp.value': vitals.lcp.value
|
|
245
258
|
}),
|
|
246
259
|
// LCP vital object with value and metadata
|
|
247
260
|
...(vitals.cls.value >= 0 && {
|
|
248
|
-
'
|
|
261
|
+
'vitals.cls.value': vitals.cls.value
|
|
249
262
|
}),
|
|
250
263
|
// CLS vital object with value and metadata
|
|
251
264
|
...(vitals.inp.value >= 0 && {
|
|
252
|
-
'
|
|
265
|
+
'vitals.inp.value': vitals.inp.value
|
|
253
266
|
}) // INP vital object with value and metadata
|
|
254
267
|
};
|
|
255
268
|
api.recordCustomEvent('MicroFrontEndTiming', eventData);
|
|
@@ -281,7 +294,7 @@ function register(agentRef, target) {
|
|
|
281
294
|
const timestamp = api.metadata.events.latestTimestamp ?? (0, _now.now)();
|
|
282
295
|
api.metadata.events.latestTimestamp = undefined;
|
|
283
296
|
const methodName = METHOD_NAMES.get(methodToCall) || 'unknown';
|
|
284
|
-
(0, _handle.handle)(
|
|
297
|
+
(0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ["API/register/".concat(methodName, "/called")], undefined, _features.FEATURE_NAMES.metrics, agentRef.ee);
|
|
285
298
|
try {
|
|
286
299
|
return methodToCall(...args, target, timestamp); // always report to target
|
|
287
300
|
} catch (err) {
|
|
@@ -9,6 +9,7 @@ var _nreum = require("../../common/window/nreum");
|
|
|
9
9
|
var _info = require("../../common/config/info");
|
|
10
10
|
var _init = require("../../common/config/init");
|
|
11
11
|
var _runtime = require("../../common/config/runtime");
|
|
12
|
+
var _constants = require("../../common/v2/constants");
|
|
12
13
|
var _runtime2 = require("../../common/constants/runtime");
|
|
13
14
|
var _publicPath = require("./public-path");
|
|
14
15
|
var _contextualEe = require("../../common/event-emitter/contextual-ee");
|
|
@@ -52,6 +53,19 @@ function configure(agent, opts = {}, loaderType, forceDrain) {
|
|
|
52
53
|
agent.info = (0, _info.mergeInfo)(info);
|
|
53
54
|
const updatedInit = agent.init;
|
|
54
55
|
agent.runtime ??= (0, _runtime.mergeRuntime)(runtime);
|
|
56
|
+
agent.runtime.v2Target ??= {
|
|
57
|
+
type: _constants.V2_TYPES.BA,
|
|
58
|
+
instance: agent.agentIdentifier,
|
|
59
|
+
get id() {
|
|
60
|
+
return agent.runtime.appMetadata?.agents?.[0]?.entityGuid;
|
|
61
|
+
},
|
|
62
|
+
get attributes() {
|
|
63
|
+
return {
|
|
64
|
+
'entity.guid': this.id,
|
|
65
|
+
appId: agent.info.applicationID
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
};
|
|
55
69
|
|
|
56
70
|
// Apply proxy settings whenever configure is called (supports late {init} setting)
|
|
57
71
|
if (updatedInit.proxy.assets) {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.featurePriority = exports.RUM = exports.LOGS = exports.JSERRORS = exports.FEATURE_TO_ENDPOINT = exports.FEATURE_NAMES = exports.EVENTS = exports.BLOBS = void 0;
|
|
6
|
+
exports.featurePriority = exports.RUM = exports.LOGS = exports.JSERRORS = exports.FEATURE_TO_ENDPOINT = exports.FEATURE_NAMES = exports.EVENTS = exports.CONNECT = exports.BLOBS = void 0;
|
|
7
7
|
/**
|
|
8
8
|
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
9
9
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -14,6 +14,7 @@ const EVENTS = exports.EVENTS = 'events';
|
|
|
14
14
|
const JSERRORS = exports.JSERRORS = 'jserrors';
|
|
15
15
|
const BLOBS = exports.BLOBS = 'browser/blobs';
|
|
16
16
|
const RUM = exports.RUM = 'rum';
|
|
17
|
+
const CONNECT = exports.CONNECT = 'connect';
|
|
17
18
|
const LOGS = exports.LOGS = 'browser/logs';
|
|
18
19
|
const FEATURE_NAMES = exports.FEATURE_NAMES = {
|
|
19
20
|
ajax: 'ajax',
|
|
@@ -38,7 +38,10 @@ const RuntimeModel = {
|
|
|
38
38
|
disabled: false,
|
|
39
39
|
/** @type {Map<string, {staged: boolean, priority: number}>} */
|
|
40
40
|
drainRegistry: new Map(),
|
|
41
|
+
connector: undefined,
|
|
41
42
|
harvester: undefined,
|
|
43
|
+
/** eagerly-created in configure() -- the v2 target representing the container (main) agent, see common/v2/utils#V2Target */
|
|
44
|
+
v2Target: undefined,
|
|
42
45
|
isolatedBacklog: false,
|
|
43
46
|
isRecording: false,
|
|
44
47
|
// true when actively recording, false when paused or stopped
|
|
@@ -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 : [
|
|
25
|
+
return targets.length ? targets : [agentRef.runtime.v2Target];
|
|
26
26
|
},
|
|
27
27
|
hasButton: false,
|
|
28
28
|
hasLink: false
|
|
@@ -0,0 +1,195 @@
|
|
|
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
|
+
// Report that we've seen a race condition on updating session state
|
|
86
|
+
handle(SUPPORTABILITY_METRIC_CHANNEL, ['Session/RaceCondition/Seen'], undefined, FEATURE_NAMES.metrics, this.#agentRef.ee);
|
|
87
|
+
// `cachedRumResponse` is stored flat (`{ app, ...flags }`, matching the old v1 RUM response), so re-nest it before applying.
|
|
88
|
+
const {
|
|
89
|
+
app,
|
|
90
|
+
...config
|
|
91
|
+
} = cachedResp;
|
|
92
|
+
this.#applyConnectResponse({
|
|
93
|
+
app,
|
|
94
|
+
config
|
|
95
|
+
});
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const shouldRetry = sent && retry; // important to note: no retry if HTTP status is 0
|
|
100
|
+
if (shouldRetry && ++this.#numOfAttempts < MAX_CONNECT_ATTEMPTS) {
|
|
101
|
+
// We use an exponential backoff calculation for the retry to avoid thundering herd, skipping any further processing.
|
|
102
|
+
const delay = Math.floor(Math.random() * CONNECT_RETRY_BASE_MS * 2 ** (this.#numOfAttempts - 1));
|
|
103
|
+
const retryHeaders = [{
|
|
104
|
+
key: 'X-Retry-Count',
|
|
105
|
+
value: this.#numOfAttempts
|
|
106
|
+
}, {
|
|
107
|
+
key: 'X-Previous-Status',
|
|
108
|
+
value: status
|
|
109
|
+
}];
|
|
110
|
+
setTimeout(() => this.makeConnectRequest(retryHeaders), delay);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (status >= 400 || status === 0) {
|
|
114
|
+
warn(18, status);
|
|
115
|
+
|
|
116
|
+
// Get estimated payload size of our backlog.
|
|
117
|
+
const textEncoder = new TextEncoder();
|
|
118
|
+
const payloadSize = Object.values(newrelic.ee.backlog).reduce((acc, value) => {
|
|
119
|
+
if (!value) return acc;
|
|
120
|
+
const encoded = textEncoder.encode(value);
|
|
121
|
+
return acc + encoded.byteLength;
|
|
122
|
+
}, 0);
|
|
123
|
+
|
|
124
|
+
// Send SMs about the (last) failed connect request, then abort agent.
|
|
125
|
+
const BCSError = 'BCS/Error/';
|
|
126
|
+
const sm = [{
|
|
127
|
+
params: {
|
|
128
|
+
name: BCSError + status
|
|
129
|
+
},
|
|
130
|
+
stats: {
|
|
131
|
+
c: 1
|
|
132
|
+
}
|
|
133
|
+
}, {
|
|
134
|
+
params: {
|
|
135
|
+
name: BCSError + 'Duration/Ms'
|
|
136
|
+
},
|
|
137
|
+
stats: {
|
|
138
|
+
c: 1,
|
|
139
|
+
t: connectEndTime - this.#connectStartTime
|
|
140
|
+
}
|
|
141
|
+
}, {
|
|
142
|
+
params: {
|
|
143
|
+
name: BCSError + 'Dropped/Bytes'
|
|
144
|
+
},
|
|
145
|
+
stats: {
|
|
146
|
+
c: 1,
|
|
147
|
+
t: payloadSize
|
|
148
|
+
}
|
|
149
|
+
}];
|
|
150
|
+
send(this.#agentRef, {
|
|
151
|
+
// note we have to send it this way because the Metrics feature will never be harvested under this branch of code (BCS failure)
|
|
152
|
+
endpoint: FEATURE_TO_ENDPOINT[FEATURE_NAMES.metrics],
|
|
153
|
+
payload: {
|
|
154
|
+
body: {
|
|
155
|
+
sm
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
featureName: FEATURE_NAMES.metrics
|
|
159
|
+
});
|
|
160
|
+
this.#agentRef.ee.abort();
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
let resp;
|
|
164
|
+
try {
|
|
165
|
+
resp = JSON.parse(responseText);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
warn(53, error);
|
|
168
|
+
this.#agentRef.ee.abort();
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
// store the response for other pages later in same session or in parallel tabs; flatten `.config` into the old v1 RUM response shape
|
|
172
|
+
if (session) session.write({
|
|
173
|
+
cachedRumResponse: {
|
|
174
|
+
app: resp.app,
|
|
175
|
+
...resp.config
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
try {
|
|
179
|
+
const wasReady = this.#agentRef.runtime.timeKeeper.ready;
|
|
180
|
+
this.#agentRef.runtime.timeKeeper.processRumRequest(xhr, this.#connectStartTime, connectEndTime, resp.app.nrServerTime);
|
|
181
|
+
if (!this.#agentRef.runtime.timeKeeper.ready) throw new Error('TimeKeeper not ready');
|
|
182
|
+
|
|
183
|
+
// If timeKeeper's origin time is ahead of nrServerTime, then the timestamp is invalid. Report a supportability metric.
|
|
184
|
+
const timeDiff = this.#agentRef.runtime.timeKeeper.correctedOriginTime - resp.app.nrServerTime;
|
|
185
|
+
if (wasReady && timeDiff > 0) {
|
|
186
|
+
handle(SUPPORTABILITY_METRIC_CHANNEL, ['Generic/TimeKeeper/InvalidTimestamp/Seen', timeDiff], undefined, FEATURE_NAMES.metrics, this.#agentRef.ee);
|
|
187
|
+
}
|
|
188
|
+
} catch (error) {
|
|
189
|
+
warn(17, error);
|
|
190
|
+
this.#agentRef.ee.abort();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
this.#applyConnectResponse(resp);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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(), '&
|
|
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));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2020-
|
|
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,
|
|
55
|
+
}, false, this.abortController?.signal);
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
abort() {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2020-
|
|
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
|
|
25
|
+
export function subscribeToEOL(cb) {
|
|
26
26
|
if (isBrowserScope) {
|
|
27
|
-
subscribeToVisibilityChange(cb, true
|
|
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 |
|
|
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:
|
|
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
|
+
};
|