@newrelic/browser-agent 1.313.1 → 1.314.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 +11 -0
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/dom/selector-path.js +12 -3
- package/dist/cjs/common/timing/time-keeper.js +18 -6
- package/dist/cjs/common/vitals/load-time.js +5 -2
- 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/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/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/load-time.js +5 -2
- 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/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/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/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/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 +1 -1
- package/src/common/dom/selector-path.js +13 -4
- package/src/common/timing/time-keeper.js +17 -6
- package/src/common/vitals/load-time.js +5 -2
- 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/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
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
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.314.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.313.1...v1.314.0) (2026-05-04)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* Add ajaxRequest.id attribute ([#1757](https://github.com/newrelic/newrelic-browser-agent/issues/1757)) ([c8a345b](https://github.com/newrelic/newrelic-browser-agent/commit/c8a345bc9cd22eb56f876c598677310a2da6be8f))
|
|
12
|
+
* Adjust timestamps for clock skew due to machine sleep ([#1749](https://github.com/newrelic/newrelic-browser-agent/issues/1749)) ([412ef8b](https://github.com/newrelic/newrelic-browser-agent/commit/412ef8b6ea80ddd13900cea5f379e12b4fafc007))
|
|
13
|
+
* Allow Session Replay to retry payloads ([#1666](https://github.com/newrelic/newrelic-browser-agent/issues/1666)) ([9874ac5](https://github.com/newrelic/newrelic-browser-agent/commit/9874ac5d851653641ed33241ad5e96678a9b9fee))
|
|
14
|
+
* Automatically Detect MFE User Actions ([#1723](https://github.com/newrelic/newrelic-browser-agent/issues/1723)) ([2c2024e](https://github.com/newrelic/newrelic-browser-agent/commit/2c2024e3cbb4b2f6afc22e2bc1c6385b07335be5))
|
|
15
|
+
* Improve PageViewTiming load timing capture ([#1764](https://github.com/newrelic/newrelic-browser-agent/issues/1764)) ([7ca2164](https://github.com/newrelic/newrelic-browser-agent/commit/7ca2164771f5c88b0a053a5af9f07d607c0e1d29))
|
|
16
|
+
|
|
6
17
|
## [1.313.1](https://github.com/newrelic/newrelic-browser-agent/compare/v1.313.0...v1.313.1) (2026-04-21)
|
|
7
18
|
|
|
8
19
|
|
|
@@ -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.
|
|
20
|
+
const VERSION = exports.VERSION = "1.314.0";
|
|
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.
|
|
20
|
+
const VERSION = exports.VERSION = "1.314.0";
|
|
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
|
}
|
|
@@ -45,14 +45,19 @@ class TimeKeeper {
|
|
|
45
45
|
* @type {boolean}
|
|
46
46
|
*/
|
|
47
47
|
#ready = false;
|
|
48
|
-
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The total measured drift in milliseconds. Represents how much performance.now()
|
|
51
|
+
* has fallen behind Date.now(), which is used to correct timestamp conversions.
|
|
52
|
+
* @type {number}
|
|
53
|
+
*/
|
|
54
|
+
#measuredDrift = 0;
|
|
49
55
|
constructor(sessionObj) {
|
|
50
56
|
this.#session = sessionObj;
|
|
51
57
|
this.processStoredDiff();
|
|
52
58
|
(0, _monkeyPatched.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.
|
|
53
59
|
}
|
|
54
60
|
#detectDrift() {
|
|
55
|
-
if (this.#reportedDrift) return;
|
|
56
61
|
try {
|
|
57
62
|
// Drift detection: measures if performance.now() and Date.now() have become desynchronized
|
|
58
63
|
// This can happen when a machine sleeps and the performance timer freezes while Date continues
|
|
@@ -63,8 +68,13 @@ class TimeKeeper {
|
|
|
63
68
|
// Note: localTimeDiff (server time offset) is NOT part of drift - that's a legitimate offset
|
|
64
69
|
const drift = Date.now() - _runtime.originTime - performance.now();
|
|
65
70
|
if (drift > 1000) {
|
|
66
|
-
this
|
|
67
|
-
|
|
71
|
+
// Check if this is new drift (increase of >1000ms from last measurement)
|
|
72
|
+
const newDrift = drift - this.#measuredDrift;
|
|
73
|
+
if (newDrift > 1000) {
|
|
74
|
+
// Update measured drift and report it
|
|
75
|
+
this.#measuredDrift = drift;
|
|
76
|
+
if (this.#session) (0, _handle.handle)(_constants.SUPPORTABILITY_METRIC_CHANNEL, ['Generic/TimeKeeper/ClockDrift/Detected', drift], undefined, _features.FEATURE_NAMES.metrics, this.#session.agentRef.ee);
|
|
77
|
+
}
|
|
68
78
|
}
|
|
69
79
|
} catch (err) {
|
|
70
80
|
// Silently ignore drift detection errors to avoid breaking normal operation
|
|
@@ -115,7 +125,8 @@ class TimeKeeper {
|
|
|
115
125
|
*/
|
|
116
126
|
convertRelativeTimestamp(relativeTime) {
|
|
117
127
|
this.#detectDrift();
|
|
118
|
-
|
|
128
|
+
// Add measured drift to compensate for performance.now() falling behind
|
|
129
|
+
return _runtime.originTime + relativeTime + this.#measuredDrift;
|
|
119
130
|
}
|
|
120
131
|
|
|
121
132
|
/**
|
|
@@ -126,7 +137,8 @@ class TimeKeeper {
|
|
|
126
137
|
*/
|
|
127
138
|
convertAbsoluteTimestamp(timestamp) {
|
|
128
139
|
this.#detectDrift();
|
|
129
|
-
|
|
140
|
+
// Subtract measured drift since we're converting from absolute to relative
|
|
141
|
+
return timestamp - _runtime.originTime - this.#measuredDrift;
|
|
130
142
|
}
|
|
131
143
|
|
|
132
144
|
/**
|
|
@@ -17,12 +17,15 @@ const loadTime = exports.loadTime = new _vitalMetric.VitalMetric(_constants.VITA
|
|
|
17
17
|
if (_runtime.isBrowserScope) {
|
|
18
18
|
const perf = _runtime.globalScope.performance;
|
|
19
19
|
const handler = () => {
|
|
20
|
-
|
|
20
|
+
// setTimeout defers the read until after the load event handler returns,
|
|
21
|
+
// ensuring loadEventEnd is populated (non-zero) — matching the web-vitals onTTFB pattern
|
|
22
|
+
setTimeout(() => {
|
|
23
|
+
if (loadTime.isValid || !perf) return;
|
|
21
24
|
const navEntry = (0, _runtime.getNavigationEntry)();
|
|
22
25
|
loadTime.update({
|
|
23
26
|
value: navEntry ? navEntry.loadEventEnd : perf.timing?.loadEventEnd - _runtime.originTime
|
|
24
27
|
});
|
|
25
|
-
}
|
|
28
|
+
}, 0);
|
|
26
29
|
};
|
|
27
30
|
(0, _load.onWindowLoad)(handler, true);
|
|
28
31
|
}
|
|
@@ -15,6 +15,7 @@ var _gql = require("./gql");
|
|
|
15
15
|
var _belSerializer = require("../../../common/serialize/bel-serializer");
|
|
16
16
|
var _nreum = require("../../../common/window/nreum");
|
|
17
17
|
var _utils = require("../../../common/v2/utils");
|
|
18
|
+
var _uniqueId = require("../../../common/ids/unique-id");
|
|
18
19
|
/**
|
|
19
20
|
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
20
21
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -87,7 +88,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
87
88
|
type,
|
|
88
89
|
startTime,
|
|
89
90
|
endTime,
|
|
90
|
-
callbackDuration: metrics.cbTime
|
|
91
|
+
callbackDuration: metrics.cbTime,
|
|
92
|
+
[_constants.AJAX_ID]: (0, _uniqueId.generateUuid)() // all AjaxRequest events should have a unique identifier to allow for easier grouping and analysis in the UI
|
|
91
93
|
};
|
|
92
94
|
if (ctx.dt) {
|
|
93
95
|
event.spanId = ctx.dt.spanId;
|
|
@@ -157,7 +159,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
157
159
|
const attrParts = (0, _belSerializer.addCustomAttributes)({
|
|
158
160
|
...(jsAttributes || {}),
|
|
159
161
|
...(event.gql || {}),
|
|
160
|
-
...(event.targetAttributes || {})
|
|
162
|
+
...(event.targetAttributes || {}),
|
|
163
|
+
// used to supply the version 2 attributes, either MFE target or duplication attributes for the main agent app
|
|
164
|
+
[_constants.AJAX_ID]: event[_constants.AJAX_ID] // all AjaxRequest events should have a unique identifier to allow for easier grouping and analysis in the UI
|
|
161
165
|
}, addString);
|
|
162
166
|
fields.unshift((0, _belSerializer.numeric)(attrParts.length));
|
|
163
167
|
insert += fields.join(',');
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.FEATURE_NAME = void 0;
|
|
6
|
+
exports.FEATURE_NAME = exports.AJAX_ID = void 0;
|
|
7
7
|
var _features = require("../../loaders/features/features");
|
|
8
8
|
/**
|
|
9
|
-
* Copyright 2020-
|
|
9
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
10
10
|
* SPDX-License-Identifier: Apache-2.0
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
const FEATURE_NAME = exports.FEATURE_NAME = _features.FEATURE_NAMES.ajax;
|
|
13
|
+
const FEATURE_NAME = exports.FEATURE_NAME = _features.FEATURE_NAMES.ajax;
|
|
14
|
+
const AJAX_ID = exports.AJAX_ID = 'ajaxRequest.id';
|
|
@@ -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)) 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);
|
|
@@ -21,7 +21,7 @@ var _cleanUrl = require("../../../common/url/clean-url");
|
|
|
21
21
|
var _featureGates = require("../../utils/feature-gates");
|
|
22
22
|
var _constants3 = require("../../../loaders/api/constants");
|
|
23
23
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } /**
|
|
24
|
-
* Copyright 2020-
|
|
24
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
25
25
|
* SPDX-License-Identifier: Apache-2.0
|
|
26
26
|
*/ /**
|
|
27
27
|
* @file Records, aggregates, and harvests session replay data.
|
|
@@ -219,6 +219,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
makeHarvestPayload() {
|
|
222
|
+
if (this.isRetrying) return this.recorder.retryPayload;
|
|
222
223
|
if (this.mode !== _constants2.MODE.FULL || this.blocked) return; // harvests should only be made in FULL mode, and not if the feature is blocked
|
|
223
224
|
if (this.shouldCompress && !this.gzipper) return; // if compression is enabled, but the libraries have not loaded, wait for them to load
|
|
224
225
|
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
|
|
@@ -246,8 +247,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
246
247
|
this.abort(_constants.ABORT_REASONS.TOO_BIG, len);
|
|
247
248
|
return;
|
|
248
249
|
}
|
|
249
|
-
|
|
250
|
-
// TODO -- Gracefully handle the buffer for retries.
|
|
251
250
|
if (!this.agentRef.runtime.session.state.sessionReplaySentFirstChunk) this.syncWithSessionManager({
|
|
252
251
|
sessionReplaySentFirstChunk: true
|
|
253
252
|
});
|
|
@@ -255,6 +254,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
255
254
|
if (!this.agentRef.runtime.session.state.traceHarvestStarted) {
|
|
256
255
|
(0, _console.warn)(59, JSON.stringify(this.agentRef.runtime.session.state));
|
|
257
256
|
}
|
|
257
|
+
this.recorder.retryPayload = payload;
|
|
258
258
|
return payload;
|
|
259
259
|
}
|
|
260
260
|
|
|
@@ -352,9 +352,18 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
352
352
|
};
|
|
353
353
|
}
|
|
354
354
|
postHarvestCleanup(result) {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
355
|
+
if (result.sent) {
|
|
356
|
+
if (result.retry) {
|
|
357
|
+
(0, _console.warn)(70);
|
|
358
|
+
this.isRetrying = true;
|
|
359
|
+
this.forceStop();
|
|
360
|
+
} else {
|
|
361
|
+
this.recorder.retryPayload = undefined;
|
|
362
|
+
if (this.isRetrying) {
|
|
363
|
+
this.isRetrying = false;
|
|
364
|
+
this.switchToFull();
|
|
365
|
+
}
|
|
366
|
+
}
|
|
358
367
|
}
|
|
359
368
|
}
|
|
360
369
|
|
|
@@ -7,7 +7,7 @@ exports.TRIGGERS = exports.RRWEB_EVENT_TYPES = exports.QUERY_PARAM_PADDING = exp
|
|
|
7
7
|
var _constants = require("../../common/session/constants");
|
|
8
8
|
var _features = require("../../loaders/features/features");
|
|
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
|
|
|
@@ -19,7 +19,7 @@ var _console = require("../../../common/util/console");
|
|
|
19
19
|
var _invoke = require("../../../common/util/invoke");
|
|
20
20
|
var _registerHandler = require("../../../common/event-emitter/register-handler");
|
|
21
21
|
/**
|
|
22
|
-
* Copyright 2020-
|
|
22
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
23
23
|
* SPDX-License-Identifier: Apache-2.0
|
|
24
24
|
*/
|
|
25
25
|
|
|
@@ -47,6 +47,8 @@ class Recorder {
|
|
|
47
47
|
this.events = new _recorderEvents.RecorderEvents(this.shouldFix);
|
|
48
48
|
/** Backlog used for a 2-part sliding window to guarantee a 15-30s buffer window */
|
|
49
49
|
this.backloggedEvents = new _recorderEvents.RecorderEvents(this.shouldFix);
|
|
50
|
+
/** Used to hold the harvest contents to facilitate retrying */
|
|
51
|
+
this.retryPayload = undefined;
|
|
50
52
|
/** Only set to true once a snapshot node has been processed. Used to block harvests from sending before we know we have a snapshot */
|
|
51
53
|
this.hasSeenSnapshot = false;
|
|
52
54
|
this.hasSeenMeta = false;
|
|
@@ -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
|
/**
|