@newrelic/browser-agent 1.254.0 → 1.255.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 +17 -0
- package/dist/cjs/common/config/state/runtime.js +2 -1
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/context/shared-context.js +1 -1
- package/dist/cjs/common/harvest/harvest.js +1 -1
- package/dist/cjs/common/timing/time-keeper.js +28 -28
- package/dist/cjs/common/vitals/cumulative-layout-shift.js +12 -5
- package/dist/cjs/common/vitals/first-contentful-paint.js +10 -6
- package/dist/cjs/common/vitals/first-input-delay.js +12 -10
- package/dist/cjs/common/vitals/first-paint.js +1 -2
- package/dist/cjs/common/vitals/interaction-to-next-paint.js +11 -7
- package/dist/cjs/common/vitals/largest-contentful-paint.js +19 -17
- package/dist/cjs/common/vitals/long-task.js +0 -1
- package/dist/cjs/common/vitals/time-to-first-byte.js +11 -6
- package/dist/cjs/common/vitals/vital-metric.js +1 -4
- package/dist/cjs/common/window/nreum.js +1 -1
- package/dist/cjs/features/ajax/aggregate/index.js +3 -2
- package/dist/cjs/features/ajax/instrument/index.js +1 -1
- package/dist/cjs/features/jserrors/aggregate/index.js +3 -2
- package/dist/cjs/features/jserrors/instrument/index.js +1 -1
- package/dist/cjs/features/page_action/aggregate/index.js +3 -2
- package/dist/cjs/features/page_view_event/aggregate/index.js +11 -5
- package/dist/cjs/features/page_view_timing/aggregate/index.js +21 -7
- package/dist/cjs/features/page_view_timing/instrument/index.js +1 -1
- package/dist/cjs/features/session_replay/aggregate/index.js +14 -9
- package/dist/cjs/features/session_replay/shared/recorder-events.js +9 -1
- package/dist/cjs/features/session_replay/shared/recorder.js +56 -12
- package/dist/cjs/features/session_trace/aggregate/index.js +1 -1
- package/dist/cjs/features/session_trace/instrument/index.js +1 -1
- package/dist/cjs/features/soft_navigations/aggregate/index.js +2 -2
- package/dist/cjs/features/soft_navigations/instrument/index.js +1 -1
- package/dist/cjs/features/spa/aggregate/index.js +19 -10
- package/dist/cjs/features/spa/instrument/index.js +1 -1
- package/dist/cjs/features/utils/feature-base.js +0 -2
- package/dist/cjs/loaders/agent-base.js +0 -2
- package/dist/cjs/loaders/agent.js +1 -1
- package/dist/cjs/loaders/api/api.js +1 -1
- package/dist/cjs/loaders/micro-agent.js +4 -7
- package/dist/esm/common/config/state/runtime.js +2 -1
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/context/shared-context.js +1 -1
- package/dist/esm/common/harvest/harvest.js +1 -1
- package/dist/esm/common/timing/time-keeper.js +28 -28
- package/dist/esm/common/vitals/cumulative-layout-shift.js +11 -4
- package/dist/esm/common/vitals/first-contentful-paint.js +9 -5
- package/dist/esm/common/vitals/first-input-delay.js +11 -9
- package/dist/esm/common/vitals/first-paint.js +1 -2
- package/dist/esm/common/vitals/interaction-to-next-paint.js +10 -6
- package/dist/esm/common/vitals/largest-contentful-paint.js +18 -16
- package/dist/esm/common/vitals/long-task.js +0 -1
- package/dist/esm/common/vitals/time-to-first-byte.js +10 -5
- package/dist/esm/common/vitals/vital-metric.js +1 -4
- package/dist/esm/common/window/nreum.js +1 -1
- package/dist/esm/features/ajax/aggregate/index.js +3 -2
- package/dist/esm/features/ajax/instrument/index.js +1 -1
- package/dist/esm/features/jserrors/aggregate/index.js +3 -2
- package/dist/esm/features/jserrors/instrument/index.js +1 -1
- package/dist/esm/features/page_action/aggregate/index.js +3 -2
- package/dist/esm/features/page_view_event/aggregate/index.js +11 -5
- package/dist/esm/features/page_view_timing/aggregate/index.js +21 -7
- package/dist/esm/features/page_view_timing/instrument/index.js +1 -1
- package/dist/esm/features/session_replay/aggregate/index.js +14 -9
- package/dist/esm/features/session_replay/shared/recorder-events.js +9 -1
- package/dist/esm/features/session_replay/shared/recorder.js +56 -12
- package/dist/esm/features/session_trace/aggregate/index.js +1 -1
- package/dist/esm/features/session_trace/instrument/index.js +1 -1
- package/dist/esm/features/soft_navigations/aggregate/index.js +2 -2
- package/dist/esm/features/soft_navigations/instrument/index.js +1 -1
- package/dist/esm/features/spa/aggregate/index.js +19 -10
- package/dist/esm/features/spa/instrument/index.js +1 -1
- package/dist/esm/features/utils/feature-base.js +0 -2
- package/dist/esm/loaders/agent-base.js +0 -2
- package/dist/esm/loaders/agent.js +1 -1
- package/dist/esm/loaders/api/api.js +1 -1
- package/dist/esm/loaders/micro-agent.js +4 -7
- package/dist/types/common/config/state/runtime.d.ts.map +1 -1
- package/dist/types/common/harvest/harvest.d.ts.map +1 -1
- package/dist/types/common/timing/time-keeper.d.ts +7 -6
- package/dist/types/common/timing/time-keeper.d.ts.map +1 -1
- package/dist/types/common/vitals/vital-metric.d.ts +1 -2
- package/dist/types/common/vitals/vital-metric.d.ts.map +1 -1
- package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/ajax/instrument/index.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_action/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/page_view_timing/instrument/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts +4 -0
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/recorder-events.d.ts +8 -0
- package/dist/types/features/session_replay/shared/recorder-events.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/recorder.d.ts +17 -1
- package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_trace/instrument/index.d.ts.map +1 -1
- package/dist/types/features/soft_navigations/instrument/index.d.ts.map +1 -1
- package/dist/types/features/spa/aggregate/index.d.ts +0 -2
- package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/utils/feature-base.d.ts +0 -1
- package/dist/types/features/utils/feature-base.d.ts.map +1 -1
- package/dist/types/loaders/agent-base.d.ts +0 -2
- package/dist/types/loaders/agent-base.d.ts.map +1 -1
- package/dist/types/loaders/micro-agent.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/common/config/state/runtime.js +2 -1
- package/src/common/context/__mocks__/shared-context.js +3 -0
- package/src/common/context/shared-context.js +1 -1
- package/src/common/harvest/harvest.js +1 -1
- package/src/common/timing/__mocks__/time-keeper.js +2 -0
- package/src/common/timing/time-keeper.js +30 -31
- package/src/common/vitals/cumulative-layout-shift.js +10 -4
- package/src/common/vitals/first-contentful-paint.js +9 -4
- package/src/common/vitals/first-input-delay.js +11 -6
- package/src/common/vitals/first-paint.js +1 -1
- package/src/common/vitals/interaction-to-next-paint.js +10 -3
- package/src/common/vitals/largest-contentful-paint.js +19 -15
- package/src/common/vitals/long-task.js +0 -1
- package/src/common/vitals/time-to-first-byte.js +5 -4
- package/src/common/vitals/vital-metric.js +2 -4
- package/src/common/window/nreum.js +1 -1
- package/src/features/ajax/aggregate/index.js +3 -2
- package/src/features/ajax/instrument/index.js +1 -1
- package/src/features/jserrors/aggregate/index.js +3 -2
- package/src/features/jserrors/instrument/index.js +1 -1
- package/src/features/page_action/aggregate/index.js +3 -2
- package/src/features/page_view_event/aggregate/index.js +13 -4
- package/src/features/page_view_timing/aggregate/index.js +16 -6
- package/src/features/page_view_timing/instrument/index.js +1 -1
- package/src/features/session_replay/aggregate/index.js +13 -9
- package/src/features/session_replay/shared/recorder-events.js +6 -1
- package/src/features/session_replay/shared/recorder.js +33 -9
- package/src/features/session_trace/aggregate/index.js +1 -1
- package/src/features/session_trace/instrument/index.js +1 -1
- package/src/features/soft_navigations/aggregate/index.js +2 -2
- package/src/features/soft_navigations/instrument/index.js +1 -1
- package/src/features/spa/aggregate/index.js +19 -8
- package/src/features/spa/instrument/index.js +1 -1
- package/src/features/utils/feature-base.js +0 -3
- package/src/loaders/agent-base.js +0 -2
- package/src/loaders/agent.js +1 -1
- package/src/loaders/api/api.js +1 -1
- package/src/loaders/micro-agent.js +4 -6
- package/src/common/vitals/__mocks__/web-vitals.js +0 -19
|
@@ -14,7 +14,7 @@ export class SharedContext {
|
|
|
14
14
|
if (Object.keys(model).includes(key)) this.sharedContext[key] = value;
|
|
15
15
|
});
|
|
16
16
|
} catch (err) {
|
|
17
|
-
warn('An error
|
|
17
|
+
warn('An error occurred while setting SharedContext', err);
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
}
|
|
@@ -9,7 +9,6 @@ import * as submitData from '../util/submit-data';
|
|
|
9
9
|
import { getLocation } from '../url/location';
|
|
10
10
|
import { getInfo, getConfigurationValue, getRuntime, getConfiguration } from '../config/config';
|
|
11
11
|
import { cleanURL } from '../url/clean-url';
|
|
12
|
-
import { now } from '../timing/now';
|
|
13
12
|
import { eventListenerOpts } from '../event-listener/event-listener-opts';
|
|
14
13
|
import { Obfuscator } from '../util/obfuscate';
|
|
15
14
|
import { applyFnToProps } from '../util/traverse';
|
|
@@ -17,6 +16,7 @@ import { SharedContext } from '../context/shared-context';
|
|
|
17
16
|
import { VERSION } from "../constants/env.npm";
|
|
18
17
|
import { isWorkerScope, isIE } from '../constants/runtime';
|
|
19
18
|
import { warn } from '../util/console';
|
|
19
|
+
import { now } from '../timing/now';
|
|
20
20
|
const warnings = {};
|
|
21
21
|
|
|
22
22
|
/**
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { gosNREUM } from '../window/nreum';
|
|
2
1
|
import { globalScope } from '../constants/runtime';
|
|
3
|
-
import { getRuntime } from '../config/config';
|
|
4
2
|
|
|
5
3
|
/**
|
|
6
4
|
* Class used to adjust the timestamp of harvested data to New Relic server time. This
|
|
@@ -8,7 +6,11 @@ import { getRuntime } from '../config/config';
|
|
|
8
6
|
* to the harvested data event offset time.
|
|
9
7
|
*/
|
|
10
8
|
export class TimeKeeper {
|
|
11
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Represents the browser origin time.
|
|
11
|
+
* @type {number}
|
|
12
|
+
*/
|
|
13
|
+
#originTime;
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* Represents the browser origin time corrected to NR server time.
|
|
@@ -22,56 +24,54 @@ export class TimeKeeper {
|
|
|
22
24
|
* @type {number}
|
|
23
25
|
*/
|
|
24
26
|
#localTimeDiff;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Represents whether the timekeeper is in a state that it can accurately convert
|
|
30
|
+
* timestamps.
|
|
31
|
+
* @type {number}
|
|
32
|
+
*/
|
|
33
|
+
#ready = false;
|
|
34
|
+
constructor() {
|
|
35
|
+
this.#originTime = globalScope.performance.timeOrigin || globalScope.performance.timing.navigationStart;
|
|
36
|
+
}
|
|
37
|
+
get ready() {
|
|
38
|
+
return this.#ready;
|
|
27
39
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return Object.keys(nr?.initializedAgents || {}).indexOf(agentIdentifier) > -1 ? nr.initializedAgents[agentIdentifier].timeKeeper : undefined;
|
|
40
|
+
get originTime() {
|
|
41
|
+
return this.#originTime;
|
|
31
42
|
}
|
|
32
|
-
get
|
|
43
|
+
get correctedOriginTime() {
|
|
33
44
|
return this.#correctedOriginTime;
|
|
34
45
|
}
|
|
35
46
|
|
|
36
47
|
/**
|
|
37
48
|
* Process a rum request to calculate NR server time.
|
|
38
49
|
* @param rumRequest {XMLHttpRequest} The xhr for the rum request
|
|
39
|
-
* @param
|
|
50
|
+
* @param startTime {number} The start time of the RUM request
|
|
51
|
+
* @param endTime {number} The end time of the RUM request
|
|
40
52
|
*/
|
|
41
|
-
processRumRequest(rumRequest,
|
|
53
|
+
processRumRequest(rumRequest, startTime, endTime) {
|
|
42
54
|
const responseDateHeader = rumRequest.getResponseHeader('Date');
|
|
43
55
|
if (!responseDateHeader) {
|
|
44
56
|
throw new Error('Missing date header on rum response.');
|
|
45
57
|
}
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
throw new Error('Missing rum request performance entry.');
|
|
49
|
-
}
|
|
50
|
-
let medianRumOffset = 0;
|
|
51
|
-
let serverOffset = 0;
|
|
52
|
-
if (typeof resourceEntries[0].responseStart === 'number' && resourceEntries[0].responseStart !== 0) {
|
|
53
|
-
// Cors is enabled and we can make a more accurate calculation of NR server time
|
|
54
|
-
medianRumOffset = (resourceEntries[0].responseStart - resourceEntries[0].requestStart) / 2;
|
|
55
|
-
serverOffset = Math.floor(resourceEntries[0].requestStart + medianRumOffset);
|
|
56
|
-
} else {
|
|
57
|
-
// Cors is disabled or erred, we need to use a less accurate calculation
|
|
58
|
-
medianRumOffset = (resourceEntries[0].responseEnd - resourceEntries[0].fetchStart) / 2;
|
|
59
|
-
serverOffset = Math.floor(resourceEntries[0].fetchStart + medianRumOffset);
|
|
60
|
-
}
|
|
58
|
+
const medianRumOffset = (endTime - startTime) / 2;
|
|
59
|
+
const serverOffset = Math.floor(startTime + medianRumOffset);
|
|
61
60
|
|
|
62
61
|
// Corrected page origin time
|
|
63
62
|
this.#correctedOriginTime = Math.floor(Date.parse(responseDateHeader) - serverOffset);
|
|
64
|
-
this.#localTimeDiff =
|
|
63
|
+
this.#localTimeDiff = this.#originTime - this.#correctedOriginTime;
|
|
65
64
|
if (Number.isNaN(this.#correctedOriginTime)) {
|
|
66
65
|
throw new Error('Date header invalid format.');
|
|
67
66
|
}
|
|
67
|
+
this.#ready = true;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
71
|
* Converts a page origin relative time to an absolute timestamp
|
|
72
72
|
* corrected to NR server time.
|
|
73
73
|
* @param relativeTime {number} The relative time of the event in milliseconds
|
|
74
|
-
* @returns {number}
|
|
74
|
+
* @returns {number} Corrected unix/epoch timestamp
|
|
75
75
|
*/
|
|
76
76
|
convertRelativeTimestamp(relativeTime) {
|
|
77
77
|
return this.#correctedOriginTime + relativeTime;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { onCLS } from 'web-vitals';
|
|
1
|
+
import { onCLS } from 'web-vitals/attribution';
|
|
2
2
|
import { VITAL_NAMES } from './constants';
|
|
3
3
|
import { VitalMetric } from './vital-metric';
|
|
4
4
|
import { isBrowserScope } from '../constants/runtime';
|
|
@@ -7,12 +7,19 @@ if (isBrowserScope) {
|
|
|
7
7
|
onCLS(_ref => {
|
|
8
8
|
let {
|
|
9
9
|
value,
|
|
10
|
-
|
|
10
|
+
attribution,
|
|
11
|
+
id
|
|
11
12
|
} = _ref;
|
|
12
|
-
|
|
13
|
+
const attrs = {
|
|
14
|
+
metricId: id,
|
|
15
|
+
largestShiftTarget: attribution.largestShiftTarget,
|
|
16
|
+
largestShiftTime: attribution.largestShiftTime,
|
|
17
|
+
largestShiftValue: attribution.largestShiftValue,
|
|
18
|
+
loadState: attribution.loadState
|
|
19
|
+
};
|
|
13
20
|
cumulativeLayoutShift.update({
|
|
14
21
|
value,
|
|
15
|
-
|
|
22
|
+
attrs
|
|
16
23
|
});
|
|
17
24
|
}, {
|
|
18
25
|
reportAllChanges: true
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { onFCP } from 'web-vitals';
|
|
1
|
+
import { onFCP } from 'web-vitals/attribution';
|
|
2
2
|
// eslint-disable-next-line camelcase
|
|
3
3
|
import { iOSBelow16, initiallyHidden, isBrowserScope } from '../constants/runtime';
|
|
4
4
|
import { VITAL_NAMES } from './constants';
|
|
@@ -16,8 +16,7 @@ if (isBrowserScope) {
|
|
|
16
16
|
paintEntries.forEach(entry => {
|
|
17
17
|
if (entry.name === 'first-contentful-paint') {
|
|
18
18
|
firstContentfulPaint.update({
|
|
19
|
-
value: Math.floor(entry.startTime)
|
|
20
|
-
entries: paintEntries
|
|
19
|
+
value: Math.floor(entry.startTime)
|
|
21
20
|
});
|
|
22
21
|
}
|
|
23
22
|
});
|
|
@@ -29,12 +28,17 @@ if (isBrowserScope) {
|
|
|
29
28
|
onFCP(_ref => {
|
|
30
29
|
let {
|
|
31
30
|
value,
|
|
32
|
-
|
|
31
|
+
attribution
|
|
33
32
|
} = _ref;
|
|
34
33
|
if (initiallyHidden || firstContentfulPaint.isValid) return;
|
|
34
|
+
const attrs = {
|
|
35
|
+
timeToFirstByte: attribution.timeToFirstByte,
|
|
36
|
+
firstByteToFCP: attribution.firstByteToFCP,
|
|
37
|
+
loadState: attribution.loadState
|
|
38
|
+
};
|
|
35
39
|
firstContentfulPaint.update({
|
|
36
40
|
value,
|
|
37
|
-
|
|
41
|
+
attrs
|
|
38
42
|
});
|
|
39
43
|
});
|
|
40
44
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { onFID } from 'web-vitals';
|
|
1
|
+
import { onFID } from 'web-vitals/attribution';
|
|
2
2
|
import { VitalMetric } from './vital-metric';
|
|
3
3
|
import { VITAL_NAMES } from './constants';
|
|
4
4
|
import { initiallyHidden, isBrowserScope } from '../constants/runtime';
|
|
@@ -7,18 +7,20 @@ if (isBrowserScope) {
|
|
|
7
7
|
onFID(_ref => {
|
|
8
8
|
let {
|
|
9
9
|
value,
|
|
10
|
-
|
|
10
|
+
attribution
|
|
11
11
|
} = _ref;
|
|
12
|
-
if (initiallyHidden || firstInputDelay.isValid
|
|
12
|
+
if (initiallyHidden || firstInputDelay.isValid) return;
|
|
13
|
+
const attrs = {
|
|
14
|
+
type: attribution.eventType,
|
|
15
|
+
fid: Math.round(value),
|
|
16
|
+
eventTarget: attribution.eventTarget,
|
|
17
|
+
loadState: attribution.loadState
|
|
18
|
+
};
|
|
13
19
|
|
|
14
20
|
// CWV will only report one (THE) first-input entry to us; fid isn't reported if there are no user interactions occurs before the *first* page hiding.
|
|
15
21
|
firstInputDelay.update({
|
|
16
|
-
value:
|
|
17
|
-
|
|
18
|
-
attrs: {
|
|
19
|
-
type: entries[0].name,
|
|
20
|
-
fid: Math.round(value)
|
|
21
|
-
}
|
|
22
|
+
value: attribution.eventTime,
|
|
23
|
+
attrs
|
|
22
24
|
});
|
|
23
25
|
});
|
|
24
26
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { onINP } from 'web-vitals';
|
|
1
|
+
import { onINP } from 'web-vitals/attribution';
|
|
2
2
|
import { VitalMetric } from './vital-metric';
|
|
3
3
|
import { VITAL_NAMES } from './constants';
|
|
4
4
|
import { isBrowserScope } from '../constants/runtime';
|
|
@@ -8,15 +8,19 @@ if (isBrowserScope) {
|
|
|
8
8
|
onINP(_ref => {
|
|
9
9
|
let {
|
|
10
10
|
value,
|
|
11
|
-
|
|
11
|
+
attribution,
|
|
12
12
|
id
|
|
13
13
|
} = _ref;
|
|
14
|
+
const attrs = {
|
|
15
|
+
metricId: id,
|
|
16
|
+
eventTarget: attribution.eventTarget,
|
|
17
|
+
eventType: attribution.eventType,
|
|
18
|
+
eventTime: attribution.eventTime,
|
|
19
|
+
loadState: attribution.loadState
|
|
20
|
+
};
|
|
14
21
|
interactionToNextPaint.update({
|
|
15
22
|
value,
|
|
16
|
-
|
|
17
|
-
attrs: {
|
|
18
|
-
metricId: id
|
|
19
|
-
}
|
|
23
|
+
attrs
|
|
20
24
|
});
|
|
21
25
|
});
|
|
22
26
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { onLCP } from 'web-vitals';
|
|
1
|
+
import { onLCP } from 'web-vitals/attribution';
|
|
2
2
|
import { VitalMetric } from './vital-metric';
|
|
3
3
|
import { VITAL_NAMES } from './constants';
|
|
4
4
|
import { initiallyHidden, isBrowserScope } from '../constants/runtime';
|
|
@@ -8,26 +8,28 @@ if (isBrowserScope) {
|
|
|
8
8
|
onLCP(_ref => {
|
|
9
9
|
let {
|
|
10
10
|
value,
|
|
11
|
-
|
|
11
|
+
attribution
|
|
12
12
|
} = _ref;
|
|
13
13
|
/* Largest Contentful Paint - As of WV v3, it still imperfectly tries to detect document vis state asap and isn't supposed to report if page starts hidden. */
|
|
14
14
|
if (initiallyHidden || largestContentfulPaint.isValid) return;
|
|
15
|
-
|
|
15
|
+
let attrs;
|
|
16
|
+
const lcpEntry = attribution.lcpEntry;
|
|
17
|
+
if (lcpEntry) {
|
|
18
|
+
attrs = {
|
|
19
|
+
size: lcpEntry.size,
|
|
20
|
+
eid: lcpEntry.id,
|
|
21
|
+
element: attribution.element,
|
|
22
|
+
timeToFirstByte: attribution.timeToFirstByte,
|
|
23
|
+
resourceLoadDelay: attribution.resourceLoadDelay,
|
|
24
|
+
resourceLoadTime: attribution.resourceLoadTime,
|
|
25
|
+
elementRenderDelay: attribution.elementRenderDelay
|
|
26
|
+
};
|
|
27
|
+
if (attribution.url) attrs.elUrl = cleanURL(attribution.url);
|
|
28
|
+
if (lcpEntry.element?.tagName) attrs.elTag = lcpEntry.element.tagName;
|
|
29
|
+
}
|
|
16
30
|
largestContentfulPaint.update({
|
|
17
31
|
value,
|
|
18
|
-
|
|
19
|
-
...(entries.length > 0 && {
|
|
20
|
-
attrs: {
|
|
21
|
-
size: lcpEntry.size,
|
|
22
|
-
eid: lcpEntry.id,
|
|
23
|
-
...(!!lcpEntry.url && {
|
|
24
|
-
elUrl: cleanURL(lcpEntry.url)
|
|
25
|
-
}),
|
|
26
|
-
...(!!lcpEntry.element?.tagName && {
|
|
27
|
-
elTag: lcpEntry.element.tagName
|
|
28
|
-
})
|
|
29
|
-
}
|
|
30
|
-
})
|
|
32
|
+
attrs
|
|
31
33
|
});
|
|
32
34
|
});
|
|
33
35
|
}
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { globalScope, isBrowserScope, isiOS, offset } from '../constants/runtime';
|
|
2
2
|
import { VITAL_NAMES } from './constants';
|
|
3
3
|
import { VitalMetric } from './vital-metric';
|
|
4
|
-
import { onTTFB } from 'web-vitals';
|
|
4
|
+
import { onTTFB } from 'web-vitals/attribution';
|
|
5
5
|
export const timeToFirstByte = new VitalMetric(VITAL_NAMES.TIME_TO_FIRST_BYTE);
|
|
6
6
|
if (isBrowserScope && typeof PerformanceNavigationTiming !== 'undefined' && !isiOS) {
|
|
7
7
|
onTTFB(_ref => {
|
|
8
8
|
let {
|
|
9
9
|
value,
|
|
10
|
-
|
|
10
|
+
attribution
|
|
11
11
|
} = _ref;
|
|
12
|
-
if (
|
|
12
|
+
if (timeToFirstByte.isValid) return;
|
|
13
|
+
timeToFirstByte.update({
|
|
13
14
|
value,
|
|
14
|
-
|
|
15
|
+
attrs: {
|
|
16
|
+
navigationEntry: attribution.navigationEntry
|
|
17
|
+
}
|
|
15
18
|
});
|
|
16
19
|
});
|
|
17
20
|
} else {
|
|
@@ -23,7 +26,9 @@ if (isBrowserScope && typeof PerformanceNavigationTiming !== 'undefined' && !isi
|
|
|
23
26
|
// ttfb is equiv to document's responseStart property in timing API --> https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/responseStart
|
|
24
27
|
timeToFirstByte.update({
|
|
25
28
|
value: entry.responseStart,
|
|
26
|
-
|
|
29
|
+
attrs: {
|
|
30
|
+
navigationEntry: entry
|
|
31
|
+
}
|
|
27
32
|
});
|
|
28
33
|
}
|
|
29
34
|
}
|
|
@@ -9,20 +9,18 @@ export class VitalMetric {
|
|
|
9
9
|
update(_ref) {
|
|
10
10
|
let {
|
|
11
11
|
value,
|
|
12
|
-
entries = [],
|
|
13
12
|
attrs = {}
|
|
14
13
|
} = _ref;
|
|
15
14
|
if (value < 0) return;
|
|
16
15
|
const state = {
|
|
17
16
|
value: this.roundingMethod(value),
|
|
18
17
|
name: this.name,
|
|
19
|
-
entries,
|
|
20
18
|
attrs
|
|
21
19
|
};
|
|
22
20
|
this.history.push(state);
|
|
23
21
|
this.#subscribers.forEach(cb => {
|
|
24
22
|
try {
|
|
25
|
-
cb(
|
|
23
|
+
cb(state);
|
|
26
24
|
} catch (e) {
|
|
27
25
|
// ignore errors
|
|
28
26
|
}
|
|
@@ -32,7 +30,6 @@ export class VitalMetric {
|
|
|
32
30
|
return this.history[this.history.length - 1] || {
|
|
33
31
|
value: undefined,
|
|
34
32
|
name: this.name,
|
|
35
|
-
entries: [],
|
|
36
33
|
attrs: {}
|
|
37
34
|
};
|
|
38
35
|
}
|
|
@@ -29,7 +29,8 @@ export class Aggregate extends AggregateBase {
|
|
|
29
29
|
scheduler.startTimer(harvestTimeSeconds);
|
|
30
30
|
this.drain();
|
|
31
31
|
});
|
|
32
|
-
const
|
|
32
|
+
const agentRuntime = getRuntime(agentIdentifier);
|
|
33
|
+
const denyList = agentRuntime.denyList;
|
|
33
34
|
setDenyList(denyList);
|
|
34
35
|
let ajaxEvents = [];
|
|
35
36
|
let spaAjaxEvents = {};
|
|
@@ -115,7 +116,7 @@ export class Aggregate extends AggregateBase {
|
|
|
115
116
|
if (xhrContext.dt) {
|
|
116
117
|
event.spanId = xhrContext.dt.spanId;
|
|
117
118
|
event.traceId = xhrContext.dt.traceId;
|
|
118
|
-
event.spanTimestamp = xhrContext.dt.timestamp;
|
|
119
|
+
event.spanTimestamp = agentRuntime.timeKeeper.correctAbsoluteTimestamp(xhrContext.dt.timestamp);
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
// parsed from the AJAX body, looking for operationName param & parsing query for operationType
|
|
@@ -8,7 +8,6 @@ import { id } from '../../../common/ids/id';
|
|
|
8
8
|
import { ffVersion, globalScope, isBrowserScope } from '../../../common/constants/runtime';
|
|
9
9
|
import { dataSize } from '../../../common/util/data-size';
|
|
10
10
|
import { eventListenerOpts } from '../../../common/event-listener/event-listener-opts';
|
|
11
|
-
import { now } from '../../../common/timing/now';
|
|
12
11
|
import { wrapFetch, wrapXhr } from '../../../common/wrap';
|
|
13
12
|
import { parseUrl } from '../../../common/url/parse-url';
|
|
14
13
|
import { DT } from './distributed-tracing';
|
|
@@ -17,6 +16,7 @@ import { InstrumentBase } from '../../utils/instrument-base';
|
|
|
17
16
|
import { FEATURE_NAME } from '../constants';
|
|
18
17
|
import { FEATURE_NAMES } from '../../../loaders/features/features';
|
|
19
18
|
import { SUPPORTABILITY_METRIC } from '../../metrics/constants';
|
|
19
|
+
import { now } from '../../../common/timing/now';
|
|
20
20
|
var handlers = ['load', 'error', 'abort', 'timeout'];
|
|
21
21
|
var handlersLen = handlers.length;
|
|
22
22
|
var origRequest = originals.REQ;
|
|
@@ -13,13 +13,13 @@ import { stringify } from '../../../common/util/stringify';
|
|
|
13
13
|
import { handle } from '../../../common/event-emitter/handle';
|
|
14
14
|
import { mapOwn } from '../../../common/util/map-own';
|
|
15
15
|
import { getInfo, getConfigurationValue, getRuntime } from '../../../common/config/config';
|
|
16
|
-
import { now } from '../../../common/timing/now';
|
|
17
16
|
import { globalScope } from '../../../common/constants/runtime';
|
|
18
17
|
import { FEATURE_NAME } from '../constants';
|
|
19
18
|
import { FEATURE_NAMES } from '../../../loaders/features/features';
|
|
20
19
|
import { AggregateBase } from '../../utils/aggregate-base';
|
|
21
20
|
import { getNREUMInitializedAgent } from '../../../common/window/nreum';
|
|
22
21
|
import { deregisterDrain } from '../../../common/drain/drain';
|
|
22
|
+
import { now } from '../../../common/timing/now';
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* @typedef {import('./compute-stack-trace.js').StackInfo} StackInfo
|
|
@@ -167,7 +167,7 @@ export class Aggregate extends AggregateBase {
|
|
|
167
167
|
if (!this.stackReported[bucketHash]) {
|
|
168
168
|
this.stackReported[bucketHash] = true;
|
|
169
169
|
params.stack_trace = truncateSize(stackInfo.stackString);
|
|
170
|
-
this.observedAt[bucketHash] = agentRuntime.
|
|
170
|
+
this.observedAt[bucketHash] = agentRuntime.timeKeeper.convertRelativeTimestamp(time);
|
|
171
171
|
} else {
|
|
172
172
|
params.browser_stack_hash = stringHashCode(stackInfo.stackString);
|
|
173
173
|
}
|
|
@@ -184,6 +184,7 @@ export class Aggregate extends AggregateBase {
|
|
|
184
184
|
}
|
|
185
185
|
if (agentRuntime?.session?.state?.sessionReplayMode) params.hasReplay = true;
|
|
186
186
|
params.firstOccurrenceTimestamp = this.observedAt[bucketHash];
|
|
187
|
+
params.timestamp = this.observedAt[bucketHash];
|
|
187
188
|
var type = internal ? 'ierr' : 'err';
|
|
188
189
|
var newMetrics = {
|
|
189
190
|
time
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { handle } from '../../../common/event-emitter/handle';
|
|
7
|
-
import { now } from '../../../common/timing/now';
|
|
8
7
|
import { InstrumentBase } from '../../utils/instrument-base';
|
|
9
8
|
import { FEATURE_NAME } from '../constants';
|
|
10
9
|
import { FEATURE_NAMES } from '../../../loaders/features/features';
|
|
@@ -12,6 +11,7 @@ import { globalScope } from '../../../common/constants/runtime';
|
|
|
12
11
|
import { eventListenerOpts } from '../../../common/event-listener/event-listener-opts';
|
|
13
12
|
import { stringify } from '../../../common/util/stringify';
|
|
14
13
|
import { UncaughtError } from './uncaught-error';
|
|
14
|
+
import { now } from '../../../common/timing/now';
|
|
15
15
|
export class Instrument extends InstrumentBase {
|
|
16
16
|
static featureName = FEATURE_NAME;
|
|
17
17
|
#seenErrors = new Set();
|
|
@@ -89,14 +89,15 @@ export class Aggregate extends AggregateBase {
|
|
|
89
89
|
width = window.document.documentElement.clientWidth;
|
|
90
90
|
height = window.document.documentElement.clientHeight;
|
|
91
91
|
}
|
|
92
|
+
const agentRuntime = getRuntime(this.agentIdentifier);
|
|
92
93
|
var defaults = {
|
|
93
|
-
timestamp: t
|
|
94
|
+
timestamp: agentRuntime.timeKeeper.convertRelativeTimestamp(t),
|
|
94
95
|
timeSinceLoad: t / 1000,
|
|
95
96
|
browserWidth: width,
|
|
96
97
|
browserHeight: height,
|
|
97
98
|
referrerUrl: this.referrerUrl,
|
|
98
99
|
currentUrl: cleanURL('' + location),
|
|
99
|
-
pageUrl: cleanURL(
|
|
100
|
+
pageUrl: cleanURL(agentRuntime.origin),
|
|
100
101
|
eventType: 'PageAction'
|
|
101
102
|
};
|
|
102
103
|
mapOwn(defaults, set);
|
|
@@ -15,6 +15,8 @@ import { drain } from '../../../common/drain/drain';
|
|
|
15
15
|
import { FEATURE_NAMES } from '../../../loaders/features/features';
|
|
16
16
|
import { handle } from '../../../common/event-emitter/handle';
|
|
17
17
|
import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants';
|
|
18
|
+
import { now } from '../../../common/timing/now';
|
|
19
|
+
import { TimeKeeper } from '../../../common/timing/time-keeper';
|
|
18
20
|
export class Aggregate extends AggregateBase {
|
|
19
21
|
static featureName = CONSTANTS.FEATURE_NAME;
|
|
20
22
|
constructor(agentIdentifier, aggregator) {
|
|
@@ -27,9 +29,9 @@ export class Aggregate extends AggregateBase {
|
|
|
27
29
|
timeToFirstByte.subscribe(_ref => {
|
|
28
30
|
let {
|
|
29
31
|
value,
|
|
30
|
-
|
|
32
|
+
attrs
|
|
31
33
|
} = _ref;
|
|
32
|
-
const navEntry =
|
|
34
|
+
const navEntry = attrs.navigationEntry;
|
|
33
35
|
this.timeToFirstByte = Math.max(value, this.timeToFirstByte);
|
|
34
36
|
this.firstByteToWindowLoad = Math.max(Math.round(navEntry.loadEventEnd - this.timeToFirstByte), this.firstByteToWindowLoad); // our "frontend" duration
|
|
35
37
|
this.firstByteToDomContent = Math.max(Math.round(navEntry.domContentLoadedEventEnd - this.timeToFirstByte), this.firstByteToDomContent); // our "dom processing" duration
|
|
@@ -108,6 +110,7 @@ export class Aggregate extends AggregateBase {
|
|
|
108
110
|
}
|
|
109
111
|
queryParameters.fp = firstPaint.current.value;
|
|
110
112
|
queryParameters.fcp = firstContentfulPaint.current.value;
|
|
113
|
+
const rumStartTime = now();
|
|
111
114
|
harvester.send({
|
|
112
115
|
endpoint: 'rum',
|
|
113
116
|
payload: {
|
|
@@ -122,16 +125,19 @@ export class Aggregate extends AggregateBase {
|
|
|
122
125
|
let {
|
|
123
126
|
status,
|
|
124
127
|
responseText,
|
|
125
|
-
xhr
|
|
126
|
-
fullUrl
|
|
128
|
+
xhr
|
|
127
129
|
} = _ref3;
|
|
130
|
+
const rumEndTime = now();
|
|
128
131
|
if (status >= 400 || status === 0) {
|
|
129
132
|
// Adding retry logic for the rum call will be a separate change
|
|
130
133
|
this.ee.abort();
|
|
131
134
|
return;
|
|
132
135
|
}
|
|
133
136
|
try {
|
|
134
|
-
|
|
137
|
+
const timeKeeper = new TimeKeeper();
|
|
138
|
+
timeKeeper.processRumRequest(xhr, rumStartTime, rumEndTime);
|
|
139
|
+
if (!timeKeeper.ready) throw new Error('TimeKeeper not ready');
|
|
140
|
+
agentRuntime.timeKeeper = timeKeeper;
|
|
135
141
|
} catch (error) {
|
|
136
142
|
handle(SUPPORTABILITY_METRIC_CHANNEL, ['PVE/NRTime/Calculation/Failed'], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
137
143
|
drain(this.agentIdentifier, FEATURE_NAMES.metrics, true);
|
|
@@ -20,6 +20,8 @@ import { interactionToNextPaint } from '../../../common/vitals/interaction-to-ne
|
|
|
20
20
|
import { largestContentfulPaint } from '../../../common/vitals/largest-contentful-paint';
|
|
21
21
|
import { timeToFirstByte } from '../../../common/vitals/time-to-first-byte';
|
|
22
22
|
import { longTask } from '../../../common/vitals/long-task';
|
|
23
|
+
import { subscribeToVisibilityChange } from '../../../common/window/page-visibility';
|
|
24
|
+
import { VITAL_NAMES } from '../../../common/vitals/constants';
|
|
23
25
|
export class Aggregate extends AggregateBase {
|
|
24
26
|
static featureName = FEATURE_NAME;
|
|
25
27
|
#handleVitalMetric = _ref => {
|
|
@@ -38,15 +40,13 @@ export class Aggregate extends AggregateBase {
|
|
|
38
40
|
this.timingsSent = [];
|
|
39
41
|
this.curSessEndRecorded = false;
|
|
40
42
|
if (getConfigurationValue(this.agentIdentifier, 'page_view_timing.long_task') === true) longTask.subscribe(this.#handleVitalMetric);
|
|
41
|
-
|
|
42
|
-
/* It's important that CWV api, like "onLCP", is called before this scheduler is initialized. The reason is because they listen to the same
|
|
43
|
-
on vis change or pagehide events, and we'd want ex. onLCP to record the timing (win the race) before we try to send "final harvest". */
|
|
44
|
-
|
|
45
43
|
registerHandler('docHidden', msTimestamp => this.endCurrentSession(msTimestamp), this.featureName, this.ee);
|
|
46
44
|
registerHandler('winPagehide', msTimestamp => this.recordPageUnload(msTimestamp), this.featureName, this.ee);
|
|
47
45
|
const initialHarvestSeconds = getConfigurationValue(this.agentIdentifier, 'page_view_timing.initialHarvestSeconds') || 10;
|
|
48
46
|
const harvestTimeSeconds = getConfigurationValue(this.agentIdentifier, 'page_view_timing.harvestTimeSeconds') || 30;
|
|
49
47
|
this.waitForFlags([]).then(() => {
|
|
48
|
+
/* It's important that CWV api, like "onLCP", is called before the **scheduler** is initialized. The reason is because they listen to the same
|
|
49
|
+
on vis change or pagehide events, and we'd want ex. onLCP to record the timing (win the race) before we try to send "final harvest". */
|
|
50
50
|
firstPaint.subscribe(this.#handleVitalMetric);
|
|
51
51
|
firstContentfulPaint.subscribe(this.#handleVitalMetric);
|
|
52
52
|
firstInputDelay.subscribe(this.#handleVitalMetric);
|
|
@@ -54,10 +54,23 @@ export class Aggregate extends AggregateBase {
|
|
|
54
54
|
interactionToNextPaint.subscribe(this.#handleVitalMetric);
|
|
55
55
|
timeToFirstByte.subscribe(_ref2 => {
|
|
56
56
|
let {
|
|
57
|
-
|
|
57
|
+
attrs
|
|
58
58
|
} = _ref2;
|
|
59
|
-
this.addTiming('load', Math.round(
|
|
59
|
+
this.addTiming('load', Math.round(attrs.navigationEntry.loadEventEnd));
|
|
60
60
|
});
|
|
61
|
+
subscribeToVisibilityChange(() => {
|
|
62
|
+
/* Downstream, the event consumer interprets all timing node value as ms-unit and converts it to seconds via division by 1000. CLS is unitless so this normally is a problem.
|
|
63
|
+
bel.6 schema also doesn't support decimal values, of which cls within [0,1). However, the two nicely cancels out, and we can multiply cls by 1000 to both negate the division
|
|
64
|
+
and send an integer > 1. We effectively lose some precision down to 3 decimal places for this workaround. E.g. (real) 0.749132... -> 749.132...-> 749 -> 0.749 (final) */
|
|
65
|
+
const {
|
|
66
|
+
name,
|
|
67
|
+
value,
|
|
68
|
+
attrs
|
|
69
|
+
} = cumulativeLayoutShift.current;
|
|
70
|
+
if (value === undefined) return;
|
|
71
|
+
this.addTiming(name, value * 1000, attrs);
|
|
72
|
+
}, true); // CLS node should only reports on vis change rather than on every change
|
|
73
|
+
|
|
61
74
|
const scheduler = new HarvestScheduler('events', {
|
|
62
75
|
onFinished: function () {
|
|
63
76
|
return _this.onHarvestFinished(...arguments);
|
|
@@ -107,8 +120,9 @@ export class Aggregate extends AggregateBase {
|
|
|
107
120
|
Issue: Because NR 'pageHide' was only sent once with what is considered the "final" CLS value, in the case that 'pageHide' fires before 'load' happens, we incorrectly a final CLS of 0 for that page.
|
|
108
121
|
Mitigation: We've set initial CLS to null so that it's omitted from timings like 'pageHide' in that edge case. It should only be included if onCLS callback was executed at least once.
|
|
109
122
|
Future: onCLS value changes should be reported directly & CLS separated into its own timing node so it's not beholden to 'pageHide' firing. It'd also be possible to report the real final CLS.
|
|
123
|
+
*cli Mar'24 update: CLS now emitted as its own timing node in addition to as-property under other nodes. The 'cls' property is unnecessary for cls nodes.
|
|
110
124
|
*/
|
|
111
|
-
if (cumulativeLayoutShift.current.value >= 0) {
|
|
125
|
+
if (name !== VITAL_NAMES.CUMULATIVE_LAYOUT_SHIFT && cumulativeLayoutShift.current.value >= 0) {
|
|
112
126
|
attrs.cls = cumulativeLayoutShift.current.value;
|
|
113
127
|
}
|
|
114
128
|
this.timings.push({
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
import { handle } from '../../../common/event-emitter/handle';
|
|
6
6
|
import { subscribeToVisibilityChange } from '../../../common/window/page-visibility';
|
|
7
7
|
import { windowAddEventListener } from '../../../common/event-listener/event-listener-opts';
|
|
8
|
-
import { now } from '../../../common/timing/now';
|
|
9
8
|
import { InstrumentBase } from '../../utils/instrument-base';
|
|
10
9
|
import { FEATURE_NAME } from '../constants';
|
|
11
10
|
import { isBrowserScope } from '../../../common/constants/runtime';
|
|
11
|
+
import { now } from '../../../common/timing/now';
|
|
12
12
|
export class Instrument extends InstrumentBase {
|
|
13
13
|
static featureName = FEATURE_NAME;
|
|
14
14
|
constructor(agentIdentifier, aggregator) {
|