@newrelic/browser-agent 1.320.1-rc.0 → 1.320.1-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/common/config/runtime.js +1 -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/event-listener/event-listener-opts.js +1 -1
- package/dist/cjs/common/harvest/connector.js +200 -0
- package/dist/cjs/common/harvest/harvester.js +5 -8
- package/dist/cjs/common/harvest/send.js +8 -7
- 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/window/page-visibility.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/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/features/features.js +2 -1
- package/dist/esm/common/config/runtime.js +1 -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/event-listener/event-listener-opts.js +1 -1
- package/dist/esm/common/harvest/connector.js +193 -0
- package/dist/esm/common/harvest/harvester.js +5 -8
- package/dist/esm/common/harvest/send.js +10 -9
- 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/window/page-visibility.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/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/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/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/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/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 +1 -0
- package/src/common/event-listener/event-listener-opts.js +1 -1
- package/src/common/harvest/connector.js +168 -0
- package/src/common/harvest/harvester.js +7 -9
- package/src/common/harvest/send.js +9 -7
- 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/window/page-visibility.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/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/features/features.js +1 -0
|
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.RRWEB_PACKAGE_NAME = exports.D
|
|
|
17
17
|
/**
|
|
18
18
|
* Exposes the version of the agent
|
|
19
19
|
*/
|
|
20
|
-
const VERSION = exports.VERSION = "1.320.1-rc.
|
|
20
|
+
const VERSION = exports.VERSION = "1.320.1-rc.1";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Exposes the build type of the agent
|
|
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.RRWEB_PACKAGE_NAME = exports.D
|
|
|
17
17
|
/**
|
|
18
18
|
* Exposes the version of the agent
|
|
19
19
|
*/
|
|
20
|
-
const VERSION = exports.VERSION = "1.320.1-rc.
|
|
20
|
+
const VERSION = exports.VERSION = "1.320.1-rc.1";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Exposes the build type of the agent
|
|
@@ -7,7 +7,7 @@ exports.documentAddEventListener = documentAddEventListener;
|
|
|
7
7
|
exports.eventListenerOpts = eventListenerOpts;
|
|
8
8
|
exports.windowAddEventListener = windowAddEventListener;
|
|
9
9
|
/**
|
|
10
|
-
* Copyright 2020-
|
|
10
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
11
11
|
* SPDX-License-Identifier: Apache-2.0
|
|
12
12
|
*/
|
|
13
13
|
function eventListenerOpts(useCapture, abortSignal) {
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.Connector = void 0;
|
|
7
|
+
var _runtime = require("../constants/runtime");
|
|
8
|
+
var _send = require("./send");
|
|
9
|
+
var _timeKeeper = require("../timing/time-keeper");
|
|
10
|
+
var _featureFlags = require("../util/feature-flags");
|
|
11
|
+
var _now = require("../timing/now");
|
|
12
|
+
var _console = require("../util/console");
|
|
13
|
+
var _env = require("../constants/env.npm");
|
|
14
|
+
var _features = require("../../loaders/features/features");
|
|
15
|
+
var _handle = require("../event-emitter/handle");
|
|
16
|
+
var _constants = require("../../features/metrics/constants");
|
|
17
|
+
/**
|
|
18
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
19
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const MAX_CONNECT_ATTEMPTS = 3;
|
|
23
|
+
const CONNECT_RETRY_BASE_MS = 3000; // affects each retry window proportionally (attempt 2: 0-3s, 3: 0-6s, 4: 0-12s, etc)
|
|
24
|
+
|
|
25
|
+
class Connector {
|
|
26
|
+
#agentRef;
|
|
27
|
+
#connectStartTime;
|
|
28
|
+
#numOfAttempts = 0;
|
|
29
|
+
constructor(agentRef) {
|
|
30
|
+
if (!agentRef.init.feature_flags.includes('rum_v2')) return; // the Connector does not run in v1; legacy PVE has its own BCS handling
|
|
31
|
+
this.#agentRef = agentRef;
|
|
32
|
+
agentRef.runtime.timeKeeper = new _timeKeeper.TimeKeeper(agentRef.runtime.session);
|
|
33
|
+
if (_runtime.isBrowserScope) {
|
|
34
|
+
const cached = agentRef.runtime.session && agentRef.runtime.session.state.cachedRumResponse;
|
|
35
|
+
if (cached) {
|
|
36
|
+
// `cachedRumResponse` is stored flat (`{ app, ...flags }`, matching the old v1 RUM response), so re-nest it before applying.
|
|
37
|
+
const {
|
|
38
|
+
app,
|
|
39
|
+
...config
|
|
40
|
+
} = cached;
|
|
41
|
+
this.#applyConnectResponse({
|
|
42
|
+
app,
|
|
43
|
+
config
|
|
44
|
+
});
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
this.makeConnectRequest();
|
|
49
|
+
}
|
|
50
|
+
makeConnectRequest(retryHeaders) {
|
|
51
|
+
this.#connectStartTime = (0, _now.now)();
|
|
52
|
+
return (0, _send.send)(this.#agentRef, {
|
|
53
|
+
endpoint: "browser/connect/2/".concat(this.#agentRef.info.licenseKey),
|
|
54
|
+
payload: {
|
|
55
|
+
body: {},
|
|
56
|
+
qs: {
|
|
57
|
+
a: this.#agentRef.info.applicationID,
|
|
58
|
+
v: _env.VERSION,
|
|
59
|
+
s: this.#agentRef.runtime.session?.state.value || '0'
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
localOpts: {
|
|
63
|
+
sendEmptyBody: true,
|
|
64
|
+
headers: retryHeaders
|
|
65
|
+
},
|
|
66
|
+
raw: true,
|
|
67
|
+
featureName: _features.CONNECT,
|
|
68
|
+
cbFinished: this.#processConnectResponse.bind(this)
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
#applyConnectResponse({
|
|
72
|
+
app,
|
|
73
|
+
config
|
|
74
|
+
}) {
|
|
75
|
+
if (!Object.keys(this.#agentRef.runtime.appMetadata).length) this.#agentRef.runtime.appMetadata = app;
|
|
76
|
+
(0, _featureFlags.activateFeatures)(config, this.#agentRef);
|
|
77
|
+
}
|
|
78
|
+
#processConnectResponse({
|
|
79
|
+
sent,
|
|
80
|
+
status,
|
|
81
|
+
retry,
|
|
82
|
+
xhr,
|
|
83
|
+
responseText
|
|
84
|
+
}) {
|
|
85
|
+
const connectEndTime = (0, _now.now)();
|
|
86
|
+
const session = this.#agentRef.runtime.session;
|
|
87
|
+
if (session) {
|
|
88
|
+
// 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.
|
|
89
|
+
const cachedResp = session.state.cachedRumResponse;
|
|
90
|
+
if (cachedResp) {
|
|
91
|
+
// `cachedRumResponse` is stored flat (`{ app, ...flags }`, matching the old v1 RUM response), so re-nest it before applying.
|
|
92
|
+
const {
|
|
93
|
+
app,
|
|
94
|
+
...config
|
|
95
|
+
} = cachedResp;
|
|
96
|
+
this.#applyConnectResponse({
|
|
97
|
+
app,
|
|
98
|
+
config
|
|
99
|
+
});
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const shouldRetry = sent && retry; // important to note: no retry if HTTP status is 0
|
|
104
|
+
if (shouldRetry && ++this.#numOfAttempts < MAX_CONNECT_ATTEMPTS) {
|
|
105
|
+
// We use an exponential backoff calculation for the retry to avoid thundering herd, skipping any further processing.
|
|
106
|
+
const delay = Math.floor(Math.random() * CONNECT_RETRY_BASE_MS * 2 ** (this.#numOfAttempts - 1));
|
|
107
|
+
const retryHeaders = [{
|
|
108
|
+
key: 'X-Retry-Count',
|
|
109
|
+
value: this.#numOfAttempts
|
|
110
|
+
}, {
|
|
111
|
+
key: 'X-Previous-Status',
|
|
112
|
+
value: status
|
|
113
|
+
}];
|
|
114
|
+
setTimeout(() => this.makeConnectRequest(retryHeaders), delay);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (status >= 400 || status === 0) {
|
|
118
|
+
(0, _console.warn)(18, status);
|
|
119
|
+
|
|
120
|
+
// Get estimated payload size of our backlog.
|
|
121
|
+
const textEncoder = new TextEncoder();
|
|
122
|
+
const payloadSize = Object.values(newrelic.ee.backlog).reduce((acc, value) => {
|
|
123
|
+
if (!value) return acc;
|
|
124
|
+
const encoded = textEncoder.encode(value);
|
|
125
|
+
return acc + encoded.byteLength;
|
|
126
|
+
}, 0);
|
|
127
|
+
|
|
128
|
+
// Send SMs about the (last) failed connect request, then abort agent.
|
|
129
|
+
const BCSError = 'BCS/Error/';
|
|
130
|
+
const sm = [{
|
|
131
|
+
params: {
|
|
132
|
+
name: BCSError + status
|
|
133
|
+
},
|
|
134
|
+
stats: {
|
|
135
|
+
c: 1
|
|
136
|
+
}
|
|
137
|
+
}, {
|
|
138
|
+
params: {
|
|
139
|
+
name: BCSError + 'Duration/Ms'
|
|
140
|
+
},
|
|
141
|
+
stats: {
|
|
142
|
+
c: 1,
|
|
143
|
+
t: connectEndTime - this.#connectStartTime
|
|
144
|
+
}
|
|
145
|
+
}, {
|
|
146
|
+
params: {
|
|
147
|
+
name: BCSError + 'Dropped/Bytes'
|
|
148
|
+
},
|
|
149
|
+
stats: {
|
|
150
|
+
c: 1,
|
|
151
|
+
t: payloadSize
|
|
152
|
+
}
|
|
153
|
+
}];
|
|
154
|
+
(0, _send.send)(this.#agentRef, {
|
|
155
|
+
// note we have to send it this way because the Metrics feature will never be harvested under this branch of code (BCS failure)
|
|
156
|
+
endpoint: _features.FEATURE_TO_ENDPOINT[_features.FEATURE_NAMES.metrics],
|
|
157
|
+
payload: {
|
|
158
|
+
body: {
|
|
159
|
+
sm
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
featureName: _features.FEATURE_NAMES.metrics
|
|
163
|
+
});
|
|
164
|
+
this.#agentRef.ee.abort();
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
let resp;
|
|
168
|
+
try {
|
|
169
|
+
resp = JSON.parse(responseText);
|
|
170
|
+
} catch (error) {
|
|
171
|
+
(0, _console.warn)(53, error);
|
|
172
|
+
this.#agentRef.ee.abort();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
// store the response for other pages later in same session or in parallel tabs; flatten `.config` into the old v1 RUM response shape
|
|
176
|
+
if (session) session.write({
|
|
177
|
+
cachedRumResponse: {
|
|
178
|
+
app: resp.app,
|
|
179
|
+
...resp.config
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
try {
|
|
183
|
+
const wasReady = this.#agentRef.runtime.timeKeeper.ready;
|
|
184
|
+
this.#agentRef.runtime.timeKeeper.processRumRequest(xhr, this.#connectStartTime, connectEndTime, resp.app.nrServerTime);
|
|
185
|
+
if (!this.#agentRef.runtime.timeKeeper.ready) throw new Error('TimeKeeper not ready');
|
|
186
|
+
|
|
187
|
+
// If timeKeeper's origin time is ahead of nrServerTime, then the timestamp is invalid. Report a supportability metric.
|
|
188
|
+
const timeDiff = this.#agentRef.runtime.timeKeeper.correctedOriginTime - resp.app.nrServerTime;
|
|
189
|
+
if (wasReady && timeDiff > 0) {
|
|
190
|
+
(0, _handle.handle)(_constants.SUPPORTABILITY_METRIC_CHANNEL, ['Generic/TimeKeeper/InvalidTimestamp/Seen', timeDiff], undefined, _features.FEATURE_NAMES.metrics, this.#agentRef.ee);
|
|
191
|
+
}
|
|
192
|
+
} catch (error) {
|
|
193
|
+
(0, _console.warn)(17, error);
|
|
194
|
+
this.#agentRef.ee.abort();
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
this.#applyConnectResponse(resp);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
exports.Connector = Connector;
|
|
@@ -26,11 +26,13 @@ class Harvester {
|
|
|
26
26
|
constructor(agentRef) {
|
|
27
27
|
this.agentRef = agentRef;
|
|
28
28
|
|
|
29
|
-
// Create obfuscator for harvest metadata (referrer URL, etc.)
|
|
30
29
|
// No event type specified - applies to all harvests regardless of feature
|
|
31
30
|
this.obfuscator = new _obfuscate.Obfuscator(agentRef);
|
|
31
|
+
}
|
|
32
|
+
startTimer(harvestInterval = this.agentRef.init.harvest.interval) {
|
|
33
|
+
if (this.#started) return;
|
|
34
|
+
this.#started = true;
|
|
32
35
|
(0, _eol.subscribeToEOL)(() => {
|
|
33
|
-
// do one last harvest round or check
|
|
34
36
|
this.initializedAggregates.forEach(aggregateInst => {
|
|
35
37
|
// let all features wrap up things needed to do before ANY harvest in case there's last minute cross-feature data dependencies
|
|
36
38
|
if (typeof aggregateInst.harvestOpts.beforeUnload === 'function') aggregateInst.harvestOpts.beforeUnload();
|
|
@@ -38,12 +40,7 @@ class Harvester {
|
|
|
38
40
|
this.initializedAggregates.forEach(aggregateInst => this.triggerHarvestFor(aggregateInst, {
|
|
39
41
|
isFinalHarvest: true
|
|
40
42
|
}));
|
|
41
|
-
|
|
42
|
-
}, false);
|
|
43
|
-
}
|
|
44
|
-
startTimer(harvestInterval = this.agentRef.init.harvest.interval) {
|
|
45
|
-
if (this.#started) return;
|
|
46
|
-
this.#started = true;
|
|
43
|
+
});
|
|
47
44
|
const onHarvestInterval = () => {
|
|
48
45
|
this.initializedAggregates.forEach(aggregateInst => this.triggerHarvestFor(aggregateInst));
|
|
49
46
|
setTimeout(onHarvestInterval, harvestInterval * 1000); // repeat in X seconds
|
|
@@ -37,7 +37,7 @@ function send(agentRef, {
|
|
|
37
37
|
endpoint,
|
|
38
38
|
payload,
|
|
39
39
|
localOpts = {},
|
|
40
|
-
submitMethod,
|
|
40
|
+
submitMethod = (0, _submitData.getSubmitMethod)(),
|
|
41
41
|
cbFinished,
|
|
42
42
|
raw,
|
|
43
43
|
featureName,
|
|
@@ -58,13 +58,15 @@ function send(agentRef, {
|
|
|
58
58
|
}
|
|
59
59
|
const protocol = agentRef.init.ssl === false ? 'http' : 'https';
|
|
60
60
|
const perceivedBeacon = agentRef.init.proxy.beacon || agentRef.info.errorBeacon;
|
|
61
|
-
const
|
|
61
|
+
const includeEndpointName = endpoint !== _features.RUM || endpointVersion !== 1;
|
|
62
|
+
const url = raw ? "".concat(protocol, "://").concat(perceivedBeacon, "/").concat(endpoint) : "".concat(protocol, "://").concat(perceivedBeacon).concat(includeEndpointName ? '/' + endpoint : '', "/").concat(endpointVersion, "/").concat(agentRef.info.licenseKey);
|
|
62
63
|
const baseParams = !raw ? baseQueryString(agentRef, qs, endpoint, harvesterObfuscator) : '';
|
|
63
64
|
let payloadParams = (0, _encode.obj)(qs, agentRef.runtime.maxBytes);
|
|
64
65
|
if (baseParams === '' && payloadParams.startsWith('&')) {
|
|
65
66
|
payloadParams = payloadParams.substring(1);
|
|
66
67
|
}
|
|
67
|
-
const
|
|
68
|
+
const queryString = "".concat(baseParams).concat(payloadParams);
|
|
69
|
+
const fullUrl = queryString ? "".concat(url, "?").concat(queryString) : url; // omit the '?' if there are no query params
|
|
68
70
|
const gzip = !!qs?.attributes?.includes('gzip');
|
|
69
71
|
|
|
70
72
|
// all gzipped data is already in the correct format and needs no transformation
|
|
@@ -79,7 +81,7 @@ function send(agentRef, {
|
|
|
79
81
|
const headers = [{
|
|
80
82
|
key: 'content-type',
|
|
81
83
|
value: 'text/plain'
|
|
82
|
-
}];
|
|
84
|
+
}, ...(localOpts.headers || [])];
|
|
83
85
|
|
|
84
86
|
/* Since workers don't support sendBeacon right now, they can only use XHR method.
|
|
85
87
|
Because they still do permit synch XHR, the idea is that at final harvest time (worker is closing),
|
|
@@ -128,6 +130,7 @@ function send(agentRef, {
|
|
|
128
130
|
}
|
|
129
131
|
function trackHarvestMetadata() {
|
|
130
132
|
try {
|
|
133
|
+
if (featureName === _features.CONNECT) return;
|
|
131
134
|
if (featureName === _features.FEATURE_NAMES.jserrors && !body?.err) return;
|
|
132
135
|
const hasReplay = baseParams.includes('hr=1');
|
|
133
136
|
const hasTrace = baseParams.includes('ht=1');
|
|
@@ -213,9 +216,7 @@ function baseQueryString(agentRef, qs, endpoint, harvesterObfuscator) {
|
|
|
213
216
|
const session = agentRef.runtime.session;
|
|
214
217
|
const hr = !!session?.state.sessionReplaySentFirstChunk && session?.state.sessionReplayMode === 1 && endpoint !== _features.JSERRORS;
|
|
215
218
|
const ht = !!session?.state.traceHarvestStarted && session?.state.sessionTraceMode === 1 && ![_features.LOGS, _features.BLOBS].includes(endpoint);
|
|
216
|
-
const qps = ['a=' + agentRef.info.applicationID, (0, _encode.param)('sa', agentRef.info.sa ? '' + agentRef.info.sa : ''), (0, _encode.param)('v', _env.VERSION), transactionNameParam(), (0, _encode.param)('ct', agentRef.runtime.customTransaction), '&rst=' + (0, _now.now)(), '&
|
|
217
|
-
// ck param DEPRECATED - still expected by backend
|
|
218
|
-
'&s=' + (session?.state.value || '0'),
|
|
219
|
+
const qps = ['a=' + agentRef.info.applicationID, (0, _encode.param)('sa', agentRef.info.sa ? '' + agentRef.info.sa : ''), (0, _encode.param)('v', _env.VERSION), transactionNameParam(), (0, _encode.param)('ct', agentRef.runtime.customTransaction), '&rst=' + (0, _now.now)(), '&s=' + (session?.state.value || '0'),
|
|
219
220
|
// the 0 id encaps all untrackable and default traffic
|
|
220
221
|
(0, _encode.param)('ref', ref), (0, _encode.param)('ptid', agentRef.runtime.ptid ? '' + agentRef.runtime.ptid : '')];
|
|
221
222
|
if (hr) qps.push((0, _encode.param)('hr', '1', qs));
|
|
@@ -9,7 +9,7 @@ var _pageVisibility = require("../window/page-visibility");
|
|
|
9
9
|
var _invoke = require("../util/invoke");
|
|
10
10
|
var _runtime = require("../constants/runtime");
|
|
11
11
|
/**
|
|
12
|
-
* Copyright 2020-
|
|
12
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
13
13
|
* SPDX-License-Identifier: Apache-2.0
|
|
14
14
|
*/
|
|
15
15
|
|
|
@@ -59,7 +59,7 @@ class InteractionTimer extends _timer.Timer {
|
|
|
59
59
|
if (state === 'hidden') this.pause();
|
|
60
60
|
// vis change --> visible is treated like a new interaction with the page
|
|
61
61
|
else this.resume();
|
|
62
|
-
}, false,
|
|
62
|
+
}, false, this.abortController?.signal);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
abort() {
|
|
@@ -7,7 +7,7 @@ exports.subscribeToEOL = subscribeToEOL;
|
|
|
7
7
|
var _runtime = require("../constants/runtime");
|
|
8
8
|
var _pageVisibility = require("../window/page-visibility");
|
|
9
9
|
/**
|
|
10
|
-
* Copyright 2020-
|
|
10
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
11
11
|
* SPDX-License-Identifier: Apache-2.0
|
|
12
12
|
*/
|
|
13
13
|
|
|
@@ -29,9 +29,9 @@ if (_runtime.isWorkerScope) {
|
|
|
29
29
|
* This is used, for example, to submit a final harvest and send all remaining data on best-effort.
|
|
30
30
|
* @param {function} cb - func to run before or during the last reliable event or time of an env's life span
|
|
31
31
|
*/
|
|
32
|
-
function subscribeToEOL(cb
|
|
32
|
+
function subscribeToEOL(cb) {
|
|
33
33
|
if (_runtime.isBrowserScope) {
|
|
34
|
-
(0, _pageVisibility.subscribeToVisibilityChange)(cb, true
|
|
34
|
+
(0, _pageVisibility.subscribeToVisibilityChange)(cb, true); // when user switches tab or hides window, esp. mobile scenario
|
|
35
35
|
} else if (_runtime.isWorkerScope) {
|
|
36
36
|
_runtime.globalScope.cleanupTasks.push(cb); // close() should run these tasks before quitting thread
|
|
37
37
|
}
|
|
@@ -73,7 +73,7 @@ var _globalEvent = require("../dispatch/global-event");
|
|
|
73
73
|
* | 50 | Failed to connect. Cannot allow registered API. |
|
|
74
74
|
* | 51 | Container agent is not available to register with. Can not connect. |
|
|
75
75
|
* | 52 | Unexpected problem encountered. There should be at least one app for harvest! |
|
|
76
|
-
* | 53 |
|
|
76
|
+
* | 53 | Failed to parse connect response. |
|
|
77
77
|
* | 54 | An experimental feature is being used. Support can not be offered for issues. |
|
|
78
78
|
* | 55 | Register API has been disabled on the container agent. |
|
|
79
79
|
* | 56 | Could not find a matching entity to store data. |
|
|
@@ -99,6 +99,7 @@ var _globalEvent = require("../dispatch/global-event");
|
|
|
99
99
|
* | 76 | Agent rejected post message, could not match with existing entity. |
|
|
100
100
|
* | 77 | Agent rejected post message, could not validate origin. |
|
|
101
101
|
* | 78 | RegisteredIframeEntity could not determine parent origin and will not register, to avoid trusting messages from any origin. |
|
|
102
|
+
* | 79 | Unable to initialize Connector and/or Harvester. |
|
|
102
103
|
*
|
|
103
104
|
* @param {number} code The warning code to emit, which will be used to link to the warning code documentation
|
|
104
105
|
* @param {*} [secondary] Secondary data to include, usually an extra message, error or object
|
|
@@ -25,7 +25,8 @@ function activateFeatures(flags, agentRef) {
|
|
|
25
25
|
// let any window level subscribers know that the agent is running, per install docs
|
|
26
26
|
(0, _globalEvent.dispatchGlobalEvent)({
|
|
27
27
|
loaded: true,
|
|
28
|
-
drained:
|
|
28
|
+
drained: !agentRef.init.feature_flags.includes('rum_v2'),
|
|
29
|
+
// v2 RUM always call activateFeatures before any group is drained (in waitForFlags)
|
|
29
30
|
type: 'lifecycle',
|
|
30
31
|
name: 'load',
|
|
31
32
|
feature: undefined,
|
|
@@ -7,7 +7,7 @@ exports.subscribeToPageUnload = subscribeToPageUnload;
|
|
|
7
7
|
exports.subscribeToVisibilityChange = subscribeToVisibilityChange;
|
|
8
8
|
var _eventListenerOpts = require("../event-listener/event-listener-opts");
|
|
9
9
|
/**
|
|
10
|
-
* Copyright 2020-
|
|
10
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
11
11
|
* SPDX-License-Identifier: Apache-2.0
|
|
12
12
|
*/
|
|
13
13
|
|
|
@@ -140,9 +140,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
140
140
|
return queryParameters;
|
|
141
141
|
};
|
|
142
142
|
this.events.add(body);
|
|
143
|
-
if (this.agentRef.runtime.harvester.triggerHarvestFor(this
|
|
144
|
-
sendEmptyBody: true
|
|
145
|
-
}).ranSend) this.sentRum = true;
|
|
143
|
+
if (this.agentRef.runtime.harvester.triggerHarvestFor(this).ranSend) this.sentRum = true;
|
|
146
144
|
}
|
|
147
145
|
serializer(eventBuffer) {
|
|
148
146
|
// this is necessary because PVE sends a single item rather than an array; in the case of undefined, this prevents sending [null] as body
|
|
@@ -183,9 +181,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
183
181
|
|
|
184
182
|
if (this.isRetrying && this.retries++ < 1) {
|
|
185
183
|
// Only retry once
|
|
186
|
-
setTimeout(() => this.agentRef.runtime.harvester.triggerHarvestFor(this,
|
|
187
|
-
sendEmptyBody: true
|
|
188
|
-
}), 5000); // Retry sending the RUM event after 5 seconds
|
|
184
|
+
setTimeout(() => this.agentRef.runtime.harvester.triggerHarvestFor(this), 5000); // Retry sending the RUM event after 5 seconds
|
|
189
185
|
return;
|
|
190
186
|
}
|
|
191
187
|
if (status >= 400 || status === 0) {
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.Aggregate = void 0;
|
|
7
|
+
var _runtime = require("../../../common/constants/runtime");
|
|
8
|
+
var _navTiming = require("../../../common/timing/nav-timing");
|
|
9
|
+
var _stringify = require("../../../common/util/stringify");
|
|
10
|
+
var CONSTANTS = _interopRequireWildcard(require("../constants"));
|
|
11
|
+
var _initializedFeatures = require("../aggregate/initialized-features");
|
|
12
|
+
var _aggregateBase = require("../../utils/aggregate-base");
|
|
13
|
+
var _firstContentfulPaint = require("../../../common/vitals/first-contentful-paint");
|
|
14
|
+
var _firstPaint = require("../../../common/vitals/first-paint");
|
|
15
|
+
var _timeToFirstByte = require("../../../common/vitals/time-to-first-byte");
|
|
16
|
+
var _obfuscate = require("../../../common/util/obfuscate");
|
|
17
|
+
var _webdriverDetection = require("../../../common/util/webdriver-detection");
|
|
18
|
+
var _events = require("../../../common/constants/events");
|
|
19
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
20
|
+
/**
|
|
21
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
22
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
class Aggregate extends _aggregateBase.AggregateBase {
|
|
26
|
+
static featureName = CONSTANTS.FEATURE_NAME;
|
|
27
|
+
#timeToFirstByte = 0;
|
|
28
|
+
#firstByteToWindowLoad = 0; // our "frontend" duration
|
|
29
|
+
#firstByteToDomContent = 0; // our "dom processing" duration
|
|
30
|
+
#obfuscator;
|
|
31
|
+
constructor(agentRef) {
|
|
32
|
+
super(agentRef, CONSTANTS.FEATURE_NAME);
|
|
33
|
+
this.#obfuscator = new _obfuscate.Obfuscator(agentRef, _events.EVENT_TYPES.PVE);
|
|
34
|
+
if (_runtime.isBrowserScope) {
|
|
35
|
+
_timeToFirstByte.timeToFirstByte.subscribe(({
|
|
36
|
+
value,
|
|
37
|
+
attrs
|
|
38
|
+
}) => {
|
|
39
|
+
const navEntry = attrs.navigationEntry;
|
|
40
|
+
this.#timeToFirstByte = Math.max(value, this.#timeToFirstByte);
|
|
41
|
+
this.#firstByteToWindowLoad = Math.max(Math.round(navEntry.loadEventEnd - this.#timeToFirstByte), this.#firstByteToWindowLoad); // our "frontend" duration
|
|
42
|
+
this.#firstByteToDomContent = Math.max(Math.round(navEntry.domContentLoadedEventEnd - this.#timeToFirstByte), this.#firstByteToDomContent); // our "dom processing" duration
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
this.waitForFlags([]).then(() => {
|
|
46
|
+
const body = this.#obfuscator.traverseAndObfuscateEvents({
|
|
47
|
+
ja: {
|
|
48
|
+
...agentRef.info.jsAttributes,
|
|
49
|
+
webdriverDetected: _webdriverDetection.webdriverDetected
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
this.events.add(body);
|
|
53
|
+
this.drain();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
queryStringsBuilder() {
|
|
57
|
+
const info = this.agentRef.info;
|
|
58
|
+
const measures = {};
|
|
59
|
+
if (info.queueTime) measures.qt = info.queueTime;
|
|
60
|
+
if (info.applicationTime) measures.ap = info.applicationTime;
|
|
61
|
+
|
|
62
|
+
// These 3 values should've been recorded after load and before this func runs. They are part of the minimum required for PageView events to be created.
|
|
63
|
+
// Following PR #428, which demands that the BA must drive entity indexing, these need to be sent even outside of the main window context where PerformanceTiming
|
|
64
|
+
// or PerformanceNavigationTiming do not exists. Hence, they'll be filled in by 0s instead in, for example, worker threads that still init the PVE module.
|
|
65
|
+
measures.be = this.#timeToFirstByte;
|
|
66
|
+
measures.fe = this.#firstByteToWindowLoad;
|
|
67
|
+
measures.dc = this.#firstByteToDomContent;
|
|
68
|
+
const queryParameters = {
|
|
69
|
+
at: info.atts,
|
|
70
|
+
af: (0, _initializedFeatures.getActivatedFeaturesFlags)(this.agentRef).join(','),
|
|
71
|
+
igp: this.agentRef.runtime.appMetadata.igp,
|
|
72
|
+
...measures,
|
|
73
|
+
fp: _firstPaint.firstPaint.current.value,
|
|
74
|
+
fcp: _firstContentfulPaint.firstContentfulPaint.current.value
|
|
75
|
+
};
|
|
76
|
+
if (_runtime.globalScope.performance) {
|
|
77
|
+
const navTimingEntry = (0, _runtime.getNavigationEntry)();
|
|
78
|
+
if (navTimingEntry) {
|
|
79
|
+
// Navigation Timing level 2 API that replaced PerformanceTiming & PerformanceNavigation
|
|
80
|
+
const perf = {
|
|
81
|
+
timing: (0, _navTiming.addPT)(_runtime.originTime, navTimingEntry, {}),
|
|
82
|
+
navigation: (0, _navTiming.addPN)(navTimingEntry, {})
|
|
83
|
+
};
|
|
84
|
+
queryParameters.perf = (0, _stringify.stringify)(perf);
|
|
85
|
+
} else if (typeof PerformanceTiming !== 'undefined') {
|
|
86
|
+
// Modern Safari iFrames and Safari pre-15 do not support level 2 timing.
|
|
87
|
+
const perf = {
|
|
88
|
+
timing: (0, _navTiming.addPT)(_runtime.originTime, _runtime.globalScope.performance.timing, {}, true),
|
|
89
|
+
navigation: (0, _navTiming.addPN)(_runtime.globalScope.performance.navigation, {})
|
|
90
|
+
};
|
|
91
|
+
queryParameters.perf = (0, _stringify.stringify)(perf);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const {
|
|
95
|
+
correctedOriginTime
|
|
96
|
+
} = this.agentRef.runtime.timeKeeper;
|
|
97
|
+
if (correctedOriginTime) queryParameters.timestamp = correctedOriginTime; // a "PageView" is set to represent the page's (server adjusted) origin time rather than when event is recorded/harvested
|
|
98
|
+
|
|
99
|
+
return queryParameters;
|
|
100
|
+
}
|
|
101
|
+
get harvestEndpointVersion() {
|
|
102
|
+
return 2;
|
|
103
|
+
}
|
|
104
|
+
serializer(eventBuffer) {
|
|
105
|
+
// this is necessary because PVE sends a single item rather than an array; in the case of undefined, this prevents sending [null] as body
|
|
106
|
+
return eventBuffer[0];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
exports.Aggregate = Aggregate;
|
|
@@ -24,7 +24,12 @@ class Instrument extends _instrumentBase.InstrumentBase {
|
|
|
24
24
|
|
|
25
25
|
/** feature specific APIs */
|
|
26
26
|
(0, _setPageViewName.setupSetPageViewNameAPI)(agentRef);
|
|
27
|
-
this.importAggregator(agentRef, () =>
|
|
27
|
+
this.importAggregator(agentRef, () => {
|
|
28
|
+
if (agentRef.init.feature_flags.includes('rum_v2')) {
|
|
29
|
+
return Promise.resolve().then(() => _interopRequireWildcard(require(/* webpackChunkName: "page_view_event-aggregate" */'../aggregate_v2')));
|
|
30
|
+
}
|
|
31
|
+
return Promise.resolve().then(() => _interopRequireWildcard(require(/* webpackChunkName: "page_view_event-aggregate" */'../aggregate')));
|
|
32
|
+
});
|
|
28
33
|
}
|
|
29
34
|
setupInspectionEvents() {
|
|
30
35
|
const dispatch = (evt, name) => {
|
|
@@ -72,7 +72,11 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
72
72
|
} = _cumulativeLayoutShift.cumulativeLayoutShift.current;
|
|
73
73
|
if (value === undefined) return;
|
|
74
74
|
this.addTiming(name, value * 1000, attrs);
|
|
75
|
-
}, true, true); // CLS node should only
|
|
75
|
+
}, true, true); // CLS node should only report on vis change rather than on every change.
|
|
76
|
+
/* NOTE: the capture=true is required -- empirically verified against a real browser (Chrome) for CLS
|
|
77
|
+
timing node to reliably be in the same final harvest as the rest, alongside Harvester#startTimer's
|
|
78
|
+
queueMicrotask deferral of the EOL harvest itself. Dropping this flag caused CLS to be split into its own,
|
|
79
|
+
separate, later harvest instead of going out together with pageHide/INP. */
|
|
76
80
|
|
|
77
81
|
this.drain();
|
|
78
82
|
});
|
|
@@ -44,6 +44,10 @@ class Instrument extends _instrumentBase.InstrumentBase {
|
|
|
44
44
|
recorder.startRecording(_constants2.TRIGGERS.PRELOAD, session?.sessionReplayMode);
|
|
45
45
|
}); // could handle specific fail-state behaviors with a .catch block here
|
|
46
46
|
}
|
|
47
|
+
|
|
48
|
+
// If the aggregate never successfully loads (missing session prerequisite, or the aggregate module itself
|
|
49
|
+
// fails to import/construct), any preload recording that already started needs to be stopped.
|
|
50
|
+
this.abortHandler = this.#abort;
|
|
47
51
|
this.importAggregator(this.agentRef, () => Promise.resolve().then(() => _interopRequireWildcard(require(/* webpackChunkName: "session_replay-aggregate" */'../aggregate'))), this);
|
|
48
52
|
|
|
49
53
|
/** If the recorder is running, we can pass error events on to the agg to help it switch to full mode later */
|
|
@@ -108,6 +112,11 @@ class Instrument extends _instrumentBase.InstrumentBase {
|
|
|
108
112
|
}); // could handle specific fail-state behaviors with a .catch block here
|
|
109
113
|
}
|
|
110
114
|
}
|
|
115
|
+
|
|
116
|
+
/** Stops any preload recording that already started if the aggregate never ends up successfully loading. */
|
|
117
|
+
#abort() {
|
|
118
|
+
this.recorder?.stopRecording();
|
|
119
|
+
}
|
|
111
120
|
}
|
|
112
121
|
exports.Instrument = Instrument;
|
|
113
122
|
const SessionReplay = exports.SessionReplay = Instrument;
|
|
@@ -216,7 +216,8 @@ class Recorder {
|
|
|
216
216
|
|
|
217
217
|
// We are making an effort to try to keep payloads manageable for unloading. If they reach the unload limit before their interval,
|
|
218
218
|
// it will send immediately. This often happens on the first snapshot, which can be significantly larger than the other payloads.
|
|
219
|
-
|
|
219
|
+
// (the aggregate may not exist yet/at all if its own bootstrap hasn't finished or failed -- nothing to harvest for in that case.)
|
|
220
|
+
if ((this.events.hasSnapshot && this.events.hasMeta || payloadSize > _agentConstants.IDEAL_PAYLOAD_SIZE) && !this.isErrorMode && this.srInstrument.featAggregate) {
|
|
220
221
|
// if we've made it to the ideal size of ~16kb before the interval timer, we should send early.
|
|
221
222
|
this.agentRef.runtime.harvester.triggerHarvestFor(this.srInstrument.featAggregate);
|
|
222
223
|
}
|
|
@@ -7,7 +7,6 @@ exports.AggregateBase = void 0;
|
|
|
7
7
|
var _featureBase = require("./feature-base");
|
|
8
8
|
var _drain = require("../../common/drain/drain");
|
|
9
9
|
var _features = require("../../loaders/features/features");
|
|
10
|
-
var _harvester = require("../../common/harvest/harvester");
|
|
11
10
|
var _eventBuffer = require("./event-buffer");
|
|
12
11
|
var _handle = require("../../common/event-emitter/handle");
|
|
13
12
|
var _constants = require("../metrics/constants");
|
|
@@ -27,7 +26,6 @@ class AggregateBase extends _featureBase.FeatureBase {
|
|
|
27
26
|
*/
|
|
28
27
|
constructor(agentRef, featureName) {
|
|
29
28
|
super(agentRef, featureName);
|
|
30
|
-
this.doOnceForAllAggregate(agentRef);
|
|
31
29
|
|
|
32
30
|
/** @type {Boolean} indicates if custom attributes are combined in each event payload for size estimation purposes. this is set to true in derived classes that need to evaluate custom attributes separately from the event payload */
|
|
33
31
|
this.customAttributesAreSeparate = false;
|
|
@@ -179,16 +177,6 @@ class AggregateBase extends _featureBase.FeatureBase {
|
|
|
179
177
|
this.events.clearSave(this.harvestOpts);
|
|
180
178
|
}
|
|
181
179
|
|
|
182
|
-
/**
|
|
183
|
-
* These are actions related to shared resources that should be initialized once by whichever feature Aggregate subclass loads first.
|
|
184
|
-
* This method should run after checkConfiguration, which may reset the agent's info/runtime object that is used here.
|
|
185
|
-
*/
|
|
186
|
-
doOnceForAllAggregate(agentRef) {
|
|
187
|
-
// Note: obfuscator is now created per-feature for their specific event types
|
|
188
|
-
|
|
189
|
-
if (!agentRef.runtime.harvester) agentRef.runtime.harvester = new _harvester.Harvester(agentRef);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
180
|
/**
|
|
193
181
|
* Report a supportability metric
|
|
194
182
|
* @param {*} metricName The tag of the name matching the Angler aggregation tag
|