@newrelic/browser-agent 1.304.0-rc.4 → 1.304.0-rc.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/constants/runtime.js +4 -2
- package/dist/cjs/common/timing/nav-timing.js +7 -2
- package/dist/cjs/common/util/mfe.js +4 -4
- package/dist/cjs/common/vitals/constants.js +1 -0
- package/dist/cjs/common/vitals/load-time.js +27 -0
- package/dist/cjs/common/vitals/time-to-first-byte.js +1 -1
- package/dist/cjs/common/window/load.js +19 -1
- package/dist/cjs/features/page_view_event/aggregate/index.js +3 -4
- package/dist/cjs/features/page_view_timing/aggregate/index.js +5 -4
- package/dist/cjs/features/session_trace/aggregate/index.js +1 -1
- package/dist/cjs/features/soft_navigations/aggregate/index.js +3 -5
- package/dist/cjs/features/spa/instrument/index.js +8 -0
- package/dist/cjs/loaders/api/register.js +1 -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/constants/runtime.js +2 -1
- package/dist/esm/common/timing/nav-timing.js +7 -2
- package/dist/esm/common/util/mfe.js +4 -4
- package/dist/esm/common/vitals/constants.js +1 -0
- package/dist/esm/common/vitals/load-time.js +20 -0
- package/dist/esm/common/vitals/time-to-first-byte.js +2 -2
- package/dist/esm/common/window/load.js +19 -1
- package/dist/esm/features/page_view_event/aggregate/index.js +4 -5
- package/dist/esm/features/page_view_timing/aggregate/index.js +5 -4
- package/dist/esm/features/session_trace/aggregate/index.js +2 -2
- package/dist/esm/features/soft_navigations/aggregate/index.js +3 -5
- package/dist/esm/features/spa/instrument/index.js +8 -0
- package/dist/esm/loaders/api/register.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/common/constants/runtime.d.ts +1 -0
- package/dist/types/common/constants/runtime.d.ts.map +1 -1
- package/dist/types/common/timing/nav-timing.d.ts.map +1 -1
- package/dist/types/common/util/mfe.d.ts +2 -5
- package/dist/types/common/util/mfe.d.ts.map +1 -1
- package/dist/types/common/vitals/constants.d.ts +1 -0
- package/dist/types/common/vitals/load-time.d.ts +3 -0
- package/dist/types/common/vitals/load-time.d.ts.map +1 -0
- package/dist/types/common/window/load.d.ts +10 -1
- package/dist/types/common/window/load.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/soft_navigations/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/spa/instrument/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/common/constants/runtime.js +2 -0
- package/src/common/timing/nav-timing.js +7 -2
- package/src/common/util/mfe.js +4 -4
- package/src/common/vitals/constants.js +1 -0
- package/src/common/vitals/load-time.js +23 -0
- package/src/common/vitals/time-to-first-byte.js +2 -2
- package/src/common/window/load.js +18 -1
- package/src/features/page_view_event/aggregate/index.js +4 -5
- package/src/features/page_view_timing/aggregate/index.js +2 -4
- package/src/features/session_trace/aggregate/index.js +2 -2
- package/src/features/soft_navigations/aggregate/index.js +3 -4
- package/src/features/spa/instrument/index.js +6 -0
- package/src/loaders/api/register.js +1 -1
|
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.RRWEB_PACKAGE_NAME = exports.D
|
|
|
17
17
|
/**
|
|
18
18
|
* Exposes the version of the agent
|
|
19
19
|
*/
|
|
20
|
-
const VERSION = exports.VERSION = "1.304.0-rc.
|
|
20
|
+
const VERSION = exports.VERSION = "1.304.0-rc.6";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Exposes the build type of the agent
|
|
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.RRWEB_PACKAGE_NAME = exports.D
|
|
|
17
17
|
/**
|
|
18
18
|
* Exposes the version of the agent
|
|
19
19
|
*/
|
|
20
|
-
const VERSION = exports.VERSION = "1.304.0-rc.
|
|
20
|
+
const VERSION = exports.VERSION = "1.304.0-rc.6";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Exposes the build type of the agent
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.originTime = exports.loadedAsDeferredBrowserScript = exports.isiOS = exports.isWorkerScope = exports.isBrowserScope = exports.initiallyHidden = exports.initialLocation = exports.iOSBelow16 = exports.globalScope = exports.ffVersion = void 0;
|
|
6
|
+
exports.supportsNavTimingL2 = exports.originTime = exports.loadedAsDeferredBrowserScript = exports.isiOS = exports.isWorkerScope = exports.isBrowserScope = exports.initiallyHidden = exports.initialLocation = exports.iOSBelow16 = exports.globalScope = exports.ffVersion = void 0;
|
|
7
7
|
var _now = require("../timing/now");
|
|
8
8
|
/**
|
|
9
9
|
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
@@ -53,4 +53,6 @@ const ffVersion = exports.ffVersion = (() => {
|
|
|
53
53
|
* according to the browser's local clock.
|
|
54
54
|
* @type {number}
|
|
55
55
|
*/
|
|
56
|
-
const originTime = exports.originTime = Date.now() - (0, _now.now)();
|
|
56
|
+
const originTime = exports.originTime = Date.now() - (0, _now.now)();
|
|
57
|
+
const supportsNavTimingL2 = () => typeof PerformanceNavigationTiming !== 'undefined' && globalScope?.performance?.getEntriesByType('navigation')?.length > 0;
|
|
58
|
+
exports.supportsNavTimingL2 = supportsNavTimingL2;
|
|
@@ -72,8 +72,13 @@ function addPT(offset, pt, v = {}, isL1Api = false) {
|
|
|
72
72
|
|
|
73
73
|
// Add Performance Navigation values to the given object
|
|
74
74
|
function addPN(pn, v) {
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
try {
|
|
76
|
+
handleValue(getPntType(pn.type), v, 'ty');
|
|
77
|
+
handleValue(pn.redirectCount, v, 'rc');
|
|
78
|
+
} catch (e) {
|
|
79
|
+
v.ty = 0;
|
|
80
|
+
v.rc = 0;
|
|
81
|
+
}
|
|
77
82
|
return v;
|
|
78
83
|
}
|
|
79
84
|
|
|
@@ -28,7 +28,7 @@ function hasValidValue(val) {
|
|
|
28
28
|
*
|
|
29
29
|
* @param {Object} [target] the registered target
|
|
30
30
|
* @param {AggregateInstance} [aggregateInstance] the aggregate instance calling the method
|
|
31
|
-
* @returns {
|
|
31
|
+
* @returns {Object} returns an empty object if args are not supplied or the aggregate instance is not supporting version 2
|
|
32
32
|
*/
|
|
33
33
|
function getVersion2Attributes(target, aggregateInstance) {
|
|
34
34
|
if (aggregateInstance?.harvestEndpointVersion !== 2) return {};
|
|
@@ -40,9 +40,9 @@ function getVersion2Attributes(target, aggregateInstance) {
|
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
42
|
return {
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
|
|
43
|
+
'source.id': target.id,
|
|
44
|
+
'source.name': target.name,
|
|
45
|
+
'source.type': target.type,
|
|
46
46
|
'parent.id': target.parent?.id || containerAgentEntityGuid
|
|
47
47
|
};
|
|
48
48
|
}
|
|
@@ -13,6 +13,7 @@ const VITAL_NAMES = exports.VITAL_NAMES = {
|
|
|
13
13
|
FIRST_CONTENTFUL_PAINT: 'fcp',
|
|
14
14
|
FIRST_INTERACTION: 'fi',
|
|
15
15
|
LARGEST_CONTENTFUL_PAINT: 'lcp',
|
|
16
|
+
LOAD_TIME: 'load',
|
|
16
17
|
CUMULATIVE_LAYOUT_SHIFT: 'cls',
|
|
17
18
|
INTERACTION_TO_NEXT_PAINT: 'inp',
|
|
18
19
|
TIME_TO_FIRST_BYTE: 'ttfb'
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.loadTime = void 0;
|
|
7
|
+
var _runtime = require("../constants/runtime");
|
|
8
|
+
var _load = require("../window/load");
|
|
9
|
+
var _constants = require("./constants");
|
|
10
|
+
var _vitalMetric = require("./vital-metric");
|
|
11
|
+
/**
|
|
12
|
+
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
13
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const loadTime = exports.loadTime = new _vitalMetric.VitalMetric(_constants.VITAL_NAMES.LOAD_TIME);
|
|
17
|
+
if (_runtime.isBrowserScope) {
|
|
18
|
+
const perf = _runtime.globalScope.performance;
|
|
19
|
+
const handler = () => {
|
|
20
|
+
if (!loadTime.isValid && perf) {
|
|
21
|
+
loadTime.update({
|
|
22
|
+
value: (0, _runtime.supportsNavTimingL2)() ? perf.getEntriesByType('navigation')?.[0]?.loadEventEnd : perf.timing?.loadEventEnd - _runtime.originTime
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
(0, _load.onWindowLoad)(handler, true);
|
|
27
|
+
}
|
|
@@ -22,7 +22,7 @@ const timeToFirstByte = exports.timeToFirstByte = new _vitalMetric.VitalMetric(_
|
|
|
22
22
|
* - in an iOS browser
|
|
23
23
|
* - cross-origin iframes specifically in firefox and safari
|
|
24
24
|
*/
|
|
25
|
-
if (_runtime.isBrowserScope &&
|
|
25
|
+
if (_runtime.isBrowserScope && (0, _runtime.supportsNavTimingL2)() && !_runtime.isiOS && window === window.parent) {
|
|
26
26
|
(0, _attribution.onTTFB)(({
|
|
27
27
|
value,
|
|
28
28
|
attribution
|
|
@@ -8,6 +8,7 @@ exports.onDOMContentLoaded = onDOMContentLoaded;
|
|
|
8
8
|
exports.onPopstateChange = onPopstateChange;
|
|
9
9
|
exports.onWindowLoad = onWindowLoad;
|
|
10
10
|
var _eventListenerOpts = require("../event-listener/event-listener-opts");
|
|
11
|
+
var _invoke = require("../util/invoke");
|
|
11
12
|
/**
|
|
12
13
|
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
13
14
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -16,9 +17,26 @@ var _eventListenerOpts = require("../event-listener/event-listener-opts");
|
|
|
16
17
|
function checkState() {
|
|
17
18
|
return typeof document === 'undefined' || document.readyState === 'complete';
|
|
18
19
|
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Executes a callback when the window 'load' event fires, or immediately if the load event has already occurred.
|
|
23
|
+
* Sets up a backup polling mechanism in the rare case that the browser does not fire the load event when the page has loaded,
|
|
24
|
+
* such as in certain iframe scenarios like ChatGPT connector frames. Cannot use document.readystatechange event here because
|
|
25
|
+
* it blocks back/forward cache in Safari browsers.
|
|
26
|
+
* @param {Function} cb
|
|
27
|
+
* @param {boolean} [useCapture]
|
|
28
|
+
* @returns {void}
|
|
29
|
+
*/
|
|
19
30
|
function onWindowLoad(cb, useCapture) {
|
|
20
31
|
if (checkState()) return cb();
|
|
21
|
-
(0,
|
|
32
|
+
const singleCb = (0, _invoke.single)(cb);
|
|
33
|
+
const poll = setInterval(() => {
|
|
34
|
+
if (checkState()) {
|
|
35
|
+
clearInterval(poll);
|
|
36
|
+
singleCb();
|
|
37
|
+
}
|
|
38
|
+
}, 500);
|
|
39
|
+
(0, _eventListenerOpts.windowAddEventListener)('load', singleCb, useCapture);
|
|
22
40
|
}
|
|
23
41
|
function onDOMContentLoaded(cb) {
|
|
24
42
|
if (checkState()) return cb();
|
|
@@ -52,9 +52,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
52
52
|
this.timeToFirstByte = Math.max(value, this.timeToFirstByte);
|
|
53
53
|
this.firstByteToWindowLoad = Math.max(Math.round(navEntry.loadEventEnd - this.timeToFirstByte), this.firstByteToWindowLoad); // our "frontend" duration
|
|
54
54
|
this.firstByteToDomContent = Math.max(Math.round(navEntry.domContentLoadedEventEnd - this.timeToFirstByte), this.firstByteToDomContent); // our "dom processing" duration
|
|
55
|
-
|
|
56
|
-
this.sendRum();
|
|
57
55
|
});
|
|
56
|
+
setTimeout(this.sendRum.bind(this), 0); // we want to sendRum after ttfb has reported something, but we dont want to wait forever incase TTFB fails to report in niche environments.
|
|
58
57
|
} else {
|
|
59
58
|
// worker agent build does not get TTFB values, use default 0 values
|
|
60
59
|
this.sendRum();
|
|
@@ -98,7 +97,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
98
97
|
}, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string');
|
|
99
98
|
}
|
|
100
99
|
if (_runtime.globalScope.performance) {
|
|
101
|
-
if (
|
|
100
|
+
if ((0, _runtime.supportsNavTimingL2)()) {
|
|
102
101
|
// Navigation Timing level 2 API that replaced PerformanceTiming & PerformanceNavigation
|
|
103
102
|
const navTimingEntry = _runtime.globalScope?.performance?.getEntriesByType('navigation')?.[0];
|
|
104
103
|
const perf = {
|
|
@@ -107,7 +106,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
107
106
|
};
|
|
108
107
|
queryParameters.perf = (0, _stringify.stringify)(perf);
|
|
109
108
|
} else if (typeof PerformanceTiming !== 'undefined') {
|
|
110
|
-
// Safari pre-15
|
|
109
|
+
// Modern Safari iFrames and Safari pre-15 do not support level 2 timing.
|
|
111
110
|
const perf = {
|
|
112
111
|
timing: (0, _navTiming.addPT)(_runtime.originTime, _runtime.globalScope.performance.timing, {}, true),
|
|
113
112
|
navigation: (0, _navTiming.addPN)(_runtime.globalScope.performance.navigation, {})
|
|
@@ -15,11 +15,11 @@ var _firstContentfulPaint = require("../../../common/vitals/first-contentful-pai
|
|
|
15
15
|
var _firstPaint = require("../../../common/vitals/first-paint");
|
|
16
16
|
var _interactionToNextPaint = require("../../../common/vitals/interaction-to-next-paint");
|
|
17
17
|
var _largestContentfulPaint = require("../../../common/vitals/largest-contentful-paint");
|
|
18
|
-
var _timeToFirstByte = require("../../../common/vitals/time-to-first-byte");
|
|
19
18
|
var _pageVisibility = require("../../../common/window/page-visibility");
|
|
20
19
|
var _constants2 = require("../../../common/vitals/constants");
|
|
21
20
|
var _runtime = require("../../../common/constants/runtime");
|
|
22
21
|
var _eventOrigin = require("../../../common/util/event-origin");
|
|
22
|
+
var _loadTime = require("../../../common/vitals/load-time");
|
|
23
23
|
/**
|
|
24
24
|
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
25
25
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -47,10 +47,11 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
47
47
|
_firstContentfulPaint.firstContentfulPaint.subscribe(this.#handleVitalMetric);
|
|
48
48
|
_largestContentfulPaint.largestContentfulPaint.subscribe(this.#handleVitalMetric);
|
|
49
49
|
_interactionToNextPaint.interactionToNextPaint.subscribe(this.#handleVitalMetric);
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
_loadTime.loadTime.subscribe(({
|
|
51
|
+
name,
|
|
52
|
+
value
|
|
52
53
|
}) => {
|
|
53
|
-
this.addTiming(
|
|
54
|
+
this.addTiming(name, Math.round(value));
|
|
54
55
|
});
|
|
55
56
|
(0, _pageVisibility.subscribeToVisibilityChange)(() => {
|
|
56
57
|
/* 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.
|
|
@@ -64,7 +64,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
64
64
|
// if another page's session entity has expired, or another page has transitioned to off and this one hasn't... we can just abort straight away here
|
|
65
65
|
if (this.sessionId !== sessionState.value || eventType === 'cross-tab' && sessionState.sessionTraceMode === _constants2.MODE.OFF) this.abort(2);
|
|
66
66
|
});
|
|
67
|
-
if (
|
|
67
|
+
if ((0, _runtime.supportsNavTimingL2)()) {
|
|
68
68
|
this.traceStorage.storeTiming(_runtime.globalScope.performance?.getEntriesByType?.('navigation')[0]);
|
|
69
69
|
} else {
|
|
70
70
|
this.traceStorage.storeTiming(_runtime.globalScope.performance?.timing, true);
|
|
@@ -7,7 +7,7 @@ exports.Aggregate = void 0;
|
|
|
7
7
|
var _handle = require("../../../common/event-emitter/handle");
|
|
8
8
|
var _registerHandler = require("../../../common/event-emitter/register-handler");
|
|
9
9
|
var _invoke = require("../../../common/util/invoke");
|
|
10
|
-
var
|
|
10
|
+
var _loadTime = require("../../../common/vitals/load-time");
|
|
11
11
|
var _features = require("../../../loaders/features/features");
|
|
12
12
|
var _aggregateBase = require("../../utils/aggregate-base");
|
|
13
13
|
var _constants = require("../constants");
|
|
@@ -37,12 +37,10 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
37
37
|
this.events.add(ixn); // add the iPL ixn to the buffer for harvest
|
|
38
38
|
this.initialPageLoadInteraction = null;
|
|
39
39
|
});
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
_loadTime.loadTime.subscribe(({
|
|
41
|
+
value: loadEventTime
|
|
42
42
|
}) => {
|
|
43
|
-
const loadEventTime = attrs.navigationEntry.loadEventEnd;
|
|
44
43
|
this.initialPageLoadInteraction.done(loadEventTime);
|
|
45
|
-
// Report metric on the initial page load time
|
|
46
44
|
this.reportSupportabilityMetric('SoftNav/Interaction/InitialPageLoad/Duration/Ms', Math.round(loadEventTime));
|
|
47
45
|
});
|
|
48
46
|
this.latestRouteSetByApi = null;
|
|
@@ -18,6 +18,7 @@ var _wrapFetch = require("../../../common/wrap/wrap-fetch");
|
|
|
18
18
|
var _wrapHistory = require("../../../common/wrap/wrap-history");
|
|
19
19
|
var _wrapMutation = require("../../../common/wrap/wrap-mutation");
|
|
20
20
|
var _interaction = require("../../../loaders/api/interaction");
|
|
21
|
+
var _load = require("../../../common/window/load");
|
|
21
22
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } /**
|
|
22
23
|
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
23
24
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -87,6 +88,13 @@ class Instrument extends _instrumentBase.InstrumentBase {
|
|
|
87
88
|
timestamp(jsonpEE, 'cb-start');
|
|
88
89
|
historyEE.on('pushState-end', trackURLChange);
|
|
89
90
|
historyEE.on('replaceState-end', trackURLChange);
|
|
91
|
+
|
|
92
|
+
/** niche cases like GPT apps cause no window.load event to fire - which breaks IPLs - so manually force one through the pipe */
|
|
93
|
+
(0, _load.onWindowLoad)(() => {
|
|
94
|
+
eventsEE.emit(FN_START, [[{
|
|
95
|
+
type: 'load'
|
|
96
|
+
}], window], undefined, true);
|
|
97
|
+
});
|
|
90
98
|
window.addEventListener('hashchange', trackURLChange, (0, _eventListenerOpts.eventListenerOpts)(true, this.removeOnAbort?.signal));
|
|
91
99
|
window.addEventListener('load', trackURLChange, (0, _eventListenerOpts.eventListenerOpts)(true, this.removeOnAbort?.signal));
|
|
92
100
|
window.addEventListener('popstate', function () {
|
|
@@ -50,7 +50,7 @@ function register(agentRef, target, parent) {
|
|
|
50
50
|
const attrs = {};
|
|
51
51
|
(0, _console.warn)(54, 'newrelic.register');
|
|
52
52
|
target ||= {};
|
|
53
|
-
target.
|
|
53
|
+
target.type = 'MFE';
|
|
54
54
|
target.licenseKey ||= agentRef.info.licenseKey; // will inherit the license key from the container agent if not provided for brevity. A future state may dictate that we need different license keys to do different things.
|
|
55
55
|
target.blocked = false;
|
|
56
56
|
target.parent = parent || {};
|
|
@@ -48,4 +48,5 @@ export const ffVersion = (() => {
|
|
|
48
48
|
* according to the browser's local clock.
|
|
49
49
|
* @type {number}
|
|
50
50
|
*/
|
|
51
|
-
export const originTime = Date.now() - now();
|
|
51
|
+
export const originTime = Date.now() - now();
|
|
52
|
+
export const supportsNavTimingL2 = () => typeof PerformanceNavigationTiming !== 'undefined' && globalScope?.performance?.getEntriesByType('navigation')?.length > 0;
|
|
@@ -64,8 +64,13 @@ export function addPT(offset, pt, v = {}, isL1Api = false) {
|
|
|
64
64
|
|
|
65
65
|
// Add Performance Navigation values to the given object
|
|
66
66
|
export function addPN(pn, v) {
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
try {
|
|
68
|
+
handleValue(getPntType(pn.type), v, 'ty');
|
|
69
|
+
handleValue(pn.redirectCount, v, 'rc');
|
|
70
|
+
} catch (e) {
|
|
71
|
+
v.ty = 0;
|
|
72
|
+
v.rc = 0;
|
|
73
|
+
}
|
|
69
74
|
return v;
|
|
70
75
|
}
|
|
71
76
|
|
|
@@ -20,7 +20,7 @@ export function hasValidValue(val) {
|
|
|
20
20
|
*
|
|
21
21
|
* @param {Object} [target] the registered target
|
|
22
22
|
* @param {AggregateInstance} [aggregateInstance] the aggregate instance calling the method
|
|
23
|
-
* @returns {
|
|
23
|
+
* @returns {Object} returns an empty object if args are not supplied or the aggregate instance is not supporting version 2
|
|
24
24
|
*/
|
|
25
25
|
export function getVersion2Attributes(target, aggregateInstance) {
|
|
26
26
|
if (aggregateInstance?.harvestEndpointVersion !== 2) return {};
|
|
@@ -32,9 +32,9 @@ export function getVersion2Attributes(target, aggregateInstance) {
|
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
34
|
return {
|
|
35
|
-
'
|
|
36
|
-
'
|
|
37
|
-
|
|
35
|
+
'source.id': target.id,
|
|
36
|
+
'source.name': target.name,
|
|
37
|
+
'source.type': target.type,
|
|
38
38
|
'parent.id': target.parent?.id || containerAgentEntityGuid
|
|
39
39
|
};
|
|
40
40
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
import { globalScope, isBrowserScope, originTime, supportsNavTimingL2 } from '../constants/runtime';
|
|
6
|
+
import { onWindowLoad } from '../window/load';
|
|
7
|
+
import { VITAL_NAMES } from './constants';
|
|
8
|
+
import { VitalMetric } from './vital-metric';
|
|
9
|
+
export const loadTime = new VitalMetric(VITAL_NAMES.LOAD_TIME);
|
|
10
|
+
if (isBrowserScope) {
|
|
11
|
+
const perf = globalScope.performance;
|
|
12
|
+
const handler = () => {
|
|
13
|
+
if (!loadTime.isValid && perf) {
|
|
14
|
+
loadTime.update({
|
|
15
|
+
value: supportsNavTimingL2() ? perf.getEntriesByType('navigation')?.[0]?.loadEventEnd : perf.timing?.loadEventEnd - originTime
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
onWindowLoad(handler, true);
|
|
20
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
|
-
import { globalScope, isBrowserScope, isiOS, originTime } from '../constants/runtime';
|
|
5
|
+
import { globalScope, isBrowserScope, isiOS, originTime, supportsNavTimingL2 } from '../constants/runtime';
|
|
6
6
|
import { VITAL_NAMES } from './constants';
|
|
7
7
|
import { VitalMetric } from './vital-metric';
|
|
8
8
|
import { onTTFB } from 'web-vitals/attribution';
|
|
@@ -15,7 +15,7 @@ export const timeToFirstByte = new VitalMetric(VITAL_NAMES.TIME_TO_FIRST_BYTE);
|
|
|
15
15
|
* - in an iOS browser
|
|
16
16
|
* - cross-origin iframes specifically in firefox and safari
|
|
17
17
|
*/
|
|
18
|
-
if (isBrowserScope &&
|
|
18
|
+
if (isBrowserScope && supportsNavTimingL2() && !isiOS && window === window.parent) {
|
|
19
19
|
onTTFB(({
|
|
20
20
|
value,
|
|
21
21
|
attribution
|
|
@@ -3,12 +3,30 @@
|
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
import { windowAddEventListener, documentAddEventListener } from '../event-listener/event-listener-opts';
|
|
6
|
+
import { single } from '../util/invoke';
|
|
6
7
|
export function checkState() {
|
|
7
8
|
return typeof document === 'undefined' || document.readyState === 'complete';
|
|
8
9
|
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Executes a callback when the window 'load' event fires, or immediately if the load event has already occurred.
|
|
13
|
+
* Sets up a backup polling mechanism in the rare case that the browser does not fire the load event when the page has loaded,
|
|
14
|
+
* such as in certain iframe scenarios like ChatGPT connector frames. Cannot use document.readystatechange event here because
|
|
15
|
+
* it blocks back/forward cache in Safari browsers.
|
|
16
|
+
* @param {Function} cb
|
|
17
|
+
* @param {boolean} [useCapture]
|
|
18
|
+
* @returns {void}
|
|
19
|
+
*/
|
|
9
20
|
export function onWindowLoad(cb, useCapture) {
|
|
10
21
|
if (checkState()) return cb();
|
|
11
|
-
|
|
22
|
+
const singleCb = single(cb);
|
|
23
|
+
const poll = setInterval(() => {
|
|
24
|
+
if (checkState()) {
|
|
25
|
+
clearInterval(poll);
|
|
26
|
+
singleCb();
|
|
27
|
+
}
|
|
28
|
+
}, 500);
|
|
29
|
+
windowAddEventListener('load', singleCb, useCapture);
|
|
12
30
|
}
|
|
13
31
|
export function onDOMContentLoaded(cb) {
|
|
14
32
|
if (checkState()) return cb();
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
|
-
import { globalScope, isBrowserScope, originTime } from '../../../common/constants/runtime';
|
|
5
|
+
import { globalScope, isBrowserScope, originTime, supportsNavTimingL2 } from '../../../common/constants/runtime';
|
|
6
6
|
import { addPT, addPN } from '../../../common/timing/nav-timing';
|
|
7
7
|
import { stringify } from '../../../common/util/stringify';
|
|
8
8
|
import { isValid } from '../../../common/config/info';
|
|
@@ -44,9 +44,8 @@ export class Aggregate extends AggregateBase {
|
|
|
44
44
|
this.timeToFirstByte = Math.max(value, this.timeToFirstByte);
|
|
45
45
|
this.firstByteToWindowLoad = Math.max(Math.round(navEntry.loadEventEnd - this.timeToFirstByte), this.firstByteToWindowLoad); // our "frontend" duration
|
|
46
46
|
this.firstByteToDomContent = Math.max(Math.round(navEntry.domContentLoadedEventEnd - this.timeToFirstByte), this.firstByteToDomContent); // our "dom processing" duration
|
|
47
|
-
|
|
48
|
-
this.sendRum();
|
|
49
47
|
});
|
|
48
|
+
setTimeout(this.sendRum.bind(this), 0); // we want to sendRum after ttfb has reported something, but we dont want to wait forever incase TTFB fails to report in niche environments.
|
|
50
49
|
} else {
|
|
51
50
|
// worker agent build does not get TTFB values, use default 0 values
|
|
52
51
|
this.sendRum();
|
|
@@ -90,7 +89,7 @@ export class Aggregate extends AggregateBase {
|
|
|
90
89
|
}, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string');
|
|
91
90
|
}
|
|
92
91
|
if (globalScope.performance) {
|
|
93
|
-
if (
|
|
92
|
+
if (supportsNavTimingL2()) {
|
|
94
93
|
// Navigation Timing level 2 API that replaced PerformanceTiming & PerformanceNavigation
|
|
95
94
|
const navTimingEntry = globalScope?.performance?.getEntriesByType('navigation')?.[0];
|
|
96
95
|
const perf = {
|
|
@@ -99,7 +98,7 @@ export class Aggregate extends AggregateBase {
|
|
|
99
98
|
};
|
|
100
99
|
queryParameters.perf = stringify(perf);
|
|
101
100
|
} else if (typeof PerformanceTiming !== 'undefined') {
|
|
102
|
-
// Safari pre-15
|
|
101
|
+
// Modern Safari iFrames and Safari pre-15 do not support level 2 timing.
|
|
103
102
|
const perf = {
|
|
104
103
|
timing: addPT(originTime, globalScope.performance.timing, {}, true),
|
|
105
104
|
navigation: addPN(globalScope.performance.navigation, {})
|
|
@@ -14,11 +14,11 @@ import { firstContentfulPaint } from '../../../common/vitals/first-contentful-pa
|
|
|
14
14
|
import { firstPaint } from '../../../common/vitals/first-paint';
|
|
15
15
|
import { interactionToNextPaint } from '../../../common/vitals/interaction-to-next-paint';
|
|
16
16
|
import { largestContentfulPaint } from '../../../common/vitals/largest-contentful-paint';
|
|
17
|
-
import { timeToFirstByte } from '../../../common/vitals/time-to-first-byte';
|
|
18
17
|
import { subscribeToVisibilityChange } from '../../../common/window/page-visibility';
|
|
19
18
|
import { VITAL_NAMES } from '../../../common/vitals/constants';
|
|
20
19
|
import { initiallyHidden } from '../../../common/constants/runtime';
|
|
21
20
|
import { eventOrigin } from '../../../common/util/event-origin';
|
|
21
|
+
import { loadTime } from '../../../common/vitals/load-time';
|
|
22
22
|
export class Aggregate extends AggregateBase {
|
|
23
23
|
static featureName = FEATURE_NAME;
|
|
24
24
|
#handleVitalMetric = ({
|
|
@@ -41,10 +41,11 @@ export class Aggregate extends AggregateBase {
|
|
|
41
41
|
firstContentfulPaint.subscribe(this.#handleVitalMetric);
|
|
42
42
|
largestContentfulPaint.subscribe(this.#handleVitalMetric);
|
|
43
43
|
interactionToNextPaint.subscribe(this.#handleVitalMetric);
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
loadTime.subscribe(({
|
|
45
|
+
name,
|
|
46
|
+
value
|
|
46
47
|
}) => {
|
|
47
|
-
this.addTiming(
|
|
48
|
+
this.addTiming(name, Math.round(value));
|
|
48
49
|
});
|
|
49
50
|
subscribeToVisibilityChange(() => {
|
|
50
51
|
/* 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.
|
|
@@ -7,7 +7,7 @@ import { FEATURE_NAME } from '../constants';
|
|
|
7
7
|
import { AggregateBase } from '../../utils/aggregate-base';
|
|
8
8
|
import { TraceStorage } from './trace/storage';
|
|
9
9
|
import { obj as encodeObj } from '../../../common/url/encode';
|
|
10
|
-
import { globalScope } from '../../../common/constants/runtime';
|
|
10
|
+
import { globalScope, supportsNavTimingL2 } from '../../../common/constants/runtime';
|
|
11
11
|
import { MODE, SESSION_EVENTS } from '../../../common/session/constants';
|
|
12
12
|
import { applyFnToProps } from '../../../common/util/traverse';
|
|
13
13
|
import { cleanURL } from '../../../common/url/clean-url';
|
|
@@ -58,7 +58,7 @@ export class Aggregate extends AggregateBase {
|
|
|
58
58
|
// if another page's session entity has expired, or another page has transitioned to off and this one hasn't... we can just abort straight away here
|
|
59
59
|
if (this.sessionId !== sessionState.value || eventType === 'cross-tab' && sessionState.sessionTraceMode === MODE.OFF) this.abort(2);
|
|
60
60
|
});
|
|
61
|
-
if (
|
|
61
|
+
if (supportsNavTimingL2()) {
|
|
62
62
|
this.traceStorage.storeTiming(globalScope.performance?.getEntriesByType?.('navigation')[0]);
|
|
63
63
|
} else {
|
|
64
64
|
this.traceStorage.storeTiming(globalScope.performance?.timing, true);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { handle } from '../../../common/event-emitter/handle';
|
|
6
6
|
import { registerHandler } from '../../../common/event-emitter/register-handler';
|
|
7
7
|
import { single } from '../../../common/util/invoke';
|
|
8
|
-
import {
|
|
8
|
+
import { loadTime } from '../../../common/vitals/load-time';
|
|
9
9
|
import { FEATURE_NAMES } from '../../../loaders/features/features';
|
|
10
10
|
import { AggregateBase } from '../../utils/aggregate-base';
|
|
11
11
|
import { API_TRIGGER_NAME, FEATURE_NAME, INTERACTION_STATUS, INTERACTION_TRIGGERS, IPL_TRIGGER_NAME, NO_LONG_TASK_WINDOW, POPSTATE_MERGE_WINDOW, POPSTATE_TRIGGER } from '../constants';
|
|
@@ -30,12 +30,10 @@ export class Aggregate extends AggregateBase {
|
|
|
30
30
|
this.events.add(ixn); // add the iPL ixn to the buffer for harvest
|
|
31
31
|
this.initialPageLoadInteraction = null;
|
|
32
32
|
});
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
loadTime.subscribe(({
|
|
34
|
+
value: loadEventTime
|
|
35
35
|
}) => {
|
|
36
|
-
const loadEventTime = attrs.navigationEntry.loadEventEnd;
|
|
37
36
|
this.initialPageLoadInteraction.done(loadEventTime);
|
|
38
|
-
// Report metric on the initial page load time
|
|
39
37
|
this.reportSupportabilityMetric('SoftNav/Interaction/InitialPageLoad/Duration/Ms', Math.round(loadEventTime));
|
|
40
38
|
});
|
|
41
39
|
this.latestRouteSetByApi = null;
|
|
@@ -16,6 +16,7 @@ import { wrapFetch } from '../../../common/wrap/wrap-fetch';
|
|
|
16
16
|
import { wrapHistory } from '../../../common/wrap/wrap-history';
|
|
17
17
|
import { wrapMutation } from '../../../common/wrap/wrap-mutation';
|
|
18
18
|
import { setupInteractionAPI } from '../../../loaders/api/interaction';
|
|
19
|
+
import { onWindowLoad } from '../../../common/window/load';
|
|
19
20
|
const {
|
|
20
21
|
FEATURE_NAME,
|
|
21
22
|
START,
|
|
@@ -81,6 +82,13 @@ export class Instrument extends InstrumentBase {
|
|
|
81
82
|
timestamp(jsonpEE, 'cb-start');
|
|
82
83
|
historyEE.on('pushState-end', trackURLChange);
|
|
83
84
|
historyEE.on('replaceState-end', trackURLChange);
|
|
85
|
+
|
|
86
|
+
/** niche cases like GPT apps cause no window.load event to fire - which breaks IPLs - so manually force one through the pipe */
|
|
87
|
+
onWindowLoad(() => {
|
|
88
|
+
eventsEE.emit(FN_START, [[{
|
|
89
|
+
type: 'load'
|
|
90
|
+
}], window], undefined, true);
|
|
91
|
+
});
|
|
84
92
|
window.addEventListener('hashchange', trackURLChange, eventListenerOpts(true, this.removeOnAbort?.signal));
|
|
85
93
|
window.addEventListener('load', trackURLChange, eventListenerOpts(true, this.removeOnAbort?.signal));
|
|
86
94
|
window.addEventListener('popstate', function () {
|
|
@@ -44,7 +44,7 @@ function register(agentRef, target, parent) {
|
|
|
44
44
|
const attrs = {};
|
|
45
45
|
warn(54, 'newrelic.register');
|
|
46
46
|
target ||= {};
|
|
47
|
-
target.
|
|
47
|
+
target.type = 'MFE';
|
|
48
48
|
target.licenseKey ||= agentRef.info.licenseKey; // will inherit the license key from the container agent if not provided for brevity. A future state may dictate that we need different license keys to do different things.
|
|
49
49
|
target.blocked = false;
|
|
50
50
|
target.parent = parent || {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/index.js","../src/cdn/experimental.js","../src/cdn/lite.js","../src/cdn/pro.js","../src/cdn/spa.js","../src/common/aggregate/aggregator.js","../src/common/aggregate/event-aggregator.js","../src/common/config/configurable.js","../src/common/config/info.js","../src/common/config/init-types.js","../src/common/config/init.js","../src/common/config/loader-config.js","../src/common/config/runtime.js","../src/common/constants/agent-constants.js","../src/common/constants/env.cdn.js","../src/common/constants/env.js","../src/common/constants/env.npm.js","../src/common/constants/runtime.js","../src/common/constants/shared-channel.js","../src/common/deny-list/deny-list.js","../src/common/dispatch/global-event.js","../src/common/dom/iframe.js","../src/common/dom/query-selector.js","../src/common/dom/selector-path.js","../src/common/drain/drain.js","../src/common/event-emitter/contextual-ee.js","../src/common/event-emitter/event-context.js","../src/common/event-emitter/handle.js","../src/common/event-emitter/register-handler.js","../src/common/event-listener/event-listener-opts.js","../src/common/harvest/harvester.js","../src/common/harvest/types.js","../src/common/ids/bundle-id.js","../src/common/ids/id.js","../src/common/ids/unique-id.js","../src/common/serialize/bel-serializer.js","../src/common/session/constants.js","../src/common/session/session-entity.js","../src/common/storage/local-storage.js","../src/common/timer/interaction-timer.js","../src/common/timer/timer.js","../src/common/timing/nav-timing.js","../src/common/timing/now.js","../src/common/timing/time-keeper.js","../src/common/unload/eol.js","../src/common/url/canonicalize-url.js","../src/common/url/clean-url.js","../src/common/url/encode.js","../src/common/url/extract-url.js","../src/common/url/location.js","../src/common/url/parse-url.js","../src/common/url/protocol.js","../src/common/util/attribute-size.js","../src/common/util/console.js","../src/common/util/data-size.js","../src/common/util/event-origin.js","../src/common/util/feature-flags.js","../src/common/util/get-or-set.js","../src/common/util/invoke.js","../src/common/util/mfe.js","../src/common/util/monkey-patched.js","../src/common/util/obfuscate.js","../src/common/util/stringify.js","../src/common/util/submit-data.js","../src/common/util/text.js","../src/common/util/traverse.js","../src/common/util/type-check.js","../src/common/vitals/constants.js","../src/common/vitals/cumulative-layout-shift.js","../src/common/vitals/first-contentful-paint.js","../src/common/vitals/first-paint.js","../src/common/vitals/interaction-to-next-paint.js","../src/common/vitals/largest-contentful-paint.js","../src/common/vitals/time-to-first-byte.js","../src/common/vitals/vital-metric.js","../src/common/window/load.js","../src/common/window/nreum.js","../src/common/window/page-visibility.js","../src/common/wrap/wrap-events.js","../src/common/wrap/wrap-fetch.js","../src/common/wrap/wrap-function.js","../src/common/wrap/wrap-history.js","../src/common/wrap/wrap-jsonp.js","../src/common/wrap/wrap-logger.js","../src/common/wrap/wrap-mutation.js","../src/common/wrap/wrap-promise.js","../src/common/wrap/wrap-timer.js","../src/common/wrap/wrap-websocket.js","../src/common/wrap/wrap-xhr.js","../src/features/ajax/constants.js","../src/features/ajax/index.js","../src/features/ajax/aggregate/gql.js","../src/features/ajax/aggregate/index.js","../src/features/ajax/instrument/distributed-tracing.js","../src/features/ajax/instrument/index.js","../src/features/ajax/instrument/response-size.js","../src/features/generic_events/constants.js","../src/features/generic_events/index.js","../src/features/generic_events/aggregate/index.js","../src/features/generic_events/aggregate/user-actions/aggregated-user-action.js","../src/features/generic_events/aggregate/user-actions/user-actions-aggregator.js","../src/features/generic_events/instrument/index.js","../src/features/jserrors/constants.js","../src/features/jserrors/index.js","../src/features/jserrors/aggregate/canonical-function-name.js","../src/features/jserrors/aggregate/cause-string.js","../src/features/jserrors/aggregate/compute-stack-trace.js","../src/features/jserrors/aggregate/format-stack-trace.js","../src/features/jserrors/aggregate/index.js","../src/features/jserrors/aggregate/internal-errors.js","../src/features/jserrors/aggregate/string-hash-code.js","../src/features/jserrors/instrument/index.js","../src/features/jserrors/shared/cast-error.js","../src/features/jserrors/shared/uncaught-error.js","../src/features/logging/constants.js","../src/features/logging/index.js","../src/features/logging/aggregate/index.js","../src/features/logging/instrument/index.js","../src/features/logging/shared/log.js","../src/features/logging/shared/utils.js","../src/features/metrics/constants.js","../src/features/metrics/index.js","../src/features/metrics/aggregate/framework-detection.js","../src/features/metrics/aggregate/harvest-metadata.js","../src/features/metrics/aggregate/index.js","../src/features/metrics/instrument/index.js","../src/features/page_action/constants.js","../src/features/page_action/index.js","../src/features/page_action/instrument/index.js","../src/features/page_view_event/constants.js","../src/features/page_view_event/index.js","../src/features/page_view_event/aggregate/index.js","../src/features/page_view_event/aggregate/initialized-features.js","../src/features/page_view_event/instrument/index.js","../src/features/page_view_timing/constants.js","../src/features/page_view_timing/index.js","../src/features/page_view_timing/aggregate/index.js","../src/features/page_view_timing/instrument/index.js","../src/features/session_replay/constants.js","../src/features/session_replay/index.js","../src/features/session_replay/aggregate/index.js","../src/features/session_replay/instrument/index.js","../src/features/session_replay/shared/recorder-events.js","../src/features/session_replay/shared/recorder.js","../src/features/session_replay/shared/stylesheet-evaluator.js","../src/features/session_replay/shared/utils.js","../src/features/session_trace/constants.js","../src/features/session_trace/index.js","../src/features/session_trace/aggregate/index.js","../src/features/session_trace/aggregate/trace/node.js","../src/features/session_trace/aggregate/trace/storage.js","../src/features/session_trace/aggregate/trace/utils.js","../src/features/session_trace/instrument/index.js","../src/features/soft_navigations/constants.js","../src/features/soft_navigations/index.js","../src/features/soft_navigations/aggregate/ajax-node.js","../src/features/soft_navigations/aggregate/bel-node.js","../src/features/soft_navigations/aggregate/index.js","../src/features/soft_navigations/aggregate/initial-page-load-interaction.js","../src/features/soft_navigations/aggregate/interaction.js","../src/features/soft_navigations/instrument/index.js","../src/features/spa/constants.js","../src/features/spa/index.js","../src/features/spa/aggregate/index.js","../src/features/spa/aggregate/interaction-node.js","../src/features/spa/aggregate/interaction.js","../src/features/spa/aggregate/serializer.js","../src/features/spa/instrument/index.js","../src/features/utils/agent-session.js","../src/features/utils/aggregate-base.js","../src/features/utils/event-buffer.js","../src/features/utils/feature-base.js","../src/features/utils/feature-gates.js","../src/features/utils/instrument-base.js","../src/features/utils/nr1-debugger.js","../src/interfaces/registered-entity.js","../src/loaders/agent-base.js","../src/loaders/agent.js","../src/loaders/api-base.js","../src/loaders/browser-agent.js","../src/loaders/micro-agent-base.js","../src/loaders/micro-agent.js","../src/loaders/api/addPageAction.js","../src/loaders/api/addRelease.js","../src/loaders/api/addToTrace.js","../src/loaders/api/consent.js","../src/loaders/api/constants.js","../src/loaders/api/finished.js","../src/loaders/api/interaction-types.js","../src/loaders/api/interaction.js","../src/loaders/api/log.js","../src/loaders/api/measure.js","../src/loaders/api/noticeError.js","../src/loaders/api/pauseReplay.js","../src/loaders/api/recordCustomEvent.js","../src/loaders/api/recordReplay.js","../src/loaders/api/register-api-types.js","../src/loaders/api/register.js","../src/loaders/api/setApplicationVersion.js","../src/loaders/api/setCustomAttribute.js","../src/loaders/api/setErrorHandler.js","../src/loaders/api/setPageViewName.js","../src/loaders/api/setUserId.js","../src/loaders/api/sharedHandlers.js","../src/loaders/api/start.js","../src/loaders/api/topLevelCallers.js","../src/loaders/api/wrapLogger.js","../src/loaders/configure/configure.js","../src/loaders/configure/nonce.js","../src/loaders/configure/public-path.js","../src/loaders/features/enabled-features.js","../src/loaders/features/featureDependencies.js","../src/loaders/features/features.js"],"version":"5.9.3"}
|
|
1
|
+
{"root":["../src/index.js","../src/cdn/experimental.js","../src/cdn/lite.js","../src/cdn/pro.js","../src/cdn/spa.js","../src/common/aggregate/aggregator.js","../src/common/aggregate/event-aggregator.js","../src/common/config/configurable.js","../src/common/config/info.js","../src/common/config/init-types.js","../src/common/config/init.js","../src/common/config/loader-config.js","../src/common/config/runtime.js","../src/common/constants/agent-constants.js","../src/common/constants/env.cdn.js","../src/common/constants/env.js","../src/common/constants/env.npm.js","../src/common/constants/runtime.js","../src/common/constants/shared-channel.js","../src/common/deny-list/deny-list.js","../src/common/dispatch/global-event.js","../src/common/dom/iframe.js","../src/common/dom/query-selector.js","../src/common/dom/selector-path.js","../src/common/drain/drain.js","../src/common/event-emitter/contextual-ee.js","../src/common/event-emitter/event-context.js","../src/common/event-emitter/handle.js","../src/common/event-emitter/register-handler.js","../src/common/event-listener/event-listener-opts.js","../src/common/harvest/harvester.js","../src/common/harvest/types.js","../src/common/ids/bundle-id.js","../src/common/ids/id.js","../src/common/ids/unique-id.js","../src/common/serialize/bel-serializer.js","../src/common/session/constants.js","../src/common/session/session-entity.js","../src/common/storage/local-storage.js","../src/common/timer/interaction-timer.js","../src/common/timer/timer.js","../src/common/timing/nav-timing.js","../src/common/timing/now.js","../src/common/timing/time-keeper.js","../src/common/unload/eol.js","../src/common/url/canonicalize-url.js","../src/common/url/clean-url.js","../src/common/url/encode.js","../src/common/url/extract-url.js","../src/common/url/location.js","../src/common/url/parse-url.js","../src/common/url/protocol.js","../src/common/util/attribute-size.js","../src/common/util/console.js","../src/common/util/data-size.js","../src/common/util/event-origin.js","../src/common/util/feature-flags.js","../src/common/util/get-or-set.js","../src/common/util/invoke.js","../src/common/util/mfe.js","../src/common/util/monkey-patched.js","../src/common/util/obfuscate.js","../src/common/util/stringify.js","../src/common/util/submit-data.js","../src/common/util/text.js","../src/common/util/traverse.js","../src/common/util/type-check.js","../src/common/vitals/constants.js","../src/common/vitals/cumulative-layout-shift.js","../src/common/vitals/first-contentful-paint.js","../src/common/vitals/first-paint.js","../src/common/vitals/interaction-to-next-paint.js","../src/common/vitals/largest-contentful-paint.js","../src/common/vitals/load-time.js","../src/common/vitals/time-to-first-byte.js","../src/common/vitals/vital-metric.js","../src/common/window/load.js","../src/common/window/nreum.js","../src/common/window/page-visibility.js","../src/common/wrap/wrap-events.js","../src/common/wrap/wrap-fetch.js","../src/common/wrap/wrap-function.js","../src/common/wrap/wrap-history.js","../src/common/wrap/wrap-jsonp.js","../src/common/wrap/wrap-logger.js","../src/common/wrap/wrap-mutation.js","../src/common/wrap/wrap-promise.js","../src/common/wrap/wrap-timer.js","../src/common/wrap/wrap-websocket.js","../src/common/wrap/wrap-xhr.js","../src/features/ajax/constants.js","../src/features/ajax/index.js","../src/features/ajax/aggregate/gql.js","../src/features/ajax/aggregate/index.js","../src/features/ajax/instrument/distributed-tracing.js","../src/features/ajax/instrument/index.js","../src/features/ajax/instrument/response-size.js","../src/features/generic_events/constants.js","../src/features/generic_events/index.js","../src/features/generic_events/aggregate/index.js","../src/features/generic_events/aggregate/user-actions/aggregated-user-action.js","../src/features/generic_events/aggregate/user-actions/user-actions-aggregator.js","../src/features/generic_events/instrument/index.js","../src/features/jserrors/constants.js","../src/features/jserrors/index.js","../src/features/jserrors/aggregate/canonical-function-name.js","../src/features/jserrors/aggregate/cause-string.js","../src/features/jserrors/aggregate/compute-stack-trace.js","../src/features/jserrors/aggregate/format-stack-trace.js","../src/features/jserrors/aggregate/index.js","../src/features/jserrors/aggregate/internal-errors.js","../src/features/jserrors/aggregate/string-hash-code.js","../src/features/jserrors/instrument/index.js","../src/features/jserrors/shared/cast-error.js","../src/features/jserrors/shared/uncaught-error.js","../src/features/logging/constants.js","../src/features/logging/index.js","../src/features/logging/aggregate/index.js","../src/features/logging/instrument/index.js","../src/features/logging/shared/log.js","../src/features/logging/shared/utils.js","../src/features/metrics/constants.js","../src/features/metrics/index.js","../src/features/metrics/aggregate/framework-detection.js","../src/features/metrics/aggregate/harvest-metadata.js","../src/features/metrics/aggregate/index.js","../src/features/metrics/instrument/index.js","../src/features/page_action/constants.js","../src/features/page_action/index.js","../src/features/page_action/instrument/index.js","../src/features/page_view_event/constants.js","../src/features/page_view_event/index.js","../src/features/page_view_event/aggregate/index.js","../src/features/page_view_event/aggregate/initialized-features.js","../src/features/page_view_event/instrument/index.js","../src/features/page_view_timing/constants.js","../src/features/page_view_timing/index.js","../src/features/page_view_timing/aggregate/index.js","../src/features/page_view_timing/instrument/index.js","../src/features/session_replay/constants.js","../src/features/session_replay/index.js","../src/features/session_replay/aggregate/index.js","../src/features/session_replay/instrument/index.js","../src/features/session_replay/shared/recorder-events.js","../src/features/session_replay/shared/recorder.js","../src/features/session_replay/shared/stylesheet-evaluator.js","../src/features/session_replay/shared/utils.js","../src/features/session_trace/constants.js","../src/features/session_trace/index.js","../src/features/session_trace/aggregate/index.js","../src/features/session_trace/aggregate/trace/node.js","../src/features/session_trace/aggregate/trace/storage.js","../src/features/session_trace/aggregate/trace/utils.js","../src/features/session_trace/instrument/index.js","../src/features/soft_navigations/constants.js","../src/features/soft_navigations/index.js","../src/features/soft_navigations/aggregate/ajax-node.js","../src/features/soft_navigations/aggregate/bel-node.js","../src/features/soft_navigations/aggregate/index.js","../src/features/soft_navigations/aggregate/initial-page-load-interaction.js","../src/features/soft_navigations/aggregate/interaction.js","../src/features/soft_navigations/instrument/index.js","../src/features/spa/constants.js","../src/features/spa/index.js","../src/features/spa/aggregate/index.js","../src/features/spa/aggregate/interaction-node.js","../src/features/spa/aggregate/interaction.js","../src/features/spa/aggregate/serializer.js","../src/features/spa/instrument/index.js","../src/features/utils/agent-session.js","../src/features/utils/aggregate-base.js","../src/features/utils/event-buffer.js","../src/features/utils/feature-base.js","../src/features/utils/feature-gates.js","../src/features/utils/instrument-base.js","../src/features/utils/nr1-debugger.js","../src/interfaces/registered-entity.js","../src/loaders/agent-base.js","../src/loaders/agent.js","../src/loaders/api-base.js","../src/loaders/browser-agent.js","../src/loaders/micro-agent-base.js","../src/loaders/micro-agent.js","../src/loaders/api/addPageAction.js","../src/loaders/api/addRelease.js","../src/loaders/api/addToTrace.js","../src/loaders/api/consent.js","../src/loaders/api/constants.js","../src/loaders/api/finished.js","../src/loaders/api/interaction-types.js","../src/loaders/api/interaction.js","../src/loaders/api/log.js","../src/loaders/api/measure.js","../src/loaders/api/noticeError.js","../src/loaders/api/pauseReplay.js","../src/loaders/api/recordCustomEvent.js","../src/loaders/api/recordReplay.js","../src/loaders/api/register-api-types.js","../src/loaders/api/register.js","../src/loaders/api/setApplicationVersion.js","../src/loaders/api/setCustomAttribute.js","../src/loaders/api/setErrorHandler.js","../src/loaders/api/setPageViewName.js","../src/loaders/api/setUserId.js","../src/loaders/api/sharedHandlers.js","../src/loaders/api/start.js","../src/loaders/api/topLevelCallers.js","../src/loaders/api/wrapLogger.js","../src/loaders/configure/configure.js","../src/loaders/configure/nonce.js","../src/loaders/configure/public-path.js","../src/loaders/features/enabled-features.js","../src/loaders/features/featureDependencies.js","../src/loaders/features/features.js"],"version":"5.9.3"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../../src/common/constants/runtime.js"],"names":[],"mappings":"AAcA;;GAEG;AACH,qCAEqB;AAErB;;GAEG;AACH,oCAeK;AAEL,oDAUI;AAEJ,oDAA6F;AAE7F,sCAA2F;AAE3F,qCAAyD;AAEzD,4BAA8E;AAE9E;;;;;;GAMG;AACH,iCAAwE;AAExE,+BAOI;AAEJ;;;;GAIG;AACH,yBAFU,MAAM,CAE4B"}
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../../src/common/constants/runtime.js"],"names":[],"mappings":"AAcA;;GAEG;AACH,qCAEqB;AAErB;;GAEG;AACH,oCAeK;AAEL,oDAUI;AAEJ,oDAA6F;AAE7F,sCAA2F;AAE3F,qCAAyD;AAEzD,4BAA8E;AAE9E;;;;;;GAMG;AACH,iCAAwE;AAExE,+BAOI;AAEJ;;;;GAIG;AACH,yBAFU,MAAM,CAE4B;AAErC,+CAA4J"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nav-timing.d.ts","sourceRoot":"","sources":["../../../../src/common/timing/nav-timing.js"],"names":[],"mappings":"AAwCA,uFAyBC;AAGD,
|
|
1
|
+
{"version":3,"file":"nav-timing.d.ts","sourceRoot":"","sources":["../../../../src/common/timing/nav-timing.js"],"names":[],"mappings":"AAwCA,uFAyBC;AAGD,4CASC;AAlDD,oCAAiC"}
|
|
@@ -14,10 +14,7 @@ export function hasValidValue(val: any): boolean;
|
|
|
14
14
|
*
|
|
15
15
|
* @param {Object} [target] the registered target
|
|
16
16
|
* @param {AggregateInstance} [aggregateInstance] the aggregate instance calling the method
|
|
17
|
-
* @returns {
|
|
17
|
+
* @returns {Object} returns an empty object if args are not supplied or the aggregate instance is not supporting version 2
|
|
18
18
|
*/
|
|
19
|
-
export function getVersion2Attributes(target?: Object, aggregateInstance?: AggregateInstance):
|
|
20
|
-
"mfe.id": any;
|
|
21
|
-
"mfe.name": string;
|
|
22
|
-
} | {};
|
|
19
|
+
export function getVersion2Attributes(target?: Object, aggregateInstance?: AggregateInstance): Object;
|
|
23
20
|
//# sourceMappingURL=mfe.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mfe.d.ts","sourceRoot":"","sources":["../../../../src/common/util/mfe.js"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,0CAHW,MAAM,GACJ,OAAO,CAInB;AAED,iDAEC;AAED;;;;;;;GAOG;AACH,+CAJW,MAAM,sBACN,iBAAiB,GACf
|
|
1
|
+
{"version":3,"file":"mfe.d.ts","sourceRoot":"","sources":["../../../../src/common/util/mfe.js"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,0CAHW,MAAM,GACJ,OAAO,CAInB;AAED,iDAEC;AAED;;;;;;;GAOG;AACH,+CAJW,MAAM,sBACN,iBAAiB,GACf,MAAM,CAiBlB"}
|
|
@@ -3,6 +3,7 @@ export namespace VITAL_NAMES {
|
|
|
3
3
|
let FIRST_CONTENTFUL_PAINT: string;
|
|
4
4
|
let FIRST_INTERACTION: string;
|
|
5
5
|
let LARGEST_CONTENTFUL_PAINT: string;
|
|
6
|
+
let LOAD_TIME: string;
|
|
6
7
|
let CUMULATIVE_LAYOUT_SHIFT: string;
|
|
7
8
|
let INTERACTION_TO_NEXT_PAINT: string;
|
|
8
9
|
let TIME_TO_FIRST_BYTE: string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-time.d.ts","sourceRoot":"","sources":["../../../../src/common/vitals/load-time.js"],"names":[],"mappings":"AASA,mCAA8D;4BAFlC,gBAAgB"}
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
export function checkState(): boolean;
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Executes a callback when the window 'load' event fires, or immediately if the load event has already occurred.
|
|
4
|
+
* Sets up a backup polling mechanism in the rare case that the browser does not fire the load event when the page has loaded,
|
|
5
|
+
* such as in certain iframe scenarios like ChatGPT connector frames. Cannot use document.readystatechange event here because
|
|
6
|
+
* it blocks back/forward cache in Safari browsers.
|
|
7
|
+
* @param {Function} cb
|
|
8
|
+
* @param {boolean} [useCapture]
|
|
9
|
+
* @returns {void}
|
|
10
|
+
*/
|
|
11
|
+
export function onWindowLoad(cb: Function, useCapture?: boolean): void;
|
|
3
12
|
export function onDOMContentLoaded(cb: any): any;
|
|
4
13
|
export function onPopstateChange(cb: any): any;
|
|
5
14
|
//# sourceMappingURL=load.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load.d.ts","sourceRoot":"","sources":["../../../../src/common/window/load.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"load.d.ts","sourceRoot":"","sources":["../../../../src/common/window/load.js"],"names":[],"mappings":"AAOA,sCAEC;AAED;;;;;;;;GAQG;AACH,wDAHW,OAAO,GACL,IAAI,CAYhB;AAED,iDAGC;AAED,+CAGC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_event/aggregate/index.js"],"names":[],"mappings":"AAuBA;IACE,2BAA2C;IAE3C,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_event/aggregate/index.js"],"names":[],"mappings":"AAuBA;IACE,2BAA2C;IAE3C,2BA4BC;IAzBC,iBAAoB;IAEpB,wBAAwB;IACxB,8BAA8B;IAC9B,8BAA8B;IAC9B,gBAAgB;IAsBlB;;;;OAIG;IACH,2BAFW,GAAC,QAoEX;IAbC;;;;;;;;;;;;;;mBAOC;IANC,iCAAyB;IAc7B,kCAEC;IAED;;;;;;aAiGC;CACF;8BA3N6B,4BAA4B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_timing/aggregate/index.js"],"names":[],"mappings":"AAsBA;IACE,2BAAiC;IAMjC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_timing/aggregate/index.js"],"names":[],"mappings":"AAsBA;IACE,2BAAiC;IAMjC,2BA4BC;IA1BC,4BAA+B;IAC/B,0BAA6B;IA2B/B;;;OAGG;IACH,6BAFW,MAAM,QAOhB;IAED;;;;MA6BC;IAED;;;OAGG;IACH,4BAFa,IAAI,CAahB;IAED,gDAUC;IAED,4BAGC;IAGD,qCAwBC;;CACF;8BAvJ6B,4BAA4B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/index.js"],"names":[],"mappings":"AAeA;IACE,2BAAiC;IACjC;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/index.js"],"names":[],"mappings":"AAeA;IACE,2BAAiC;IACjC;;OA+DC;IA1DC,2BAAwC;IACxC,iBAA8B;IAE9B,uDAA0E;IAc1E,yBAA+B;IAC/B,0CAAiC;IACjC,yBAA4C;IAyC9C,qCAUC;IAED,0EAmBC;IAED,2BAiBC;IAED;;;;;;;OAOG;IACH,6BAHW,mBAAmB,OAsB7B;;CAwGF;8BAhQ6B,4BAA4B;2CAGf,iCAAiC;4BAChD,eAAe"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/spa/instrument/index.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/spa/instrument/index.js"],"names":[],"mappings":"AAwBA;;GAEG;AACH;IACE,2BAAiC;IACjC,2BA+FC;IAtFG,2CAA0C;;CA6F/C;AAED,oCAA6B;+BAhIE,6BAA6B"}
|
package/package.json
CHANGED
|
@@ -83,3 +83,5 @@ export const ffVersion = (() => {
|
|
|
83
83
|
* @type {number}
|
|
84
84
|
*/
|
|
85
85
|
export const originTime = Date.now() - now()
|
|
86
|
+
|
|
87
|
+
export const supportsNavTimingL2 = () => typeof PerformanceNavigationTiming !== 'undefined' && globalScope?.performance?.getEntriesByType('navigation')?.length > 0
|
|
@@ -67,8 +67,13 @@ export function addPT (offset, pt, v = {}, isL1Api = false) {
|
|
|
67
67
|
|
|
68
68
|
// Add Performance Navigation values to the given object
|
|
69
69
|
export function addPN (pn, v) {
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
try {
|
|
71
|
+
handleValue(getPntType(pn.type), v, 'ty')
|
|
72
|
+
handleValue(pn.redirectCount, v, 'rc')
|
|
73
|
+
} catch (e) {
|
|
74
|
+
v.ty = 0
|
|
75
|
+
v.rc = 0
|
|
76
|
+
}
|
|
72
77
|
return v
|
|
73
78
|
}
|
|
74
79
|
|
package/src/common/util/mfe.js
CHANGED
|
@@ -21,7 +21,7 @@ export function hasValidValue (val) {
|
|
|
21
21
|
*
|
|
22
22
|
* @param {Object} [target] the registered target
|
|
23
23
|
* @param {AggregateInstance} [aggregateInstance] the aggregate instance calling the method
|
|
24
|
-
* @returns {
|
|
24
|
+
* @returns {Object} returns an empty object if args are not supplied or the aggregate instance is not supporting version 2
|
|
25
25
|
*/
|
|
26
26
|
export function getVersion2Attributes (target, aggregateInstance) {
|
|
27
27
|
if (aggregateInstance?.harvestEndpointVersion !== 2) return {}
|
|
@@ -33,9 +33,9 @@ export function getVersion2Attributes (target, aggregateInstance) {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
return {
|
|
36
|
-
'
|
|
37
|
-
'
|
|
38
|
-
|
|
36
|
+
'source.id': target.id,
|
|
37
|
+
'source.name': target.name,
|
|
38
|
+
'source.type': target.type,
|
|
39
39
|
'parent.id': target.parent?.id || containerAgentEntityGuid
|
|
40
40
|
}
|
|
41
41
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
import { globalScope, isBrowserScope, originTime, supportsNavTimingL2 } from '../constants/runtime'
|
|
6
|
+
import { onWindowLoad } from '../window/load'
|
|
7
|
+
import { VITAL_NAMES } from './constants'
|
|
8
|
+
import { VitalMetric } from './vital-metric'
|
|
9
|
+
|
|
10
|
+
export const loadTime = new VitalMetric(VITAL_NAMES.LOAD_TIME)
|
|
11
|
+
|
|
12
|
+
if (isBrowserScope) {
|
|
13
|
+
const perf = globalScope.performance
|
|
14
|
+
const handler = () => {
|
|
15
|
+
if (!loadTime.isValid && perf) {
|
|
16
|
+
loadTime.update({
|
|
17
|
+
value: supportsNavTimingL2() ? perf.getEntriesByType('navigation')?.[0]?.loadEventEnd : perf.timing?.loadEventEnd - originTime
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
onWindowLoad(handler, true)
|
|
23
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
|
-
import { globalScope, isBrowserScope, isiOS, originTime } from '../constants/runtime'
|
|
5
|
+
import { globalScope, isBrowserScope, isiOS, originTime, supportsNavTimingL2 } from '../constants/runtime'
|
|
6
6
|
import { VITAL_NAMES } from './constants'
|
|
7
7
|
import { VitalMetric } from './vital-metric'
|
|
8
8
|
import { onTTFB } from 'web-vitals/attribution'
|
|
@@ -16,7 +16,7 @@ export const timeToFirstByte = new VitalMetric(VITAL_NAMES.TIME_TO_FIRST_BYTE)
|
|
|
16
16
|
* - in an iOS browser
|
|
17
17
|
* - cross-origin iframes specifically in firefox and safari
|
|
18
18
|
*/
|
|
19
|
-
if (isBrowserScope &&
|
|
19
|
+
if (isBrowserScope && supportsNavTimingL2() && !isiOS && window === window.parent) {
|
|
20
20
|
onTTFB(({ value, attribution }) => {
|
|
21
21
|
if (timeToFirstByte.isValid) return
|
|
22
22
|
timeToFirstByte.update({ value, attrs: { navigationEntry: attribution.navigationEntry } })
|
|
@@ -3,14 +3,31 @@
|
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
import { windowAddEventListener, documentAddEventListener } from '../event-listener/event-listener-opts'
|
|
6
|
+
import { single } from '../util/invoke'
|
|
6
7
|
|
|
7
8
|
export function checkState () {
|
|
8
9
|
return (typeof document === 'undefined' || document.readyState === 'complete')
|
|
9
10
|
}
|
|
10
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Executes a callback when the window 'load' event fires, or immediately if the load event has already occurred.
|
|
14
|
+
* Sets up a backup polling mechanism in the rare case that the browser does not fire the load event when the page has loaded,
|
|
15
|
+
* such as in certain iframe scenarios like ChatGPT connector frames. Cannot use document.readystatechange event here because
|
|
16
|
+
* it blocks back/forward cache in Safari browsers.
|
|
17
|
+
* @param {Function} cb
|
|
18
|
+
* @param {boolean} [useCapture]
|
|
19
|
+
* @returns {void}
|
|
20
|
+
*/
|
|
11
21
|
export function onWindowLoad (cb, useCapture) {
|
|
12
22
|
if (checkState()) return cb()
|
|
13
|
-
|
|
23
|
+
const singleCb = single(cb)
|
|
24
|
+
const poll = setInterval(() => {
|
|
25
|
+
if (checkState()) {
|
|
26
|
+
clearInterval(poll)
|
|
27
|
+
singleCb()
|
|
28
|
+
}
|
|
29
|
+
}, 500)
|
|
30
|
+
windowAddEventListener('load', singleCb, useCapture)
|
|
14
31
|
}
|
|
15
32
|
|
|
16
33
|
export function onDOMContentLoaded (cb) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
|
-
import { globalScope, isBrowserScope, originTime } from '../../../common/constants/runtime'
|
|
5
|
+
import { globalScope, isBrowserScope, originTime, supportsNavTimingL2 } from '../../../common/constants/runtime'
|
|
6
6
|
import { addPT, addPN } from '../../../common/timing/nav-timing'
|
|
7
7
|
import { stringify } from '../../../common/util/stringify'
|
|
8
8
|
import { isValid } from '../../../common/config/info'
|
|
@@ -46,9 +46,8 @@ export class Aggregate extends AggregateBase {
|
|
|
46
46
|
this.timeToFirstByte = Math.max(value, this.timeToFirstByte)
|
|
47
47
|
this.firstByteToWindowLoad = Math.max(Math.round(navEntry.loadEventEnd - this.timeToFirstByte), this.firstByteToWindowLoad) // our "frontend" duration
|
|
48
48
|
this.firstByteToDomContent = Math.max(Math.round(navEntry.domContentLoadedEventEnd - this.timeToFirstByte), this.firstByteToDomContent) // our "dom processing" duration
|
|
49
|
-
|
|
50
|
-
this.sendRum()
|
|
51
49
|
})
|
|
50
|
+
setTimeout(this.sendRum.bind(this), 0) // we want to sendRum after ttfb has reported something, but we dont want to wait forever incase TTFB fails to report in niche environments.
|
|
52
51
|
} else {
|
|
53
52
|
// worker agent build does not get TTFB values, use default 0 values
|
|
54
53
|
this.sendRum()
|
|
@@ -94,14 +93,14 @@ export class Aggregate extends AggregateBase {
|
|
|
94
93
|
}
|
|
95
94
|
|
|
96
95
|
if (globalScope.performance) {
|
|
97
|
-
if (
|
|
96
|
+
if (supportsNavTimingL2()) { // Navigation Timing level 2 API that replaced PerformanceTiming & PerformanceNavigation
|
|
98
97
|
const navTimingEntry = globalScope?.performance?.getEntriesByType('navigation')?.[0]
|
|
99
98
|
const perf = ({
|
|
100
99
|
timing: addPT(originTime, navTimingEntry, {}),
|
|
101
100
|
navigation: addPN(navTimingEntry, {})
|
|
102
101
|
})
|
|
103
102
|
queryParameters.perf = stringify(perf)
|
|
104
|
-
} else if (typeof PerformanceTiming !== 'undefined') { // Safari pre-15
|
|
103
|
+
} else if (typeof PerformanceTiming !== 'undefined') { // Modern Safari iFrames and Safari pre-15 do not support level 2 timing.
|
|
105
104
|
const perf = ({
|
|
106
105
|
timing: addPT(originTime, globalScope.performance.timing, {}, true),
|
|
107
106
|
navigation: addPN(globalScope.performance.navigation, {})
|
|
@@ -14,11 +14,11 @@ import { firstContentfulPaint } from '../../../common/vitals/first-contentful-pa
|
|
|
14
14
|
import { firstPaint } from '../../../common/vitals/first-paint'
|
|
15
15
|
import { interactionToNextPaint } from '../../../common/vitals/interaction-to-next-paint'
|
|
16
16
|
import { largestContentfulPaint } from '../../../common/vitals/largest-contentful-paint'
|
|
17
|
-
import { timeToFirstByte } from '../../../common/vitals/time-to-first-byte'
|
|
18
17
|
import { subscribeToVisibilityChange } from '../../../common/window/page-visibility'
|
|
19
18
|
import { VITAL_NAMES } from '../../../common/vitals/constants'
|
|
20
19
|
import { initiallyHidden } from '../../../common/constants/runtime'
|
|
21
20
|
import { eventOrigin } from '../../../common/util/event-origin'
|
|
21
|
+
import { loadTime } from '../../../common/vitals/load-time'
|
|
22
22
|
|
|
23
23
|
export class Aggregate extends AggregateBase {
|
|
24
24
|
static featureName = FEATURE_NAME
|
|
@@ -43,9 +43,7 @@ export class Aggregate extends AggregateBase {
|
|
|
43
43
|
firstContentfulPaint.subscribe(this.#handleVitalMetric)
|
|
44
44
|
largestContentfulPaint.subscribe(this.#handleVitalMetric)
|
|
45
45
|
interactionToNextPaint.subscribe(this.#handleVitalMetric)
|
|
46
|
-
|
|
47
|
-
this.addTiming('load', Math.round(attrs.navigationEntry.loadEventEnd))
|
|
48
|
-
})
|
|
46
|
+
loadTime.subscribe(({ name, value }) => { this.addTiming(name, Math.round(value)) })
|
|
49
47
|
subscribeToVisibilityChange(() => {
|
|
50
48
|
/* 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.
|
|
51
49
|
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
|
|
@@ -7,7 +7,7 @@ import { FEATURE_NAME } from '../constants'
|
|
|
7
7
|
import { AggregateBase } from '../../utils/aggregate-base'
|
|
8
8
|
import { TraceStorage } from './trace/storage'
|
|
9
9
|
import { obj as encodeObj } from '../../../common/url/encode'
|
|
10
|
-
import { globalScope } from '../../../common/constants/runtime'
|
|
10
|
+
import { globalScope, supportsNavTimingL2 } from '../../../common/constants/runtime'
|
|
11
11
|
import { MODE, SESSION_EVENTS } from '../../../common/session/constants'
|
|
12
12
|
import { applyFnToProps } from '../../../common/util/traverse'
|
|
13
13
|
import { cleanURL } from '../../../common/url/clean-url'
|
|
@@ -62,7 +62,7 @@ export class Aggregate extends AggregateBase {
|
|
|
62
62
|
if (this.sessionId !== sessionState.value || (eventType === 'cross-tab' && sessionState.sessionTraceMode === MODE.OFF)) this.abort(2)
|
|
63
63
|
})
|
|
64
64
|
|
|
65
|
-
if (
|
|
65
|
+
if (supportsNavTimingL2()) {
|
|
66
66
|
this.traceStorage.storeTiming(globalScope.performance?.getEntriesByType?.('navigation')[0])
|
|
67
67
|
} else {
|
|
68
68
|
this.traceStorage.storeTiming(globalScope.performance?.timing, true)
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { handle } from '../../../common/event-emitter/handle'
|
|
6
6
|
import { registerHandler } from '../../../common/event-emitter/register-handler'
|
|
7
7
|
import { single } from '../../../common/util/invoke'
|
|
8
|
-
import {
|
|
8
|
+
import { loadTime } from '../../../common/vitals/load-time'
|
|
9
9
|
import { FEATURE_NAMES } from '../../../loaders/features/features'
|
|
10
10
|
import { AggregateBase } from '../../utils/aggregate-base'
|
|
11
11
|
import { API_TRIGGER_NAME, FEATURE_NAME, INTERACTION_STATUS, INTERACTION_TRIGGERS, IPL_TRIGGER_NAME, NO_LONG_TASK_WINDOW, POPSTATE_MERGE_WINDOW, POPSTATE_TRIGGER } from '../constants'
|
|
@@ -31,10 +31,9 @@ export class Aggregate extends AggregateBase {
|
|
|
31
31
|
this.events.add(ixn) // add the iPL ixn to the buffer for harvest
|
|
32
32
|
this.initialPageLoadInteraction = null
|
|
33
33
|
})
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
|
|
35
|
+
loadTime.subscribe(({ value: loadEventTime }) => {
|
|
36
36
|
this.initialPageLoadInteraction.done(loadEventTime)
|
|
37
|
-
// Report metric on the initial page load time
|
|
38
37
|
this.reportSupportabilityMetric('SoftNav/Interaction/InitialPageLoad/Duration/Ms', Math.round(loadEventTime))
|
|
39
38
|
})
|
|
40
39
|
|
|
@@ -16,6 +16,7 @@ import { wrapFetch } from '../../../common/wrap/wrap-fetch'
|
|
|
16
16
|
import { wrapHistory } from '../../../common/wrap/wrap-history'
|
|
17
17
|
import { wrapMutation } from '../../../common/wrap/wrap-mutation'
|
|
18
18
|
import { setupInteractionAPI } from '../../../loaders/api/interaction'
|
|
19
|
+
import { onWindowLoad } from '../../../common/window/load'
|
|
19
20
|
|
|
20
21
|
const {
|
|
21
22
|
FEATURE_NAME, START, END, BODY, CB_END, JS_TIME, FETCH, FN_START, CB_START, FN_END
|
|
@@ -81,6 +82,11 @@ export class Instrument extends InstrumentBase {
|
|
|
81
82
|
historyEE.on('pushState-end', trackURLChange)
|
|
82
83
|
historyEE.on('replaceState-end', trackURLChange)
|
|
83
84
|
|
|
85
|
+
/** niche cases like GPT apps cause no window.load event to fire - which breaks IPLs - so manually force one through the pipe */
|
|
86
|
+
onWindowLoad(() => {
|
|
87
|
+
eventsEE.emit(FN_START, [[{ type: 'load' }], window], undefined, true)
|
|
88
|
+
})
|
|
89
|
+
|
|
84
90
|
window.addEventListener('hashchange', trackURLChange, eventListenerOpts(true, this.removeOnAbort?.signal))
|
|
85
91
|
window.addEventListener('load', trackURLChange, eventListenerOpts(true, this.removeOnAbort?.signal))
|
|
86
92
|
window.addEventListener('popstate', function () {
|
|
@@ -45,7 +45,7 @@ function register (agentRef, target, parent) {
|
|
|
45
45
|
warn(54, 'newrelic.register')
|
|
46
46
|
|
|
47
47
|
target ||= {}
|
|
48
|
-
target.
|
|
48
|
+
target.type = 'MFE'
|
|
49
49
|
target.licenseKey ||= agentRef.info.licenseKey // will inherit the license key from the container agent if not provided for brevity. A future state may dictate that we need different license keys to do different things.
|
|
50
50
|
target.blocked = false
|
|
51
51
|
target.parent = parent || {}
|