@newrelic/browser-agent 1.313.1 → 1.314.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/dist/cjs/common/constants/agent-constants.js +2 -1
- 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/common/timing/time-keeper.js +18 -6
- package/dist/cjs/common/vitals/cumulative-layout-shift.js +3 -2
- package/dist/cjs/common/vitals/interaction-to-next-paint.js +3 -2
- package/dist/cjs/common/vitals/largest-contentful-paint.js +2 -1
- package/dist/cjs/common/vitals/load-time.js +5 -2
- package/dist/cjs/common/vitals/vital-metric.js +7 -4
- package/dist/cjs/features/ajax/aggregate/index.js +6 -2
- package/dist/cjs/features/ajax/constants.js +4 -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/cjs/features/page_view_timing/aggregate/index.js +27 -6
- package/dist/cjs/features/session_replay/aggregate/index.js +15 -6
- package/dist/cjs/features/session_replay/constants.js +1 -1
- package/dist/cjs/features/session_replay/shared/recorder.js +3 -1
- package/dist/cjs/features/soft_navigations/aggregate/ajax-node.js +7 -3
- package/dist/esm/common/constants/agent-constants.js +2 -1
- 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/common/timing/time-keeper.js +18 -6
- package/dist/esm/common/vitals/cumulative-layout-shift.js +3 -2
- package/dist/esm/common/vitals/interaction-to-next-paint.js +3 -2
- package/dist/esm/common/vitals/largest-contentful-paint.js +2 -1
- package/dist/esm/common/vitals/load-time.js +5 -2
- package/dist/esm/common/vitals/vital-metric.js +7 -4
- package/dist/esm/features/ajax/aggregate/index.js +7 -3
- package/dist/esm/features/ajax/constants.js +3 -2
- 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/esm/features/page_view_timing/aggregate/index.js +27 -6
- package/dist/esm/features/session_replay/aggregate/index.js +15 -6
- package/dist/esm/features/session_replay/constants.js +1 -1
- package/dist/esm/features/session_replay/shared/recorder.js +3 -1
- package/dist/esm/features/soft_navigations/aggregate/ajax-node.js +5 -1
- package/dist/types/common/constants/agent-constants.d.ts +1 -0
- package/dist/types/common/constants/agent-constants.d.ts.map +1 -1
- 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/common/timing/time-keeper.d.ts.map +1 -1
- package/dist/types/common/vitals/vital-metric.d.ts +3 -2
- package/dist/types/common/vitals/vital-metric.d.ts.map +1 -1
- package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/ajax/constants.d.ts +1 -0
- package/dist/types/features/ajax/constants.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/dist/types/features/page_view_timing/aggregate/index.d.ts +1 -1
- package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts +1 -11
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/recorder.d.ts +2 -0
- package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
- package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts +1 -0
- package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/common/constants/agent-constants.js +2 -1
- package/src/common/dom/selector-path.js +13 -4
- package/src/common/timing/time-keeper.js +17 -6
- package/src/common/vitals/cumulative-layout-shift.js +2 -2
- package/src/common/vitals/interaction-to-next-paint.js +2 -2
- package/src/common/vitals/largest-contentful-paint.js +1 -1
- package/src/common/vitals/load-time.js +5 -2
- package/src/common/vitals/vital-metric.js +6 -4
- package/src/features/ajax/aggregate/index.js +6 -3
- package/src/features/ajax/constants.js +3 -1
- 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
- package/src/features/page_view_timing/aggregate/index.js +14 -6
- package/src/features/session_replay/aggregate/index.js +16 -5
- package/src/features/session_replay/constants.js +1 -1
- package/src/features/session_replay/shared/recorder.js +3 -1
- package/src/features/soft_navigations/aggregate/ajax-node.js +4 -1
|
@@ -5,7 +5,8 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.AjaxNode = void 0;
|
|
7
7
|
var _belSerializer = require("../../../common/serialize/bel-serializer");
|
|
8
|
-
var _constants = require("
|
|
8
|
+
var _constants = require("../../ajax/constants");
|
|
9
|
+
var _constants2 = require("../constants");
|
|
9
10
|
var _belNode = require("./bel-node");
|
|
10
11
|
/**
|
|
11
12
|
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
@@ -15,7 +16,7 @@ var _belNode = require("./bel-node");
|
|
|
15
16
|
class AjaxNode extends _belNode.BelNode {
|
|
16
17
|
constructor(ajaxEvent, ajaxContext) {
|
|
17
18
|
super();
|
|
18
|
-
this.belType =
|
|
19
|
+
this.belType = _constants2.NODE_TYPE.AJAX;
|
|
19
20
|
this.method = ajaxEvent.method;
|
|
20
21
|
this.status = ajaxEvent.status;
|
|
21
22
|
this.domain = ajaxEvent.domain;
|
|
@@ -28,6 +29,8 @@ class AjaxNode extends _belNode.BelNode {
|
|
|
28
29
|
this.spanTimestamp = ajaxEvent.spanTimestamp;
|
|
29
30
|
this.gql = ajaxEvent.gql;
|
|
30
31
|
this.targetAttributes = ajaxEvent.targetAttributes;
|
|
32
|
+
this[_constants.AJAX_ID] = ajaxEvent[_constants.AJAX_ID]; // all AjaxRequest events should have a unique identifier to allow for easier grouping and analysis in the UI
|
|
33
|
+
|
|
31
34
|
this.start = ajaxEvent.startTime;
|
|
32
35
|
this.end = ajaxEvent.endTime;
|
|
33
36
|
if (ajaxContext?.latestLongtaskEnd) {
|
|
@@ -53,7 +56,8 @@ class AjaxNode extends _belNode.BelNode {
|
|
|
53
56
|
addString(this.method), (0, _belSerializer.numeric)(this.status), addString(this.domain), addString(this.path), (0, _belSerializer.numeric)(this.txSize), (0, _belSerializer.numeric)(this.rxSize), this.requestedWith, addString(this.nodeId), (0, _belSerializer.nullable)(this.spanId, addString, true) + (0, _belSerializer.nullable)(this.traceId, addString, true) + (0, _belSerializer.nullable)(this.spanTimestamp, _belSerializer.numeric)];
|
|
54
57
|
let allAttachedNodes = (0, _belSerializer.addCustomAttributes)({
|
|
55
58
|
...(this.gql || {}),
|
|
56
|
-
...(this.targetAttributes || {})
|
|
59
|
+
...(this.targetAttributes || {}),
|
|
60
|
+
[_constants.AJAX_ID]: this[_constants.AJAX_ID]
|
|
57
61
|
}, addString);
|
|
58
62
|
this.children.forEach(node => allAttachedNodes.push(node.serialize())); // no children is expected under ajax nodes at this time
|
|
59
63
|
|
|
@@ -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
|
}
|
|
@@ -39,14 +39,19 @@ export class TimeKeeper {
|
|
|
39
39
|
* @type {boolean}
|
|
40
40
|
*/
|
|
41
41
|
#ready = false;
|
|
42
|
-
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The total measured drift in milliseconds. Represents how much performance.now()
|
|
45
|
+
* has fallen behind Date.now(), which is used to correct timestamp conversions.
|
|
46
|
+
* @type {number}
|
|
47
|
+
*/
|
|
48
|
+
#measuredDrift = 0;
|
|
43
49
|
constructor(sessionObj) {
|
|
44
50
|
this.#session = sessionObj;
|
|
45
51
|
this.processStoredDiff();
|
|
46
52
|
isNative(performance.now, Date.now); // will warn the user if these are not native functions. We need these to be native for time in the agent to be accurate in general.
|
|
47
53
|
}
|
|
48
54
|
#detectDrift() {
|
|
49
|
-
if (this.#reportedDrift) return;
|
|
50
55
|
try {
|
|
51
56
|
// Drift detection: measures if performance.now() and Date.now() have become desynchronized
|
|
52
57
|
// This can happen when a machine sleeps and the performance timer freezes while Date continues
|
|
@@ -57,8 +62,13 @@ export class TimeKeeper {
|
|
|
57
62
|
// Note: localTimeDiff (server time offset) is NOT part of drift - that's a legitimate offset
|
|
58
63
|
const drift = Date.now() - originTime - performance.now();
|
|
59
64
|
if (drift > 1000) {
|
|
60
|
-
this
|
|
61
|
-
|
|
65
|
+
// Check if this is new drift (increase of >1000ms from last measurement)
|
|
66
|
+
const newDrift = drift - this.#measuredDrift;
|
|
67
|
+
if (newDrift > 1000) {
|
|
68
|
+
// Update measured drift and report it
|
|
69
|
+
this.#measuredDrift = drift;
|
|
70
|
+
if (this.#session) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Generic/TimeKeeper/ClockDrift/Detected', drift], undefined, FEATURE_NAMES.metrics, this.#session.agentRef.ee);
|
|
71
|
+
}
|
|
62
72
|
}
|
|
63
73
|
} catch (err) {
|
|
64
74
|
// Silently ignore drift detection errors to avoid breaking normal operation
|
|
@@ -109,7 +119,8 @@ export class TimeKeeper {
|
|
|
109
119
|
*/
|
|
110
120
|
convertRelativeTimestamp(relativeTime) {
|
|
111
121
|
this.#detectDrift();
|
|
112
|
-
|
|
122
|
+
// Add measured drift to compensate for performance.now() falling behind
|
|
123
|
+
return originTime + relativeTime + this.#measuredDrift;
|
|
113
124
|
}
|
|
114
125
|
|
|
115
126
|
/**
|
|
@@ -120,7 +131,8 @@ export class TimeKeeper {
|
|
|
120
131
|
*/
|
|
121
132
|
convertAbsoluteTimestamp(timestamp) {
|
|
122
133
|
this.#detectDrift();
|
|
123
|
-
|
|
134
|
+
// Subtract measured drift since we're converting from absolute to relative
|
|
135
|
+
return timestamp - originTime - this.#measuredDrift;
|
|
124
136
|
}
|
|
125
137
|
|
|
126
138
|
/**
|
|
@@ -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 { onCLS } from 'web-vitals/attribution';
|
|
@@ -22,7 +22,8 @@ if (isBrowserScope) {
|
|
|
22
22
|
};
|
|
23
23
|
cumulativeLayoutShift.update({
|
|
24
24
|
value,
|
|
25
|
-
attrs
|
|
25
|
+
attrs,
|
|
26
|
+
element: attribution.largestShiftSource?.node
|
|
26
27
|
});
|
|
27
28
|
}, {
|
|
28
29
|
reportAllChanges: true
|
|
@@ -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 { onINP } from 'web-vitals/attribution';
|
|
@@ -31,7 +31,8 @@ if (isBrowserScope) {
|
|
|
31
31
|
};
|
|
32
32
|
interactionToNextPaint.update({
|
|
33
33
|
value,
|
|
34
|
-
attrs
|
|
34
|
+
attrs,
|
|
35
|
+
element: attribution.interactionTargetElement
|
|
35
36
|
});
|
|
36
37
|
});
|
|
37
38
|
}
|
|
@@ -10,12 +10,15 @@ export const loadTime = new VitalMetric(VITAL_NAMES.LOAD_TIME);
|
|
|
10
10
|
if (isBrowserScope) {
|
|
11
11
|
const perf = globalScope.performance;
|
|
12
12
|
const handler = () => {
|
|
13
|
-
|
|
13
|
+
// setTimeout defers the read until after the load event handler returns,
|
|
14
|
+
// ensuring loadEventEnd is populated (non-zero) — matching the web-vitals onTTFB pattern
|
|
15
|
+
setTimeout(() => {
|
|
16
|
+
if (loadTime.isValid || !perf) return;
|
|
14
17
|
const navEntry = getNavigationEntry();
|
|
15
18
|
loadTime.update({
|
|
16
19
|
value: navEntry ? navEntry.loadEventEnd : perf.timing?.loadEventEnd - originTime
|
|
17
20
|
});
|
|
18
|
-
}
|
|
21
|
+
}, 0);
|
|
19
22
|
};
|
|
20
23
|
onWindowLoad(handler, true);
|
|
21
24
|
}
|
|
@@ -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
|
export class VitalMetric {
|
|
@@ -12,13 +12,15 @@ export class VitalMetric {
|
|
|
12
12
|
}
|
|
13
13
|
update({
|
|
14
14
|
value,
|
|
15
|
-
attrs = {}
|
|
15
|
+
attrs = {},
|
|
16
|
+
element
|
|
16
17
|
}) {
|
|
17
18
|
if (value === undefined || value === null || value < 0) return;
|
|
18
19
|
const state = {
|
|
19
20
|
value: this.roundingMethod(value),
|
|
20
21
|
name: this.name,
|
|
21
|
-
attrs
|
|
22
|
+
attrs,
|
|
23
|
+
element
|
|
22
24
|
};
|
|
23
25
|
this.history.push(state);
|
|
24
26
|
this.#subscribers.forEach(cb => {
|
|
@@ -33,7 +35,8 @@ export class VitalMetric {
|
|
|
33
35
|
return this.history[this.history.length - 1] || {
|
|
34
36
|
value: undefined,
|
|
35
37
|
name: this.name,
|
|
36
|
-
attrs: {}
|
|
38
|
+
attrs: {},
|
|
39
|
+
element: undefined
|
|
37
40
|
};
|
|
38
41
|
}
|
|
39
42
|
get isValid() {
|
|
@@ -6,13 +6,14 @@ import { registerHandler } from '../../../common/event-emitter/register-handler'
|
|
|
6
6
|
import { stringify } from '../../../common/util/stringify';
|
|
7
7
|
import { handle } from '../../../common/event-emitter/handle';
|
|
8
8
|
import { setDenyList, shouldCollectEvent } from '../../../common/deny-list/deny-list';
|
|
9
|
-
import { FEATURE_NAME } from '../constants';
|
|
9
|
+
import { AJAX_ID, FEATURE_NAME } from '../constants';
|
|
10
10
|
import { FEATURE_NAMES } from '../../../loaders/features/features';
|
|
11
11
|
import { AggregateBase } from '../../utils/aggregate-base';
|
|
12
12
|
import { parseGQL } from './gql';
|
|
13
13
|
import { nullable, numeric, getAddStringContext, addCustomAttributes } from '../../../common/serialize/bel-serializer';
|
|
14
14
|
import { gosNREUMOriginals } from '../../../common/window/nreum';
|
|
15
15
|
import { getVersion2Attributes, getVersion2DuplicationAttributes, shouldDuplicate } from '../../../common/v2/utils';
|
|
16
|
+
import { generateUuid } from '../../../common/ids/unique-id';
|
|
16
17
|
export class Aggregate extends AggregateBase {
|
|
17
18
|
static featureName = FEATURE_NAME;
|
|
18
19
|
constructor(agentRef) {
|
|
@@ -80,7 +81,8 @@ export class Aggregate extends AggregateBase {
|
|
|
80
81
|
type,
|
|
81
82
|
startTime,
|
|
82
83
|
endTime,
|
|
83
|
-
callbackDuration: metrics.cbTime
|
|
84
|
+
callbackDuration: metrics.cbTime,
|
|
85
|
+
[AJAX_ID]: generateUuid() // all AjaxRequest events should have a unique identifier to allow for easier grouping and analysis in the UI
|
|
84
86
|
};
|
|
85
87
|
if (ctx.dt) {
|
|
86
88
|
event.spanId = ctx.dt.spanId;
|
|
@@ -150,7 +152,9 @@ export class Aggregate extends AggregateBase {
|
|
|
150
152
|
const attrParts = addCustomAttributes({
|
|
151
153
|
...(jsAttributes || {}),
|
|
152
154
|
...(event.gql || {}),
|
|
153
|
-
...(event.targetAttributes || {})
|
|
155
|
+
...(event.targetAttributes || {}),
|
|
156
|
+
// used to supply the version 2 attributes, either MFE target or duplication attributes for the main agent app
|
|
157
|
+
[AJAX_ID]: event[AJAX_ID] // all AjaxRequest events should have a unique identifier to allow for easier grouping and analysis in the UI
|
|
154
158
|
}, addString);
|
|
155
159
|
fields.unshift(numeric(attrParts.length));
|
|
156
160
|
insert += fields.join(',');
|
|
@@ -1,6 +1,7 @@
|
|
|
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 { FEATURE_NAMES } from '../../loaders/features/features';
|
|
6
|
-
export const FEATURE_NAME = FEATURE_NAMES.ajax;
|
|
6
|
+
export const FEATURE_NAME = FEATURE_NAMES.ajax;
|
|
7
|
+
export const AJAX_ID = 'ajaxRequest.id';
|
|
@@ -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)) 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);
|
|
@@ -20,15 +20,18 @@ import { initiallyHidden, getNavigationEntry, initialLocation } from '../../../c
|
|
|
20
20
|
import { eventOrigin } from '../../../common/util/event-origin';
|
|
21
21
|
import { loadTime } from '../../../common/vitals/load-time';
|
|
22
22
|
import { webdriverDetected } from '../../../common/util/webdriver-detection';
|
|
23
|
+
import { analyzeElemPath } from '../../../common/dom/selector-path';
|
|
24
|
+
import { getVersion2Attributes, getVersion2DuplicationAttributes, shouldDuplicate } from '../../../common/v2/utils';
|
|
23
25
|
import { cleanURL } from '../../../common/url/clean-url';
|
|
24
26
|
export class Aggregate extends AggregateBase {
|
|
25
27
|
static featureName = FEATURE_NAME;
|
|
26
28
|
#handleVitalMetric = ({
|
|
27
29
|
name,
|
|
28
30
|
value,
|
|
29
|
-
attrs
|
|
31
|
+
attrs,
|
|
32
|
+
element
|
|
30
33
|
}) => {
|
|
31
|
-
this.addTiming(name, value, attrs);
|
|
34
|
+
this.addTiming(name, value, attrs, element);
|
|
32
35
|
};
|
|
33
36
|
constructor(agentRef) {
|
|
34
37
|
super(agentRef, FEATURE_NAME);
|
|
@@ -56,10 +59,11 @@ export class Aggregate extends AggregateBase {
|
|
|
56
59
|
const {
|
|
57
60
|
name,
|
|
58
61
|
value,
|
|
59
|
-
attrs
|
|
62
|
+
attrs,
|
|
63
|
+
element
|
|
60
64
|
} = cumulativeLayoutShift.current;
|
|
61
65
|
if (value === undefined) return;
|
|
62
|
-
this.addTiming(name, value * 1000, attrs);
|
|
66
|
+
this.addTiming(name, value * 1000, attrs, element);
|
|
63
67
|
}, true, true); // CLS node should only reports on vis change rather than on every change
|
|
64
68
|
|
|
65
69
|
this.drain();
|
|
@@ -77,7 +81,7 @@ export class Aggregate extends AggregateBase {
|
|
|
77
81
|
this.curSessEndRecorded = true;
|
|
78
82
|
}
|
|
79
83
|
}
|
|
80
|
-
addTiming(name, value, attrs) {
|
|
84
|
+
addTiming(name, value, attrs, element) {
|
|
81
85
|
attrs = attrs || {};
|
|
82
86
|
attrs.pageUrl = cleanURL(getNavigationEntry()?.name || initialLocation);
|
|
83
87
|
addConnectionAttributes(attrs); // network conditions may differ from the actual for VitalMetrics when they were captured
|
|
@@ -98,7 +102,24 @@ export class Aggregate extends AggregateBase {
|
|
|
98
102
|
value,
|
|
99
103
|
attrs
|
|
100
104
|
};
|
|
101
|
-
this.
|
|
105
|
+
const targets = analyzeElemPath(element, [], this.agentRef).targets;
|
|
106
|
+
if (!targets.length) targets.push(undefined);
|
|
107
|
+
targets.forEach(target => {
|
|
108
|
+
this.events.add({
|
|
109
|
+
...timing,
|
|
110
|
+
attrs: {
|
|
111
|
+
...attrs,
|
|
112
|
+
...getVersion2Attributes(target, this)
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
if (shouldDuplicate(target, this.agentRef)) this.events.add({
|
|
116
|
+
...timing,
|
|
117
|
+
attrs: {
|
|
118
|
+
...attrs,
|
|
119
|
+
...getVersion2DuplicationAttributes(target, this)
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
});
|
|
102
123
|
handle('pvtAdded', [name, value, attrs], undefined, FEATURE_NAMES.sessionTrace, this.ee);
|
|
103
124
|
this.checkForFirstInteraction();
|
|
104
125
|
|
|
@@ -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
|
/**
|
|
@@ -215,6 +215,7 @@ export class Aggregate extends AggregateBase {
|
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
217
|
makeHarvestPayload() {
|
|
218
|
+
if (this.isRetrying) return this.recorder.retryPayload;
|
|
218
219
|
if (this.mode !== MODE.FULL || this.blocked) return; // harvests should only be made in FULL mode, and not if the feature is blocked
|
|
219
220
|
if (this.shouldCompress && !this.gzipper) return; // if compression is enabled, but the libraries have not loaded, wait for them to load
|
|
220
221
|
if (!this.recorder || !this.timeKeeper?.ready || !(this.recorder.hasSeenSnapshot && this.recorder.hasSeenMeta)) return; // if the recorder or the timekeeper is not ready, or the recorder has not yet seen a snapshot, do not harvest
|
|
@@ -242,8 +243,6 @@ export class Aggregate extends AggregateBase {
|
|
|
242
243
|
this.abort(ABORT_REASONS.TOO_BIG, len);
|
|
243
244
|
return;
|
|
244
245
|
}
|
|
245
|
-
|
|
246
|
-
// TODO -- Gracefully handle the buffer for retries.
|
|
247
246
|
if (!this.agentRef.runtime.session.state.sessionReplaySentFirstChunk) this.syncWithSessionManager({
|
|
248
247
|
sessionReplaySentFirstChunk: true
|
|
249
248
|
});
|
|
@@ -251,6 +250,7 @@ export class Aggregate extends AggregateBase {
|
|
|
251
250
|
if (!this.agentRef.runtime.session.state.traceHarvestStarted) {
|
|
252
251
|
warn(59, JSON.stringify(this.agentRef.runtime.session.state));
|
|
253
252
|
}
|
|
253
|
+
this.recorder.retryPayload = payload;
|
|
254
254
|
return payload;
|
|
255
255
|
}
|
|
256
256
|
|
|
@@ -348,9 +348,18 @@ export class Aggregate extends AggregateBase {
|
|
|
348
348
|
};
|
|
349
349
|
}
|
|
350
350
|
postHarvestCleanup(result) {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
351
|
+
if (result.sent) {
|
|
352
|
+
if (result.retry) {
|
|
353
|
+
warn(70);
|
|
354
|
+
this.isRetrying = true;
|
|
355
|
+
this.forceStop();
|
|
356
|
+
} else {
|
|
357
|
+
this.recorder.retryPayload = undefined;
|
|
358
|
+
if (this.isRetrying) {
|
|
359
|
+
this.isRetrying = false;
|
|
360
|
+
this.switchToFull();
|
|
361
|
+
}
|
|
362
|
+
}
|
|
354
363
|
}
|
|
355
364
|
}
|
|
356
365
|
|