@newrelic/browser-agent 1.298.0 → 1.299.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 +14 -0
- package/README.md +12 -2
- 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 +60 -44
- package/dist/cjs/common/harvest/harvester.js +37 -7
- package/dist/cjs/common/url/extract-url.js +21 -0
- package/dist/cjs/features/ajax/instrument/index.js +2 -9
- package/dist/cjs/features/generic_events/aggregate/index.js +29 -8
- package/dist/cjs/features/generic_events/aggregate/user-actions/aggregated-user-action.js +5 -3
- package/dist/cjs/features/generic_events/aggregate/user-actions/user-actions-aggregator.js +71 -33
- package/dist/cjs/features/generic_events/constants.js +2 -1
- package/dist/cjs/features/generic_events/instrument/index.js +45 -0
- package/dist/cjs/features/metrics/aggregate/framework-detection.js +2 -0
- package/dist/cjs/features/metrics/aggregate/harvest-metadata.js +45 -0
- package/dist/cjs/features/metrics/aggregate/index.js +21 -0
- package/dist/cjs/features/session_replay/aggregate/index.js +1 -1
- package/dist/cjs/features/session_replay/shared/recorder.js +6 -2
- package/dist/cjs/loaders/api/noticeError.js +1 -0
- package/dist/cjs/loaders/configure/configure.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/dom/selector-path.js +58 -42
- package/dist/esm/common/harvest/harvester.js +37 -7
- package/dist/esm/common/url/extract-url.js +15 -0
- package/dist/esm/features/ajax/instrument/index.js +2 -9
- package/dist/esm/features/generic_events/aggregate/index.js +29 -8
- package/dist/esm/features/generic_events/aggregate/user-actions/aggregated-user-action.js +5 -3
- package/dist/esm/features/generic_events/aggregate/user-actions/user-actions-aggregator.js +72 -34
- package/dist/esm/features/generic_events/constants.js +1 -0
- package/dist/esm/features/generic_events/instrument/index.js +46 -1
- package/dist/esm/features/metrics/aggregate/framework-detection.js +2 -0
- package/dist/esm/features/metrics/aggregate/harvest-metadata.js +39 -0
- package/dist/esm/features/metrics/aggregate/index.js +21 -0
- package/dist/esm/features/session_replay/aggregate/index.js +1 -1
- package/dist/esm/features/session_replay/shared/recorder.js +6 -2
- package/dist/esm/loaders/api/noticeError.js +1 -0
- package/dist/esm/loaders/configure/configure.js +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/common/dom/selector-path.d.ts +6 -1
- package/dist/types/common/dom/selector-path.d.ts.map +1 -1
- package/dist/types/common/harvest/harvester.d.ts.map +1 -1
- package/dist/types/common/url/extract-url.d.ts +7 -0
- package/dist/types/common/url/extract-url.d.ts.map +1 -0
- package/dist/types/features/ajax/instrument/index.d.ts.map +1 -1
- package/dist/types/features/generic_events/aggregate/index.d.ts +1 -3
- package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/generic_events/aggregate/user-actions/aggregated-user-action.d.ts +3 -1
- package/dist/types/features/generic_events/aggregate/user-actions/aggregated-user-action.d.ts.map +1 -1
- package/dist/types/features/generic_events/aggregate/user-actions/user-actions-aggregator.d.ts +3 -0
- package/dist/types/features/generic_events/aggregate/user-actions/user-actions-aggregator.d.ts.map +1 -1
- package/dist/types/features/generic_events/constants.d.ts +1 -0
- package/dist/types/features/generic_events/constants.d.ts.map +1 -1
- package/dist/types/features/generic_events/instrument/index.d.ts +2 -0
- package/dist/types/features/generic_events/instrument/index.d.ts.map +1 -1
- package/dist/types/features/metrics/aggregate/framework-detection.d.ts.map +1 -1
- package/dist/types/features/metrics/aggregate/harvest-metadata.d.ts +6 -0
- package/dist/types/features/metrics/aggregate/harvest-metadata.d.ts.map +1 -0
- package/dist/types/features/metrics/aggregate/index.d.ts +2 -0
- package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
- package/dist/types/loaders/api/noticeError.d.ts.map +1 -1
- package/dist/types/loaders/configure/configure.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/common/dom/selector-path.js +51 -39
- package/src/common/harvest/harvester.js +34 -7
- package/src/common/url/extract-url.js +17 -0
- package/src/features/ajax/instrument/index.js +2 -10
- package/src/features/generic_events/aggregate/index.js +23 -8
- package/src/features/generic_events/aggregate/user-actions/aggregated-user-action.js +5 -3
- package/src/features/generic_events/aggregate/user-actions/user-actions-aggregator.js +80 -24
- package/src/features/generic_events/constants.js +2 -0
- package/src/features/generic_events/instrument/index.js +51 -1
- package/src/features/metrics/aggregate/framework-detection.js +2 -0
- package/src/features/metrics/aggregate/harvest-metadata.js +42 -0
- package/src/features/metrics/aggregate/index.js +21 -0
- package/src/features/session_replay/aggregate/index.js +1 -1
- package/src/features/session_replay/shared/recorder.js +5 -1
- package/src/loaders/api/noticeError.js +1 -0
- package/src/loaders/configure/configure.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [1.299.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.298.0...v1.299.0) (2025-10-07)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* Add Flutter supportability metric ([#1580](https://github.com/newrelic/newrelic-browser-agent/issues/1580)) ([5083067](https://github.com/newrelic/newrelic-browser-agent/commit/5083067e1dab2fed972af8010d95c07ba11ec436))
|
|
12
|
+
* Add user frustration signals to UserAction ([#1534](https://github.com/newrelic/newrelic-browser-agent/issues/1534)) ([4d654c3](https://github.com/newrelic/newrelic-browser-agent/commit/4d654c3e4cef906885545b36142fc03da34d525c))
|
|
13
|
+
* Evaluate the accuracy of cross-feature attribution ([#1573](https://github.com/newrelic/newrelic-browser-agent/issues/1573)) ([b1e03e2](https://github.com/newrelic/newrelic-browser-agent/commit/b1e03e216ec4eedb77cbb49241d51b5526b97997))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* Fix recordReplay API inconsistencies ([#1582](https://github.com/newrelic/newrelic-browser-agent/issues/1582)) ([e4465c1](https://github.com/newrelic/newrelic-browser-agent/commit/e4465c1ebfd6f50d3fce99074ae190ce3bc2f4e4))
|
|
19
|
+
|
|
6
20
|
## [1.298.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.297.1...v1.298.0) (2025-09-19)
|
|
7
21
|
|
|
8
22
|
|
package/README.md
CHANGED
|
@@ -389,9 +389,19 @@ A lot of new frameworks support the concept of server-side rendering the pages o
|
|
|
389
389
|
|
|
390
390
|
## Support
|
|
391
391
|
|
|
392
|
-
|
|
392
|
+
Should you need assistance with New Relic products, you are in good hands with several support channels.
|
|
393
393
|
|
|
394
|
-
|
|
394
|
+
If the issue has been confirmed as a bug or is a feature request, please file a GitHub issue.
|
|
395
|
+
|
|
396
|
+
### Support Channels
|
|
397
|
+
|
|
398
|
+
- [New Relic Documentation](https://docs.newrelic.com/docs/browser/browser-monitoring/getting-started/introduction-browser-monitoring/): Comprehensive guidance for using our platform
|
|
399
|
+
|
|
400
|
+
- [New Relic Community](https://support.newrelic.com/s/category/Category__c/Default): The best place to engage in troubleshooting questions
|
|
401
|
+
|
|
402
|
+
- [New Relic University](https://learn.newrelic.com/): A range of online training for New Relic users of every level
|
|
403
|
+
|
|
404
|
+
- [New Relic Technical Support](https://support.newrelic.com/s/#): 24/7/365 ticketed support. Read more about our [Technical Support Offerings](https://docs.newrelic.com/docs/licenses/license-information/general-usage-licenses/global-technical-support-offerings/).
|
|
395
405
|
|
|
396
406
|
## Contribute
|
|
397
407
|
|
|
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
|
|
|
17
17
|
/**
|
|
18
18
|
* Exposes the version of the agent
|
|
19
19
|
*/
|
|
20
|
-
const VERSION = exports.VERSION = "1.
|
|
20
|
+
const VERSION = exports.VERSION = "1.299.0";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Exposes the build type of the agent
|
|
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
|
|
|
17
17
|
/**
|
|
18
18
|
* Exposes the version of the agent
|
|
19
19
|
*/
|
|
20
|
-
const VERSION = exports.VERSION = "1.
|
|
20
|
+
const VERSION = exports.VERSION = "1.299.0";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Exposes the build type of the agent
|
|
@@ -3,68 +3,84 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.
|
|
6
|
+
exports.analyzeElemPath = void 0;
|
|
7
7
|
/**
|
|
8
8
|
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
9
9
|
* SPDX-License-Identifier: Apache-2.0
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* Generates a CSS selector path for the given element, if possible
|
|
13
|
+
* Generates a CSS selector path for the given element, if possible.
|
|
14
|
+
* Also gather metadata about the element's nearest fields, and whether there are any links or buttons in the path.
|
|
15
|
+
*
|
|
16
|
+
* Starts with simple cases like window or document and progresses to more complex dom-tree traversals as needed.
|
|
17
|
+
* Will return path: undefined if no other path can be determined.
|
|
18
|
+
*
|
|
14
19
|
* @param {HTMLElement} elem
|
|
15
|
-
* @param {
|
|
16
|
-
* @
|
|
17
|
-
* @returns {string|undefined}
|
|
20
|
+
* @param {Array<string>} [targetFields=[]] specifies which fields to gather from the nearest element in the path
|
|
21
|
+
* @returns {{path: (undefined|string), nearestFields: {}, hasButton: boolean, hasLink: boolean}}
|
|
18
22
|
*/
|
|
19
|
-
const
|
|
20
|
-
|
|
23
|
+
const analyzeElemPath = (elem, targetFields = []) => {
|
|
24
|
+
const result = {
|
|
21
25
|
path: undefined,
|
|
22
|
-
nearestFields: {}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
let i = 1;
|
|
27
|
-
const {
|
|
28
|
-
tagName
|
|
29
|
-
} = node;
|
|
30
|
-
while (node.previousElementSibling) {
|
|
31
|
-
if (node.previousElementSibling.tagName === tagName) i++;
|
|
32
|
-
node = node.previousElementSibling;
|
|
33
|
-
}
|
|
34
|
-
return i;
|
|
35
|
-
} catch (err) {
|
|
36
|
-
// do nothing for now. An invalid child count will make the path selector not return a nth-of-type selector statement
|
|
37
|
-
}
|
|
26
|
+
nearestFields: {},
|
|
27
|
+
hasButton: false,
|
|
28
|
+
hasLink: false
|
|
38
29
|
};
|
|
30
|
+
if (!elem) return result;
|
|
31
|
+
if (elem === window) {
|
|
32
|
+
result.path = 'window';
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
if (elem === document) {
|
|
36
|
+
result.path = 'document';
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
39
|
let pathSelector = '';
|
|
40
|
-
|
|
41
|
-
const nearestFields = {};
|
|
40
|
+
const index = getNthOfTypeIndex(elem);
|
|
42
41
|
try {
|
|
43
42
|
while (elem?.tagName) {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
} = elem;
|
|
43
|
+
const tagName = elem.tagName.toLowerCase();
|
|
44
|
+
result.hasLink ||= tagName === 'a';
|
|
45
|
+
result.hasButton ||= tagName === 'button' || tagName === 'input' && elem.type.toLowerCase() === 'button';
|
|
48
46
|
targetFields.forEach(field => {
|
|
49
|
-
nearestFields[nearestAttrName(field)] ||= elem[field]?.baseVal || elem[field];
|
|
47
|
+
result.nearestFields[nearestAttrName(field)] ||= elem[field]?.baseVal || elem[field];
|
|
50
48
|
});
|
|
51
|
-
|
|
52
|
-
pathSelector = selector;
|
|
49
|
+
pathSelector = buildPathSelector(elem, pathSelector);
|
|
53
50
|
elem = elem.parentNode;
|
|
54
51
|
}
|
|
55
52
|
} catch (err) {
|
|
56
53
|
// do nothing for now
|
|
57
54
|
}
|
|
58
|
-
|
|
59
|
-
return
|
|
60
|
-
path,
|
|
61
|
-
nearestFields
|
|
62
|
-
};
|
|
63
|
-
function nearestAttrName(originalFieldName) {
|
|
64
|
-
/** preserve original renaming structure for pre-existing field maps */
|
|
65
|
-
if (originalFieldName === 'tagName') originalFieldName = 'tag';
|
|
66
|
-
if (originalFieldName === 'className') originalFieldName = 'class';
|
|
67
|
-
return "nearest".concat(originalFieldName.charAt(0).toUpperCase() + originalFieldName.slice(1));
|
|
68
|
-
}
|
|
55
|
+
result.path = pathSelector ? index ? "".concat(pathSelector, ":nth-of-type(").concat(index, ")") : pathSelector : undefined;
|
|
56
|
+
return result;
|
|
69
57
|
};
|
|
70
|
-
exports.
|
|
58
|
+
exports.analyzeElemPath = analyzeElemPath;
|
|
59
|
+
function buildPathSelector(elem, pathSelector) {
|
|
60
|
+
const {
|
|
61
|
+
id,
|
|
62
|
+
localName
|
|
63
|
+
} = elem;
|
|
64
|
+
return [localName, id ? "#".concat(id) : '', pathSelector ? ">".concat(pathSelector) : ''].join('');
|
|
65
|
+
}
|
|
66
|
+
function getNthOfTypeIndex(node) {
|
|
67
|
+
try {
|
|
68
|
+
let i = 1;
|
|
69
|
+
const {
|
|
70
|
+
tagName
|
|
71
|
+
} = node;
|
|
72
|
+
while (node.previousElementSibling) {
|
|
73
|
+
if (node.previousElementSibling.tagName === tagName) i++;
|
|
74
|
+
node = node.previousElementSibling;
|
|
75
|
+
}
|
|
76
|
+
return i;
|
|
77
|
+
} catch (err) {
|
|
78
|
+
// do nothing for now. An invalid child count will make the path selector not return a nth-of-type selector statement
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function nearestAttrName(originalFieldName) {
|
|
82
|
+
/** preserve original renaming structure for pre-existing field maps */
|
|
83
|
+
if (originalFieldName === 'tagName') originalFieldName = 'tag';
|
|
84
|
+
if (originalFieldName === 'className') originalFieldName = 'class';
|
|
85
|
+
return "nearest".concat(originalFieldName.charAt(0).toUpperCase() + originalFieldName.slice(1));
|
|
86
|
+
}
|
|
@@ -153,14 +153,16 @@ function send(agentRef, {
|
|
|
153
153
|
}
|
|
154
154
|
const fullUrl = "".concat(url, "?").concat(baseParams).concat(payloadParams);
|
|
155
155
|
const gzip = !!qs?.attributes?.includes('gzip');
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
156
|
+
|
|
157
|
+
// all gzipped data is already in the correct format and needs no transformation
|
|
158
|
+
// all features going to 'events' endpoint should already be serialized & stringified
|
|
159
|
+
let stringBody = gzip || endpoint === _features.EVENTS ? body : (0, _stringify.stringify)(body);
|
|
161
160
|
|
|
162
161
|
// If body is null, undefined, or an empty object or array after stringifying, send an empty string instead.
|
|
163
|
-
if (!
|
|
162
|
+
if (!stringBody || stringBody.length === 0 || stringBody === '{}' || stringBody === '[]') stringBody = '';
|
|
163
|
+
|
|
164
|
+
// Warn--once per endpoint--if the agent tries to send large payloads
|
|
165
|
+
if (endpoint !== _features.BLOBS && stringBody.length > 750000 && (warnings[endpoint] = (warnings[endpoint] || 0) + 1) === 1) (0, _console.warn)(28, endpoint);
|
|
164
166
|
const headers = [{
|
|
165
167
|
key: 'content-type',
|
|
166
168
|
value: 'text/plain'
|
|
@@ -172,7 +174,7 @@ function send(agentRef, {
|
|
|
172
174
|
Following the removal of img-element method. */
|
|
173
175
|
let result = submitMethod({
|
|
174
176
|
url: fullUrl,
|
|
175
|
-
body,
|
|
177
|
+
body: stringBody,
|
|
176
178
|
sync: localOpts.isFinalHarvest && _runtime.isWorkerScope,
|
|
177
179
|
headers
|
|
178
180
|
});
|
|
@@ -192,6 +194,9 @@ function send(agentRef, {
|
|
|
192
194
|
};
|
|
193
195
|
if (localOpts.needResponse) cbResult.responseText = this.responseText;
|
|
194
196
|
cbFinished(cbResult);
|
|
197
|
+
|
|
198
|
+
/** temporary audit of consistency of harvest metadata flags */
|
|
199
|
+
if (!shouldRetry(this.status)) trackHarvestMetadata();
|
|
195
200
|
}, (0, _eventListenerOpts.eventListenerOpts)(false));
|
|
196
201
|
} else if (submitMethod === _submitData.xhrFetch) {
|
|
197
202
|
result.then(async function (response) {
|
|
@@ -206,8 +211,33 @@ function send(agentRef, {
|
|
|
206
211
|
};
|
|
207
212
|
if (localOpts.needResponse) cbResult.responseText = await response.text();
|
|
208
213
|
cbFinished(cbResult);
|
|
214
|
+
/** temporary audit of consistency of harvest metadata flags */
|
|
215
|
+
if (!shouldRetry(status)) trackHarvestMetadata();
|
|
209
216
|
});
|
|
210
217
|
}
|
|
218
|
+
function trackHarvestMetadata() {
|
|
219
|
+
try {
|
|
220
|
+
if (featureName === _features.FEATURE_NAMES.jserrors && !body?.err) return;
|
|
221
|
+
const hasReplay = baseParams.includes('hr=1');
|
|
222
|
+
const hasTrace = baseParams.includes('ht=1');
|
|
223
|
+
const hasError = qs?.attributes?.includes('hasError=true');
|
|
224
|
+
(0, _handle.handle)('harvest-metadata', [{
|
|
225
|
+
[featureName]: {
|
|
226
|
+
...(hasReplay && {
|
|
227
|
+
hasReplay
|
|
228
|
+
}),
|
|
229
|
+
...(hasTrace && {
|
|
230
|
+
hasTrace
|
|
231
|
+
}),
|
|
232
|
+
...(hasError && {
|
|
233
|
+
hasError
|
|
234
|
+
})
|
|
235
|
+
}
|
|
236
|
+
}], undefined, _features.FEATURE_NAMES.metrics, agentRef.ee);
|
|
237
|
+
} catch (err) {
|
|
238
|
+
// do nothing
|
|
239
|
+
}
|
|
240
|
+
}
|
|
211
241
|
}
|
|
212
242
|
(0, _globalEvent.dispatchGlobalEvent)({
|
|
213
243
|
agentIdentifier: agentRef.agentIdentifier,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.extractUrl = extractUrl;
|
|
7
|
+
var _runtime = require("../constants/runtime");
|
|
8
|
+
var _nreum = require("../window/nreum");
|
|
9
|
+
/**
|
|
10
|
+
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
11
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Extracts a URL from various target types.
|
|
16
|
+
* @param {string|Request|URL} target - The target to extract the URL from. It can be a string, a Fetch Request object, or a URL object.
|
|
17
|
+
* @returns {string|undefined} The extracted URL as a string, or undefined if the target type is not supported.
|
|
18
|
+
*/
|
|
19
|
+
function extractUrl(target) {
|
|
20
|
+
if (typeof target === 'string') return target;else if (target instanceof (0, _nreum.gosNREUMOriginals)().o.REQ) return target.url;else if (_runtime.globalScope?.URL && target instanceof URL) return target.href;
|
|
21
|
+
}
|
|
@@ -21,6 +21,7 @@ var _features = require("../../../loaders/features/features");
|
|
|
21
21
|
var _constants2 = require("../../metrics/constants");
|
|
22
22
|
var _now = require("../../../common/timing/now");
|
|
23
23
|
var _denyList = require("../../../common/deny-list/deny-list");
|
|
24
|
+
var _extractUrl = require("../../../common/url/extract-url");
|
|
24
25
|
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
25
26
|
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } /**
|
|
26
27
|
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
@@ -298,15 +299,7 @@ function subscribeToEvents(agentRef, ee, handler, dt) {
|
|
|
298
299
|
if (fetchArguments.length >= 2) this.opts = fetchArguments[1];
|
|
299
300
|
var opts = this.opts || {};
|
|
300
301
|
var target = this.target;
|
|
301
|
-
|
|
302
|
-
if (typeof target === 'string') {
|
|
303
|
-
url = target;
|
|
304
|
-
} else if (typeof target === 'object' && target instanceof origRequest) {
|
|
305
|
-
url = target.url;
|
|
306
|
-
} else if (_runtime.globalScope?.URL && typeof target === 'object' && target instanceof URL) {
|
|
307
|
-
url = target.href;
|
|
308
|
-
}
|
|
309
|
-
addUrl(this, url);
|
|
302
|
+
addUrl(this, (0, _extractUrl.extractUrl)(target));
|
|
310
303
|
var method = ('' + (target && target instanceof origRequest && target.method || opts.method || 'GET')).toUpperCase();
|
|
311
304
|
this.params.method = method;
|
|
312
305
|
this.body = opts.body;
|
|
@@ -23,6 +23,7 @@ var _typeCheck = require("../../../common/util/type-check");
|
|
|
23
23
|
|
|
24
24
|
class Aggregate extends _aggregateBase.AggregateBase {
|
|
25
25
|
static featureName = _constants.FEATURE_NAME;
|
|
26
|
+
#userActionAggregator;
|
|
26
27
|
constructor(agentRef) {
|
|
27
28
|
super(agentRef, _constants.FEATURE_NAME);
|
|
28
29
|
this.referrerUrl = _runtime.isBrowserScope && document.referrer ? (0, _cleanUrl.cleanURL)(document.referrer) : undefined;
|
|
@@ -32,7 +33,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
32
33
|
this.deregisterDrain();
|
|
33
34
|
return;
|
|
34
35
|
}
|
|
35
|
-
this
|
|
36
|
+
this.#trackSupportabilityMetrics();
|
|
36
37
|
(0, _registerHandler.registerHandler)('api-recordCustomEvent', (timestamp, eventType, attributes) => {
|
|
37
38
|
if (_constants.RESERVED_EVENT_TYPES.includes(eventType)) return (0, _console.warn)(46);
|
|
38
39
|
this.addEvent({
|
|
@@ -60,8 +61,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
60
61
|
}
|
|
61
62
|
let addUserAction = () => {/** no-op */};
|
|
62
63
|
if (_runtime.isBrowserScope && agentRef.init.user_actions.enabled) {
|
|
63
|
-
this
|
|
64
|
-
this.harvestOpts.beforeUnload = () => addUserAction?.(this
|
|
64
|
+
this.#userActionAggregator = new _userActionsAggregator.UserActionsAggregator(agentRef.init.feature_flags.includes('user_frustrations'));
|
|
65
|
+
this.harvestOpts.beforeUnload = () => addUserAction?.(this.#userActionAggregator.aggregationEvent);
|
|
65
66
|
addUserAction = aggregatedUserAction => {
|
|
66
67
|
try {
|
|
67
68
|
/** The aggregator process only returns an event when it is "done" aggregating -
|
|
@@ -72,7 +73,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
72
73
|
timeStamp,
|
|
73
74
|
type
|
|
74
75
|
} = aggregatedUserAction.event;
|
|
75
|
-
|
|
76
|
+
const userActionEvent = {
|
|
76
77
|
eventType: 'UserAction',
|
|
77
78
|
timestamp: this.toEpoch(timeStamp),
|
|
78
79
|
action: type,
|
|
@@ -90,8 +91,16 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
90
91
|
if (canTrustTargetAttribute(field)) acc[targetAttrName(field)] = String(target[field]).trim().slice(0, 128);
|
|
91
92
|
return acc;
|
|
92
93
|
}, {}),
|
|
93
|
-
...aggregatedUserAction.nearestTargetFields
|
|
94
|
-
|
|
94
|
+
...aggregatedUserAction.nearestTargetFields,
|
|
95
|
+
...(aggregatedUserAction.deadClick && {
|
|
96
|
+
deadClick: true
|
|
97
|
+
}),
|
|
98
|
+
...(aggregatedUserAction.errorClick && {
|
|
99
|
+
errorClick: true
|
|
100
|
+
})
|
|
101
|
+
};
|
|
102
|
+
this.addEvent(userActionEvent);
|
|
103
|
+
this.#trackUserActionSM(userActionEvent);
|
|
95
104
|
|
|
96
105
|
/**
|
|
97
106
|
* Returns the original target field name with `target` prepended and camelCased
|
|
@@ -121,8 +130,15 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
121
130
|
};
|
|
122
131
|
(0, _registerHandler.registerHandler)('ua', evt => {
|
|
123
132
|
/** the processor will return the previously aggregated event if it has been completed by processing the current event */
|
|
124
|
-
addUserAction(this
|
|
133
|
+
addUserAction(this.#userActionAggregator.process(evt, this.agentRef.init.user_actions.elementAttributes));
|
|
134
|
+
}, this.featureName, this.ee);
|
|
135
|
+
(0, _registerHandler.registerHandler)('navChange', () => {
|
|
136
|
+
this.#userActionAggregator.isLiveClick();
|
|
125
137
|
}, this.featureName, this.ee);
|
|
138
|
+
(0, _registerHandler.registerHandler)('uaXhr', () => {
|
|
139
|
+
this.#userActionAggregator.isLiveClick();
|
|
140
|
+
}, this.featureName, this.ee);
|
|
141
|
+
(0, _registerHandler.registerHandler)('uaErr', () => this.#userActionAggregator.markAsErrorClick(), this.featureName, this.ee);
|
|
126
142
|
}
|
|
127
143
|
|
|
128
144
|
/**
|
|
@@ -298,7 +314,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
298
314
|
toEpoch(timestamp) {
|
|
299
315
|
return Math.floor(this.agentRef.runtime.timeKeeper.correctRelativeTimestamp(timestamp));
|
|
300
316
|
}
|
|
301
|
-
trackSupportabilityMetrics() {
|
|
317
|
+
#trackSupportabilityMetrics() {
|
|
302
318
|
/** track usage SMs to improve these experimental features */
|
|
303
319
|
const configPerfTag = 'Config/Performance/';
|
|
304
320
|
if (this.agentRef.init.performance.capture_marks) this.reportSupportabilityMetric(configPerfTag + 'CaptureMarks/Enabled');
|
|
@@ -308,5 +324,10 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
308
324
|
if (this.agentRef.init.performance.resources.first_party_domains?.length !== 0) this.reportSupportabilityMetric(configPerfTag + 'Resources/FirstPartyDomains/Changed');
|
|
309
325
|
if (this.agentRef.init.performance.resources.ignore_newrelic === false) this.reportSupportabilityMetric(configPerfTag + 'Resources/IgnoreNewrelic/Changed');
|
|
310
326
|
}
|
|
327
|
+
#trackUserActionSM(ua) {
|
|
328
|
+
if (ua.rageClick) this.reportSupportabilityMetric('UserAction/RageClick/Seen');
|
|
329
|
+
if (ua.deadClick) this.reportSupportabilityMetric('UserAction/DeadClick/Seen');
|
|
330
|
+
if (ua.errorClick) this.reportSupportabilityMetric('UserAction/ErrorClick/Seen');
|
|
331
|
+
}
|
|
311
332
|
}
|
|
312
333
|
exports.Aggregate = Aggregate;
|
|
@@ -12,15 +12,17 @@ var _cleanUrl = require("../../../../common/url/clean-url");
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
class AggregatedUserAction {
|
|
15
|
-
constructor(evt,
|
|
15
|
+
constructor(evt, selectorInfo) {
|
|
16
16
|
this.event = evt;
|
|
17
17
|
this.count = 1;
|
|
18
18
|
this.originMs = Math.floor(evt.timeStamp);
|
|
19
19
|
this.relativeMs = [0];
|
|
20
|
-
this.selectorPath =
|
|
20
|
+
this.selectorPath = selectorInfo.path;
|
|
21
21
|
this.rageClick = undefined;
|
|
22
|
-
this.nearestTargetFields =
|
|
22
|
+
this.nearestTargetFields = selectorInfo.nearestFields;
|
|
23
23
|
this.currentUrl = (0, _cleanUrl.cleanURL)('' + location);
|
|
24
|
+
this.deadClick = false;
|
|
25
|
+
this.errorClick = false;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
/**
|
|
@@ -7,6 +7,8 @@ exports.UserActionsAggregator = void 0;
|
|
|
7
7
|
var _selectorPath = require("../../../../common/dom/selector-path");
|
|
8
8
|
var _constants = require("../../constants");
|
|
9
9
|
var _aggregatedUserAction = require("./aggregated-user-action");
|
|
10
|
+
var _timer = require("../../../../common/timer/timer");
|
|
11
|
+
var _nreum = require("../../../../common/window/nreum");
|
|
10
12
|
/**
|
|
11
13
|
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
12
14
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -16,6 +18,16 @@ class UserActionsAggregator {
|
|
|
16
18
|
/** @type {AggregatedUserAction=} */
|
|
17
19
|
#aggregationEvent = undefined;
|
|
18
20
|
#aggregationKey = '';
|
|
21
|
+
#ufEnabled = false;
|
|
22
|
+
#deadClickTimer = undefined;
|
|
23
|
+
#domObserver = undefined;
|
|
24
|
+
#errorClickTimer = undefined;
|
|
25
|
+
constructor(userFrustrationsEnabled) {
|
|
26
|
+
if (userFrustrationsEnabled && (0, _nreum.gosNREUMOriginals)().o.MO) {
|
|
27
|
+
this.#domObserver = new MutationObserver(this.isLiveClick.bind(this));
|
|
28
|
+
this.#ufEnabled = true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
19
31
|
get aggregationEvent() {
|
|
20
32
|
// if this is accessed externally, we need to be done aggregating on it
|
|
21
33
|
// to prevent potential mutability and duplication issues, so the state is cleared upon returning.
|
|
@@ -33,50 +45,75 @@ class UserActionsAggregator {
|
|
|
33
45
|
*/
|
|
34
46
|
process(evt, targetFields) {
|
|
35
47
|
if (!evt) return;
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const aggregationKey = getAggregationKey(evt,
|
|
48
|
+
const targetElem = _constants.OBSERVED_WINDOW_EVENTS.includes(evt.type) ? window : evt.target;
|
|
49
|
+
const selectorInfo = (0, _selectorPath.analyzeElemPath)(targetElem, targetFields);
|
|
50
|
+
|
|
51
|
+
// if selectorInfo.path is undefined, aggregation will be skipped for this event
|
|
52
|
+
const aggregationKey = getAggregationKey(evt, selectorInfo.path);
|
|
41
53
|
if (!!aggregationKey && aggregationKey === this.#aggregationKey) {
|
|
42
54
|
// an aggregation exists already, so lets just continue to increment
|
|
43
55
|
this.#aggregationEvent.aggregate(evt);
|
|
44
56
|
} else {
|
|
45
57
|
// return the prev existing one (if there is one)
|
|
46
58
|
const finishedEvent = this.#aggregationEvent;
|
|
47
|
-
|
|
59
|
+
if (this.#ufEnabled) {
|
|
60
|
+
this.#deadClickCleanup();
|
|
61
|
+
this.#errorClickCleanup();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// then start new event aggregation
|
|
48
65
|
this.#aggregationKey = aggregationKey;
|
|
49
|
-
this.#aggregationEvent = new _aggregatedUserAction.AggregatedUserAction(evt,
|
|
66
|
+
this.#aggregationEvent = new _aggregatedUserAction.AggregatedUserAction(evt, selectorInfo);
|
|
67
|
+
if (this.#ufEnabled && evt.type === 'click' && (selectorInfo.hasButton || selectorInfo.hasLink)) {
|
|
68
|
+
this.#deadClickSetup(this.#aggregationEvent);
|
|
69
|
+
this.#errorClickSetup();
|
|
70
|
+
}
|
|
50
71
|
return finishedEvent;
|
|
51
72
|
}
|
|
52
73
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
markAsErrorClick() {
|
|
75
|
+
if (this.#aggregationEvent && this.#errorClickTimer) {
|
|
76
|
+
this.#aggregationEvent.errorClick = true;
|
|
77
|
+
this.#errorClickCleanup();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
#errorClickSetup() {
|
|
81
|
+
this.#errorClickTimer = new _timer.Timer({
|
|
82
|
+
onEnd: () => {
|
|
83
|
+
this.#errorClickCleanup();
|
|
84
|
+
}
|
|
85
|
+
}, _constants.FRUSTRATION_TIMEOUT_MS);
|
|
86
|
+
}
|
|
87
|
+
#errorClickCleanup() {
|
|
88
|
+
this.#errorClickTimer?.clear();
|
|
89
|
+
this.#errorClickTimer = undefined;
|
|
90
|
+
}
|
|
91
|
+
#deadClickSetup(userAction) {
|
|
92
|
+
if (this.#isEvaluatingDeadClick() || !this.#domObserver) return;
|
|
93
|
+
this.#domObserver.observe(document, {
|
|
94
|
+
attributes: true,
|
|
95
|
+
characterData: true,
|
|
96
|
+
childList: true,
|
|
97
|
+
subtree: true
|
|
98
|
+
});
|
|
99
|
+
this.#deadClickTimer = new _timer.Timer({
|
|
100
|
+
onEnd: () => {
|
|
101
|
+
userAction.deadClick = true;
|
|
102
|
+
this.#deadClickCleanup();
|
|
103
|
+
}
|
|
104
|
+
}, _constants.FRUSTRATION_TIMEOUT_MS);
|
|
105
|
+
}
|
|
106
|
+
#deadClickCleanup() {
|
|
107
|
+
this.#domObserver?.disconnect();
|
|
108
|
+
this.#deadClickTimer?.clear();
|
|
109
|
+
this.#deadClickTimer = undefined;
|
|
110
|
+
}
|
|
111
|
+
#isEvaluatingDeadClick() {
|
|
112
|
+
return this.#deadClickTimer !== undefined;
|
|
113
|
+
}
|
|
114
|
+
isLiveClick() {
|
|
115
|
+
if (this.#isEvaluatingDeadClick()) this.#deadClickCleanup();
|
|
74
116
|
}
|
|
75
|
-
// if STILL no selectorPath, it will return undefined which will skip aggregation for this event
|
|
76
|
-
return {
|
|
77
|
-
selectorPath,
|
|
78
|
-
nearestTargetFields
|
|
79
|
-
};
|
|
80
117
|
}
|
|
81
118
|
|
|
82
119
|
/**
|
|
@@ -86,6 +123,7 @@ function getSelectorPath(evt, targetFields) {
|
|
|
86
123
|
* @param {string} selectorPath
|
|
87
124
|
* @returns {string}
|
|
88
125
|
*/
|
|
126
|
+
exports.UserActionsAggregator = UserActionsAggregator;
|
|
89
127
|
function getAggregationKey(evt, selectorPath) {
|
|
90
128
|
let aggregationKey = evt.type;
|
|
91
129
|
/** aggregate all scrollends into one set (if sequential), no matter what their target is
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.RESERVED_EVENT_TYPES = exports.RAGE_CLICK_THRESHOLD_MS = exports.RAGE_CLICK_THRESHOLD_EVENTS = exports.OBSERVED_WINDOW_EVENTS = exports.OBSERVED_EVENTS = exports.FEATURE_NAME = exports.FEATURE_FLAGS = void 0;
|
|
6
|
+
exports.RESERVED_EVENT_TYPES = exports.RAGE_CLICK_THRESHOLD_MS = exports.RAGE_CLICK_THRESHOLD_EVENTS = exports.OBSERVED_WINDOW_EVENTS = exports.OBSERVED_EVENTS = exports.FRUSTRATION_TIMEOUT_MS = exports.FEATURE_NAME = exports.FEATURE_FLAGS = void 0;
|
|
7
7
|
var _features = require("../../loaders/features/features");
|
|
8
8
|
/**
|
|
9
9
|
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
@@ -15,6 +15,7 @@ const OBSERVED_EVENTS = exports.OBSERVED_EVENTS = ['auxclick', 'click', 'copy',
|
|
|
15
15
|
const OBSERVED_WINDOW_EVENTS = exports.OBSERVED_WINDOW_EVENTS = ['focus', 'blur'];
|
|
16
16
|
const RAGE_CLICK_THRESHOLD_EVENTS = exports.RAGE_CLICK_THRESHOLD_EVENTS = 4;
|
|
17
17
|
const RAGE_CLICK_THRESHOLD_MS = exports.RAGE_CLICK_THRESHOLD_MS = 1000;
|
|
18
|
+
const FRUSTRATION_TIMEOUT_MS = exports.FRUSTRATION_TIMEOUT_MS = 2000;
|
|
18
19
|
const RESERVED_EVENT_TYPES = exports.RESERVED_EVENT_TYPES = ['PageAction', 'UserAction', 'BrowserPerformance'];
|
|
19
20
|
const FEATURE_FLAGS = exports.FEATURE_FLAGS = {
|
|
20
21
|
MARKS: 'experimental.marks',
|
|
@@ -15,6 +15,12 @@ var _register = require("../../../loaders/api/register");
|
|
|
15
15
|
var _measure = require("../../../loaders/api/measure");
|
|
16
16
|
var _instrumentBase = require("../../utils/instrument-base");
|
|
17
17
|
var _constants = require("../constants");
|
|
18
|
+
var _features = require("../../../loaders/features/features");
|
|
19
|
+
var _wrapHistory = require("../../../common/wrap/wrap-history");
|
|
20
|
+
var _wrapFetch = require("../../../common/wrap/wrap-fetch");
|
|
21
|
+
var _wrapXhr = require("../../../common/wrap/wrap-xhr");
|
|
22
|
+
var _parseUrl = require("../../../common/url/parse-url");
|
|
23
|
+
var _extractUrl = require("../../../common/url/extract-url");
|
|
18
24
|
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
19
25
|
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } /**
|
|
20
26
|
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
@@ -58,6 +64,45 @@ class Instrument extends _instrumentBase.InstrumentBase {
|
|
|
58
64
|
buffered: true
|
|
59
65
|
});
|
|
60
66
|
}
|
|
67
|
+
const historyEE = (0, _wrapHistory.wrapHistory)(this.ee);
|
|
68
|
+
historyEE.on('pushState-end', navigationChange);
|
|
69
|
+
historyEE.on('replaceState-end', navigationChange);
|
|
70
|
+
window.addEventListener('hashchange', navigationChange, (0, _eventListenerOpts.eventListenerOpts)(true, this.removeOnAbort?.signal));
|
|
71
|
+
window.addEventListener('popstate', navigationChange, (0, _eventListenerOpts.eventListenerOpts)(true, this.removeOnAbort?.signal));
|
|
72
|
+
function navigationChange() {
|
|
73
|
+
historyEE.emit('navChange');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
this.removeOnAbort = new AbortController();
|
|
78
|
+
} catch (e) {}
|
|
79
|
+
this.abortHandler = () => {
|
|
80
|
+
this.removeOnAbort?.abort();
|
|
81
|
+
this.abortHandler = undefined; // weakly allow this abort op to run only once
|
|
82
|
+
};
|
|
83
|
+
_runtime.globalScope.addEventListener('error', () => {
|
|
84
|
+
(0, _handle.handle)('uaErr', [], undefined, _features.FEATURE_NAMES.genericEvents, this.ee);
|
|
85
|
+
}, (0, _eventListenerOpts.eventListenerOpts)(false, this.removeOnAbort?.signal));
|
|
86
|
+
(0, _wrapFetch.wrapFetch)(this.ee);
|
|
87
|
+
(0, _wrapXhr.wrapXhr)(this.ee);
|
|
88
|
+
this.ee.on('open-xhr-start', (args, xhr) => {
|
|
89
|
+
if (!isInternalTraffic(args[1])) {
|
|
90
|
+
xhr.addEventListener('readystatechange', () => {
|
|
91
|
+
if (xhr.readyState === 2) {
|
|
92
|
+
// HEADERS_RECEIVED
|
|
93
|
+
(0, _handle.handle)('uaXhr', [], undefined, _features.FEATURE_NAMES.genericEvents, this.ee);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
this.ee.on('fetch-start', fetchArguments => {
|
|
99
|
+
if (fetchArguments.length >= 1 && !isInternalTraffic((0, _extractUrl.extractUrl)(fetchArguments[0]))) {
|
|
100
|
+
(0, _handle.handle)('uaXhr', [], undefined, _features.FEATURE_NAMES.genericEvents, this.ee);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
function isInternalTraffic(url) {
|
|
104
|
+
const parsedUrl = (0, _parseUrl.parseUrl)(url);
|
|
105
|
+
return agentRef.beacons.includes(parsedUrl.hostname + ':' + parsedUrl.port);
|
|
61
106
|
}
|
|
62
107
|
|
|
63
108
|
/** If any of the sources are active, import the aggregator. otherwise deregister */
|