@newrelic/browser-agent 1.313.1-rc.2 → 1.313.1-rc.3
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/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/dom/selector-path.js +12 -3
- package/dist/cjs/features/generic_events/aggregate/index.js +60 -53
- package/dist/cjs/features/generic_events/aggregate/user-actions/aggregated-user-action.js +2 -1
- package/dist/cjs/features/generic_events/aggregate/user-actions/user-actions-aggregator.js +4 -3
- 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 +13 -3
- package/dist/esm/features/generic_events/aggregate/index.js +61 -54
- package/dist/esm/features/generic_events/aggregate/user-actions/aggregated-user-action.js +2 -1
- package/dist/esm/features/generic_events/aggregate/user-actions/user-actions-aggregator.js +4 -3
- package/dist/types/common/dom/selector-path.d.ts +2 -1
- package/dist/types/common/dom/selector-path.d.ts.map +1 -1
- 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 +1 -0
- 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 +2 -0
- package/dist/types/features/generic_events/aggregate/user-actions/user-actions-aggregator.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/common/dom/selector-path.js +13 -4
- package/src/features/generic_events/aggregate/index.js +42 -39
- package/src/features/generic_events/aggregate/user-actions/aggregated-user-action.js +2 -1
- package/src/features/generic_events/aggregate/user-actions/user-actions-aggregator.js +4 -3
|
@@ -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.313.1-rc.
|
|
20
|
+
const VERSION = exports.VERSION = "1.313.1-rc.3";
|
|
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.313.1-rc.
|
|
20
|
+
const VERSION = exports.VERSION = "1.313.1-rc.3";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Exposes the build type of the agent
|
|
@@ -4,8 +4,9 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.analyzeElemPath = void 0;
|
|
7
|
+
var _utils = require("../v2/utils");
|
|
7
8
|
/**
|
|
8
|
-
* Copyright 2020-
|
|
9
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
9
10
|
* SPDX-License-Identifier: Apache-2.0
|
|
10
11
|
*/
|
|
11
12
|
|
|
@@ -18,12 +19,16 @@ exports.analyzeElemPath = void 0;
|
|
|
18
19
|
*
|
|
19
20
|
* @param {HTMLElement} elem
|
|
20
21
|
* @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}}
|
|
22
|
+
* @returns {{path: (undefined|string), nearestFields: {}, targets: Array, hasButton: boolean, hasLink: boolean}}
|
|
22
23
|
*/
|
|
23
|
-
const analyzeElemPath = (elem, targetFields = []) => {
|
|
24
|
+
const analyzeElemPath = (elem, targetFields = [], agentRef) => {
|
|
25
|
+
const targets = [];
|
|
24
26
|
const result = {
|
|
25
27
|
path: undefined,
|
|
26
28
|
nearestFields: {},
|
|
29
|
+
get targets() {
|
|
30
|
+
return targets.length ? targets : [undefined];
|
|
31
|
+
},
|
|
27
32
|
hasButton: false,
|
|
28
33
|
hasLink: false
|
|
29
34
|
};
|
|
@@ -46,6 +51,10 @@ const analyzeElemPath = (elem, targetFields = []) => {
|
|
|
46
51
|
targetFields.forEach(field => {
|
|
47
52
|
result.nearestFields[nearestAttrName(field)] ||= elem[field]?.baseVal || elem[field];
|
|
48
53
|
});
|
|
54
|
+
const dataAttrs = elem?.dataset;
|
|
55
|
+
if (dataAttrs.nrMfeId) {
|
|
56
|
+
targets.push(...(0, _utils.getRegisteredTargetsFromId)(dataAttrs.nrMfeId, agentRef));
|
|
57
|
+
}
|
|
49
58
|
pathSelector = buildPathSelector(elem, pathSelector);
|
|
50
59
|
elem = elem.parentNode;
|
|
51
60
|
}
|
|
@@ -61,7 +61,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
61
61
|
}
|
|
62
62
|
let addUserAction = () => {/** no-op */};
|
|
63
63
|
if (_runtime.isBrowserScope && agentRef.init.user_actions.enabled) {
|
|
64
|
-
this.#userActionAggregator = new _userActionsAggregator.UserActionsAggregator();
|
|
64
|
+
this.#userActionAggregator = new _userActionsAggregator.UserActionsAggregator(this.agentRef);
|
|
65
65
|
this.harvestOpts.beforeUnload = () => addUserAction?.(this.#userActionAggregator.aggregationEvent);
|
|
66
66
|
addUserAction = aggregatedUserAction => {
|
|
67
67
|
try {
|
|
@@ -73,56 +73,58 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
73
73
|
timeStamp,
|
|
74
74
|
type
|
|
75
75
|
} = aggregatedUserAction.event;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
deadClick
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
errorClick
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
76
|
+
aggregatedUserAction.targets.forEach(mfeTarget => {
|
|
77
|
+
const userActionEvent = {
|
|
78
|
+
eventType: 'UserAction',
|
|
79
|
+
timestamp: this.#toEpoch(timeStamp),
|
|
80
|
+
action: type,
|
|
81
|
+
actionCount: aggregatedUserAction.count,
|
|
82
|
+
actionDuration: aggregatedUserAction.relativeMs[aggregatedUserAction.relativeMs.length - 1],
|
|
83
|
+
actionMs: aggregatedUserAction.relativeMs,
|
|
84
|
+
rageClick: aggregatedUserAction.rageClick,
|
|
85
|
+
target: aggregatedUserAction.selectorPath,
|
|
86
|
+
currentUrl: aggregatedUserAction.currentUrl,
|
|
87
|
+
...((0, _iframe.isIFrameWindow)(window) && {
|
|
88
|
+
iframe: true
|
|
89
|
+
}),
|
|
90
|
+
...this.agentRef.init.user_actions.elementAttributes.reduce((acc, field) => {
|
|
91
|
+
/** prevent us from capturing an obscenely long value */
|
|
92
|
+
if (canTrustTargetAttribute(field)) acc[targetAttrName(field)] = String(target[field]).trim().slice(0, 128);
|
|
93
|
+
return acc;
|
|
94
|
+
}, {}),
|
|
95
|
+
...aggregatedUserAction.nearestTargetFields,
|
|
96
|
+
...(aggregatedUserAction.deadClick && {
|
|
97
|
+
deadClick: true
|
|
98
|
+
}),
|
|
99
|
+
...(aggregatedUserAction.errorClick && {
|
|
100
|
+
errorClick: true
|
|
101
|
+
})
|
|
102
|
+
};
|
|
103
|
+
this.addEvent(userActionEvent, mfeTarget);
|
|
104
|
+
this.#trackUserActionSM(userActionEvent);
|
|
104
105
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Returns the original target field name with `target` prepended and camelCased
|
|
108
|
+
* @param {string} originalFieldName
|
|
109
|
+
* @returns {string} the target field name
|
|
110
|
+
*/
|
|
111
|
+
function targetAttrName(originalFieldName) {
|
|
112
|
+
/** preserve original renaming structure for pre-existing field maps */
|
|
113
|
+
if (originalFieldName === 'tagName') originalFieldName = 'tag';
|
|
114
|
+
if (originalFieldName === 'className') originalFieldName = 'class';
|
|
115
|
+
/** return the original field name, cap'd and prepended with target to match formatting */
|
|
116
|
+
return "target".concat(originalFieldName.charAt(0).toUpperCase() + originalFieldName.slice(1));
|
|
117
|
+
}
|
|
117
118
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Only trust attributes that exist on HTML element targets, which excludes the window and the document targets
|
|
121
|
+
* @param {string} attribute The attribute to check for on the target element
|
|
122
|
+
* @returns {boolean} Whether the target element has the attribute and can be trusted
|
|
123
|
+
*/
|
|
124
|
+
function canTrustTargetAttribute(attribute) {
|
|
125
|
+
return !!(aggregatedUserAction.selectorPath !== 'window' && aggregatedUserAction.selectorPath !== 'document' && target instanceof HTMLElement && target?.[attribute]);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
126
128
|
}
|
|
127
129
|
} catch (e) {
|
|
128
130
|
// do nothing for now
|
|
@@ -322,9 +324,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
322
324
|
timestamp: this.#toEpoch((0, _now.now)()),
|
|
323
325
|
/** all generic events require pageUrl(s) */
|
|
324
326
|
pageUrl: (0, _cleanUrl.cleanURL)('' + _runtime.initialLocation),
|
|
325
|
-
currentUrl: (0, _cleanUrl.cleanURL)('' + location)
|
|
326
|
-
/** Specific attributes only supplied if harvesting to endpoint version 2 */
|
|
327
|
-
...(0, _utils.getVersion2Attributes)(target, this)
|
|
327
|
+
currentUrl: (0, _cleanUrl.cleanURL)('' + location)
|
|
328
328
|
};
|
|
329
329
|
const eventAttributes = {
|
|
330
330
|
/** Agent-level custom attributes */
|
|
@@ -334,7 +334,14 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
334
334
|
/** Event-specific attributes take precedence over agent-level custom attributes and fallbacks */
|
|
335
335
|
...obj
|
|
336
336
|
};
|
|
337
|
-
this.events.add(
|
|
337
|
+
this.events.add({
|
|
338
|
+
...eventAttributes,
|
|
339
|
+
...(0, _utils.getVersion2Attributes)(target, this)
|
|
340
|
+
});
|
|
341
|
+
if ((0, _utils.shouldDuplicate)(target, this.agentRef)) this.addEvent({
|
|
342
|
+
...eventAttributes,
|
|
343
|
+
...(0, _utils.getVersion2DuplicationAttributes)(target, this)
|
|
344
|
+
});
|
|
338
345
|
}
|
|
339
346
|
serializer(eventBuffer) {
|
|
340
347
|
return (0, _traverse.applyFnToProps)({
|
|
@@ -7,7 +7,7 @@ exports.AggregatedUserAction = void 0;
|
|
|
7
7
|
var _constants = require("../../constants");
|
|
8
8
|
var _cleanUrl = require("../../../../common/url/clean-url");
|
|
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
|
|
|
@@ -23,6 +23,7 @@ class AggregatedUserAction {
|
|
|
23
23
|
this.currentUrl = (0, _cleanUrl.cleanURL)('' + location);
|
|
24
24
|
this.deadClick = false;
|
|
25
25
|
this.errorClick = false;
|
|
26
|
+
this.targets = selectorInfo.targets;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
/**
|
|
@@ -10,7 +10,7 @@ var _aggregatedUserAction = require("./aggregated-user-action");
|
|
|
10
10
|
var _timer = require("../../../../common/timer/timer");
|
|
11
11
|
var _nreum = require("../../../../common/window/nreum");
|
|
12
12
|
/**
|
|
13
|
-
* Copyright 2020-
|
|
13
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
14
14
|
* SPDX-License-Identifier: Apache-2.0
|
|
15
15
|
*/
|
|
16
16
|
|
|
@@ -21,7 +21,8 @@ class UserActionsAggregator {
|
|
|
21
21
|
#deadClickTimer = undefined;
|
|
22
22
|
#domObserver = undefined;
|
|
23
23
|
#errorClickTimer = undefined;
|
|
24
|
-
constructor() {
|
|
24
|
+
constructor(agentRef) {
|
|
25
|
+
this.agentRef = agentRef;
|
|
25
26
|
if ((0, _nreum.gosNREUMOriginals)().o.MO) {
|
|
26
27
|
this.#domObserver = new MutationObserver(this.isLiveClick.bind(this));
|
|
27
28
|
}
|
|
@@ -44,7 +45,7 @@ class UserActionsAggregator {
|
|
|
44
45
|
process(evt, targetFields) {
|
|
45
46
|
if (!evt) return;
|
|
46
47
|
const targetElem = _constants.OBSERVED_WINDOW_EVENTS.includes(evt.type) ? window : evt.target;
|
|
47
|
-
const selectorInfo = (0, _selectorPath.analyzeElemPath)(targetElem, targetFields);
|
|
48
|
+
const selectorInfo = (0, _selectorPath.analyzeElemPath)(targetElem, targetFields, this.agentRef);
|
|
48
49
|
|
|
49
50
|
// if selectorInfo.path is undefined, aggregation will be skipped for this event
|
|
50
51
|
const aggregationKey = getAggregationKey(evt, selectorInfo.path);
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2020-
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { getRegisteredTargetsFromId } from '../v2/utils';
|
|
7
|
+
|
|
6
8
|
/**
|
|
7
9
|
* Generates a CSS selector path for the given element, if possible.
|
|
8
10
|
* Also gather metadata about the element's nearest fields, and whether there are any links or buttons in the path.
|
|
@@ -12,12 +14,16 @@
|
|
|
12
14
|
*
|
|
13
15
|
* @param {HTMLElement} elem
|
|
14
16
|
* @param {Array<string>} [targetFields=[]] specifies which fields to gather from the nearest element in the path
|
|
15
|
-
* @returns {{path: (undefined|string), nearestFields: {}, hasButton: boolean, hasLink: boolean}}
|
|
17
|
+
* @returns {{path: (undefined|string), nearestFields: {}, targets: Array, hasButton: boolean, hasLink: boolean}}
|
|
16
18
|
*/
|
|
17
|
-
export const analyzeElemPath = (elem, targetFields = []) => {
|
|
19
|
+
export const analyzeElemPath = (elem, targetFields = [], agentRef) => {
|
|
20
|
+
const targets = [];
|
|
18
21
|
const result = {
|
|
19
22
|
path: undefined,
|
|
20
23
|
nearestFields: {},
|
|
24
|
+
get targets() {
|
|
25
|
+
return targets.length ? targets : [undefined];
|
|
26
|
+
},
|
|
21
27
|
hasButton: false,
|
|
22
28
|
hasLink: false
|
|
23
29
|
};
|
|
@@ -40,6 +46,10 @@ export const analyzeElemPath = (elem, targetFields = []) => {
|
|
|
40
46
|
targetFields.forEach(field => {
|
|
41
47
|
result.nearestFields[nearestAttrName(field)] ||= elem[field]?.baseVal || elem[field];
|
|
42
48
|
});
|
|
49
|
+
const dataAttrs = elem?.dataset;
|
|
50
|
+
if (dataAttrs.nrMfeId) {
|
|
51
|
+
targets.push(...getRegisteredTargetsFromId(dataAttrs.nrMfeId, agentRef));
|
|
52
|
+
}
|
|
43
53
|
pathSelector = buildPathSelector(elem, pathSelector);
|
|
44
54
|
elem = elem.parentNode;
|
|
45
55
|
}
|
|
@@ -14,7 +14,7 @@ import { applyFnToProps } from '../../../common/util/traverse';
|
|
|
14
14
|
import { UserActionsAggregator } from './user-actions/user-actions-aggregator';
|
|
15
15
|
import { isIFrameWindow } from '../../../common/dom/iframe';
|
|
16
16
|
import { isPureObject } from '../../../common/util/type-check';
|
|
17
|
-
import { getVersion2Attributes } from '../../../common/v2/utils';
|
|
17
|
+
import { getVersion2Attributes, getVersion2DuplicationAttributes, shouldDuplicate } from '../../../common/v2/utils';
|
|
18
18
|
export class Aggregate extends AggregateBase {
|
|
19
19
|
static featureName = FEATURE_NAME;
|
|
20
20
|
#userActionAggregator;
|
|
@@ -54,7 +54,7 @@ export class Aggregate extends AggregateBase {
|
|
|
54
54
|
}
|
|
55
55
|
let addUserAction = () => {/** no-op */};
|
|
56
56
|
if (isBrowserScope && agentRef.init.user_actions.enabled) {
|
|
57
|
-
this.#userActionAggregator = new UserActionsAggregator();
|
|
57
|
+
this.#userActionAggregator = new UserActionsAggregator(this.agentRef);
|
|
58
58
|
this.harvestOpts.beforeUnload = () => addUserAction?.(this.#userActionAggregator.aggregationEvent);
|
|
59
59
|
addUserAction = aggregatedUserAction => {
|
|
60
60
|
try {
|
|
@@ -66,56 +66,58 @@ export class Aggregate extends AggregateBase {
|
|
|
66
66
|
timeStamp,
|
|
67
67
|
type
|
|
68
68
|
} = aggregatedUserAction.event;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
deadClick
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
errorClick
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
69
|
+
aggregatedUserAction.targets.forEach(mfeTarget => {
|
|
70
|
+
const userActionEvent = {
|
|
71
|
+
eventType: 'UserAction',
|
|
72
|
+
timestamp: this.#toEpoch(timeStamp),
|
|
73
|
+
action: type,
|
|
74
|
+
actionCount: aggregatedUserAction.count,
|
|
75
|
+
actionDuration: aggregatedUserAction.relativeMs[aggregatedUserAction.relativeMs.length - 1],
|
|
76
|
+
actionMs: aggregatedUserAction.relativeMs,
|
|
77
|
+
rageClick: aggregatedUserAction.rageClick,
|
|
78
|
+
target: aggregatedUserAction.selectorPath,
|
|
79
|
+
currentUrl: aggregatedUserAction.currentUrl,
|
|
80
|
+
...(isIFrameWindow(window) && {
|
|
81
|
+
iframe: true
|
|
82
|
+
}),
|
|
83
|
+
...this.agentRef.init.user_actions.elementAttributes.reduce((acc, field) => {
|
|
84
|
+
/** prevent us from capturing an obscenely long value */
|
|
85
|
+
if (canTrustTargetAttribute(field)) acc[targetAttrName(field)] = String(target[field]).trim().slice(0, 128);
|
|
86
|
+
return acc;
|
|
87
|
+
}, {}),
|
|
88
|
+
...aggregatedUserAction.nearestTargetFields,
|
|
89
|
+
...(aggregatedUserAction.deadClick && {
|
|
90
|
+
deadClick: true
|
|
91
|
+
}),
|
|
92
|
+
...(aggregatedUserAction.errorClick && {
|
|
93
|
+
errorClick: true
|
|
94
|
+
})
|
|
95
|
+
};
|
|
96
|
+
this.addEvent(userActionEvent, mfeTarget);
|
|
97
|
+
this.#trackUserActionSM(userActionEvent);
|
|
97
98
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
99
|
+
/**
|
|
100
|
+
* Returns the original target field name with `target` prepended and camelCased
|
|
101
|
+
* @param {string} originalFieldName
|
|
102
|
+
* @returns {string} the target field name
|
|
103
|
+
*/
|
|
104
|
+
function targetAttrName(originalFieldName) {
|
|
105
|
+
/** preserve original renaming structure for pre-existing field maps */
|
|
106
|
+
if (originalFieldName === 'tagName') originalFieldName = 'tag';
|
|
107
|
+
if (originalFieldName === 'className') originalFieldName = 'class';
|
|
108
|
+
/** return the original field name, cap'd and prepended with target to match formatting */
|
|
109
|
+
return "target".concat(originalFieldName.charAt(0).toUpperCase() + originalFieldName.slice(1));
|
|
110
|
+
}
|
|
110
111
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Only trust attributes that exist on HTML element targets, which excludes the window and the document targets
|
|
114
|
+
* @param {string} attribute The attribute to check for on the target element
|
|
115
|
+
* @returns {boolean} Whether the target element has the attribute and can be trusted
|
|
116
|
+
*/
|
|
117
|
+
function canTrustTargetAttribute(attribute) {
|
|
118
|
+
return !!(aggregatedUserAction.selectorPath !== 'window' && aggregatedUserAction.selectorPath !== 'document' && target instanceof HTMLElement && target?.[attribute]);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
119
121
|
}
|
|
120
122
|
} catch (e) {
|
|
121
123
|
// do nothing for now
|
|
@@ -315,9 +317,7 @@ export class Aggregate extends AggregateBase {
|
|
|
315
317
|
timestamp: this.#toEpoch(now()),
|
|
316
318
|
/** all generic events require pageUrl(s) */
|
|
317
319
|
pageUrl: cleanURL('' + initialLocation),
|
|
318
|
-
currentUrl: cleanURL('' + location)
|
|
319
|
-
/** Specific attributes only supplied if harvesting to endpoint version 2 */
|
|
320
|
-
...getVersion2Attributes(target, this)
|
|
320
|
+
currentUrl: cleanURL('' + location)
|
|
321
321
|
};
|
|
322
322
|
const eventAttributes = {
|
|
323
323
|
/** Agent-level custom attributes */
|
|
@@ -327,7 +327,14 @@ export class Aggregate extends AggregateBase {
|
|
|
327
327
|
/** Event-specific attributes take precedence over agent-level custom attributes and fallbacks */
|
|
328
328
|
...obj
|
|
329
329
|
};
|
|
330
|
-
this.events.add(
|
|
330
|
+
this.events.add({
|
|
331
|
+
...eventAttributes,
|
|
332
|
+
...getVersion2Attributes(target, this)
|
|
333
|
+
});
|
|
334
|
+
if (shouldDuplicate(target, this.agentRef)) this.addEvent({
|
|
335
|
+
...eventAttributes,
|
|
336
|
+
...getVersion2DuplicationAttributes(target, this)
|
|
337
|
+
});
|
|
331
338
|
}
|
|
332
339
|
serializer(eventBuffer) {
|
|
333
340
|
return applyFnToProps({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2020-
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
import { RAGE_CLICK_THRESHOLD_EVENTS, RAGE_CLICK_THRESHOLD_MS } from '../../constants';
|
|
@@ -16,6 +16,7 @@ export class AggregatedUserAction {
|
|
|
16
16
|
this.currentUrl = cleanURL('' + location);
|
|
17
17
|
this.deadClick = false;
|
|
18
18
|
this.errorClick = false;
|
|
19
|
+
this.targets = selectorInfo.targets;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2020-
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
import { analyzeElemPath } from '../../../../common/dom/selector-path';
|
|
@@ -14,7 +14,8 @@ export class UserActionsAggregator {
|
|
|
14
14
|
#deadClickTimer = undefined;
|
|
15
15
|
#domObserver = undefined;
|
|
16
16
|
#errorClickTimer = undefined;
|
|
17
|
-
constructor() {
|
|
17
|
+
constructor(agentRef) {
|
|
18
|
+
this.agentRef = agentRef;
|
|
18
19
|
if (gosNREUMOriginals().o.MO) {
|
|
19
20
|
this.#domObserver = new MutationObserver(this.isLiveClick.bind(this));
|
|
20
21
|
}
|
|
@@ -37,7 +38,7 @@ export class UserActionsAggregator {
|
|
|
37
38
|
process(evt, targetFields) {
|
|
38
39
|
if (!evt) return;
|
|
39
40
|
const targetElem = OBSERVED_WINDOW_EVENTS.includes(evt.type) ? window : evt.target;
|
|
40
|
-
const selectorInfo = analyzeElemPath(targetElem, targetFields);
|
|
41
|
+
const selectorInfo = analyzeElemPath(targetElem, targetFields, this.agentRef);
|
|
41
42
|
|
|
42
43
|
// if selectorInfo.path is undefined, aggregation will be skipped for this event
|
|
43
44
|
const aggregationKey = getAggregationKey(evt, selectorInfo.path);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export function analyzeElemPath(elem: HTMLElement, targetFields?: Array<string
|
|
1
|
+
export function analyzeElemPath(elem: HTMLElement, targetFields?: Array<string>, agentRef: any): {
|
|
2
2
|
path: (undefined | string);
|
|
3
3
|
nearestFields: {};
|
|
4
|
+
targets: any[];
|
|
4
5
|
hasButton: boolean;
|
|
5
6
|
hasLink: boolean;
|
|
6
7
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selector-path.d.ts","sourceRoot":"","sources":["../../../../src/common/dom/selector-path.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"selector-path.d.ts","sourceRoot":"","sources":["../../../../src/common/dom/selector-path.js"],"names":[],"mappings":"AAkBO,sCAJI,WAAW,iBACX,KAAK,CAAC,MAAM,CAAC,kBACX;IAAC,IAAI,EAAE,CAAC,SAAS,GAAC,MAAM,CAAC,CAAC;IAAC,aAAa,EAAE,EAAE,CAAC;IAAC,OAAO,QAAQ;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAC,CAkC/G"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/generic_events/aggregate/index.js"],"names":[],"mappings":"AAkBA;IACE,2BAAiC;IAGjC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/generic_events/aggregate/index.js"],"names":[],"mappings":"AAkBA;IACE,2BAAiC;IAGjC,2BAyQC;IAvQC,gCAAkG;IA0QpG;;;;;;;;;;;;OAYG;IACH,eAJW,MAAM,YAAC,WACP,MAAM,YAAC,QAkCjB;IAED,qCAEC;IAED;;;MAEC;;CAsBF;8BApW6B,4BAA4B"}
|
package/dist/types/features/generic_events/aggregate/user-actions/aggregated-user-action.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export class AggregatedUserAction {
|
|
|
10
10
|
currentUrl: string;
|
|
11
11
|
deadClick: boolean;
|
|
12
12
|
errorClick: boolean;
|
|
13
|
+
targets: any;
|
|
13
14
|
/**
|
|
14
15
|
* Aggregates the count and maintains the relative MS array for matching events
|
|
15
16
|
* Will determine if a rage click was observed as part of the aggregation
|
package/dist/types/features/generic_events/aggregate/user-actions/aggregated-user-action.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aggregated-user-action.d.ts","sourceRoot":"","sources":["../../../../../../src/features/generic_events/aggregate/user-actions/aggregated-user-action.js"],"names":[],"mappings":"AAOA;IACE,
|
|
1
|
+
{"version":3,"file":"aggregated-user-action.d.ts","sourceRoot":"","sources":["../../../../../../src/features/generic_events/aggregate/user-actions/aggregated-user-action.js"],"names":[],"mappings":"AAOA;IACE,yCAYC;IAXC,WAAgB;IAChB,cAAc;IACd,iBAAyC;IACzC,qBAAqB;IACrB,kBAAqC;IACrC,+BAA0B;IAC1B,yBAAqD;IACrD,mBAAyC;IACzC,mBAAsB;IACtB,oBAAuB;IACvB,aAAmC;IAGrC;;;;;OAKG;IACH,eAHW,KAAK,GACH,IAAI,CAMhB;IAED;;;OAGG;IACH,eAFa,OAAO,CAKnB;CACF"}
|
package/dist/types/features/generic_events/aggregate/user-actions/user-actions-aggregator.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export class UserActionsAggregator {
|
|
2
|
+
constructor(agentRef: any);
|
|
3
|
+
agentRef: any;
|
|
2
4
|
get aggregationEvent(): AggregatedUserAction | undefined;
|
|
3
5
|
/**
|
|
4
6
|
* Process the event and determine if a new aggregation set should be made or if it should increment the current aggregation
|
package/dist/types/features/generic_events/aggregate/user-actions/user-actions-aggregator.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-actions-aggregator.d.ts","sourceRoot":"","sources":["../../../../../../src/features/generic_events/aggregate/user-actions/user-actions-aggregator.js"],"names":[],"mappings":"AAUA;
|
|
1
|
+
{"version":3,"file":"user-actions-aggregator.d.ts","sourceRoot":"","sources":["../../../../../../src/features/generic_events/aggregate/user-actions/user-actions-aggregator.js"],"names":[],"mappings":"AAUA;IAQE,2BAKC;IAJC,cAAwB;IAM1B,yDAQC;IAED;;;;OAIG;IACH,aAHW,KAAK,sBACH,oBAAoB,GAAC,SAAS,CA2B1C;IAED,yBAKC;IA0CD,oBAEC;;CACF;qCA/GoC,0BAA0B"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2020-
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { getRegisteredTargetsFromId } from '../v2/utils'
|
|
7
|
+
|
|
6
8
|
/**
|
|
7
9
|
* Generates a CSS selector path for the given element, if possible.
|
|
8
10
|
* Also gather metadata about the element's nearest fields, and whether there are any links or buttons in the path.
|
|
@@ -12,10 +14,11 @@
|
|
|
12
14
|
*
|
|
13
15
|
* @param {HTMLElement} elem
|
|
14
16
|
* @param {Array<string>} [targetFields=[]] specifies which fields to gather from the nearest element in the path
|
|
15
|
-
* @returns {{path: (undefined|string), nearestFields: {}, hasButton: boolean, hasLink: boolean}}
|
|
17
|
+
* @returns {{path: (undefined|string), nearestFields: {}, targets: Array, hasButton: boolean, hasLink: boolean}}
|
|
16
18
|
*/
|
|
17
|
-
export const analyzeElemPath = (elem, targetFields = []) => {
|
|
18
|
-
const
|
|
19
|
+
export const analyzeElemPath = (elem, targetFields = [], agentRef) => {
|
|
20
|
+
const targets = []
|
|
21
|
+
const result = { path: undefined, nearestFields: {}, get targets () { return targets.length ? targets : [undefined] }, hasButton: false, hasLink: false }
|
|
19
22
|
if (!elem) return result
|
|
20
23
|
if (elem === window) { result.path = 'window'; return result }
|
|
21
24
|
if (elem === document) { result.path = 'document'; return result }
|
|
@@ -30,6 +33,12 @@ export const analyzeElemPath = (elem, targetFields = []) => {
|
|
|
30
33
|
result.hasButton ||= tagName === 'button' || (tagName === 'input' && elem.type.toLowerCase() === 'button')
|
|
31
34
|
|
|
32
35
|
targetFields.forEach(field => { result.nearestFields[nearestAttrName(field)] ||= (elem[field]?.baseVal || elem[field]) })
|
|
36
|
+
|
|
37
|
+
const dataAttrs = elem?.dataset
|
|
38
|
+
if (dataAttrs.nrMfeId) {
|
|
39
|
+
targets.push(...getRegisteredTargetsFromId(dataAttrs.nrMfeId, agentRef))
|
|
40
|
+
}
|
|
41
|
+
|
|
33
42
|
pathSelector = buildPathSelector(elem, pathSelector)
|
|
34
43
|
elem = elem.parentNode
|
|
35
44
|
}
|
|
@@ -14,7 +14,7 @@ import { applyFnToProps } from '../../../common/util/traverse'
|
|
|
14
14
|
import { UserActionsAggregator } from './user-actions/user-actions-aggregator'
|
|
15
15
|
import { isIFrameWindow } from '../../../common/dom/iframe'
|
|
16
16
|
import { isPureObject } from '../../../common/util/type-check'
|
|
17
|
-
import { getVersion2Attributes } from '../../../common/v2/utils'
|
|
17
|
+
import { getVersion2Attributes, getVersion2DuplicationAttributes, shouldDuplicate } from '../../../common/v2/utils'
|
|
18
18
|
|
|
19
19
|
export class Aggregate extends AggregateBase {
|
|
20
20
|
static featureName = FEATURE_NAME
|
|
@@ -61,7 +61,7 @@ export class Aggregate extends AggregateBase {
|
|
|
61
61
|
|
|
62
62
|
let addUserAction = () => { /** no-op */ }
|
|
63
63
|
if (isBrowserScope && agentRef.init.user_actions.enabled) {
|
|
64
|
-
this.#userActionAggregator = new UserActionsAggregator()
|
|
64
|
+
this.#userActionAggregator = new UserActionsAggregator(this.agentRef)
|
|
65
65
|
this.harvestOpts.beforeUnload = () => addUserAction?.(this.#userActionAggregator.aggregationEvent)
|
|
66
66
|
|
|
67
67
|
addUserAction = (aggregatedUserAction) => {
|
|
@@ -70,50 +70,54 @@ export class Aggregate extends AggregateBase {
|
|
|
70
70
|
* so we still need to validate that an event was given to this method before we try to add */
|
|
71
71
|
if (aggregatedUserAction?.event) {
|
|
72
72
|
const { target, timeStamp, type } = aggregatedUserAction.event
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
73
|
+
|
|
74
|
+
aggregatedUserAction.targets.forEach(mfeTarget => {
|
|
75
|
+
const userActionEvent = {
|
|
76
|
+
eventType: 'UserAction',
|
|
77
|
+
timestamp: this.#toEpoch(timeStamp),
|
|
78
|
+
action: type,
|
|
79
|
+
actionCount: aggregatedUserAction.count,
|
|
80
|
+
actionDuration: aggregatedUserAction.relativeMs[aggregatedUserAction.relativeMs.length - 1],
|
|
81
|
+
actionMs: aggregatedUserAction.relativeMs,
|
|
82
|
+
rageClick: aggregatedUserAction.rageClick,
|
|
83
|
+
target: aggregatedUserAction.selectorPath,
|
|
84
|
+
currentUrl: aggregatedUserAction.currentUrl,
|
|
85
|
+
...(isIFrameWindow(window) && { iframe: true }),
|
|
86
|
+
...(this.agentRef.init.user_actions.elementAttributes.reduce((acc, field) => {
|
|
85
87
|
/** prevent us from capturing an obscenely long value */
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
88
|
+
if (canTrustTargetAttribute(field)) acc[targetAttrName(field)] = String(target[field]).trim().slice(0, 128)
|
|
89
|
+
return acc
|
|
90
|
+
}, {})),
|
|
91
|
+
...aggregatedUserAction.nearestTargetFields,
|
|
92
|
+
...(aggregatedUserAction.deadClick && { deadClick: true }),
|
|
93
|
+
...(aggregatedUserAction.errorClick && { errorClick: true })
|
|
94
|
+
}
|
|
95
|
+
this.addEvent(userActionEvent, mfeTarget)
|
|
96
|
+
|
|
97
|
+
this.#trackUserActionSM(userActionEvent)
|
|
98
|
+
|
|
99
|
+
/**
|
|
97
100
|
* Returns the original target field name with `target` prepended and camelCased
|
|
98
101
|
* @param {string} originalFieldName
|
|
99
102
|
* @returns {string} the target field name
|
|
100
103
|
*/
|
|
101
|
-
|
|
104
|
+
function targetAttrName (originalFieldName) {
|
|
102
105
|
/** preserve original renaming structure for pre-existing field maps */
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
if (originalFieldName === 'tagName') originalFieldName = 'tag'
|
|
107
|
+
if (originalFieldName === 'className') originalFieldName = 'class'
|
|
108
|
+
/** return the original field name, cap'd and prepended with target to match formatting */
|
|
109
|
+
return `target${originalFieldName.charAt(0).toUpperCase() + originalFieldName.slice(1)}`
|
|
110
|
+
}
|
|
108
111
|
|
|
109
|
-
|
|
112
|
+
/**
|
|
110
113
|
* Only trust attributes that exist on HTML element targets, which excludes the window and the document targets
|
|
111
114
|
* @param {string} attribute The attribute to check for on the target element
|
|
112
115
|
* @returns {boolean} Whether the target element has the attribute and can be trusted
|
|
113
116
|
*/
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
+
function canTrustTargetAttribute (attribute) {
|
|
118
|
+
return !!(aggregatedUserAction.selectorPath !== 'window' && aggregatedUserAction.selectorPath !== 'document' && target instanceof HTMLElement && target?.[attribute])
|
|
119
|
+
}
|
|
120
|
+
})
|
|
117
121
|
}
|
|
118
122
|
} catch (e) {
|
|
119
123
|
// do nothing for now
|
|
@@ -314,9 +318,7 @@ export class Aggregate extends AggregateBase {
|
|
|
314
318
|
timestamp: this.#toEpoch(now()),
|
|
315
319
|
/** all generic events require pageUrl(s) */
|
|
316
320
|
pageUrl: cleanURL('' + initialLocation),
|
|
317
|
-
currentUrl: cleanURL('' + location)
|
|
318
|
-
/** Specific attributes only supplied if harvesting to endpoint version 2 */
|
|
319
|
-
...(getVersion2Attributes(target, this))
|
|
321
|
+
currentUrl: cleanURL('' + location)
|
|
320
322
|
}
|
|
321
323
|
|
|
322
324
|
const eventAttributes = {
|
|
@@ -328,7 +330,8 @@ export class Aggregate extends AggregateBase {
|
|
|
328
330
|
...obj
|
|
329
331
|
}
|
|
330
332
|
|
|
331
|
-
this.events.add(eventAttributes)
|
|
333
|
+
this.events.add({ ...eventAttributes, ...getVersion2Attributes(target, this) })
|
|
334
|
+
if (shouldDuplicate(target, this.agentRef)) this.addEvent({ ...eventAttributes, ...getVersion2DuplicationAttributes(target, this) })
|
|
332
335
|
}
|
|
333
336
|
|
|
334
337
|
serializer (eventBuffer) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2020-
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
import { RAGE_CLICK_THRESHOLD_EVENTS, RAGE_CLICK_THRESHOLD_MS } from '../../constants'
|
|
@@ -17,6 +17,7 @@ export class AggregatedUserAction {
|
|
|
17
17
|
this.currentUrl = cleanURL('' + location)
|
|
18
18
|
this.deadClick = false
|
|
19
19
|
this.errorClick = false
|
|
20
|
+
this.targets = selectorInfo.targets
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2020-
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
import { analyzeElemPath } from '../../../../common/dom/selector-path'
|
|
@@ -16,7 +16,8 @@ export class UserActionsAggregator {
|
|
|
16
16
|
#domObserver = undefined
|
|
17
17
|
#errorClickTimer = undefined
|
|
18
18
|
|
|
19
|
-
constructor () {
|
|
19
|
+
constructor (agentRef) {
|
|
20
|
+
this.agentRef = agentRef
|
|
20
21
|
if (gosNREUMOriginals().o.MO) {
|
|
21
22
|
this.#domObserver = new MutationObserver(this.isLiveClick.bind(this))
|
|
22
23
|
}
|
|
@@ -40,7 +41,7 @@ export class UserActionsAggregator {
|
|
|
40
41
|
process (evt, targetFields) {
|
|
41
42
|
if (!evt) return
|
|
42
43
|
const targetElem = OBSERVED_WINDOW_EVENTS.includes(evt.type) ? window : evt.target
|
|
43
|
-
const selectorInfo = analyzeElemPath(targetElem, targetFields)
|
|
44
|
+
const selectorInfo = analyzeElemPath(targetElem, targetFields, this.agentRef)
|
|
44
45
|
|
|
45
46
|
// if selectorInfo.path is undefined, aggregation will be skipped for this event
|
|
46
47
|
const aggregationKey = getAggregationKey(evt, selectorInfo.path)
|