@newrelic/browser-agent 1.259.0 → 1.260.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 +20 -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/constants/runtime.js +2 -1
- package/dist/cjs/common/session/session-entity.js +8 -1
- package/dist/cjs/common/timing/time-keeper.js +11 -32
- package/dist/cjs/features/jserrors/aggregate/index.js +1 -2
- package/dist/cjs/features/jserrors/instrument/index.js +3 -4
- package/dist/cjs/features/page_view_event/aggregate/index.js +7 -5
- package/dist/cjs/features/session_replay/shared/recorder.js +8 -1
- package/dist/cjs/features/session_trace/aggregate/index.js +1 -0
- package/dist/cjs/features/session_trace/aggregate/trace/storage.js +1 -1
- package/dist/cjs/features/soft_navigations/aggregate/bel-node.js +1 -2
- package/dist/cjs/features/soft_navigations/aggregate/index.js +1 -1
- package/dist/cjs/features/soft_navigations/aggregate/interaction.js +5 -4
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/constants/runtime.js +3 -1
- package/dist/esm/common/session/session-entity.js +8 -1
- package/dist/esm/common/timing/time-keeper.js +11 -32
- package/dist/esm/features/jserrors/aggregate/index.js +1 -2
- package/dist/esm/features/jserrors/instrument/index.js +3 -4
- package/dist/esm/features/page_view_event/aggregate/index.js +7 -5
- package/dist/esm/features/session_replay/shared/recorder.js +8 -1
- package/dist/esm/features/session_trace/aggregate/index.js +1 -0
- package/dist/esm/features/session_trace/aggregate/trace/storage.js +1 -1
- package/dist/esm/features/soft_navigations/aggregate/bel-node.js +1 -2
- package/dist/esm/features/soft_navigations/aggregate/index.js +1 -1
- package/dist/esm/features/soft_navigations/aggregate/interaction.js +5 -4
- package/dist/types/common/constants/runtime.d.ts +0 -6
- package/dist/types/common/constants/runtime.d.ts.map +1 -1
- package/dist/types/common/session/session-entity.d.ts.map +1 -1
- package/dist/types/common/timing/time-keeper.d.ts +2 -0
- package/dist/types/common/timing/time-keeper.d.ts.map +1 -1
- package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/jserrors/instrument/index.d.ts.map +1 -1
- package/dist/types/features/page_view_event/aggregate/index.d.ts +2 -0
- package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/index.d.ts +2 -2
- package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/trace/storage.d.ts.map +1 -1
- package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts +0 -1
- package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts.map +1 -1
- package/dist/types/features/soft_navigations/aggregate/bel-node.d.ts +1 -1
- package/dist/types/features/soft_navigations/aggregate/bel-node.d.ts.map +1 -1
- package/dist/types/features/soft_navigations/aggregate/interaction.d.ts +0 -1
- package/dist/types/features/soft_navigations/aggregate/interaction.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/common/constants/runtime.js +3 -1
- package/src/common/session/session-entity.js +3 -1
- package/src/common/timing/time-keeper.js +11 -32
- package/src/features/jserrors/aggregate/index.js +1 -2
- package/src/features/jserrors/instrument/index.js +3 -4
- package/src/features/page_view_event/aggregate/index.js +8 -4
- package/src/features/session_replay/shared/recorder.js +8 -1
- package/src/features/session_trace/aggregate/index.js +1 -0
- package/src/features/session_trace/aggregate/trace/storage.js +1 -2
- package/src/features/soft_navigations/aggregate/bel-node.js +1 -3
- package/src/features/soft_navigations/aggregate/index.js +1 -1
- package/src/features/soft_navigations/aggregate/interaction.js +5 -4
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,26 @@
|
|
|
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.260.1](https://github.com/newrelic/newrelic-browser-agent/compare/v1.260.0...v1.260.1) (2024-05-16)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* Undefined stns in blob Trace ([#1039](https://github.com/newrelic/newrelic-browser-agent/issues/1039)) ([1a87991](https://github.com/newrelic/newrelic-browser-agent/commit/1a87991143aa1f220ae29edbf1e8ea89598bcc44))
|
|
12
|
+
|
|
13
|
+
## [1.260.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.259.0...v1.260.0) (2024-05-13)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
* Improve time stamping of page view events ([#1026](https://github.com/newrelic/newrelic-browser-agent/issues/1026)) ([67a658d](https://github.com/newrelic/newrelic-browser-agent/commit/67a658d2645b680a479175dff06a4fd95bd6086a))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Bug Fixes
|
|
22
|
+
|
|
23
|
+
* Add internal error handler to session replay recorder ([#1029](https://github.com/newrelic/newrelic-browser-agent/issues/1029)) ([84c101c](https://github.com/newrelic/newrelic-browser-agent/commit/84c101ccbff8da207bdf215714f49b7d49941388))
|
|
24
|
+
* Adjust session entity to not race between tabs ([#1032](https://github.com/newrelic/newrelic-browser-agent/issues/1032)) ([d86becf](https://github.com/newrelic/newrelic-browser-agent/commit/d86becf2fc02aa133430332aa4e4c2bc26297750))
|
|
25
|
+
|
|
6
26
|
## [1.259.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.258.2...v1.259.0) (2024-05-08)
|
|
7
27
|
|
|
8
28
|
|
|
@@ -12,7 +12,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
|
|
|
12
12
|
/**
|
|
13
13
|
* Exposes the version of the agent
|
|
14
14
|
*/
|
|
15
|
-
const VERSION = exports.VERSION = "1.
|
|
15
|
+
const VERSION = exports.VERSION = "1.260.1";
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Exposes the build type of the agent
|
|
@@ -12,7 +12,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
|
|
|
12
12
|
/**
|
|
13
13
|
* Exposes the version of the agent
|
|
14
14
|
*/
|
|
15
|
-
const VERSION = exports.VERSION = "1.
|
|
15
|
+
const VERSION = exports.VERSION = "1.260.1";
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Exposes the build type of the agent
|
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.supportsSendBeacon = exports.originTime = exports.loadedAsDeferredBrowserScript = exports.isiOS = exports.isWorkerScope = exports.isIE = exports.isBrowserScope = exports.initiallyHidden = exports.initialLocation = exports.iOSBelow16 = exports.globalScope = exports.ffVersion = void 0;
|
|
7
|
+
var _now = require("../timing/now");
|
|
7
8
|
/**
|
|
8
9
|
* @file Contains constants about the environment the agent is running
|
|
9
10
|
* within. These values are derived at the time the agent is first loaded.
|
|
@@ -50,4 +51,4 @@ const supportsSendBeacon = exports.supportsSendBeacon = !!globalScope.navigator?
|
|
|
50
51
|
* according to the browser's local clock.
|
|
51
52
|
* @type {number}
|
|
52
53
|
*/
|
|
53
|
-
const originTime = exports.originTime =
|
|
54
|
+
const originTime = exports.originTime = Date.now() - (0, _now.now)();
|
|
@@ -74,8 +74,15 @@ class SessionEntity {
|
|
|
74
74
|
expiresMs = _constants.DEFAULT_EXPIRES_MS,
|
|
75
75
|
inactiveMs = _constants.DEFAULT_INACTIVE_MS
|
|
76
76
|
} = _ref;
|
|
77
|
+
/** Ensure that certain properties are preserved across a reset if already set */
|
|
78
|
+
const persistentAttributes = {
|
|
79
|
+
serverTimeDiff: this.state.serverTimeDiff || model.serverTimeDiff
|
|
80
|
+
};
|
|
77
81
|
this.state = {};
|
|
78
|
-
this.sync(
|
|
82
|
+
this.sync({
|
|
83
|
+
...model,
|
|
84
|
+
...persistentAttributes
|
|
85
|
+
});
|
|
79
86
|
|
|
80
87
|
// value is intended to act as the primary value of the k=v pair
|
|
81
88
|
this.state.value = value;
|
|
@@ -5,9 +5,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.TimeKeeper = void 0;
|
|
7
7
|
var _runtime = require("../constants/runtime");
|
|
8
|
-
var _contextualEe = require("../event-emitter/contextual-ee");
|
|
9
8
|
var _config = require("../config/config");
|
|
10
|
-
var _constants = require("../session/constants");
|
|
11
9
|
/**
|
|
12
10
|
* Class used to adjust the timestamp of harvested data to New Relic server time. This
|
|
13
11
|
* is done by tracking the performance timings of the RUM call and applying a calculation
|
|
@@ -41,18 +39,7 @@ class TimeKeeper {
|
|
|
41
39
|
#ready = false;
|
|
42
40
|
constructor(agentIdentifier) {
|
|
43
41
|
this.#session = (0, _config.getRuntime)(agentIdentifier)?.session;
|
|
44
|
-
|
|
45
|
-
const ee = _contextualEe.ee.get(agentIdentifier);
|
|
46
|
-
ee.on(_constants.SESSION_EVENTS.UPDATE, this.#processSessionUpdate.bind(this));
|
|
47
|
-
ee.on(_constants.SESSION_EVENTS.STARTED, () => {
|
|
48
|
-
if (this.#ready) {
|
|
49
|
-
this.#session.write({
|
|
50
|
-
serverTimeDiff: this.#localTimeDiff
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
this.#processSessionUpdate(null, this.#session.read());
|
|
55
|
-
}
|
|
42
|
+
this.processStoredDiff();
|
|
56
43
|
}
|
|
57
44
|
get ready() {
|
|
58
45
|
return this.#ready;
|
|
@@ -68,22 +55,22 @@ class TimeKeeper {
|
|
|
68
55
|
* @param endTime {number} The end time of the RUM request
|
|
69
56
|
*/
|
|
70
57
|
processRumRequest(rumRequest, startTime, endTime) {
|
|
58
|
+
this.processStoredDiff();
|
|
71
59
|
if (this.#ready) return; // Server time calculated from session entity
|
|
72
|
-
|
|
73
60
|
const responseDateHeader = rumRequest.getResponseHeader('Date');
|
|
74
61
|
if (!responseDateHeader) {
|
|
75
62
|
throw new Error('Missing date header on rum response.');
|
|
76
63
|
}
|
|
77
64
|
const medianRumOffset = (endTime - startTime) / 2;
|
|
78
|
-
const serverOffset =
|
|
65
|
+
const serverOffset = startTime + medianRumOffset;
|
|
79
66
|
|
|
80
67
|
// Corrected page origin time
|
|
81
68
|
this.#correctedOriginTime = Math.floor(Date.parse(responseDateHeader) - serverOffset);
|
|
82
69
|
this.#localTimeDiff = _runtime.originTime - this.#correctedOriginTime;
|
|
83
|
-
if (
|
|
70
|
+
if (isNaN(this.#correctedOriginTime)) {
|
|
84
71
|
throw new Error('Date header invalid format.');
|
|
85
72
|
}
|
|
86
|
-
|
|
73
|
+
this.#session?.write({
|
|
87
74
|
serverTimeDiff: this.#localTimeDiff
|
|
88
75
|
});
|
|
89
76
|
this.#ready = true;
|
|
@@ -96,7 +83,7 @@ class TimeKeeper {
|
|
|
96
83
|
* @returns {number} Corrected unix/epoch timestamp
|
|
97
84
|
*/
|
|
98
85
|
convertRelativeTimestamp(relativeTime) {
|
|
99
|
-
return this.#correctedOriginTime + relativeTime;
|
|
86
|
+
return Math.floor(this.#correctedOriginTime + relativeTime);
|
|
100
87
|
}
|
|
101
88
|
|
|
102
89
|
/**
|
|
@@ -108,19 +95,11 @@ class TimeKeeper {
|
|
|
108
95
|
return Math.floor(timestamp - this.#localTimeDiff);
|
|
109
96
|
}
|
|
110
97
|
|
|
111
|
-
/**
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
#processSessionUpdate(type, data) {
|
|
117
|
-
if (typeof data?.serverTimeDiff !== 'number') return;
|
|
118
|
-
if (!type && !this.#ready ||
|
|
119
|
-
// This captures the initial read from the session entity when the timekeeper first initializes
|
|
120
|
-
type === _constants.SESSION_EVENT_TYPES.CROSS_TAB // This captures any cross-tab write of the session entity
|
|
121
|
-
) {
|
|
122
|
-
// This captures the initial read from the session entity when the timekeeper first initializes
|
|
123
|
-
this.#localTimeDiff = data.serverTimeDiff;
|
|
98
|
+
/** Process the session entity and use the info to set the main time calculations if present */
|
|
99
|
+
processStoredDiff() {
|
|
100
|
+
const storedServerTimeDiff = this.#session?.read()?.serverTimeDiff;
|
|
101
|
+
if (typeof storedServerTimeDiff === 'number' && !isNaN(storedServerTimeDiff)) {
|
|
102
|
+
this.#localTimeDiff = storedServerTimeDiff;
|
|
124
103
|
this.#correctedOriginTime = _runtime.originTime - this.#localTimeDiff;
|
|
125
104
|
this.#ready = true;
|
|
126
105
|
}
|
|
@@ -198,8 +198,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
198
198
|
time
|
|
199
199
|
};
|
|
200
200
|
|
|
201
|
-
//
|
|
202
|
-
// and spa annotates the error with interaction info
|
|
201
|
+
// Trace sends the error in its payload, and both trace & replay simply listens for any error to occur.
|
|
203
202
|
const jsErrorEvent = [type, bucketHash, params, newMetrics, customAttributes];
|
|
204
203
|
(0, _handle.handle)('trace-jserror', jsErrorEvent, undefined, _features.FEATURE_NAMES.sessionTrace, this.ee);
|
|
205
204
|
// still send EE events for other features such as above, but stop this one from aggregating internal data
|
|
@@ -7,7 +7,6 @@ exports.Instrument = void 0;
|
|
|
7
7
|
var _handle = require("../../../common/event-emitter/handle");
|
|
8
8
|
var _instrumentBase = require("../../utils/instrument-base");
|
|
9
9
|
var _constants = require("../constants");
|
|
10
|
-
var _features = require("../../../loaders/features/features");
|
|
11
10
|
var _runtime = require("../../../common/constants/runtime");
|
|
12
11
|
var _eventListenerOpts = require("../../../common/event-listener/event-listener-opts");
|
|
13
12
|
var _now = require("../../../common/timing/now");
|
|
@@ -30,7 +29,7 @@ class Instrument extends _instrumentBase.InstrumentBase {
|
|
|
30
29
|
} catch (e) {}
|
|
31
30
|
this.ee.on('internal-error', error => {
|
|
32
31
|
if (!this.abortHandler) return;
|
|
33
|
-
(0, _handle.handle)('ierr', [(0, _castError.castError)(error), (0, _now.now)(), true, {}, this.#replayRunning], undefined,
|
|
32
|
+
(0, _handle.handle)('ierr', [(0, _castError.castError)(error), (0, _now.now)(), true, {}, this.#replayRunning], undefined, this.featureName, this.ee);
|
|
34
33
|
});
|
|
35
34
|
this.ee.on(_constants2.SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, isRunning => {
|
|
36
35
|
this.#replayRunning = isRunning;
|
|
@@ -39,11 +38,11 @@ class Instrument extends _instrumentBase.InstrumentBase {
|
|
|
39
38
|
if (!this.abortHandler) return;
|
|
40
39
|
(0, _handle.handle)('err', [(0, _castError.castPromiseRejectionEvent)(promiseRejectionEvent), (0, _now.now)(), false, {
|
|
41
40
|
unhandledPromiseRejection: 1
|
|
42
|
-
}, this.#replayRunning], undefined,
|
|
41
|
+
}, this.#replayRunning], undefined, this.featureName, this.ee);
|
|
43
42
|
}, (0, _eventListenerOpts.eventListenerOpts)(false, this.removeOnAbort?.signal));
|
|
44
43
|
_runtime.globalScope.addEventListener('error', errorEvent => {
|
|
45
44
|
if (!this.abortHandler) return;
|
|
46
|
-
(0, _handle.handle)('err', [(0, _castError.castErrorEvent)(errorEvent), (0, _now.now)(), false, {}, this.#replayRunning], undefined,
|
|
45
|
+
(0, _handle.handle)('err', [(0, _castError.castErrorEvent)(errorEvent), (0, _now.now)(), false, {}, this.#replayRunning], undefined, this.featureName, this.ee);
|
|
47
46
|
}, (0, _eventListenerOpts.eventListenerOpts)(false, this.removeOnAbort?.signal));
|
|
48
47
|
this.abortHandler = this.#abort; // we also use this as a flag to denote that the feature is active or on and handling errors
|
|
49
48
|
this.importAggregator();
|
|
@@ -32,7 +32,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
32
32
|
this.timeToFirstByte = 0;
|
|
33
33
|
this.firstByteToWindowLoad = 0; // our "frontend" duration
|
|
34
34
|
this.firstByteToDomContent = 0; // our "dom processing" duration
|
|
35
|
-
|
|
35
|
+
this.timeKeeper = new _timeKeeper.TimeKeeper(this.agentIdentifier);
|
|
36
36
|
if (_runtime.isBrowserScope) {
|
|
37
37
|
_timeToFirstByte.timeToFirstByte.subscribe(_ref => {
|
|
38
38
|
let {
|
|
@@ -118,6 +118,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
118
118
|
}
|
|
119
119
|
queryParameters.fp = _firstPaint.firstPaint.current.value;
|
|
120
120
|
queryParameters.fcp = _firstContentfulPaint.firstContentfulPaint.current.value;
|
|
121
|
+
if (this.timeKeeper?.ready) {
|
|
122
|
+
queryParameters.timestamp = this.timeKeeper.convertRelativeTimestamp((0, _now.now)());
|
|
123
|
+
}
|
|
121
124
|
const rumStartTime = (0, _now.now)();
|
|
122
125
|
harvester.send({
|
|
123
126
|
endpoint: 'rum',
|
|
@@ -142,10 +145,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
142
145
|
return;
|
|
143
146
|
}
|
|
144
147
|
try {
|
|
145
|
-
|
|
146
|
-
timeKeeper.
|
|
147
|
-
|
|
148
|
-
agentRuntime.timeKeeper = timeKeeper;
|
|
148
|
+
this.timeKeeper.processRumRequest(xhr, rumStartTime, rumEndTime);
|
|
149
|
+
if (!this.timeKeeper.ready) throw new Error('TimeKeeper not ready');
|
|
150
|
+
agentRuntime.timeKeeper = this.timeKeeper;
|
|
149
151
|
} catch (error) {
|
|
150
152
|
(0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['PVE/NRTime/Calculation/Failed'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
|
|
151
153
|
(0, _drain.drain)(this.agentIdentifier, _features.FEATURE_NAMES.metrics, true);
|
|
@@ -101,7 +101,14 @@ class Recorder {
|
|
|
101
101
|
inlineStylesheet: inline_stylesheet,
|
|
102
102
|
inlineImages: inline_images,
|
|
103
103
|
collectFonts: collect_fonts,
|
|
104
|
-
checkoutEveryNms: _constants.CHECKOUT_MS[this.parent.mode]
|
|
104
|
+
checkoutEveryNms: _constants.CHECKOUT_MS[this.parent.mode],
|
|
105
|
+
/** Emits errors thrown by rrweb directly before bubbling them up to the window */
|
|
106
|
+
errorHandler: err => {
|
|
107
|
+
/** capture rrweb errors as "internal" errors only */
|
|
108
|
+
this.parent.ee.emit('internal-error', [err]);
|
|
109
|
+
/** returning true informs rrweb to swallow the error instead of throwing it to the window */
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
105
112
|
});
|
|
106
113
|
this.stopRecording = () => {
|
|
107
114
|
this.recording = false;
|
|
@@ -278,7 +278,7 @@ class TraceStorage {
|
|
|
278
278
|
// Ajax (FEATURE) events--XML & fetches--pipes into ST here.
|
|
279
279
|
storeXhrAgg(type, name, params, metrics) {
|
|
280
280
|
if (type !== 'xhr') return;
|
|
281
|
-
this.storeSTN(new _node.TraceNode('Ajax', metrics.time, metrics.time + metrics.duration, "".concat(params.status, " ").concat(params.method, ": ").concat(params.host).concat(params.pathname)));
|
|
281
|
+
this.storeSTN(new _node.TraceNode('Ajax', metrics.time, metrics.time + metrics.duration, "".concat(params.status, " ").concat(params.method, ": ").concat(params.host).concat(params.pathname), 'ajax'));
|
|
282
282
|
}
|
|
283
283
|
restoreNode(name, listOfSTNodes) {
|
|
284
284
|
if (this.nodeCount >= _constants2.MAX_NODES_PER_HARVEST) return;
|
|
@@ -4,13 +4,12 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.BelNode = void 0;
|
|
7
|
-
var _now = require("../../../common/timing/now");
|
|
8
7
|
let nodesSeen = 0;
|
|
9
8
|
class BelNode {
|
|
10
9
|
belType;
|
|
11
10
|
/** List of other BelNode derivatives. Each children should be of a subclass that implements its own 'serialize' function. */
|
|
12
11
|
children = [];
|
|
13
|
-
start
|
|
12
|
+
start;
|
|
14
13
|
end;
|
|
15
14
|
callbackEnd = 0;
|
|
16
15
|
callbackDuration = 0;
|
|
@@ -67,7 +67,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
// By default, a complete UI driven interaction requires event -> URL change -> DOM mod in that exact order.
|
|
70
|
-
(0, _registerHandler.registerHandler)('newUIEvent', event => this.startUIInteraction(event.type, event.timeStamp, event.target), this.featureName, this.ee);
|
|
70
|
+
(0, _registerHandler.registerHandler)('newUIEvent', event => this.startUIInteraction(event.type, Math.floor(event.timeStamp), event.target), this.featureName, this.ee);
|
|
71
71
|
(0, _registerHandler.registerHandler)('newURL', (timestamp, url) => this.interactionInProgress?.updateHistory(timestamp, url), this.featureName, this.ee);
|
|
72
72
|
(0, _registerHandler.registerHandler)('newDom', timestamp => {
|
|
73
73
|
this.interactionInProgress?.updateDom(timestamp);
|
|
@@ -8,6 +8,7 @@ var _config = require("../../../common/config/config");
|
|
|
8
8
|
var _runtime = require("../../../common/constants/runtime");
|
|
9
9
|
var _uniqueId = require("../../../common/ids/unique-id");
|
|
10
10
|
var _belSerializer = require("../../../common/serialize/bel-serializer");
|
|
11
|
+
var _now = require("../../../common/timing/now");
|
|
11
12
|
var _cleanUrl = require("../../../common/url/clean-url");
|
|
12
13
|
var _constants = require("../constants");
|
|
13
14
|
var _belNode = require("./bel-node");
|
|
@@ -44,11 +45,11 @@ class Interaction extends _belNode.BelNode {
|
|
|
44
45
|
if (this.trigger === _constants.API_TRIGGER_NAME) this.createdByApi = true;
|
|
45
46
|
}
|
|
46
47
|
updateDom(timestamp) {
|
|
47
|
-
this.domTimestamp = timestamp ||
|
|
48
|
+
this.domTimestamp = timestamp || (0, _now.now)(); // default timestamp should be precise for accurate isActiveDuring calculations
|
|
48
49
|
}
|
|
49
50
|
updateHistory(timestamp, newUrl) {
|
|
50
51
|
this.newURL = newUrl || '' + _runtime.globalScope?.location;
|
|
51
|
-
this.historyTimestamp = timestamp ||
|
|
52
|
+
this.historyTimestamp = timestamp || (0, _now.now)();
|
|
52
53
|
}
|
|
53
54
|
seenHistoryAndDomChange() {
|
|
54
55
|
return this.historyTimestamp > 0 && this.domTimestamp > this.historyTimestamp; // URL must change before DOM does
|
|
@@ -118,9 +119,9 @@ class Interaction extends _belNode.BelNode {
|
|
|
118
119
|
// IMPORTANT: The order in which addString is called matters and correlates to the order in which string shows up in the harvest payload. Do not re-order the following code.
|
|
119
120
|
const fields = [(0, _belSerializer.numeric)(this.belType), 0,
|
|
120
121
|
// this will be overwritten below with number of attached nodes
|
|
121
|
-
(0, _belSerializer.numeric)(
|
|
122
|
+
(0, _belSerializer.numeric)(this.start - firstStartTimeOfPayload),
|
|
122
123
|
// relative to first node
|
|
123
|
-
(0, _belSerializer.numeric)(
|
|
124
|
+
(0, _belSerializer.numeric)(this.end - this.start),
|
|
124
125
|
// end -- relative to start
|
|
125
126
|
(0, _belSerializer.numeric)(this.callbackEnd),
|
|
126
127
|
// cbEnd -- relative to start; not used by BrowserInteraction events
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* @license Apache-2.0
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { now } from '../timing/now';
|
|
9
|
+
|
|
8
10
|
/**
|
|
9
11
|
* Indicates if the agent is running within a normal browser window context.
|
|
10
12
|
*/
|
|
@@ -44,4 +46,4 @@ export const supportsSendBeacon = !!globalScope.navigator?.sendBeacon;
|
|
|
44
46
|
* according to the browser's local clock.
|
|
45
47
|
* @type {number}
|
|
46
48
|
*/
|
|
47
|
-
export const originTime =
|
|
49
|
+
export const originTime = Date.now() - now();
|
|
@@ -69,8 +69,15 @@ export class SessionEntity {
|
|
|
69
69
|
expiresMs = DEFAULT_EXPIRES_MS,
|
|
70
70
|
inactiveMs = DEFAULT_INACTIVE_MS
|
|
71
71
|
} = _ref;
|
|
72
|
+
/** Ensure that certain properties are preserved across a reset if already set */
|
|
73
|
+
const persistentAttributes = {
|
|
74
|
+
serverTimeDiff: this.state.serverTimeDiff || model.serverTimeDiff
|
|
75
|
+
};
|
|
72
76
|
this.state = {};
|
|
73
|
-
this.sync(
|
|
77
|
+
this.sync({
|
|
78
|
+
...model,
|
|
79
|
+
...persistentAttributes
|
|
80
|
+
});
|
|
74
81
|
|
|
75
82
|
// value is intended to act as the primary value of the k=v pair
|
|
76
83
|
this.state.value = value;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { originTime } from '../constants/runtime';
|
|
2
|
-
import { ee as baseEE } from '../event-emitter/contextual-ee';
|
|
3
2
|
import { getRuntime } from '../config/config';
|
|
4
|
-
import { SESSION_EVENT_TYPES, SESSION_EVENTS } from '../session/constants';
|
|
5
3
|
|
|
6
4
|
/**
|
|
7
5
|
* Class used to adjust the timestamp of harvested data to New Relic server time. This
|
|
@@ -36,18 +34,7 @@ export class TimeKeeper {
|
|
|
36
34
|
#ready = false;
|
|
37
35
|
constructor(agentIdentifier) {
|
|
38
36
|
this.#session = getRuntime(agentIdentifier)?.session;
|
|
39
|
-
|
|
40
|
-
const ee = baseEE.get(agentIdentifier);
|
|
41
|
-
ee.on(SESSION_EVENTS.UPDATE, this.#processSessionUpdate.bind(this));
|
|
42
|
-
ee.on(SESSION_EVENTS.STARTED, () => {
|
|
43
|
-
if (this.#ready) {
|
|
44
|
-
this.#session.write({
|
|
45
|
-
serverTimeDiff: this.#localTimeDiff
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
this.#processSessionUpdate(null, this.#session.read());
|
|
50
|
-
}
|
|
37
|
+
this.processStoredDiff();
|
|
51
38
|
}
|
|
52
39
|
get ready() {
|
|
53
40
|
return this.#ready;
|
|
@@ -63,22 +50,22 @@ export class TimeKeeper {
|
|
|
63
50
|
* @param endTime {number} The end time of the RUM request
|
|
64
51
|
*/
|
|
65
52
|
processRumRequest(rumRequest, startTime, endTime) {
|
|
53
|
+
this.processStoredDiff();
|
|
66
54
|
if (this.#ready) return; // Server time calculated from session entity
|
|
67
|
-
|
|
68
55
|
const responseDateHeader = rumRequest.getResponseHeader('Date');
|
|
69
56
|
if (!responseDateHeader) {
|
|
70
57
|
throw new Error('Missing date header on rum response.');
|
|
71
58
|
}
|
|
72
59
|
const medianRumOffset = (endTime - startTime) / 2;
|
|
73
|
-
const serverOffset =
|
|
60
|
+
const serverOffset = startTime + medianRumOffset;
|
|
74
61
|
|
|
75
62
|
// Corrected page origin time
|
|
76
63
|
this.#correctedOriginTime = Math.floor(Date.parse(responseDateHeader) - serverOffset);
|
|
77
64
|
this.#localTimeDiff = originTime - this.#correctedOriginTime;
|
|
78
|
-
if (
|
|
65
|
+
if (isNaN(this.#correctedOriginTime)) {
|
|
79
66
|
throw new Error('Date header invalid format.');
|
|
80
67
|
}
|
|
81
|
-
|
|
68
|
+
this.#session?.write({
|
|
82
69
|
serverTimeDiff: this.#localTimeDiff
|
|
83
70
|
});
|
|
84
71
|
this.#ready = true;
|
|
@@ -91,7 +78,7 @@ export class TimeKeeper {
|
|
|
91
78
|
* @returns {number} Corrected unix/epoch timestamp
|
|
92
79
|
*/
|
|
93
80
|
convertRelativeTimestamp(relativeTime) {
|
|
94
|
-
return this.#correctedOriginTime + relativeTime;
|
|
81
|
+
return Math.floor(this.#correctedOriginTime + relativeTime);
|
|
95
82
|
}
|
|
96
83
|
|
|
97
84
|
/**
|
|
@@ -103,19 +90,11 @@ export class TimeKeeper {
|
|
|
103
90
|
return Math.floor(timestamp - this.#localTimeDiff);
|
|
104
91
|
}
|
|
105
92
|
|
|
106
|
-
/**
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
#processSessionUpdate(type, data) {
|
|
112
|
-
if (typeof data?.serverTimeDiff !== 'number') return;
|
|
113
|
-
if (!type && !this.#ready ||
|
|
114
|
-
// This captures the initial read from the session entity when the timekeeper first initializes
|
|
115
|
-
type === SESSION_EVENT_TYPES.CROSS_TAB // This captures any cross-tab write of the session entity
|
|
116
|
-
) {
|
|
117
|
-
// This captures the initial read from the session entity when the timekeeper first initializes
|
|
118
|
-
this.#localTimeDiff = data.serverTimeDiff;
|
|
93
|
+
/** Process the session entity and use the info to set the main time calculations if present */
|
|
94
|
+
processStoredDiff() {
|
|
95
|
+
const storedServerTimeDiff = this.#session?.read()?.serverTimeDiff;
|
|
96
|
+
if (typeof storedServerTimeDiff === 'number' && !isNaN(storedServerTimeDiff)) {
|
|
97
|
+
this.#localTimeDiff = storedServerTimeDiff;
|
|
119
98
|
this.#correctedOriginTime = originTime - this.#localTimeDiff;
|
|
120
99
|
this.#ready = true;
|
|
121
100
|
}
|
|
@@ -193,8 +193,7 @@ export class Aggregate extends AggregateBase {
|
|
|
193
193
|
time
|
|
194
194
|
};
|
|
195
195
|
|
|
196
|
-
//
|
|
197
|
-
// and spa annotates the error with interaction info
|
|
196
|
+
// Trace sends the error in its payload, and both trace & replay simply listens for any error to occur.
|
|
198
197
|
const jsErrorEvent = [type, bucketHash, params, newMetrics, customAttributes];
|
|
199
198
|
handle('trace-jserror', jsErrorEvent, undefined, FEATURE_NAMES.sessionTrace, this.ee);
|
|
200
199
|
// still send EE events for other features such as above, but stop this one from aggregating internal data
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
import { handle } from '../../../common/event-emitter/handle';
|
|
7
7
|
import { InstrumentBase } from '../../utils/instrument-base';
|
|
8
8
|
import { FEATURE_NAME } from '../constants';
|
|
9
|
-
import { FEATURE_NAMES } from '../../../loaders/features/features';
|
|
10
9
|
import { globalScope } from '../../../common/constants/runtime';
|
|
11
10
|
import { eventListenerOpts } from '../../../common/event-listener/event-listener-opts';
|
|
12
11
|
import { now } from '../../../common/timing/now';
|
|
@@ -24,7 +23,7 @@ export class Instrument extends InstrumentBase {
|
|
|
24
23
|
} catch (e) {}
|
|
25
24
|
this.ee.on('internal-error', error => {
|
|
26
25
|
if (!this.abortHandler) return;
|
|
27
|
-
handle('ierr', [castError(error), now(), true, {}, this.#replayRunning], undefined,
|
|
26
|
+
handle('ierr', [castError(error), now(), true, {}, this.#replayRunning], undefined, this.featureName, this.ee);
|
|
28
27
|
});
|
|
29
28
|
this.ee.on(SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, isRunning => {
|
|
30
29
|
this.#replayRunning = isRunning;
|
|
@@ -33,11 +32,11 @@ export class Instrument extends InstrumentBase {
|
|
|
33
32
|
if (!this.abortHandler) return;
|
|
34
33
|
handle('err', [castPromiseRejectionEvent(promiseRejectionEvent), now(), false, {
|
|
35
34
|
unhandledPromiseRejection: 1
|
|
36
|
-
}, this.#replayRunning], undefined,
|
|
35
|
+
}, this.#replayRunning], undefined, this.featureName, this.ee);
|
|
37
36
|
}, eventListenerOpts(false, this.removeOnAbort?.signal));
|
|
38
37
|
globalScope.addEventListener('error', errorEvent => {
|
|
39
38
|
if (!this.abortHandler) return;
|
|
40
|
-
handle('err', [castErrorEvent(errorEvent), now(), false, {}, this.#replayRunning], undefined,
|
|
39
|
+
handle('err', [castErrorEvent(errorEvent), now(), false, {}, this.#replayRunning], undefined, this.featureName, this.ee);
|
|
41
40
|
}, eventListenerOpts(false, this.removeOnAbort?.signal));
|
|
42
41
|
this.abortHandler = this.#abort; // we also use this as a flag to denote that the feature is active or on and handling errors
|
|
43
42
|
this.importAggregator();
|
|
@@ -24,7 +24,7 @@ export class Aggregate extends AggregateBase {
|
|
|
24
24
|
this.timeToFirstByte = 0;
|
|
25
25
|
this.firstByteToWindowLoad = 0; // our "frontend" duration
|
|
26
26
|
this.firstByteToDomContent = 0; // our "dom processing" duration
|
|
27
|
-
|
|
27
|
+
this.timeKeeper = new TimeKeeper(this.agentIdentifier);
|
|
28
28
|
if (isBrowserScope) {
|
|
29
29
|
timeToFirstByte.subscribe(_ref => {
|
|
30
30
|
let {
|
|
@@ -110,6 +110,9 @@ export class Aggregate extends AggregateBase {
|
|
|
110
110
|
}
|
|
111
111
|
queryParameters.fp = firstPaint.current.value;
|
|
112
112
|
queryParameters.fcp = firstContentfulPaint.current.value;
|
|
113
|
+
if (this.timeKeeper?.ready) {
|
|
114
|
+
queryParameters.timestamp = this.timeKeeper.convertRelativeTimestamp(now());
|
|
115
|
+
}
|
|
113
116
|
const rumStartTime = now();
|
|
114
117
|
harvester.send({
|
|
115
118
|
endpoint: 'rum',
|
|
@@ -134,10 +137,9 @@ export class Aggregate extends AggregateBase {
|
|
|
134
137
|
return;
|
|
135
138
|
}
|
|
136
139
|
try {
|
|
137
|
-
|
|
138
|
-
timeKeeper.
|
|
139
|
-
|
|
140
|
-
agentRuntime.timeKeeper = timeKeeper;
|
|
140
|
+
this.timeKeeper.processRumRequest(xhr, rumStartTime, rumEndTime);
|
|
141
|
+
if (!this.timeKeeper.ready) throw new Error('TimeKeeper not ready');
|
|
142
|
+
agentRuntime.timeKeeper = this.timeKeeper;
|
|
141
143
|
} catch (error) {
|
|
142
144
|
handle(SUPPORTABILITY_METRIC_CHANNEL, ['PVE/NRTime/Calculation/Failed'], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
143
145
|
drain(this.agentIdentifier, FEATURE_NAMES.metrics, true);
|
|
@@ -95,7 +95,14 @@ export class Recorder {
|
|
|
95
95
|
inlineStylesheet: inline_stylesheet,
|
|
96
96
|
inlineImages: inline_images,
|
|
97
97
|
collectFonts: collect_fonts,
|
|
98
|
-
checkoutEveryNms: CHECKOUT_MS[this.parent.mode]
|
|
98
|
+
checkoutEveryNms: CHECKOUT_MS[this.parent.mode],
|
|
99
|
+
/** Emits errors thrown by rrweb directly before bubbling them up to the window */
|
|
100
|
+
errorHandler: err => {
|
|
101
|
+
/** capture rrweb errors as "internal" errors only */
|
|
102
|
+
this.parent.ee.emit('internal-error', [err]);
|
|
103
|
+
/** returning true informs rrweb to swallow the error instead of throwing it to the window */
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
99
106
|
});
|
|
100
107
|
this.stopRecording = () => {
|
|
101
108
|
this.recording = false;
|
|
@@ -272,7 +272,7 @@ export class TraceStorage {
|
|
|
272
272
|
// Ajax (FEATURE) events--XML & fetches--pipes into ST here.
|
|
273
273
|
storeXhrAgg(type, name, params, metrics) {
|
|
274
274
|
if (type !== 'xhr') return;
|
|
275
|
-
this.storeSTN(new TraceNode('Ajax', metrics.time, metrics.time + metrics.duration, "".concat(params.status, " ").concat(params.method, ": ").concat(params.host).concat(params.pathname)));
|
|
275
|
+
this.storeSTN(new TraceNode('Ajax', metrics.time, metrics.time + metrics.duration, "".concat(params.status, " ").concat(params.method, ": ").concat(params.host).concat(params.pathname), 'ajax'));
|
|
276
276
|
}
|
|
277
277
|
restoreNode(name, listOfSTNodes) {
|
|
278
278
|
if (this.nodeCount >= MAX_NODES_PER_HARVEST) return;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { now } from '../../../common/timing/now';
|
|
2
1
|
let nodesSeen = 0;
|
|
3
2
|
export class BelNode {
|
|
4
3
|
belType;
|
|
5
4
|
/** List of other BelNode derivatives. Each children should be of a subclass that implements its own 'serialize' function. */
|
|
6
5
|
children = [];
|
|
7
|
-
start
|
|
6
|
+
start;
|
|
8
7
|
end;
|
|
9
8
|
callbackEnd = 0;
|
|
10
9
|
callbackDuration = 0;
|
|
@@ -61,7 +61,7 @@ export class Aggregate extends AggregateBase {
|
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
// By default, a complete UI driven interaction requires event -> URL change -> DOM mod in that exact order.
|
|
64
|
-
registerHandler('newUIEvent', event => this.startUIInteraction(event.type, event.timeStamp, event.target), this.featureName, this.ee);
|
|
64
|
+
registerHandler('newUIEvent', event => this.startUIInteraction(event.type, Math.floor(event.timeStamp), event.target), this.featureName, this.ee);
|
|
65
65
|
registerHandler('newURL', (timestamp, url) => this.interactionInProgress?.updateHistory(timestamp, url), this.featureName, this.ee);
|
|
66
66
|
registerHandler('newDom', timestamp => {
|
|
67
67
|
this.interactionInProgress?.updateDom(timestamp);
|
|
@@ -2,6 +2,7 @@ import { getInfo } from '../../../common/config/config';
|
|
|
2
2
|
import { globalScope, initialLocation } from '../../../common/constants/runtime';
|
|
3
3
|
import { generateUuid } from '../../../common/ids/unique-id';
|
|
4
4
|
import { addCustomAttributes, getAddStringContext, nullable, numeric } from '../../../common/serialize/bel-serializer';
|
|
5
|
+
import { now } from '../../../common/timing/now';
|
|
5
6
|
import { cleanURL } from '../../../common/url/clean-url';
|
|
6
7
|
import { NODE_TYPE, INTERACTION_STATUS, INTERACTION_TYPE, API_TRIGGER_NAME } from '../constants';
|
|
7
8
|
import { BelNode } from './bel-node';
|
|
@@ -39,11 +40,11 @@ export class Interaction extends BelNode {
|
|
|
39
40
|
if (this.trigger === API_TRIGGER_NAME) this.createdByApi = true;
|
|
40
41
|
}
|
|
41
42
|
updateDom(timestamp) {
|
|
42
|
-
this.domTimestamp = timestamp ||
|
|
43
|
+
this.domTimestamp = timestamp || now(); // default timestamp should be precise for accurate isActiveDuring calculations
|
|
43
44
|
}
|
|
44
45
|
updateHistory(timestamp, newUrl) {
|
|
45
46
|
this.newURL = newUrl || '' + globalScope?.location;
|
|
46
|
-
this.historyTimestamp = timestamp ||
|
|
47
|
+
this.historyTimestamp = timestamp || now();
|
|
47
48
|
}
|
|
48
49
|
seenHistoryAndDomChange() {
|
|
49
50
|
return this.historyTimestamp > 0 && this.domTimestamp > this.historyTimestamp; // URL must change before DOM does
|
|
@@ -113,9 +114,9 @@ export class Interaction extends BelNode {
|
|
|
113
114
|
// IMPORTANT: The order in which addString is called matters and correlates to the order in which string shows up in the harvest payload. Do not re-order the following code.
|
|
114
115
|
const fields = [numeric(this.belType), 0,
|
|
115
116
|
// this will be overwritten below with number of attached nodes
|
|
116
|
-
numeric(
|
|
117
|
+
numeric(this.start - firstStartTimeOfPayload),
|
|
117
118
|
// relative to first node
|
|
118
|
-
numeric(
|
|
119
|
+
numeric(this.end - this.start),
|
|
119
120
|
// end -- relative to start
|
|
120
121
|
numeric(this.callbackEnd),
|
|
121
122
|
// cbEnd -- relative to start; not used by BrowserInteraction events
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file Contains constants about the environment the agent is running
|
|
3
|
-
* within. These values are derived at the time the agent is first loaded.
|
|
4
|
-
* @copyright 2023 New Relic Corporation. All rights reserved.
|
|
5
|
-
* @license Apache-2.0
|
|
6
|
-
*/
|
|
7
1
|
/**
|
|
8
2
|
* Indicates if the agent is running within a normal browser window context.
|
|
9
3
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../../src/common/constants/runtime.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../../src/common/constants/runtime.js"],"names":[],"mappings":"AASA;;GAEG;AACH,qCAEqB;AAErB;;GAEG;AACH,oCAeK;AAEL,oDAUI;AAEJ,oDAA6F;AAE7F,sCAA2F;AAE3F,qCAAyD;AAEzD,4BAA8E;AAE9E;;;;;;GAMG;AACH,iCAAwE;AAExE,+BAOI;AAEJ,2BAA2E;AAE3E,yCAAqE;AAErE;;;;GAIG;AACH,yBAFU,MAAM,CAE4B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-entity.d.ts","sourceRoot":"","sources":["../../../../src/common/session/session-entity.js"],"names":[],"mappings":"AA8BA;IACE;;;;;OAKG;IACH,uBA0BC;IApBC,qBAAsC;IACtC,aAAsB;IACtB,UAAe;IAGf,SAAc;IAEd,QAAiC;IAenC;;;;
|
|
1
|
+
{"version":3,"file":"session-entity.d.ts","sourceRoot":"","sources":["../../../../src/common/session/session-entity.js"],"names":[],"mappings":"AA8BA;IACE;;;;;OAKG;IACH,uBA0BC;IApBC,qBAAsC;IACtC,aAAsB;IACtB,UAAe;IAGf,SAAc;IAEd,QAAiC;IAenC;;;;aA4EC;IAnEC,8BAA0B;IAC1B,+BAA4B;IAc1B,gCAOqC;IAUrC,4CAkBsC;IAexC,iCAAuB;IAKzB,wBAEC;IAED,sBAEC;IAED;;;OAGG;IACH,QAFa,MAAM,CA6BlB;IAED;;;;;;OAMG;IACH,YAHW,MAAM,GACJ,MAAM,CAkBlB;IAED,gBAuBC;IAED;;OAEG;IACH,gBAIC;IAED;;;OAGG;IACH,qBAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;OAGG;IACH,gBAHW,MAAM,GACJ,OAAO,CAKnB;IAED,yDAUC;IAED,6DAIC;IAED;;;OAGG;IACH,6BAHW,MAAM,GACJ,MAAM,CAIlB;IAED,gDAaC;IAHG,YAAuD;CAI5D;sBAzSqB,gBAAgB;iCAGL,4BAA4B"}
|
|
@@ -27,6 +27,8 @@ export class TimeKeeper {
|
|
|
27
27
|
* @return {number} Corrected unix/epoch timestamp
|
|
28
28
|
*/
|
|
29
29
|
correctAbsoluteTimestamp(timestamp: number): number;
|
|
30
|
+
/** Process the session entity and use the info to set the main time calculations if present */
|
|
31
|
+
processStoredDiff(): void;
|
|
30
32
|
#private;
|
|
31
33
|
}
|
|
32
34
|
//# sourceMappingURL=time-keeper.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"time-keeper.d.ts","sourceRoot":"","sources":["../../../../src/common/timing/time-keeper.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"time-keeper.d.ts","sourceRoot":"","sources":["../../../../src/common/timing/time-keeper.js"],"names":[],"mappings":"AAGA;;;;GAIG;AACH;IA2BE,kCAGC;IAED,oBAEC;IAED,kCAEC;IAED;;;;;OAKG;IACH,8BAJsB,cAAc,aACf,MAAM,WACR,MAAM,QAuBxB;IAED;;;;;OAKG;IACH,uCAHwB,MAAM,GACjB,MAAM,CAIlB;IAED;;;;OAIG;IACH,oCAHqB,MAAM,GACf,MAAM,CAIjB;IAED,+FAA+F;IAC/F,0BAOC;;CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/aggregate/index.js"],"names":[],"mappings":"AAyBA;;GAEG;AAEH;IACE,2BAAiC;IACjC,mDAgCC;IA7BC,kBAAuB;IACvB,eAAoB;IACpB,qBAA0B;IAC1B,2BAAgC;IAChC,mCAA4B;IAC5B,qBAAwB;IA0B1B;;;MAwBC;IAED,qCAWC;IAED,8BAEC;IAED,oEAMC;IAED;;;;;;OAMG;IACH,qCAHW,SAAS,GACP,MAAM,CAgBlB;IAED,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/aggregate/index.js"],"names":[],"mappings":"AAyBA;;GAEG;AAEH;IACE,2BAAiC;IACjC,mDAgCC;IA7BC,kBAAuB;IACvB,eAAoB;IACpB,qBAA0B;IAC1B,2BAAgC;IAChC,mCAA4B;IAC5B,qBAAwB;IA0B1B;;;MAwBC;IAED,qCAWC;IAED,8BAEC;IAED,oEAMC;IAED;;;;;;OAMG;IACH,qCAHW,SAAS,GACP,MAAM,CAgBlB;IAED,4FA4FC;IA4BD,yDA6BC;IAED,qFAOC;;CAwBF;wBAvSY,OAAO,0BAA0B,EAAE,SAAS;8BAN3B,4BAA4B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/instrument/index.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/instrument/index.js"],"names":[],"mappings":"AAcA;IACE,2BAAiC;IAIjC,mEA6BC;IAxBG,2CAA0C;IAsB5C,yBAA+B;;CASlC;+BAjD8B,6BAA6B"}
|
|
@@ -4,7 +4,9 @@ export class Aggregate extends AggregateBase {
|
|
|
4
4
|
timeToFirstByte: number;
|
|
5
5
|
firstByteToWindowLoad: number;
|
|
6
6
|
firstByteToDomContent: number;
|
|
7
|
+
timeKeeper: TimeKeeper;
|
|
7
8
|
sendRum(): void;
|
|
8
9
|
}
|
|
9
10
|
import { AggregateBase } from '../../utils/aggregate-base';
|
|
11
|
+
import { TimeKeeper } from '../../../common/timing/time-keeper';
|
|
10
12
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_event/aggregate/index.js"],"names":[],"mappings":"AAoBA;IACE,2BAA2C;IAC3C,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_event/aggregate/index.js"],"names":[],"mappings":"AAoBA;IACE,2BAA2C;IAC3C,mDAqBC;IAlBC,wBAAwB;IACxB,8BAA8B;IAC9B,8BAA8B;IAC9B,uBAAsD;IAiBxD,gBAsGC;CACF;8BA3I6B,4BAA4B;2BAS/B,oCAAoC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/recorder.js"],"names":[],"mappings":"AAYA;IAUE,yBAkBC;IAdC,iEAAiE;IACjE,mBAAsB;IACtB,6DAA6D;IAC7D,oCAAuC;IACvC,kIAAkI;IAClI,kBAAqB;IACrB,sDAAsD;IACtD,YAAoB;IACpB,oEAAoE;IACpE,6BAAqH;IACrH,0FAA0F;IAC1F,eAA6C;IAC7C,uIAAuI;IACvI,0BAAyE;IAG3E;;;;;;;;;MAYC;IAED,mFAAmF;IACnF,oBAKC;IAED,qDAAqD;IACrD,
|
|
1
|
+
{"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/recorder.js"],"names":[],"mappings":"AAYA;IAUE,yBAkBC;IAdC,iEAAiE;IACjE,mBAAsB;IACtB,6DAA6D;IAC7D,oCAAuC;IACvC,kIAAkI;IAClI,kBAAqB;IACrB,sDAAsD;IACtD,YAAoB;IACpB,oEAAoE;IACpE,6BAAqH;IACrH,0FAA0F;IAC1F,eAA6C;IAC7C,uIAAuI;IACvI,0BAAyE;IAG3E;;;;;;;;;MAYC;IAED,mFAAmF;IACnF,oBAKC;IAED,qDAAqD;IACrD,uBAsCC;IAED;;;;;OAKG;IACH,yCA0BC;IAED,0HAA0H;IAC1H,yCAqDC;IA3CG,8BAAoB;IA6CxB,0HAA0H;IAC1H,yBAOC;IAED,wBAEC;IAED,gCAAgC;IAChC,uCAGC;IAED;;;SAGK;IACL,oCAGC;;CACF;+BA3N8B,mBAAmB"}
|
|
@@ -4,7 +4,7 @@ export class Aggregate extends AggregateBase {
|
|
|
4
4
|
agentRuntime: any;
|
|
5
5
|
agentInfo: any;
|
|
6
6
|
/** A buffer to hold on to harvested traces in the case that a retry must be made later */
|
|
7
|
-
sentTrace: any[] | null
|
|
7
|
+
sentTrace: any[] | null;
|
|
8
8
|
harvestTimeSeconds: any;
|
|
9
9
|
/** Tied to the entitlement flag response from BCS. Will short circuit operations of the agg if false */
|
|
10
10
|
entitled: any;
|
|
@@ -31,7 +31,7 @@ export class Aggregate extends AggregateBase {
|
|
|
31
31
|
timestamp: any;
|
|
32
32
|
attributes: string;
|
|
33
33
|
};
|
|
34
|
-
body: any[]
|
|
34
|
+
body: any[];
|
|
35
35
|
} | undefined;
|
|
36
36
|
/** When the harvest scheduler finishes, this callback is executed. It's main purpose is to determine if the payload needs to be retried
|
|
37
37
|
* and if so, it will take all data from the temporary buffer and place it back into the traceStorage module
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_trace/aggregate/index.js"],"names":[],"mappings":"AAcA;IACE,2BAAiC;IAEjC,mDAmBC;IAjBC,kBAA+C;IAC/C,eAAyC;IAEzC,0FAA0F;IAC1F,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_trace/aggregate/index.js"],"names":[],"mappings":"AAcA;IACE,2BAAiC;IAEjC,mDAmBC;IAjBC,kBAA+C;IAC/C,eAAyC;IAEzC,0FAA0F;IAC1F,wBAAqB;IACrB,wBAA0G;IAC1G,0GAA0G;IAC1G,cAAyB;IACzB,mIAAmI;IACnI,uBAA0B;IAC1B,0CAA0C;IAC1C,oBAAuB;IACvB,wIAAwI;IACxI,2BAA0C;IAM5C,gLAAgL;IAChL,mEA6DC;IA1CyD,UAA4D;IAGpH,iCAAuB;IAOvB,wCAKQ;IA6BV,kJAAkJ;IAClJ,wBAIC;IAED,iJAAiJ;IACjJ;;;;;;;;;;kBA6DC;IAED;;OAEG;IACH,qCAKC;IAED,8DAA8D;IAC9D,qBAUC;IAED,2DAA2D;IAC3D,cAKC;CACF;8BAvM6B,4BAA4B;6BAC7B,iBAAiB;iCAJb,2CAA2C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../../../../src/features/session_trace/aggregate/trace/storage.js"],"names":[],"mappings":"AAyBA,+HAA+H;AAC/H;IAQE,yBAEC;IATD,kBAAa;IACb,UAAU;IACV,0BAA4B;IAC5B,wBAAmB;IACnB,mBAAgB;IAChB,2BAA4B;IAG1B,YAAoB;IAGtB,gGAAgG;IAChG,yBAiBC;IAED;;;;OAIG;IACH,2BAHW,MAAM,GACJ,MAAM,CAsBlB;IAED,oEAAoE;IACpE;;;;;;;;MAsBC;IAED,mEA6BC;IAED,oDAOC;IAED,oCAkBC;IAGD,uEAcC;IAED,oDAKC;IAED,wBAwBC;IAED,uCAuBC;IAGD,gDAEC;IAID,qCAaC;IAGD,qEAGC;IAGD,
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../../../../src/features/session_trace/aggregate/trace/storage.js"],"names":[],"mappings":"AAyBA,+HAA+H;AAC/H;IAQE,yBAEC;IATD,kBAAa;IACb,UAAU;IACV,0BAA4B;IAC5B,wBAAmB;IACnB,mBAAgB;IAChB,2BAA4B;IAG1B,YAAoB;IAGtB,gGAAgG;IAChG,yBAiBC;IAED;;;;OAIG;IACH,2BAHW,MAAM,GACJ,MAAM,CAsBlB;IAED,oEAAoE;IACpE;;;;;;;;MAsBC;IAED,mEA6BC;IAED,oDAOC;IAED,oCAkBC;IAGD,uEAcC;IAED,oDAKC;IAED,wBAwBC;IAED,uCAuBC;IAGD,gDAEC;IAID,qCAaC;IAGD,qEAGC;IAGD,mEAGC;IAED,iDAKC;;CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ajax-node.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/ajax-node.js"],"names":[],"mappings":"AAIA;IACE,kDAiBC;IAfC,gBAA6B;IAC7B,YAA8B;IAC9B,YAA8B;IAC9B,YAA8B;IAC9B,UAA0B;IAC1B,YAAmC;IACnC,YAAoC;IACpC,+BAAwD;IACxD,YAA8B;IAC9B,aAAgC;IAChC,mBAA4C;IAC5C,SAAwB;
|
|
1
|
+
{"version":3,"file":"ajax-node.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/ajax-node.js"],"names":[],"mappings":"AAIA;IACE,kDAiBC;IAfC,gBAA6B;IAC7B,YAA8B;IAC9B,YAA8B;IAC9B,YAA8B;IAC9B,UAA0B;IAC1B,YAAmC;IACnC,YAAoC;IACpC,+BAAwD;IACxD,YAA8B;IAC9B,aAAgC;IAChC,mBAA4C;IAC5C,SAAwB;IAM1B,6CA+BC;CACF;wBAtDuB,YAAY"}
|
|
@@ -3,7 +3,7 @@ export class BelNode {
|
|
|
3
3
|
belType: any;
|
|
4
4
|
/** List of other BelNode derivatives. Each children should be of a subclass that implements its own 'serialize' function. */
|
|
5
5
|
children: any[];
|
|
6
|
-
start:
|
|
6
|
+
start: any;
|
|
7
7
|
end: any;
|
|
8
8
|
callbackEnd: number;
|
|
9
9
|
callbackDuration: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bel-node.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/bel-node.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bel-node.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/bel-node.js"],"names":[],"mappings":"AAEA;IAUE,kCAGC;IAZD,aAAO;IACP,6HAA6H;IAC7H,gBAAa;IACb,WAAK;IACL,SAAG;IACH,oBAAe;IACf,yBAAoB;IACpB,eAAoB;IAIlB,qBAAsC;IAGxC,2BAEC;IAED,+CAA+C;IAC/C,kBAAe;CAChB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interaction.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/interaction.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"interaction.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/interaction.js"],"names":[],"mappings":"AASA;;IAEI;AACJ;IAoBE,+FAYC;IA/BD,WAAmB;IACnB,uBAAgC;IAChC,eAAmC;IACnC,eAAmC;IACnC,gBAAU;IACV,qBAAqB;IACrB,oBAAoB;IACpB,eAAS;IACT,aAAO;IACP,cAAQ;IACR,+EAA+E;IAC/E,eAA8B;IAC9B,qBAAgB;IAChB,yBAAoB;IACpB,sBAAoB;IACpB,6BAA2B;IAC3B,cAAW;IACX,uBAAiB;IAIf,gBAAoC;IACpC,aAAsB;IAEtB,cAAiC;IACjC,wCAGE;IACF,mBAAyC;IAAxB,qBAAwB;IAI3C,gCAEC;IAED,iDAGC;IAED,mCAEC;IAED,8BAIC;IAED,kCAUC;IAwBD;;;;;OAKG;IACH,0BAHW,mBAAmB,WAM7B;IAGD,uBAAoB;IACpB,iCAA8B;IAC9B,sBAAmB;IAEnB,gDA2CC;;CACF;wBAxJuB,YAAY"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@newrelic/browser-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.260.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
|
|
6
6
|
"description": "New Relic Browser Agent",
|
|
@@ -214,6 +214,7 @@
|
|
|
214
214
|
"eslint-plugin-sonarjs": "^0.23.0",
|
|
215
215
|
"fastify": "^4.25.2",
|
|
216
216
|
"fastify-plugin": "^4.5.1",
|
|
217
|
+
"form-data": "^4.0.0",
|
|
217
218
|
"fs-extra": "^11.2.0",
|
|
218
219
|
"function-bind": "^1.1.1",
|
|
219
220
|
"glob": "^10.2.5",
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* @license Apache-2.0
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { now } from '../timing/now'
|
|
9
|
+
|
|
8
10
|
/**
|
|
9
11
|
* Indicates if the agent is running within a normal browser window context.
|
|
10
12
|
*/
|
|
@@ -79,4 +81,4 @@ export const supportsSendBeacon = !!globalScope.navigator?.sendBeacon
|
|
|
79
81
|
* according to the browser's local clock.
|
|
80
82
|
* @type {number}
|
|
81
83
|
*/
|
|
82
|
-
export const originTime =
|
|
84
|
+
export const originTime = Date.now() - now()
|
|
@@ -64,8 +64,10 @@ export class SessionEntity {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
setup ({ value = generateRandomHexString(16), expiresMs = DEFAULT_EXPIRES_MS, inactiveMs = DEFAULT_INACTIVE_MS }) {
|
|
67
|
+
/** Ensure that certain properties are preserved across a reset if already set */
|
|
68
|
+
const persistentAttributes = { serverTimeDiff: this.state.serverTimeDiff || model.serverTimeDiff }
|
|
67
69
|
this.state = {}
|
|
68
|
-
this.sync(model)
|
|
70
|
+
this.sync({ ...model, ...persistentAttributes })
|
|
69
71
|
|
|
70
72
|
// value is intended to act as the primary value of the k=v pair
|
|
71
73
|
this.state.value = value
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { originTime } from '../constants/runtime'
|
|
2
|
-
import { ee as baseEE } from '../event-emitter/contextual-ee'
|
|
3
2
|
import { getRuntime } from '../config/config'
|
|
4
|
-
import { SESSION_EVENT_TYPES, SESSION_EVENTS } from '../session/constants'
|
|
5
3
|
|
|
6
4
|
/**
|
|
7
5
|
* Class used to adjust the timestamp of harvested data to New Relic server time. This
|
|
@@ -37,17 +35,7 @@ export class TimeKeeper {
|
|
|
37
35
|
|
|
38
36
|
constructor (agentIdentifier) {
|
|
39
37
|
this.#session = getRuntime(agentIdentifier)?.session
|
|
40
|
-
|
|
41
|
-
if (this.#session) {
|
|
42
|
-
const ee = baseEE.get(agentIdentifier)
|
|
43
|
-
ee.on(SESSION_EVENTS.UPDATE, this.#processSessionUpdate.bind(this))
|
|
44
|
-
ee.on(SESSION_EVENTS.STARTED, () => {
|
|
45
|
-
if (this.#ready) {
|
|
46
|
-
this.#session.write({ serverTimeDiff: this.#localTimeDiff })
|
|
47
|
-
}
|
|
48
|
-
})
|
|
49
|
-
this.#processSessionUpdate(null, this.#session.read())
|
|
50
|
-
}
|
|
38
|
+
this.processStoredDiff()
|
|
51
39
|
}
|
|
52
40
|
|
|
53
41
|
get ready () {
|
|
@@ -65,25 +53,25 @@ export class TimeKeeper {
|
|
|
65
53
|
* @param endTime {number} The end time of the RUM request
|
|
66
54
|
*/
|
|
67
55
|
processRumRequest (rumRequest, startTime, endTime) {
|
|
56
|
+
this.processStoredDiff()
|
|
68
57
|
if (this.#ready) return // Server time calculated from session entity
|
|
69
|
-
|
|
70
58
|
const responseDateHeader = rumRequest.getResponseHeader('Date')
|
|
71
59
|
if (!responseDateHeader) {
|
|
72
60
|
throw new Error('Missing date header on rum response.')
|
|
73
61
|
}
|
|
74
62
|
|
|
75
63
|
const medianRumOffset = (endTime - startTime) / 2
|
|
76
|
-
const serverOffset =
|
|
64
|
+
const serverOffset = startTime + medianRumOffset
|
|
77
65
|
|
|
78
66
|
// Corrected page origin time
|
|
79
67
|
this.#correctedOriginTime = Math.floor(Date.parse(responseDateHeader) - serverOffset)
|
|
80
68
|
this.#localTimeDiff = originTime - this.#correctedOriginTime
|
|
81
69
|
|
|
82
|
-
if (
|
|
70
|
+
if (isNaN(this.#correctedOriginTime)) {
|
|
83
71
|
throw new Error('Date header invalid format.')
|
|
84
72
|
}
|
|
85
73
|
|
|
86
|
-
|
|
74
|
+
this.#session?.write({ serverTimeDiff: this.#localTimeDiff })
|
|
87
75
|
this.#ready = true
|
|
88
76
|
}
|
|
89
77
|
|
|
@@ -94,7 +82,7 @@ export class TimeKeeper {
|
|
|
94
82
|
* @returns {number} Corrected unix/epoch timestamp
|
|
95
83
|
*/
|
|
96
84
|
convertRelativeTimestamp (relativeTime) {
|
|
97
|
-
return this.#correctedOriginTime + relativeTime
|
|
85
|
+
return Math.floor(this.#correctedOriginTime + relativeTime)
|
|
98
86
|
}
|
|
99
87
|
|
|
100
88
|
/**
|
|
@@ -106,20 +94,11 @@ export class TimeKeeper {
|
|
|
106
94
|
return Math.floor(timestamp - this.#localTimeDiff)
|
|
107
95
|
}
|
|
108
96
|
|
|
109
|
-
/**
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
#processSessionUpdate (type, data) {
|
|
115
|
-
if (typeof data?.serverTimeDiff !== 'number') return
|
|
116
|
-
|
|
117
|
-
if (
|
|
118
|
-
(!type && !this.#ready) || // This captures the initial read from the session entity when the timekeeper first initializes
|
|
119
|
-
type === SESSION_EVENT_TYPES.CROSS_TAB // This captures any cross-tab write of the session entity
|
|
120
|
-
) {
|
|
121
|
-
// This captures the initial read from the session entity when the timekeeper first initializes
|
|
122
|
-
this.#localTimeDiff = data.serverTimeDiff
|
|
97
|
+
/** Process the session entity and use the info to set the main time calculations if present */
|
|
98
|
+
processStoredDiff () {
|
|
99
|
+
const storedServerTimeDiff = this.#session?.read()?.serverTimeDiff
|
|
100
|
+
if (typeof storedServerTimeDiff === 'number' && !isNaN(storedServerTimeDiff)) {
|
|
101
|
+
this.#localTimeDiff = storedServerTimeDiff
|
|
123
102
|
this.#correctedOriginTime = originTime - this.#localTimeDiff
|
|
124
103
|
this.#ready = true
|
|
125
104
|
}
|
|
@@ -201,8 +201,7 @@ export class Aggregate extends AggregateBase {
|
|
|
201
201
|
var type = internal ? 'ierr' : 'err'
|
|
202
202
|
var newMetrics = { time }
|
|
203
203
|
|
|
204
|
-
//
|
|
205
|
-
// and spa annotates the error with interaction info
|
|
204
|
+
// Trace sends the error in its payload, and both trace & replay simply listens for any error to occur.
|
|
206
205
|
const jsErrorEvent = [type, bucketHash, params, newMetrics, customAttributes]
|
|
207
206
|
handle('trace-jserror', jsErrorEvent, undefined, FEATURE_NAMES.sessionTrace, this.ee)
|
|
208
207
|
// still send EE events for other features such as above, but stop this one from aggregating internal data
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
import { handle } from '../../../common/event-emitter/handle'
|
|
7
7
|
import { InstrumentBase } from '../../utils/instrument-base'
|
|
8
8
|
import { FEATURE_NAME } from '../constants'
|
|
9
|
-
import { FEATURE_NAMES } from '../../../loaders/features/features'
|
|
10
9
|
import { globalScope } from '../../../common/constants/runtime'
|
|
11
10
|
import { eventListenerOpts } from '../../../common/event-listener/event-listener-opts'
|
|
12
11
|
import { now } from '../../../common/timing/now'
|
|
@@ -28,7 +27,7 @@ export class Instrument extends InstrumentBase {
|
|
|
28
27
|
|
|
29
28
|
this.ee.on('internal-error', (error) => {
|
|
30
29
|
if (!this.abortHandler) return
|
|
31
|
-
handle('ierr', [castError(error), now(), true, {}, this.#replayRunning], undefined,
|
|
30
|
+
handle('ierr', [castError(error), now(), true, {}, this.#replayRunning], undefined, this.featureName, this.ee)
|
|
32
31
|
})
|
|
33
32
|
|
|
34
33
|
this.ee.on(SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, (isRunning) => {
|
|
@@ -37,12 +36,12 @@ export class Instrument extends InstrumentBase {
|
|
|
37
36
|
|
|
38
37
|
globalScope.addEventListener('unhandledrejection', (promiseRejectionEvent) => {
|
|
39
38
|
if (!this.abortHandler) return
|
|
40
|
-
handle('err', [castPromiseRejectionEvent(promiseRejectionEvent), now(), false, { unhandledPromiseRejection: 1 }, this.#replayRunning], undefined,
|
|
39
|
+
handle('err', [castPromiseRejectionEvent(promiseRejectionEvent), now(), false, { unhandledPromiseRejection: 1 }, this.#replayRunning], undefined, this.featureName, this.ee)
|
|
41
40
|
}, eventListenerOpts(false, this.removeOnAbort?.signal))
|
|
42
41
|
|
|
43
42
|
globalScope.addEventListener('error', (errorEvent) => {
|
|
44
43
|
if (!this.abortHandler) return
|
|
45
|
-
handle('err', [castErrorEvent(errorEvent), now(), false, {}, this.#replayRunning], undefined,
|
|
44
|
+
handle('err', [castErrorEvent(errorEvent), now(), false, {}, this.#replayRunning], undefined, this.featureName, this.ee)
|
|
46
45
|
}, eventListenerOpts(false, this.removeOnAbort?.signal))
|
|
47
46
|
|
|
48
47
|
this.abortHandler = this.#abort // we also use this as a flag to denote that the feature is active or on and handling errors
|
|
@@ -26,6 +26,7 @@ export class Aggregate extends AggregateBase {
|
|
|
26
26
|
this.timeToFirstByte = 0
|
|
27
27
|
this.firstByteToWindowLoad = 0 // our "frontend" duration
|
|
28
28
|
this.firstByteToDomContent = 0 // our "dom processing" duration
|
|
29
|
+
this.timeKeeper = new TimeKeeper(this.agentIdentifier)
|
|
29
30
|
|
|
30
31
|
if (isBrowserScope) {
|
|
31
32
|
timeToFirstByte.subscribe(({ value, attrs }) => {
|
|
@@ -102,6 +103,10 @@ export class Aggregate extends AggregateBase {
|
|
|
102
103
|
queryParameters.fp = firstPaint.current.value
|
|
103
104
|
queryParameters.fcp = firstContentfulPaint.current.value
|
|
104
105
|
|
|
106
|
+
if (this.timeKeeper?.ready) {
|
|
107
|
+
queryParameters.timestamp = this.timeKeeper.convertRelativeTimestamp(now())
|
|
108
|
+
}
|
|
109
|
+
|
|
105
110
|
const rumStartTime = now()
|
|
106
111
|
harvester.send({
|
|
107
112
|
endpoint: 'rum',
|
|
@@ -117,11 +122,10 @@ export class Aggregate extends AggregateBase {
|
|
|
117
122
|
}
|
|
118
123
|
|
|
119
124
|
try {
|
|
120
|
-
|
|
121
|
-
timeKeeper.
|
|
122
|
-
if (!timeKeeper.ready) throw new Error('TimeKeeper not ready')
|
|
125
|
+
this.timeKeeper.processRumRequest(xhr, rumStartTime, rumEndTime)
|
|
126
|
+
if (!this.timeKeeper.ready) throw new Error('TimeKeeper not ready')
|
|
123
127
|
|
|
124
|
-
agentRuntime.timeKeeper = timeKeeper
|
|
128
|
+
agentRuntime.timeKeeper = this.timeKeeper
|
|
125
129
|
} catch (error) {
|
|
126
130
|
handle(SUPPORTABILITY_METRIC_CHANNEL, ['PVE/NRTime/Calculation/Failed'], undefined, FEATURE_NAMES.metrics, this.ee)
|
|
127
131
|
drain(this.agentIdentifier, FEATURE_NAMES.metrics, true)
|
|
@@ -86,7 +86,14 @@ export class Recorder {
|
|
|
86
86
|
inlineStylesheet: inline_stylesheet,
|
|
87
87
|
inlineImages: inline_images,
|
|
88
88
|
collectFonts: collect_fonts,
|
|
89
|
-
checkoutEveryNms: CHECKOUT_MS[this.parent.mode]
|
|
89
|
+
checkoutEveryNms: CHECKOUT_MS[this.parent.mode],
|
|
90
|
+
/** Emits errors thrown by rrweb directly before bubbling them up to the window */
|
|
91
|
+
errorHandler: (err) => {
|
|
92
|
+
/** capture rrweb errors as "internal" errors only */
|
|
93
|
+
this.parent.ee.emit('internal-error', [err])
|
|
94
|
+
/** returning true informs rrweb to swallow the error instead of throwing it to the window */
|
|
95
|
+
return true
|
|
96
|
+
}
|
|
90
97
|
})
|
|
91
98
|
|
|
92
99
|
this.stopRecording = () => {
|
|
@@ -116,6 +116,7 @@ export class Aggregate extends AggregateBase {
|
|
|
116
116
|
|
|
117
117
|
/** Get the ST nodes from the traceStorage buffer. This also returns helpful metadata about the payload. */
|
|
118
118
|
const { stns, earliestTimeStamp, latestTimeStamp } = this.traceStorage.takeSTNs()
|
|
119
|
+
if (!stns) return // there are no trace nodes
|
|
119
120
|
if (options.retry) {
|
|
120
121
|
this.sentTrace = stns
|
|
121
122
|
}
|
|
@@ -274,8 +274,7 @@ export class TraceStorage {
|
|
|
274
274
|
// Ajax (FEATURE) events--XML & fetches--pipes into ST here.
|
|
275
275
|
storeXhrAgg (type, name, params, metrics) {
|
|
276
276
|
if (type !== 'xhr') return
|
|
277
|
-
this.storeSTN(new TraceNode('Ajax', metrics.time, metrics.time + metrics.duration, `${params.status} ${params.method}: ${params.host}${params.pathname
|
|
278
|
-
}`))
|
|
277
|
+
this.storeSTN(new TraceNode('Ajax', metrics.time, metrics.time + metrics.duration, `${params.status} ${params.method}: ${params.host}${params.pathname}`, 'ajax'))
|
|
279
278
|
}
|
|
280
279
|
|
|
281
280
|
restoreNode (name, listOfSTNodes) {
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import { now } from '../../../common/timing/now'
|
|
2
|
-
|
|
3
1
|
let nodesSeen = 0
|
|
4
2
|
|
|
5
3
|
export class BelNode {
|
|
6
4
|
belType
|
|
7
5
|
/** List of other BelNode derivatives. Each children should be of a subclass that implements its own 'serialize' function. */
|
|
8
6
|
children = []
|
|
9
|
-
start
|
|
7
|
+
start
|
|
10
8
|
end
|
|
11
9
|
callbackEnd = 0
|
|
12
10
|
callbackDuration = 0
|
|
@@ -55,7 +55,7 @@ export class Aggregate extends AggregateBase {
|
|
|
55
55
|
})
|
|
56
56
|
|
|
57
57
|
// By default, a complete UI driven interaction requires event -> URL change -> DOM mod in that exact order.
|
|
58
|
-
registerHandler('newUIEvent', (event) => this.startUIInteraction(event.type, event.timeStamp, event.target), this.featureName, this.ee)
|
|
58
|
+
registerHandler('newUIEvent', (event) => this.startUIInteraction(event.type, Math.floor(event.timeStamp), event.target), this.featureName, this.ee)
|
|
59
59
|
registerHandler('newURL', (timestamp, url) => this.interactionInProgress?.updateHistory(timestamp, url), this.featureName, this.ee)
|
|
60
60
|
registerHandler('newDom', timestamp => {
|
|
61
61
|
this.interactionInProgress?.updateDom(timestamp)
|
|
@@ -2,6 +2,7 @@ import { getInfo } from '../../../common/config/config'
|
|
|
2
2
|
import { globalScope, initialLocation } from '../../../common/constants/runtime'
|
|
3
3
|
import { generateUuid } from '../../../common/ids/unique-id'
|
|
4
4
|
import { addCustomAttributes, getAddStringContext, nullable, numeric } from '../../../common/serialize/bel-serializer'
|
|
5
|
+
import { now } from '../../../common/timing/now'
|
|
5
6
|
import { cleanURL } from '../../../common/url/clean-url'
|
|
6
7
|
import { NODE_TYPE, INTERACTION_STATUS, INTERACTION_TYPE, API_TRIGGER_NAME } from '../constants'
|
|
7
8
|
import { BelNode } from './bel-node'
|
|
@@ -44,12 +45,12 @@ export class Interaction extends BelNode {
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
updateDom (timestamp) {
|
|
47
|
-
this.domTimestamp = (timestamp ||
|
|
48
|
+
this.domTimestamp = (timestamp || now()) // default timestamp should be precise for accurate isActiveDuring calculations
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
updateHistory (timestamp, newUrl) {
|
|
51
52
|
this.newURL = newUrl || '' + globalScope?.location
|
|
52
|
-
this.historyTimestamp = (timestamp ||
|
|
53
|
+
this.historyTimestamp = (timestamp || now())
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
seenHistoryAndDomChange () {
|
|
@@ -124,8 +125,8 @@ export class Interaction extends BelNode {
|
|
|
124
125
|
const fields = [
|
|
125
126
|
numeric(this.belType),
|
|
126
127
|
0, // this will be overwritten below with number of attached nodes
|
|
127
|
-
numeric(
|
|
128
|
-
numeric(
|
|
128
|
+
numeric(this.start - firstStartTimeOfPayload), // relative to first node
|
|
129
|
+
numeric(this.end - this.start), // end -- relative to start
|
|
129
130
|
numeric(this.callbackEnd), // cbEnd -- relative to start; not used by BrowserInteraction events
|
|
130
131
|
numeric(this.callbackDuration), // not relative
|
|
131
132
|
addString(this.trigger),
|