@newrelic/browser-agent 1.302.0-rc.6 → 1.302.0-rc.8
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/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 +13 -9
- package/dist/cjs/common/harvest/types.js +0 -1
- package/dist/cjs/common/session/session-entity.js +4 -2
- package/dist/cjs/features/page_view_event/aggregate/index.js +36 -22
- package/dist/cjs/features/page_view_event/instrument/index.js +0 -4
- package/dist/cjs/features/utils/agent-session.js +13 -0
- package/dist/cjs/features/utils/instrument-base.js +7 -8
- package/dist/cjs/loaders/agent.js +2 -0
- package/dist/cjs/loaders/api/consent.js +24 -0
- package/dist/cjs/loaders/api/constants.js +3 -2
- package/dist/cjs/loaders/api-base.js +10 -0
- 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 +13 -9
- package/dist/esm/common/harvest/types.js +0 -1
- package/dist/esm/common/session/session-entity.js +4 -2
- package/dist/esm/features/page_view_event/aggregate/index.js +36 -22
- package/dist/esm/features/page_view_event/instrument/index.js +0 -4
- package/dist/esm/features/utils/agent-session.js +13 -0
- package/dist/esm/features/utils/instrument-base.js +7 -8
- package/dist/esm/loaders/agent.js +2 -0
- package/dist/esm/loaders/api/consent.js +17 -0
- package/dist/esm/loaders/api/constants.js +2 -1
- package/dist/esm/loaders/api-base.js +11 -1
- 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.map +1 -1
- package/dist/types/common/harvest/types.d.ts +0 -2
- 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/features/page_view_event/aggregate/index.d.ts +22 -3
- package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/page_view_event/instrument/index.d.ts.map +1 -1
- package/dist/types/features/utils/agent-session.d.ts.map +1 -1
- package/dist/types/features/utils/instrument-base.d.ts +1 -0
- 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/consent.d.ts +2 -0
- package/dist/types/loaders/api/consent.d.ts.map +1 -0
- package/dist/types/loaders/api/constants.d.ts +1 -0
- package/dist/types/loaders/api/constants.d.ts.map +1 -1
- package/dist/types/loaders/api-base.d.ts +7 -0
- package/dist/types/loaders/api-base.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/common/config/init-types.js +2 -0
- package/src/common/config/init.js +1 -0
- package/src/common/harvest/harvester.js +11 -8
- package/src/common/harvest/types.js +0 -1
- package/src/common/session/session-entity.js +6 -2
- package/src/features/page_view_event/aggregate/index.js +29 -15
- package/src/features/page_view_event/instrument/index.js +0 -4
- package/src/features/utils/agent-session.js +12 -0
- package/src/features/utils/instrument-base.js +7 -9
- package/src/loaders/agent.js +2 -0
- package/src/loaders/api/consent.js +18 -0
- package/src/loaders/api/constants.js +1 -0
- package/src/loaders/api-base.js +11 -1
|
@@ -89,6 +89,8 @@ exports.default = void 0;
|
|
|
89
89
|
* @property {boolean} [spa.enabled] - Turn on/off the single page application feature (on by default). NOTE: the SPA feature is deprecated and under removal procedure.
|
|
90
90
|
* @property {boolean} [spa.autoStart] - If true, the agent will automatically start the single page application feature. Otherwise, it will be in a deferred state until the `start` API method is called.
|
|
91
91
|
* @property {boolean} [ssl] - If explicitly false, the agent will use HTTP instead of HTTPS. This setting should NOT be used.
|
|
92
|
+
* @property {Object} [browser_consent_mode]
|
|
93
|
+
* @property {boolean} [browser_consent_mode.enabled] - If true, the agent will use consent mode for whether to allow or disallow data harvest.
|
|
92
94
|
* @property {Object} [user_actions]
|
|
93
95
|
* @property {boolean} [user_actions.enabled] - Must be true to allow UserAction events to be captured.
|
|
94
96
|
* @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.
|
|
@@ -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.302.0-rc.
|
|
20
|
+
const VERSION = exports.VERSION = "1.302.0-rc.8";
|
|
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.302.0-rc.
|
|
20
|
+
const VERSION = exports.VERSION = "1.302.0-rc.8";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Exposes the build type of the agent
|
|
@@ -25,8 +25,10 @@ var _globalEvent = require("../dispatch/global-event");
|
|
|
25
25
|
* SPDX-License-Identifier: Apache-2.0
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
-
const
|
|
29
|
-
const
|
|
28
|
+
const RETRY = 'Harvester/Retry/';
|
|
29
|
+
const RETRY_ATTEMPTED = RETRY + 'Attempted/';
|
|
30
|
+
const RETRY_FAILED = RETRY + 'Failed/';
|
|
31
|
+
const RETRY_SUCCEEDED = RETRY + 'Succeeded/';
|
|
30
32
|
class Harvester {
|
|
31
33
|
#started = false;
|
|
32
34
|
initializedAggregates = [];
|
|
@@ -67,11 +69,11 @@ class Harvester {
|
|
|
67
69
|
endpointVersion: aggregateInst.harvestEndpointVersion || 1
|
|
68
70
|
};
|
|
69
71
|
if (aggregateInst.blocked) return output;
|
|
72
|
+
if (this.agentRef.init?.browser_consent_mode?.enabled && !this.agentRef.runtime?.session?.state?.consent) return output;
|
|
70
73
|
const submitMethod = (0, _submitData.getSubmitMethod)(localOpts);
|
|
71
74
|
if (!submitMethod) return output;
|
|
72
75
|
const shouldRetryOnFail = !localOpts.isFinalHarvest && submitMethod === _submitData.xhr; // always retry all features harvests except for final
|
|
73
|
-
output.payload =
|
|
74
|
-
|
|
76
|
+
output.payload = aggregateInst.makeHarvestPayload(shouldRetryOnFail, localOpts);
|
|
75
77
|
if (!output.payload) return output;
|
|
76
78
|
send(this.agentRef, {
|
|
77
79
|
endpoint: _features.FEATURE_TO_ENDPOINT[aggregateInst.featureName],
|
|
@@ -93,7 +95,9 @@ class Harvester {
|
|
|
93
95
|
function cbFinished(result) {
|
|
94
96
|
if (aggregateInst.harvestOpts.prevAttemptCode) {
|
|
95
97
|
// this means we just retried a harvest that last failed
|
|
96
|
-
(0, _handle.handle)(_constants.SUPPORTABILITY_METRIC_CHANNEL, [
|
|
98
|
+
const reportSM = message => (0, _handle.handle)(_constants.SUPPORTABILITY_METRIC_CHANNEL, [message], undefined, _features.FEATURE_NAMES.metrics, aggregateInst.ee);
|
|
99
|
+
reportSM(RETRY_ATTEMPTED + aggregateInst.featureName);
|
|
100
|
+
reportSM((result.retry ? RETRY_FAILED : RETRY_SUCCEEDED) + aggregateInst.harvestOpts.prevAttemptCode);
|
|
97
101
|
delete aggregateInst.harvestOpts.prevAttemptCode; // always reset last observation so we don't falsely report again next harvest
|
|
98
102
|
// In case this re-attempt failed again, that'll be handled (re-marked again) next.
|
|
99
103
|
}
|
|
@@ -183,9 +187,9 @@ function send(agentRef, {
|
|
|
183
187
|
status: this.status,
|
|
184
188
|
retry: shouldRetry(this.status),
|
|
185
189
|
fullUrl,
|
|
186
|
-
xhr: this
|
|
190
|
+
xhr: this,
|
|
191
|
+
responseText: this.responseText
|
|
187
192
|
};
|
|
188
|
-
if (localOpts.needResponse) cbResult.responseText = this.responseText;
|
|
189
193
|
cbFinished(cbResult);
|
|
190
194
|
|
|
191
195
|
/** temporary audit of consistency of harvest metadata flags */
|
|
@@ -199,9 +203,9 @@ function send(agentRef, {
|
|
|
199
203
|
status,
|
|
200
204
|
retry: shouldRetry(status),
|
|
201
205
|
fullUrl,
|
|
202
|
-
fetchResponse: response
|
|
206
|
+
fetchResponse: response,
|
|
207
|
+
responseText: await response.text()
|
|
203
208
|
};
|
|
204
|
-
if (localOpts.needResponse) cbResult.responseText = await response.text();
|
|
205
209
|
cbFinished(cbResult);
|
|
206
210
|
/** temporary audit of consistency of harvest metadata flags */
|
|
207
211
|
if (!shouldRetry(status)) trackHarvestMetadata();
|
|
@@ -30,7 +30,6 @@ exports.unused = void 0;
|
|
|
30
30
|
* @property {HarvestEndpointIdentifier} endpoint The endpoint to use (jserrors, events, resources etc.)
|
|
31
31
|
* @property {HarvestPayload} payload Object representing payload.
|
|
32
32
|
* @property {object} localOpts Additional options for sending data
|
|
33
|
-
* @property {boolean} localOpts.needResponse Specify whether the caller expects a response data.
|
|
34
33
|
* @property {boolean} localOpts.isFinalHarvest Specify whether the call is a final harvest during page unload.
|
|
35
34
|
* @property {boolean} localOpts.sendEmptyBody Specify whether the call should be made even if the body is empty. Useful for rum calls.
|
|
36
35
|
* @property {boolean} localOpts.forceNoRetry Don't save the buffered data in the case of a need to retry the transmission.
|
|
@@ -39,7 +39,8 @@ const model = {
|
|
|
39
39
|
serverTimeDiff: null,
|
|
40
40
|
// set by TimeKeeper; "undefined" value will not be stringified and stored but "null" will
|
|
41
41
|
custom: {},
|
|
42
|
-
numOfResets: 0
|
|
42
|
+
numOfResets: 0,
|
|
43
|
+
consent: false // set by consent() API call
|
|
43
44
|
};
|
|
44
45
|
class SessionEntity {
|
|
45
46
|
/**
|
|
@@ -90,7 +91,8 @@ class SessionEntity {
|
|
|
90
91
|
}) {
|
|
91
92
|
/** Ensure that certain properties are preserved across a reset if already set */
|
|
92
93
|
const persistentAttributes = {
|
|
93
|
-
serverTimeDiff: this.state.serverTimeDiff || model.serverTimeDiff
|
|
94
|
+
serverTimeDiff: this.state.serverTimeDiff || model.serverTimeDiff,
|
|
95
|
+
consent: this.state.consent || model.consent
|
|
94
96
|
};
|
|
95
97
|
this.state = {};
|
|
96
98
|
this.sync({
|
|
@@ -32,10 +32,12 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
32
32
|
static featureName = CONSTANTS.FEATURE_NAME;
|
|
33
33
|
constructor(agentRef) {
|
|
34
34
|
super(agentRef, CONSTANTS.FEATURE_NAME);
|
|
35
|
+
this.sentRum = false; // flag to facilitate calling sendRum() once externally (by the consent API in agent-session.js)
|
|
36
|
+
|
|
35
37
|
this.timeToFirstByte = 0;
|
|
36
38
|
this.firstByteToWindowLoad = 0; // our "frontend" duration
|
|
37
39
|
this.firstByteToDomContent = 0; // our "dom processing" duration
|
|
38
|
-
|
|
40
|
+
this.retries = 0;
|
|
39
41
|
if (!(0, _info.isValid)(agentRef.info)) {
|
|
40
42
|
this.ee.abort();
|
|
41
43
|
return (0, _console.warn)(43);
|
|
@@ -63,12 +65,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
63
65
|
*
|
|
64
66
|
* @param {Function} cb A function to run once the RUM call has finished - Defaults to activateFeatures
|
|
65
67
|
* @param {*} customAttributes custom attributes to attach to the RUM call - Defaults to info.js
|
|
66
|
-
* @param {*} target The target to harvest to
|
|
67
68
|
*/
|
|
68
|
-
sendRum(customAttributes = this.agentRef.info.jsAttributes
|
|
69
|
-
licenseKey: this.agentRef.info.licenseKey,
|
|
70
|
-
applicationID: this.agentRef.info.applicationID
|
|
71
|
-
}) {
|
|
69
|
+
sendRum(customAttributes = this.agentRef.info.jsAttributes) {
|
|
72
70
|
const info = this.agentRef.info;
|
|
73
71
|
const measures = {};
|
|
74
72
|
if (info.queueTime) measures.qt = info.queueTime;
|
|
@@ -119,27 +117,30 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
119
117
|
}
|
|
120
118
|
queryParameters.fp = _firstPaint.firstPaint.current.value;
|
|
121
119
|
queryParameters.fcp = _firstContentfulPaint.firstContentfulPaint.current.value;
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
},
|
|
135
|
-
needResponse: true,
|
|
120
|
+
this.queryStringsBuilder = () => {
|
|
121
|
+
// this will be called by AggregateBase.makeHarvestPayload every time harvest is triggered to be qs
|
|
122
|
+
this.rumStartTime = (0, _now.now)(); // this should be reset at the beginning of each RUM call for proper timeKeeper calculation in coordination with postHarvestCleanup
|
|
123
|
+
const timeKeeper = this.agentRef.runtime.timeKeeper;
|
|
124
|
+
if (timeKeeper?.ready) {
|
|
125
|
+
queryParameters.timestamp = Math.floor(timeKeeper.correctRelativeTimestamp(this.rumStartTime));
|
|
126
|
+
}
|
|
127
|
+
return queryParameters;
|
|
128
|
+
};
|
|
129
|
+
this.events.add(body);
|
|
130
|
+
if (this.agentRef.runtime.harvester.triggerHarvestFor(this, {
|
|
136
131
|
sendEmptyBody: true
|
|
137
|
-
});
|
|
132
|
+
}).ranSend) this.sentRum = true;
|
|
133
|
+
}
|
|
134
|
+
serializer(eventBuffer) {
|
|
135
|
+
// this is necessary because PVE sends a single item rather than an array; in the case of undefined, this prevents sending [null] as body
|
|
136
|
+
return eventBuffer[0];
|
|
138
137
|
}
|
|
139
138
|
postHarvestCleanup({
|
|
139
|
+
sent,
|
|
140
140
|
status,
|
|
141
141
|
responseText,
|
|
142
|
-
xhr
|
|
142
|
+
xhr,
|
|
143
|
+
retry
|
|
143
144
|
}) {
|
|
144
145
|
const rumEndTime = (0, _now.now)();
|
|
145
146
|
let app, flags;
|
|
@@ -152,8 +153,20 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
152
153
|
// wont set entity stuff here, if main agent will later abort, if registered agent, nothing will happen
|
|
153
154
|
(0, _console.warn)(53, error);
|
|
154
155
|
}
|
|
156
|
+
super.postHarvestCleanup({
|
|
157
|
+
sent,
|
|
158
|
+
retry
|
|
159
|
+
}); // this will set isRetrying & re-buffer the body if request is to be retried
|
|
160
|
+
if (this.isRetrying && this.retries++ < 1) {
|
|
161
|
+
// Only retry once
|
|
162
|
+
setTimeout(() => this.agentRef.runtime.harvester.triggerHarvestFor(this, {
|
|
163
|
+
sendEmptyBody: true
|
|
164
|
+
}), 5000); // Retry sending the RUM event after 5 seconds
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
155
167
|
if (status >= 400 || status === 0) {
|
|
156
168
|
(0, _console.warn)(18, status);
|
|
169
|
+
this.blocked = true;
|
|
157
170
|
|
|
158
171
|
// Get estimated payload size of our backlog
|
|
159
172
|
const textEncoder = new TextEncoder();
|
|
@@ -217,6 +230,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
217
230
|
}
|
|
218
231
|
} catch (error) {
|
|
219
232
|
this.ee.abort();
|
|
233
|
+
this.blocked = true;
|
|
220
234
|
(0, _console.warn)(17, error);
|
|
221
235
|
return;
|
|
222
236
|
}
|
|
@@ -4,7 +4,6 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.PageViewEvent = exports.Instrument = void 0;
|
|
7
|
-
var _handle = require("../../../common/event-emitter/handle");
|
|
8
7
|
var _setPageViewName = require("../../../loaders/api/setPageViewName");
|
|
9
8
|
var _instrumentBase = require("../../utils/instrument-base");
|
|
10
9
|
var CONSTANTS = _interopRequireWildcard(require("../constants"));
|
|
@@ -25,9 +24,6 @@ class Instrument extends _instrumentBase.InstrumentBase {
|
|
|
25
24
|
|
|
26
25
|
/** feature specific APIs */
|
|
27
26
|
(0, _setPageViewName.setupSetPageViewNameAPI)(agentRef);
|
|
28
|
-
|
|
29
|
-
/** messages from the register API that can trigger a new RUM call */
|
|
30
|
-
this.ee.on('api-send-rum', (attrs, target) => (0, _handle.handle)('send-rum', [attrs, target], undefined, this.featureName, this.ee));
|
|
31
27
|
this.importAggregator(agentRef, () => Promise.resolve().then(() => _interopRequireWildcard(require(/* webpackChunkName: "page_view_event-aggregate" */'../aggregate'))));
|
|
32
28
|
}
|
|
33
29
|
setupInspectionEvents(agentIdentifier) {
|
|
@@ -59,6 +59,19 @@ function setupAgentSession(agentRef) {
|
|
|
59
59
|
(0, _registerHandler.registerHandler)('api-setUserId', (time, key, value) => {
|
|
60
60
|
agentRef.runtime.session.syncCustomAttribute(key, value);
|
|
61
61
|
}, 'session', sharedEE);
|
|
62
|
+
(0, _registerHandler.registerHandler)('api-consent', accept => {
|
|
63
|
+
agentRef.runtime.session.write({
|
|
64
|
+
consent: accept === undefined ? true : accept
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// call sendRum if it wasn't called yet
|
|
68
|
+
agentRef.features.page_view_event.onAggregateImported.then(loaded => {
|
|
69
|
+
const pveAgg = agentRef.features.page_view_event.featAggregate;
|
|
70
|
+
if (loaded && !pveAgg.sentRum) {
|
|
71
|
+
pveAgg.sendRum();
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}, 'session', sharedEE);
|
|
62
75
|
(0, _drain.drain)(agentRef.agentIdentifier, 'session');
|
|
63
76
|
return agentRef.runtime.session;
|
|
64
77
|
}
|
|
@@ -50,7 +50,10 @@ class InstrumentBase extends _featureBase.FeatureBase {
|
|
|
50
50
|
* @type {Promise} Assigned immediately after @see importAggregator runs. Serves as a signal for when the inner async fn finishes execution. Useful for features to await
|
|
51
51
|
* one another if there are inter-features dependencies.
|
|
52
52
|
*/
|
|
53
|
-
this.
|
|
53
|
+
this.loadedSuccessfully = undefined;
|
|
54
|
+
this.onAggregateImported = new Promise(resolve => {
|
|
55
|
+
this.loadedSuccessfully = resolve;
|
|
56
|
+
});
|
|
54
57
|
|
|
55
58
|
/**
|
|
56
59
|
* used in conjunction with newrelic.start() to defer harvesting in features
|
|
@@ -82,10 +85,6 @@ class InstrumentBase extends _featureBase.FeatureBase {
|
|
|
82
85
|
*/
|
|
83
86
|
importAggregator(agentRef, fetchAggregator, argsObjFromInstrument = {}) {
|
|
84
87
|
if (this.featAggregate) return;
|
|
85
|
-
let loadedSuccessfully;
|
|
86
|
-
this.onAggregateImported = new Promise(resolve => {
|
|
87
|
-
loadedSuccessfully = resolve;
|
|
88
|
-
});
|
|
89
88
|
const importLater = async () => {
|
|
90
89
|
// wait for the deferred promise to resolve before proceeding
|
|
91
90
|
// this will resolve immediately if the feature is auto-started,
|
|
@@ -113,7 +112,7 @@ class InstrumentBase extends _featureBase.FeatureBase {
|
|
|
113
112
|
try {
|
|
114
113
|
if (!this.#shouldImportAgg(this.featureName, session, agentRef.init)) {
|
|
115
114
|
(0, _drain.drain)(this.agentIdentifier, this.featureName);
|
|
116
|
-
loadedSuccessfully(false); // aggregate module isn't loaded at all
|
|
115
|
+
this.loadedSuccessfully(false); // aggregate module isn't loaded at all
|
|
117
116
|
return;
|
|
118
117
|
}
|
|
119
118
|
const {
|
|
@@ -121,13 +120,13 @@ class InstrumentBase extends _featureBase.FeatureBase {
|
|
|
121
120
|
} = await fetchAggregator();
|
|
122
121
|
this.featAggregate = new Aggregate(agentRef, argsObjFromInstrument);
|
|
123
122
|
agentRef.runtime.harvester.initializedAggregates.push(this.featAggregate); // "subscribe" the feature to future harvest intervals (PVE will start the timer)
|
|
124
|
-
loadedSuccessfully(true);
|
|
123
|
+
this.loadedSuccessfully(true);
|
|
125
124
|
} catch (e) {
|
|
126
125
|
(0, _console.warn)(34, e);
|
|
127
126
|
this.abortHandler?.(); // undo any important alterations made to the page
|
|
128
127
|
// not supported yet but nice to do: "abort" this agent's EE for this feature specifically
|
|
129
128
|
(0, _drain.drain)(this.agentIdentifier, this.featureName, true);
|
|
130
|
-
loadedSuccessfully(false);
|
|
129
|
+
this.loadedSuccessfully(false);
|
|
131
130
|
if (this.ee) this.ee.abort();
|
|
132
131
|
}
|
|
133
132
|
};
|
|
@@ -19,6 +19,7 @@ var _setCustomAttribute = require("./api/setCustomAttribute");
|
|
|
19
19
|
var _setUserId = require("./api/setUserId");
|
|
20
20
|
var _setApplicationVersion = require("./api/setApplicationVersion");
|
|
21
21
|
var _start = require("./api/start");
|
|
22
|
+
var _consent = require("./api/consent");
|
|
22
23
|
/**
|
|
23
24
|
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
24
25
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -77,6 +78,7 @@ class Agent extends _agentBase.AgentBase {
|
|
|
77
78
|
(0, _setUserId.setupSetUserIdAPI)(this);
|
|
78
79
|
(0, _setApplicationVersion.setupSetApplicationVersionAPI)(this);
|
|
79
80
|
(0, _start.setupStartAPI)(this);
|
|
81
|
+
(0, _consent.setupConsentAPI)(this);
|
|
80
82
|
this.run();
|
|
81
83
|
}
|
|
82
84
|
get config() {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.setupConsentAPI = setupConsentAPI;
|
|
7
|
+
var _constants = require("./constants");
|
|
8
|
+
var _sharedHandlers = require("./sharedHandlers");
|
|
9
|
+
var _handle = require("../../common/event-emitter/handle");
|
|
10
|
+
var _console = require("../../common/util/console");
|
|
11
|
+
/**
|
|
12
|
+
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
13
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
function setupConsentAPI(agent) {
|
|
17
|
+
(0, _sharedHandlers.setupAPI)(_constants.CONSENT, function (accept) {
|
|
18
|
+
if (accept !== undefined && typeof accept !== 'boolean') {
|
|
19
|
+
(0, _console.warn)(65, typeof accept);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
(0, _handle.handle)(_constants.prefix + _constants.CONSENT, [accept], undefined, 'session', agent.ee);
|
|
23
|
+
}, agent);
|
|
24
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.spaPrefix = exports.prefix = exports.WRAP_LOGGER = exports.START = exports.SET_USER_ID = exports.SET_PAGE_VIEW_NAME = exports.SET_ERROR_HANDLER = exports.SET_CUSTOM_ATTRIBUTE = exports.SET_CURRENT_ROUTE_NAME = exports.SET_APPLICATION_VERSION = exports.REGISTER = exports.RECORD_REPLAY = exports.RECORD_CUSTOM_EVENT = exports.PAUSE_REPLAY = exports.NOTICE_ERROR = exports.MEASURE = exports.LOG = exports.INTERACTION = exports.FINISHED = exports.ADD_TO_TRACE = exports.ADD_RELEASE = exports.ADD_PAGE_ACTION = void 0;
|
|
6
|
+
exports.spaPrefix = exports.prefix = exports.WRAP_LOGGER = exports.START = exports.SET_USER_ID = exports.SET_PAGE_VIEW_NAME = exports.SET_ERROR_HANDLER = exports.SET_CUSTOM_ATTRIBUTE = exports.SET_CURRENT_ROUTE_NAME = exports.SET_APPLICATION_VERSION = exports.REGISTER = exports.RECORD_REPLAY = exports.RECORD_CUSTOM_EVENT = exports.PAUSE_REPLAY = exports.NOTICE_ERROR = exports.MEASURE = exports.LOG = exports.INTERACTION = exports.FINISHED = exports.CONSENT = exports.ADD_TO_TRACE = exports.ADD_RELEASE = exports.ADD_PAGE_ACTION = void 0;
|
|
7
7
|
/**
|
|
8
8
|
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
9
9
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -29,4 +29,5 @@ const SET_PAGE_VIEW_NAME = exports.SET_PAGE_VIEW_NAME = 'setPageViewName';
|
|
|
29
29
|
const SET_USER_ID = exports.SET_USER_ID = 'setUserId';
|
|
30
30
|
const START = exports.START = 'start';
|
|
31
31
|
const WRAP_LOGGER = exports.WRAP_LOGGER = 'wrapLogger';
|
|
32
|
-
const MEASURE = exports.MEASURE = 'measure';
|
|
32
|
+
const MEASURE = exports.MEASURE = 'measure';
|
|
33
|
+
const CONSENT = exports.CONSENT = 'consent';
|
|
@@ -230,5 +230,15 @@ class ApiBase {
|
|
|
230
230
|
measure(name, options) {
|
|
231
231
|
return this.#callMethod(_constants.MEASURE, name, options);
|
|
232
232
|
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Accepts or rejects consent when the agent is configured to require consent before harvesting.
|
|
236
|
+
* The consent state is stored in session storage inside the NRBA_SESSION object.
|
|
237
|
+
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/consent/}
|
|
238
|
+
* @param {boolean?} accept Whether to accept or reject consent. Defaults to true (accept) if left undefined.
|
|
239
|
+
*/
|
|
240
|
+
consent(accept) {
|
|
241
|
+
return this.#callMethod(_constants.CONSENT, accept);
|
|
242
|
+
}
|
|
233
243
|
}
|
|
234
244
|
exports.ApiBase = ApiBase;
|
|
@@ -84,6 +84,8 @@
|
|
|
84
84
|
* @property {boolean} [spa.enabled] - Turn on/off the single page application feature (on by default). NOTE: the SPA feature is deprecated and under removal procedure.
|
|
85
85
|
* @property {boolean} [spa.autoStart] - If true, the agent will automatically start the single page application feature. Otherwise, it will be in a deferred state until the `start` API method is called.
|
|
86
86
|
* @property {boolean} [ssl] - If explicitly false, the agent will use HTTP instead of HTTPS. This setting should NOT be used.
|
|
87
|
+
* @property {Object} [browser_consent_mode]
|
|
88
|
+
* @property {boolean} [browser_consent_mode.enabled] - If true, the agent will use consent mode for whether to allow or disallow data harvest.
|
|
87
89
|
* @property {Object} [user_actions]
|
|
88
90
|
* @property {boolean} [user_actions.enabled] - Must be true to allow UserAction events to be captured.
|
|
89
91
|
* @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.
|
|
@@ -17,8 +17,10 @@ import { stringify } from '../util/stringify';
|
|
|
17
17
|
import { getSubmitMethod, xhr as xhrMethod, xhrFetch as fetchMethod } from '../util/submit-data';
|
|
18
18
|
import { activatedFeatures } from '../util/feature-flags';
|
|
19
19
|
import { dispatchGlobalEvent } from '../dispatch/global-event';
|
|
20
|
-
const
|
|
21
|
-
const
|
|
20
|
+
const RETRY = 'Harvester/Retry/';
|
|
21
|
+
const RETRY_ATTEMPTED = RETRY + 'Attempted/';
|
|
22
|
+
const RETRY_FAILED = RETRY + 'Failed/';
|
|
23
|
+
const RETRY_SUCCEEDED = RETRY + 'Succeeded/';
|
|
22
24
|
export class Harvester {
|
|
23
25
|
#started = false;
|
|
24
26
|
initializedAggregates = [];
|
|
@@ -59,11 +61,11 @@ export class Harvester {
|
|
|
59
61
|
endpointVersion: aggregateInst.harvestEndpointVersion || 1
|
|
60
62
|
};
|
|
61
63
|
if (aggregateInst.blocked) return output;
|
|
64
|
+
if (this.agentRef.init?.browser_consent_mode?.enabled && !this.agentRef.runtime?.session?.state?.consent) return output;
|
|
62
65
|
const submitMethod = getSubmitMethod(localOpts);
|
|
63
66
|
if (!submitMethod) return output;
|
|
64
67
|
const shouldRetryOnFail = !localOpts.isFinalHarvest && submitMethod === xhrMethod; // always retry all features harvests except for final
|
|
65
|
-
output.payload =
|
|
66
|
-
|
|
68
|
+
output.payload = aggregateInst.makeHarvestPayload(shouldRetryOnFail, localOpts);
|
|
67
69
|
if (!output.payload) return output;
|
|
68
70
|
send(this.agentRef, {
|
|
69
71
|
endpoint: FEATURE_TO_ENDPOINT[aggregateInst.featureName],
|
|
@@ -85,7 +87,9 @@ export class Harvester {
|
|
|
85
87
|
function cbFinished(result) {
|
|
86
88
|
if (aggregateInst.harvestOpts.prevAttemptCode) {
|
|
87
89
|
// this means we just retried a harvest that last failed
|
|
88
|
-
handle(SUPPORTABILITY_METRIC_CHANNEL, [
|
|
90
|
+
const reportSM = message => handle(SUPPORTABILITY_METRIC_CHANNEL, [message], undefined, FEATURE_NAMES.metrics, aggregateInst.ee);
|
|
91
|
+
reportSM(RETRY_ATTEMPTED + aggregateInst.featureName);
|
|
92
|
+
reportSM((result.retry ? RETRY_FAILED : RETRY_SUCCEEDED) + aggregateInst.harvestOpts.prevAttemptCode);
|
|
89
93
|
delete aggregateInst.harvestOpts.prevAttemptCode; // always reset last observation so we don't falsely report again next harvest
|
|
90
94
|
// In case this re-attempt failed again, that'll be handled (re-marked again) next.
|
|
91
95
|
}
|
|
@@ -175,9 +179,9 @@ export function send(agentRef, {
|
|
|
175
179
|
status: this.status,
|
|
176
180
|
retry: shouldRetry(this.status),
|
|
177
181
|
fullUrl,
|
|
178
|
-
xhr: this
|
|
182
|
+
xhr: this,
|
|
183
|
+
responseText: this.responseText
|
|
179
184
|
};
|
|
180
|
-
if (localOpts.needResponse) cbResult.responseText = this.responseText;
|
|
181
185
|
cbFinished(cbResult);
|
|
182
186
|
|
|
183
187
|
/** temporary audit of consistency of harvest metadata flags */
|
|
@@ -191,9 +195,9 @@ export function send(agentRef, {
|
|
|
191
195
|
status,
|
|
192
196
|
retry: shouldRetry(status),
|
|
193
197
|
fullUrl,
|
|
194
|
-
fetchResponse: response
|
|
198
|
+
fetchResponse: response,
|
|
199
|
+
responseText: await response.text()
|
|
195
200
|
};
|
|
196
|
-
if (localOpts.needResponse) cbResult.responseText = await response.text();
|
|
197
201
|
cbFinished(cbResult);
|
|
198
202
|
/** temporary audit of consistency of harvest metadata flags */
|
|
199
203
|
if (!shouldRetry(status)) trackHarvestMetadata();
|
|
@@ -24,7 +24,6 @@
|
|
|
24
24
|
* @property {HarvestEndpointIdentifier} endpoint The endpoint to use (jserrors, events, resources etc.)
|
|
25
25
|
* @property {HarvestPayload} payload Object representing payload.
|
|
26
26
|
* @property {object} localOpts Additional options for sending data
|
|
27
|
-
* @property {boolean} localOpts.needResponse Specify whether the caller expects a response data.
|
|
28
27
|
* @property {boolean} localOpts.isFinalHarvest Specify whether the call is a final harvest during page unload.
|
|
29
28
|
* @property {boolean} localOpts.sendEmptyBody Specify whether the call should be made even if the body is empty. Useful for rum calls.
|
|
30
29
|
* @property {boolean} localOpts.forceNoRetry Don't save the buffered data in the case of a need to retry the transmission.
|
|
@@ -33,7 +33,8 @@ const model = {
|
|
|
33
33
|
serverTimeDiff: null,
|
|
34
34
|
// set by TimeKeeper; "undefined" value will not be stringified and stored but "null" will
|
|
35
35
|
custom: {},
|
|
36
|
-
numOfResets: 0
|
|
36
|
+
numOfResets: 0,
|
|
37
|
+
consent: false // set by consent() API call
|
|
37
38
|
};
|
|
38
39
|
export class SessionEntity {
|
|
39
40
|
/**
|
|
@@ -84,7 +85,8 @@ export class SessionEntity {
|
|
|
84
85
|
}) {
|
|
85
86
|
/** Ensure that certain properties are preserved across a reset if already set */
|
|
86
87
|
const persistentAttributes = {
|
|
87
|
-
serverTimeDiff: this.state.serverTimeDiff || model.serverTimeDiff
|
|
88
|
+
serverTimeDiff: this.state.serverTimeDiff || model.serverTimeDiff,
|
|
89
|
+
consent: this.state.consent || model.consent
|
|
88
90
|
};
|
|
89
91
|
this.state = {};
|
|
90
92
|
this.sync({
|
|
@@ -24,10 +24,12 @@ export class Aggregate extends AggregateBase {
|
|
|
24
24
|
static featureName = CONSTANTS.FEATURE_NAME;
|
|
25
25
|
constructor(agentRef) {
|
|
26
26
|
super(agentRef, CONSTANTS.FEATURE_NAME);
|
|
27
|
+
this.sentRum = false; // flag to facilitate calling sendRum() once externally (by the consent API in agent-session.js)
|
|
28
|
+
|
|
27
29
|
this.timeToFirstByte = 0;
|
|
28
30
|
this.firstByteToWindowLoad = 0; // our "frontend" duration
|
|
29
31
|
this.firstByteToDomContent = 0; // our "dom processing" duration
|
|
30
|
-
|
|
32
|
+
this.retries = 0;
|
|
31
33
|
if (!isValid(agentRef.info)) {
|
|
32
34
|
this.ee.abort();
|
|
33
35
|
return warn(43);
|
|
@@ -55,12 +57,8 @@ export class Aggregate extends AggregateBase {
|
|
|
55
57
|
*
|
|
56
58
|
* @param {Function} cb A function to run once the RUM call has finished - Defaults to activateFeatures
|
|
57
59
|
* @param {*} customAttributes custom attributes to attach to the RUM call - Defaults to info.js
|
|
58
|
-
* @param {*} target The target to harvest to
|
|
59
60
|
*/
|
|
60
|
-
sendRum(customAttributes = this.agentRef.info.jsAttributes
|
|
61
|
-
licenseKey: this.agentRef.info.licenseKey,
|
|
62
|
-
applicationID: this.agentRef.info.applicationID
|
|
63
|
-
}) {
|
|
61
|
+
sendRum(customAttributes = this.agentRef.info.jsAttributes) {
|
|
64
62
|
const info = this.agentRef.info;
|
|
65
63
|
const measures = {};
|
|
66
64
|
if (info.queueTime) measures.qt = info.queueTime;
|
|
@@ -111,27 +109,30 @@ export class Aggregate extends AggregateBase {
|
|
|
111
109
|
}
|
|
112
110
|
queryParameters.fp = firstPaint.current.value;
|
|
113
111
|
queryParameters.fcp = firstContentfulPaint.current.value;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
|
-
needResponse: true,
|
|
112
|
+
this.queryStringsBuilder = () => {
|
|
113
|
+
// this will be called by AggregateBase.makeHarvestPayload every time harvest is triggered to be qs
|
|
114
|
+
this.rumStartTime = now(); // this should be reset at the beginning of each RUM call for proper timeKeeper calculation in coordination with postHarvestCleanup
|
|
115
|
+
const timeKeeper = this.agentRef.runtime.timeKeeper;
|
|
116
|
+
if (timeKeeper?.ready) {
|
|
117
|
+
queryParameters.timestamp = Math.floor(timeKeeper.correctRelativeTimestamp(this.rumStartTime));
|
|
118
|
+
}
|
|
119
|
+
return queryParameters;
|
|
120
|
+
};
|
|
121
|
+
this.events.add(body);
|
|
122
|
+
if (this.agentRef.runtime.harvester.triggerHarvestFor(this, {
|
|
128
123
|
sendEmptyBody: true
|
|
129
|
-
});
|
|
124
|
+
}).ranSend) this.sentRum = true;
|
|
125
|
+
}
|
|
126
|
+
serializer(eventBuffer) {
|
|
127
|
+
// this is necessary because PVE sends a single item rather than an array; in the case of undefined, this prevents sending [null] as body
|
|
128
|
+
return eventBuffer[0];
|
|
130
129
|
}
|
|
131
130
|
postHarvestCleanup({
|
|
131
|
+
sent,
|
|
132
132
|
status,
|
|
133
133
|
responseText,
|
|
134
|
-
xhr
|
|
134
|
+
xhr,
|
|
135
|
+
retry
|
|
135
136
|
}) {
|
|
136
137
|
const rumEndTime = now();
|
|
137
138
|
let app, flags;
|
|
@@ -144,8 +145,20 @@ export class Aggregate extends AggregateBase {
|
|
|
144
145
|
// wont set entity stuff here, if main agent will later abort, if registered agent, nothing will happen
|
|
145
146
|
warn(53, error);
|
|
146
147
|
}
|
|
148
|
+
super.postHarvestCleanup({
|
|
149
|
+
sent,
|
|
150
|
+
retry
|
|
151
|
+
}); // this will set isRetrying & re-buffer the body if request is to be retried
|
|
152
|
+
if (this.isRetrying && this.retries++ < 1) {
|
|
153
|
+
// Only retry once
|
|
154
|
+
setTimeout(() => this.agentRef.runtime.harvester.triggerHarvestFor(this, {
|
|
155
|
+
sendEmptyBody: true
|
|
156
|
+
}), 5000); // Retry sending the RUM event after 5 seconds
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
147
159
|
if (status >= 400 || status === 0) {
|
|
148
160
|
warn(18, status);
|
|
161
|
+
this.blocked = true;
|
|
149
162
|
|
|
150
163
|
// Get estimated payload size of our backlog
|
|
151
164
|
const textEncoder = new TextEncoder();
|
|
@@ -209,6 +222,7 @@ export class Aggregate extends AggregateBase {
|
|
|
209
222
|
}
|
|
210
223
|
} catch (error) {
|
|
211
224
|
this.ee.abort();
|
|
225
|
+
this.blocked = true;
|
|
212
226
|
warn(17, error);
|
|
213
227
|
return;
|
|
214
228
|
}
|