@newrelic/browser-agent 1.309.0 → 1.310.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.
Files changed (133) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +0 -2
  3. package/dist/cjs/common/config/init-types.js +1 -4
  4. package/dist/cjs/common/config/init.js +1 -5
  5. package/dist/cjs/common/constants/env.cdn.js +1 -1
  6. package/dist/cjs/common/constants/env.npm.js +1 -1
  7. package/dist/cjs/common/util/browser-stack-matchers.js +15 -0
  8. package/dist/cjs/common/util/script-tracker.js +184 -0
  9. package/dist/cjs/common/wrap/wrap-fetch.js +2 -2
  10. package/dist/cjs/common/wrap/wrap-history.js +2 -2
  11. package/dist/cjs/common/wrap/wrap-logger.js +2 -2
  12. package/dist/cjs/common/wrap/wrap-xhr.js +2 -2
  13. package/dist/cjs/features/jserrors/aggregate/compute-stack-trace.js +6 -10
  14. package/dist/cjs/features/page_view_event/aggregate/initialized-features.js +1 -2
  15. package/dist/cjs/index.js +3 -3
  16. package/dist/cjs/interfaces/registered-entity.js +2 -1
  17. package/dist/cjs/loaders/agent.js +1 -4
  18. package/dist/cjs/loaders/api/register-api-types.js +20 -8
  19. package/dist/cjs/loaders/api/register.js +36 -2
  20. package/dist/cjs/loaders/api-base.js +1 -1
  21. package/dist/cjs/loaders/features/features.js +6 -9
  22. package/dist/esm/common/config/init-types.js +1 -4
  23. package/dist/esm/common/config/init.js +1 -5
  24. package/dist/esm/common/constants/env.cdn.js +1 -1
  25. package/dist/esm/common/constants/env.npm.js +1 -1
  26. package/dist/esm/common/util/browser-stack-matchers.js +9 -0
  27. package/dist/esm/common/util/script-tracker.js +179 -0
  28. package/dist/esm/common/wrap/wrap-fetch.js +2 -2
  29. package/dist/esm/common/wrap/wrap-history.js +2 -2
  30. package/dist/esm/common/wrap/wrap-logger.js +2 -2
  31. package/dist/esm/common/wrap/wrap-xhr.js +2 -2
  32. package/dist/esm/features/jserrors/aggregate/compute-stack-trace.js +2 -6
  33. package/dist/esm/features/page_view_event/aggregate/initialized-features.js +1 -2
  34. package/dist/esm/index.js +2 -2
  35. package/dist/esm/interfaces/registered-entity.js +2 -1
  36. package/dist/esm/loaders/agent.js +1 -4
  37. package/dist/esm/loaders/api/register-api-types.js +22 -8
  38. package/dist/esm/loaders/api/register.js +36 -2
  39. package/dist/esm/loaders/api-base.js +1 -1
  40. package/dist/esm/loaders/features/features.js +6 -9
  41. package/dist/tsconfig.tsbuildinfo +1 -1
  42. package/dist/types/common/config/init-types.d.ts +0 -10
  43. package/dist/types/common/config/init.d.ts.map +1 -1
  44. package/dist/types/common/util/browser-stack-matchers.d.ts +10 -0
  45. package/dist/types/common/util/browser-stack-matchers.d.ts.map +1 -0
  46. package/dist/types/common/util/script-tracker.d.ts +7 -0
  47. package/dist/types/common/util/script-tracker.d.ts.map +1 -0
  48. package/dist/types/features/jserrors/aggregate/compute-stack-trace.d.ts.map +1 -1
  49. package/dist/types/features/page_view_event/aggregate/initialized-features.d.ts.map +1 -1
  50. package/dist/types/index.d.ts +1 -1
  51. package/dist/types/interfaces/registered-entity.d.ts +4 -5
  52. package/dist/types/interfaces/registered-entity.d.ts.map +1 -1
  53. package/dist/types/loaders/agent.d.ts.map +1 -1
  54. package/dist/types/loaders/api/register-api-types.d.ts +58 -14
  55. package/dist/types/loaders/api/register-api-types.d.ts.map +1 -1
  56. package/dist/types/loaders/api/register.d.ts.map +1 -1
  57. package/dist/types/loaders/api-base.d.ts +4 -5
  58. package/dist/types/loaders/api-base.d.ts.map +1 -1
  59. package/dist/types/loaders/features/features.d.ts +2 -5
  60. package/dist/types/loaders/features/features.d.ts.map +1 -1
  61. package/package.json +1 -9
  62. package/src/common/config/init-types.js +1 -4
  63. package/src/common/config/init.js +1 -2
  64. package/src/common/util/browser-stack-matchers.js +9 -0
  65. package/src/common/util/script-tracker.js +171 -0
  66. package/src/common/wrap/wrap-fetch.js +2 -2
  67. package/src/common/wrap/wrap-history.js +2 -2
  68. package/src/common/wrap/wrap-logger.js +2 -2
  69. package/src/common/wrap/wrap-xhr.js +2 -2
  70. package/src/features/jserrors/aggregate/compute-stack-trace.js +2 -7
  71. package/src/features/page_view_event/aggregate/initialized-features.js +1 -2
  72. package/src/index.js +2 -2
  73. package/src/interfaces/registered-entity.js +2 -1
  74. package/src/loaders/agent.js +0 -4
  75. package/src/loaders/api/register-api-types.js +22 -8
  76. package/src/loaders/api/register.js +31 -2
  77. package/src/loaders/api-base.js +1 -1
  78. package/src/loaders/features/features.js +6 -9
  79. package/dist/cjs/common/wrap/wrap-jsonp.js +0 -137
  80. package/dist/cjs/common/wrap/wrap-mutation.js +0 -62
  81. package/dist/cjs/common/wrap/wrap-promise.js +0 -166
  82. package/dist/cjs/common/wrap/wrap-timer.js +0 -71
  83. package/dist/cjs/features/spa/aggregate/index.js +0 -672
  84. package/dist/cjs/features/spa/aggregate/interaction-node.js +0 -83
  85. package/dist/cjs/features/spa/aggregate/interaction.js +0 -95
  86. package/dist/cjs/features/spa/aggregate/serializer.js +0 -150
  87. package/dist/cjs/features/spa/constants.js +0 -35
  88. package/dist/cjs/features/spa/index.js +0 -12
  89. package/dist/cjs/features/spa/instrument/index.js +0 -136
  90. package/dist/esm/common/wrap/wrap-jsonp.js +0 -130
  91. package/dist/esm/common/wrap/wrap-mutation.js +0 -55
  92. package/dist/esm/common/wrap/wrap-promise.js +0 -159
  93. package/dist/esm/common/wrap/wrap-timer.js +0 -64
  94. package/dist/esm/features/spa/aggregate/index.js +0 -663
  95. package/dist/esm/features/spa/aggregate/interaction-node.js +0 -77
  96. package/dist/esm/features/spa/aggregate/interaction.js +0 -88
  97. package/dist/esm/features/spa/aggregate/serializer.js +0 -142
  98. package/dist/esm/features/spa/constants.js +0 -28
  99. package/dist/esm/features/spa/index.js +0 -5
  100. package/dist/esm/features/spa/instrument/index.js +0 -129
  101. package/dist/types/common/wrap/wrap-jsonp.d.ts +0 -16
  102. package/dist/types/common/wrap/wrap-jsonp.d.ts.map +0 -1
  103. package/dist/types/common/wrap/wrap-mutation.d.ts +0 -16
  104. package/dist/types/common/wrap/wrap-mutation.d.ts.map +0 -1
  105. package/dist/types/common/wrap/wrap-promise.d.ts +0 -17
  106. package/dist/types/common/wrap/wrap-promise.d.ts.map +0 -1
  107. package/dist/types/common/wrap/wrap-timer.d.ts +0 -17
  108. package/dist/types/common/wrap/wrap-timer.d.ts.map +0 -1
  109. package/dist/types/features/spa/aggregate/index.d.ts +0 -24
  110. package/dist/types/features/spa/aggregate/index.d.ts.map +0 -1
  111. package/dist/types/features/spa/aggregate/interaction-node.d.ts +0 -15
  112. package/dist/types/features/spa/aggregate/interaction-node.d.ts.map +0 -1
  113. package/dist/types/features/spa/aggregate/interaction.d.ts +0 -19
  114. package/dist/types/features/spa/aggregate/interaction.d.ts.map +0 -1
  115. package/dist/types/features/spa/aggregate/serializer.d.ts +0 -17
  116. package/dist/types/features/spa/aggregate/serializer.d.ts.map +0 -1
  117. package/dist/types/features/spa/constants.d.ts +0 -23
  118. package/dist/types/features/spa/constants.d.ts.map +0 -1
  119. package/dist/types/features/spa/index.d.ts +0 -2
  120. package/dist/types/features/spa/index.d.ts.map +0 -1
  121. package/dist/types/features/spa/instrument/index.d.ts +0 -12
  122. package/dist/types/features/spa/instrument/index.d.ts.map +0 -1
  123. package/src/common/wrap/wrap-jsonp.js +0 -142
  124. package/src/common/wrap/wrap-mutation.js +0 -58
  125. package/src/common/wrap/wrap-promise.js +0 -176
  126. package/src/common/wrap/wrap-timer.js +0 -70
  127. package/src/features/spa/aggregate/index.js +0 -734
  128. package/src/features/spa/aggregate/interaction-node.js +0 -85
  129. package/src/features/spa/aggregate/interaction.js +0 -108
  130. package/src/features/spa/aggregate/serializer.js +0 -200
  131. package/src/features/spa/constants.js +0 -39
  132. package/src/features/spa/index.js +0 -5
  133. package/src/features/spa/instrument/index.js +0 -134
package/CHANGELOG.md CHANGED
@@ -3,6 +3,20 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [1.310.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.309.0...v1.310.0) (2026-02-17)
7
+
8
+
9
+ ### Features
10
+
11
+ * better MFE timing handling ([#1692](https://github.com/newrelic/newrelic-browser-agent/issues/1692)) ([92a83e5](https://github.com/newrelic/newrelic-browser-agent/commit/92a83e522e39cd145b751728df8083cc63a5d162))
12
+ * Capture MFE timings as MicroFrontEndTiming Events ([#1676](https://github.com/newrelic/newrelic-browser-agent/issues/1676)) ([6824f1e](https://github.com/newrelic/newrelic-browser-agent/commit/6824f1eaf002d8732ca948f0e5b4d6bd7802d199))
13
+ * Removing old SPA feature & related wrappings entirely ([#1689](https://github.com/newrelic/newrelic-browser-agent/issues/1689)) ([81bfd8f](https://github.com/newrelic/newrelic-browser-agent/commit/81bfd8f686611b6443132d34e31fdb79b95808a1))
14
+
15
+
16
+ ### Bug Fixes
17
+
18
+ * Update measure API types ([#1694](https://github.com/newrelic/newrelic-browser-agent/issues/1694)) ([dd2951b](https://github.com/newrelic/newrelic-browser-agent/commit/dd2951bd4786a96f5d1aea9c062da79d16b603ef))
19
+
6
20
  ## [1.309.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.308.0...v1.309.0) (2026-02-03)
7
21
 
8
22
 
package/README.md CHANGED
@@ -92,7 +92,6 @@ The following features may be disabled by adding `init` entries as shown above.
92
92
  - `session_replay`
93
93
  - `session_trace`
94
94
  - `soft_navigations`
95
- - `spa`
96
95
 
97
96
  ***Individual event types within the `generic_events` feature can also be disabled. See [Disabling Individual Generic Events](#disabling-individual-generic-events)***
98
97
 
@@ -180,7 +179,6 @@ import { PageViewTiming } from '@newrelic/browser-agent/features/page_view_timin
180
179
  import { SessionReplay } from '@newrelic/browser-agent/features/session_replay';
181
180
  import { SessionTrace } from '@newrelic/browser-agent/features/session_trace';
182
181
  import { SoftNav } from '@newrelic/browser-agent/features/soft_navigations';
183
- import { Spa } from '@newrelic/browser-agent/features/spa';
184
182
  ```
185
183
 
186
184
  ### Example 1 - "Page Load Agent"
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = void 0;
7
7
  /**
8
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
8
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
9
9
  * SPDX-License-Identifier: Apache-2.0
10
10
  *
11
11
  */
@@ -85,9 +85,6 @@ exports.default = void 0;
85
85
  * @property {Object} [soft_navigations]
86
86
  * @property {boolean} [soft_navigations.enabled] - Turn on/off the soft navigations feature (on by default).
87
87
  * @property {boolean} [soft_navigations.autoStart] - If true, the agent will automatically start the soft navigations feature. Otherwise, it will be in a deferred state until the `start` API method is called.
88
- * @property {Object} [spa]
89
- * @property {boolean} [spa.enabled] - Turn on/off the single page application feature (on by default). NOTE: the SPA feature is deprecated and under removal procedure.
90
- * @property {boolean} [spa.autoStart] - If true, the agent will automatically start the single page application feature. Otherwise, it will be in a deferred state until the `start` API method is called.
91
88
  * @property {boolean} [ssl] - If explicitly false, the agent will use HTTP instead of HTTPS. This setting should NOT be used.
92
89
  * @property {Object} [browser_consent_mode]
93
90
  * @property {boolean} [browser_consent_mode.enabled] - If true, the agent will use consent mode for whether to allow or disallow data harvest.
@@ -10,7 +10,7 @@ var _constants2 = require("../session/constants");
10
10
  var _console = require("../util/console");
11
11
  var _configurable = require("./configurable");
12
12
  /**
13
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
13
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
14
14
  * SPDX-License-Identifier: Apache-2.0
15
15
  */
16
16
 
@@ -196,10 +196,6 @@ const InitModelFn = () => {
196
196
  enabled: true,
197
197
  autoStart: true
198
198
  },
199
- spa: {
200
- enabled: true,
201
- autoStart: true
202
- },
203
199
  ssl: undefined,
204
200
  user_actions: {
205
201
  enabled: true,
@@ -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.309.0";
20
+ const VERSION = exports.VERSION = "1.310.0";
21
21
 
22
22
  /**
23
23
  * Exposes the build type of the agent
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.RRWEB_PACKAGE_NAME = exports.D
17
17
  /**
18
18
  * Exposes the version of the agent
19
19
  */
20
- const VERSION = exports.VERSION = "1.309.0";
20
+ const VERSION = exports.VERSION = "1.310.0";
21
21
 
22
22
  /**
23
23
  * Exposes the build type of the agent
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.ieEval = exports.gecko = exports.classNameRegex = exports.chromeEval = exports.chrome = void 0;
7
+ /**
8
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
9
+ * SPDX-License-Identifier: Apache-2.0
10
+ */
11
+ const classNameRegex = exports.classNameRegex = /function (.+?)\s*\(/;
12
+ const chromeEval = exports.chromeEval = /^\s*at .+ \(eval at \S+ \((?:(?:file|http|https):[^)]+)?\)(?:, [^:]*:\d+:\d+)?\)$/i;
13
+ const ieEval = exports.ieEval = /^\s*at Function code \(Function code:\d+:\d+\)\s*/i;
14
+ const chrome = exports.chrome = /^\s*at (?:((?:\[object object\])?(?:[^(]*\([^)]*\))*[^()]*(?: \[as \S+\])?) )?\(?((?:file|http|https|chrome-extension):.*?)?:(\d+)(?::(\d+))?\)?\s*$/i;
15
+ const gecko = exports.gecko = /^\s*(?:([^@]*)(?:\(.*?\))?@)?((?:file|http|https|chrome|safari-extension).*?):(\d+)(?::(\d+))?\s*$/i;
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.findScriptTimings = findScriptTimings;
7
+ var _runtime = require("../constants/runtime");
8
+ var _now = require("../timing/now");
9
+ var _cleanUrl = require("../url/clean-url");
10
+ var _browserStackMatchers = require("./browser-stack-matchers");
11
+ /**
12
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
13
+ * SPDX-License-Identifier: Apache-2.0
14
+ */
15
+
16
+ /**
17
+ * @typedef {import('./register-api-types').RegisterAPITimings} RegisterAPITimings
18
+ */
19
+
20
+ /** @type {(entry: PerformanceEntry) => boolean} - A shared function to determine if a performance entry is a valid script or link resource for evaluation */
21
+ const validEntryCriteria = entry => entry.initiatorType === 'script' || entry.initiatorType === 'link' && entry.name.endsWith('.js');
22
+
23
+ /** @type {Set<PerformanceResourceTiming>} - A set of resource timing objects that are "valid" -- see "validEntryCriteria" */
24
+ const scripts = new Set();
25
+ /** @type {Array<{ test: (entry: PerformanceEntry) => boolean, addedAt: number }>} an array of PerformanceObserver subscribers to check for late emissions */
26
+ let poSubscribers = [];
27
+ if (_runtime.globalScope.PerformanceObserver?.supportedEntryTypes.includes('resource')) {
28
+ /** We must track the script assets this way, because the performance buffer can fill up and when it does that
29
+ * it stops accepting new entries (instead of dropping old entries), which means if the register API is called
30
+ * after the buffer fills up we won't be able to get the script timing information from the resource timing API
31
+ */
32
+ const scriptObserver = new PerformanceObserver(list => {
33
+ list.getEntries().forEach(entry => {
34
+ if (validEntryCriteria(entry)) {
35
+ if (scripts.size > 250) scripts.delete(scripts.values().next().value); // keep the set from growing indefinitely, we only need to check recent entries against the stack of the register API caller, so we can drop old entries as new ones come in
36
+ scripts.add(entry);
37
+ const canClear = [];
38
+ poSubscribers.forEach(({
39
+ test,
40
+ addedAt
41
+ }, idx) => {
42
+ if (test(entry) || (0, _now.now)() - addedAt > 10000) canClear.push(idx); // Clear subscribers that have resolved or have been pending for more than 10 seconds
43
+ });
44
+ poSubscribers = poSubscribers.filter((_, idx) => !canClear.includes(idx));
45
+ }
46
+ });
47
+ });
48
+ scriptObserver.observe({
49
+ type: 'resource',
50
+ buffered: true
51
+ });
52
+ }
53
+
54
+ /**
55
+ * Extracts URLs from stack traces using the same logic as compute-stack-trace.js
56
+ * @param {string} stack The error stack trace
57
+ * @returns {string[]} Array of cleaned URLs found in the stack trace
58
+ */
59
+ function extractUrlsFromStack(stack) {
60
+ if (!stack || typeof stack !== 'string') return [];
61
+ const urls = new Set();
62
+ const lines = stack.split('\n');
63
+ for (const line of lines) {
64
+ // Try gecko format first, then chrome
65
+ const parts = line.match(_browserStackMatchers.gecko) || line.match(_browserStackMatchers.chrome);
66
+ if (parts && parts[2]) {
67
+ urls.add((0, _cleanUrl.cleanURL)(parts[2]));
68
+ }
69
+ }
70
+ return [...urls];
71
+ }
72
+
73
+ /**
74
+ * Returns a deep stack trace by temporarily increasing the stack trace limit.
75
+ * @returns {Error.stack | undefined}
76
+ */
77
+ function getDeepStackTrace() {
78
+ let stack;
79
+ try {
80
+ const originalStackLimit = Error.stackTraceLimit;
81
+ Error.stackTraceLimit = 50;
82
+ stack = new Error().stack;
83
+ Error.stackTraceLimit = originalStackLimit;
84
+ } catch (e) {
85
+ stack = new Error().stack;
86
+ }
87
+ return stack;
88
+ }
89
+
90
+ /**
91
+ * Indicates whether the provided URL matches any script preload link tags in the document.
92
+ * @param {string} targetUrl The URL to match against preload tags
93
+ * @returns {boolean} True if a matching preload link is found, false otherwise
94
+ */
95
+ function wasPreloaded(targetUrl) {
96
+ if (!targetUrl || !_runtime.globalScope.document) return false;
97
+ try {
98
+ const linkTags = _runtime.globalScope.document.querySelectorAll('link[rel="preload"][as="script"]');
99
+ for (const link of linkTags) {
100
+ // link.href is resolved to an absolute URL by the browser (even if supplied as relative), so we can match exactly against the cleaned target URL
101
+ if ((0, _cleanUrl.cleanURL)(link.href) === targetUrl) return true;
102
+ }
103
+ } catch (error) {
104
+ // Don't let DOM parsing errors break anything
105
+ }
106
+ return false;
107
+ }
108
+
109
+ /**
110
+ * Uses the stack of the initiator function, returns script timing information if a script can be found with the resource timing API matching the URL found in the stack.
111
+ * @returns {RegisterAPITimings} Object containing script fetch start and end times, and the asset URL if found
112
+ */
113
+ function findScriptTimings() {
114
+ const timings = {
115
+ registeredAt: (0, _now.now)(),
116
+ reportedAt: undefined,
117
+ fetchStart: 0,
118
+ fetchEnd: 0,
119
+ asset: undefined,
120
+ type: 'unknown'
121
+ };
122
+ const stack = getDeepStackTrace();
123
+ if (!stack) return timings;
124
+ const navUrl = _runtime.globalScope.performance?.getEntriesByType('navigation')?.find(entry => entry.initiatorType === 'navigation')?.name || '';
125
+ try {
126
+ const mfeScriptUrl = extractUrlsFromStack(stack).at(-1); // array of URLs from the stack of the register API caller, the MFE script should be at the bottom
127
+ if (!mfeScriptUrl) return timings;
128
+ if (navUrl.includes(mfeScriptUrl)) {
129
+ // this means the stack is indicating that the registration came from an inline script or eval, so we won't find a matching script resource - return early with just the URL
130
+ timings.asset = (0, _cleanUrl.cleanURL)(navUrl);
131
+ timings.type = 'inline';
132
+ return timings;
133
+ }
134
+
135
+ // try to find it in the static list, which updates faster than the PO. This cant be solely trusted, since the buffer can fill up and stop accepting entries,
136
+ // but it can still help in cases where the check is made before the asset is emitted by the performance observer and the buffer is not full. Fallback to checking the PO
137
+ // entries that have been buffered as seen in the PO callback if its not found in the static list.
138
+ const match = performance.getEntriesByType('resource').find(entryMatchesMfe) || [...scripts].find(entryMatchesMfe);
139
+ if (match) {
140
+ setMatchedAttributes(match);
141
+ } else {
142
+ // We didnt find a match with the PO, nor a static lookup... check if its preloaded and if so, set basic fallbacks and try to associate with a later script entry if possible, this can happen if the preload is reported late in the PO observer callback
143
+ if (wasPreloaded(mfeScriptUrl)) {
144
+ timings.asset = mfeScriptUrl;
145
+ timings.type = 'preload';
146
+ // wait for a late PO callback... The timings object can be mutated after the fact since we return a pointer and not a cloned object
147
+ poSubscribers.push({
148
+ addedAt: (0, _now.now)(),
149
+ test: entry => {
150
+ if (entryMatchesMfe(entry)) {
151
+ setMatchedAttributes(entry);
152
+ return true; // return true so that we know to clear this callback from the pending list since we found our match, otherwise it will stay in the list and be called for future entries which is unnecessary after we found our match and can cause performance issues if there are a lot of future entries and pending callbacks
153
+ }
154
+ return false;
155
+ }
156
+ });
157
+ }
158
+ }
159
+
160
+ /**
161
+ * A matcher function to determine if a performance entry corresponds to the MFE script based on URL matching. It checks if either the entry URL ends with the MFE script URL or vice versa, to account for potential differences in how URLs are represented in the stack trace versus the resource timing entries.
162
+ * @param {PerformanceResourceTiming} entry - The resource timing entry to compare to the MFE script
163
+ * @returns {boolean} True if we think the entry matches the MFE script, false otherwise
164
+ */
165
+ function entryMatchesMfe(entry) {
166
+ const entryUrl = (0, _cleanUrl.cleanURL)(entry.name);
167
+ return entryUrl.endsWith(mfeScriptUrl) || mfeScriptUrl.endsWith(entryUrl);
168
+ }
169
+
170
+ /**
171
+ * A helper function to set the matched timing attributes on the timings object based on a performance entry. This is called when we have identified a resource timing entry that we believe corresponds to the MFE script, and we want to extract the fetch start and end times, as well as the asset URL and type.
172
+ * @param {PerformanceResourceTiming} entry - The resource timing entry to base values off of
173
+ */
174
+ function setMatchedAttributes(entry) {
175
+ timings.fetchStart = Math.floor(entry.startTime);
176
+ timings.fetchEnd = Math.floor(entry.responseEnd);
177
+ timings.asset = entry.name;
178
+ timings.type = entry.initiatorType;
179
+ }
180
+ } catch (error) {
181
+ // Don't let stack parsing errors break anything
182
+ }
183
+ return timings;
184
+ }
@@ -8,13 +8,13 @@ exports.wrapFetch = wrapFetch;
8
8
  var _contextualEe = require("../event-emitter/contextual-ee");
9
9
  var _runtime = require("../constants/runtime");
10
10
  /**
11
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
11
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
12
12
  * SPDX-License-Identifier: Apache-2.0
13
13
  */
14
14
 
15
15
  /**
16
16
  * @file Wraps `fetch` and related methods for instrumentation.
17
- * This module is used by: ajax, spa.
17
+ * This module is used by: ajax.
18
18
  */
19
19
 
20
20
  var prefix = 'fetch-';
@@ -9,13 +9,13 @@ var _contextualEe = require("../event-emitter/contextual-ee");
9
9
  var _wrapFunction = require("./wrap-function");
10
10
  var _runtime = require("../constants/runtime");
11
11
  /**
12
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
12
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
13
13
  * SPDX-License-Identifier: Apache-2.0
14
14
  */
15
15
 
16
16
  /**
17
17
  * @file Wraps `pushState` and `replaceState` methods of `window.history` object for instrumentation.
18
- * This module is used by: session_trace, spa.
18
+ * This module is used by: session_trace, soft_navigations.
19
19
  */
20
20
 
21
21
  const wrapped = {};
@@ -10,13 +10,13 @@ var _eventContext = require("../event-emitter/event-context");
10
10
  var _console = require("../util/console");
11
11
  var _wrapFunction = require("./wrap-function");
12
12
  /**
13
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
13
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
14
14
  * SPDX-License-Identifier: Apache-2.0
15
15
  */
16
16
 
17
17
  /**
18
18
  * @file Wraps native timeout and interval methods for instrumentation.
19
- * This module is used by: jserrors, spa.
19
+ * This module is used by: logging.
20
20
  */
21
21
 
22
22
  const contextMap = new Map();
@@ -12,13 +12,13 @@ var _wrapFunction = require("./wrap-function");
12
12
  var _runtime = require("../constants/runtime");
13
13
  var _console = require("../util/console");
14
14
  /**
15
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
15
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
16
16
  * SPDX-License-Identifier: Apache-2.0
17
17
  */
18
18
 
19
19
  /**
20
20
  * @file Wraps AJAX (XHR) related methods for instrumentation.
21
- * This module is used by: ajax, jserrors, spa.
21
+ * This module is used by: ajax, jserrors.
22
22
  */
23
23
 
24
24
  const wrapped = {};
@@ -6,8 +6,9 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.computeStackTrace = computeStackTrace;
7
7
  var _formatStackTrace = require("./format-stack-trace");
8
8
  var _canonicalizeUrl = require("../../../common/url/canonicalize-url");
9
+ var _browserStackMatchers = require("../../../common/util/browser-stack-matchers");
9
10
  /**
10
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
11
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
11
12
  * SPDX-License-Identifier: Apache-2.0
12
13
  */
13
14
 
@@ -66,11 +67,6 @@ var _canonicalizeUrl = require("../../../common/url/canonicalize-url");
66
67
  // ex.name = ReferenceError
67
68
 
68
69
  var debug = false;
69
- var classNameRegex = /function (.+?)\s*\(/;
70
- var chrome = /^\s*at (?:((?:\[object object\])?(?:[^(]*\([^)]*\))*[^()]*(?: \[as \S+\])?) )?\(?((?:file|http|https|chrome-extension):.*?)?:(\d+)(?::(\d+))?\)?\s*$/i;
71
- var gecko = /^\s*(?:(\S*|global code)(?:\(.*?\))?@)?((?:file|http|https|chrome|safari-extension).*?):(\d+)(?::(\d+))?\s*$/i;
72
- var chromeEval = /^\s*at .+ \(eval at \S+ \((?:(?:file|http|https):[^)]+)?\)(?:, [^:]*:\d+:\d+)?\)$/i;
73
- var ieEval = /^\s*at Function code \(Function code:\d+:\d+\)\s*/i;
74
70
 
75
71
  /**
76
72
  * Represents an error with a stack trace.
@@ -194,8 +190,8 @@ function parseStackProp(info, line) {
194
190
  * name, line number, and column number (if available).
195
191
  */
196
192
  function getStackElement(line) {
197
- var parts = line.match(gecko);
198
- if (!parts) parts = line.match(chrome);
193
+ var parts = line.match(_browserStackMatchers.gecko);
194
+ if (!parts) parts = line.match(_browserStackMatchers.chrome);
199
195
  if (parts) {
200
196
  return {
201
197
  url: parts[2],
@@ -204,7 +200,7 @@ function getStackElement(line) {
204
200
  column: parts[4] ? +parts[4] : null
205
201
  };
206
202
  }
207
- if (line.match(chromeEval) || line.match(ieEval) || line === 'anonymous') {
203
+ if (line.match(_browserStackMatchers.chromeEval) || line.match(_browserStackMatchers.ieEval) || line === 'anonymous') {
208
204
  return {
209
205
  func: 'evaluated code'
210
206
  };
@@ -283,7 +279,7 @@ function computeStackTraceWithMessageOnly(ex) {
283
279
  * @returns {string} The name of the constructor function, or 'unknown' if the name cannot be determined.
284
280
  */
285
281
  function getClassName(obj) {
286
- var results = classNameRegex.exec(String(obj.constructor));
282
+ var results = _browserStackMatchers.classNameRegex.exec(String(obj.constructor));
287
283
  return results && results.length > 1 ? results[1] : 'unknown';
288
284
  }
289
285
 
@@ -7,7 +7,7 @@ exports.getActivatedFeaturesFlags = getActivatedFeaturesFlags;
7
7
  var _features = require("../../../loaders/features/features");
8
8
  var _nreum = require("../../../common/window/nreum");
9
9
  /**
10
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
10
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
11
11
  * SPDX-License-Identifier: Apache-2.0
12
12
  */
13
13
 
@@ -36,7 +36,6 @@ function getActivatedFeaturesFlags(agentId) {
36
36
  flagArr.push('stn');
37
37
  break;
38
38
  case _features.FEATURE_NAMES.softNav:
39
- case _features.FEATURE_NAMES.spa:
40
39
  flagArr.push('spa');
41
40
  break;
42
41
  }
package/dist/cjs/index.js CHANGED
@@ -81,10 +81,10 @@ Object.defineProperty(exports, "SessionTrace", {
81
81
  return _session_trace.SessionTrace;
82
82
  }
83
83
  });
84
- Object.defineProperty(exports, "Spa", {
84
+ Object.defineProperty(exports, "SoftNav", {
85
85
  enumerable: true,
86
86
  get: function () {
87
- return _spa.Spa;
87
+ return _soft_navigations.SoftNav;
88
88
  }
89
89
  });
90
90
  var _agent = require("./loaders/agent");
@@ -100,4 +100,4 @@ var _page_view_event = require("./features/page_view_event");
100
100
  var _page_view_timing = require("./features/page_view_timing");
101
101
  var _session_trace = require("./features/session_trace");
102
102
  var _session_replay = require("./features/session_replay");
103
- var _spa = require("./features/spa");
103
+ var _soft_navigations = require("./features/soft_navigations");
@@ -27,6 +27,7 @@ class RegisteredEntity {
27
27
  /** @type {RegisterAPIMetadata} */
28
28
  metadata = {
29
29
  target: {},
30
+ timings: {},
30
31
  customAttributes: {}
31
32
  };
32
33
 
@@ -93,7 +94,7 @@ class RegisteredEntity {
93
94
  * Measures a task that is recorded as a BrowserPerformance event.
94
95
  * {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/measure/}
95
96
  * @param {string} name The name of the task
96
- * @param {{start: number, end: number, duration: number, customAttributes: object}} [options] An object used to control the way the measure API operates
97
+ * @param {{start?: number|PerformanceMark, end?: number|PerformanceMark, customAttributes?: object}} [options] An object used to control the way the measure API operates
97
98
  * @returns {{start: number, end: number, duration: number, customAttributes: object}} Measurement details
98
99
  */
99
100
  measure(name, options) {
@@ -99,10 +99,7 @@ class Agent extends _agentBase.AgentBase {
99
99
  featuresToStart.sort((a, b) => _features.featurePriority[a.featureName] - _features.featurePriority[b.featureName]);
100
100
  featuresToStart.forEach(InstrumentCtor => {
101
101
  if (!enabledFeatures[InstrumentCtor.featureName] && InstrumentCtor.featureName !== _features.FEATURE_NAMES.pageViewEvent) return; // PVE is required to run even if it's marked disabled
102
- if (InstrumentCtor.featureName === _features.FEATURE_NAMES.spa) {
103
- (0, _console.warn)(67);
104
- return; // Skip old SPA
105
- }
102
+
106
103
  const dependencies = (0, _featureDependencies.getFeatureDependencyNames)(InstrumentCtor.featureName);
107
104
  const missingDependencies = dependencies.filter(featName => !(featName in this.features)); // any other feature(s) this depends on should've been initialized on prior iterations by priority order
108
105
  if (missingDependencies.length > 0) {
@@ -16,7 +16,7 @@ exports.default = void 0;
16
16
  * @property {(target: RegisterAPIConstructor) => RegisterAPI} register - Record a custom event for the registered entity.
17
17
  * @property {() => void} deregister - Deregister the registered entity, which blocks its use and captures end of life timings.
18
18
  * @property {(eventType: string, attributes?: Object) => void} recordCustomEvent - Record a custom event for the registered entity.
19
- * @property {(eventType: string, options?: {start: number, end: number, duration: number, customAttributes: object}) => ({start: number, end: number, duration: number, customAttributes: object})} measure - Measures a task that is recorded as a BrowserPerformance event.
19
+ * @property {(eventType: string, options?: {start?: number|PerformanceMark, end?: number|PerformanceMark, customAttributes?: object}) => ({start: number, end: number, duration: number, customAttributes: object})} measure - Measures a task that is recorded as a BrowserPerformance event.
20
20
  * @property {(value: string | null) => void} setApplicationVersion - Add an application.version attribute to all outgoing data for the registered entity.
21
21
  * @property {(name: string, value: string | number | boolean | null, persist?: boolean) => void} setCustomAttribute - Add a custom attribute to outgoing data for the registered entity.
22
22
  * @property {(value: string | null, resetSession?: boolean) => void} setUserId - Add an enduser.id attribute to all outgoing API data for the registered entity. Note: a registered entity will not be able to initiate a session reset. It must be done from the main agent.
@@ -33,12 +33,24 @@ exports.default = void 0;
33
33
  /**
34
34
  * @typedef {Object} RegisterAPIMetadata
35
35
  * @property {Object} customAttributes - The custom attributes for the registered entity.
36
- * @property {Object} target - The options for the registered entity.
37
- * @property {string} [target.licenseKey] - The license key for the registered entity. If none was supplied, it will assume the license key from the main agent.
38
- * @property {string} target.id - The ID for the registered entity.
39
- * @property {string} target.name - The name returned for the registered entity.
40
- * @property {{[key: string]: any}} [target.tags] - The tags for the registered entity as key-value pairs.
41
- * @property {string} [target.parentId] - The parentId for the registered entity. If none was supplied, it will assume the entity guid from the main agent.
42
- * @property {boolean} [target.isolated] - When true, each registration creates an isolated instance. When false, multiple registrations with the same id and isolated: false will share a single instance, including all custom attributes, ids, names, and metadata. Calling deregister on a shared instance will deregister it for all entities using the instance. Defaults to true.
36
+ * @property {RegisterAPITimings} timings - The timing metrics for the registered entity.
37
+ * @property {RegisterAPITarget} target - The options for the registered entity.
38
+ */
39
+ /**
40
+ * @typedef {Object} RegisterAPITarget
41
+ * @property {string} id - The ID for the registered entity.
42
+ * @property {string} name - The name returned for the registered entity.
43
+ * @property {{[key: string]: any}} [tags] - The tags for the registered entity as key-value pairs.
44
+ * @property {string} [parentId] - The parentId for the registered entity. If none was supplied, it will assume the entity guid from the main agent.
45
+ * @property {boolean} [isolated] - When true, each registration creates an isolated instance. When false, multiple registrations with the same id and isolated: false will share a single instance, including all custom attributes, ids, names, and metadata. Calling deregister on a shared instance will deregister it for all entities using the instance. Defaults to true.
46
+ */
47
+ /**
48
+ * @typedef {Object} RegisterAPITimings
49
+ * @property {number} registeredAt - The timestamp when the registered entity was created.
50
+ * @property {number} [reportedAt] - The timestamp when the registered entity was deregistered.
51
+ * @property {number} fetchStart - The timestamp when the registered entity began fetching.
52
+ * @property {number} fetchEnd - The timestamp when the registered entity finished fetching.
53
+ * @property {Object} [asset] - The asset path (if found) for the registered entity.
54
+ * @property {string} type - The type of timing associated with the registered entity, 'script' or 'link' if found with the performance resource API, 'inline' if found to be associated with the root document URL, or 'unknown' if no associated resource could be found.
43
55
  */
44
56
  var _default = exports.default = {};
@@ -18,6 +18,8 @@ var _noticeError = require("./noticeError");
18
18
  var _invoke = require("../../common/util/invoke");
19
19
  var _measure = require("./measure");
20
20
  var _recordCustomEvent = require("./recordCustomEvent");
21
+ var _pageVisibility = require("../../common/window/page-visibility");
22
+ var _scriptTracker = require("../../common/util/script-tracker");
21
23
  /**
22
24
  * Copyright 2020-2026 New Relic, Inc. All rights reserved.
23
25
  * SPDX-License-Identifier: Apache-2.0
@@ -56,6 +58,7 @@ function register(agentRef, target, parent) {
56
58
  target.blocked = false;
57
59
  target.parent = parent || {};
58
60
  if (typeof target.tags !== 'object' || target.tags === null || Array.isArray(target.tags)) target.tags = {};
61
+ const timings = (0, _scriptTracker.findScriptTimings)();
59
62
  const attrs = {};
60
63
 
61
64
  // Process tags object and add to attrs, excluding protected keys
@@ -106,6 +109,7 @@ function register(agentRef, target, parent) {
106
109
  }, agentRef], target),
107
110
  deregister: () => {
108
111
  /** note: blocking this instance will disable access for all entities sharing the instance, and will invalidate it from the v2 checks */
112
+ reportTimings();
109
113
  block((0, _invoke.single)(() => (0, _console.warn)(68)));
110
114
  },
111
115
  log: (message, options = {}) => report(_log.log, [message, {
@@ -137,7 +141,8 @@ function register(agentRef, target, parent) {
137
141
  /** metadata */
138
142
  metadata: {
139
143
  customAttributes: attrs,
140
- target
144
+ target,
145
+ timings
141
146
  }
142
147
  };
143
148
 
@@ -151,7 +156,36 @@ function register(agentRef, target, parent) {
151
156
  };
152
157
 
153
158
  /** only allow registered APIs to be tracked in the agent runtime */
154
- if (!isBlocked()) registeredEntities.push(api);
159
+ if (!isBlocked()) {
160
+ registeredEntities.push(api);
161
+ (0, _pageVisibility.subscribeToPageUnload)(reportTimings);
162
+ }
163
+
164
+ /**
165
+ * Reports the gathered timings for the registered entity through a custom event to the container agent. Only reports once
166
+ * by checking for the presence of the reportedAt timing.
167
+ * @returns {void}
168
+ */
169
+ function reportTimings() {
170
+ // only ever report the timings the first time this is called
171
+ if (timings.reportedAt) return;
172
+ timings.reportedAt = (0, _now.now)();
173
+ api.recordCustomEvent('MicroFrontEndTiming', {
174
+ assetUrl: timings.asset,
175
+ // the url of the script that was registered, or undefined if it could not be determined (inline or no match)
176
+ assetType: timings.type,
177
+ // the type of asset that was associated with the timings, one of 'script', 'link' (if preloaded and found in the resource timing buffer), 'preload' (if preloaded but not found in the resource timing buffer), or "unknown" if it could not be determined
178
+ timeToLoad: timings.registeredAt - timings.fetchStart,
179
+ // fetchStart to registeredAt
180
+ timeToBeRequested: timings.fetchStart,
181
+ // origin to fetchStart
182
+ timeToFetch: timings.fetchEnd - timings.fetchStart,
183
+ // fetchStart to fetchEnd
184
+ timeToRegister: timings.registeredAt - timings.fetchEnd,
185
+ // fetchEnd to registeredAt
186
+ timeAlive: timings.reportedAt - timings.registeredAt // registeredAt to reportedAt
187
+ });
188
+ }
155
189
 
156
190
  /**
157
191
  * Sets a value local to the registered API attrs. Will do nothing if APIs are deregistered.
@@ -225,7 +225,7 @@ class ApiBase {
225
225
  * Measures a task that is recorded as a BrowserPerformance event.
226
226
  * {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/measure/}
227
227
  * @param {string} name The name of the task
228
- * @param {{start: number, end: number, duration: number, customAttributes: object}} [options] An object used to control the way the measure API operates
228
+ * @param {{start?: number|PerformanceMark, end?: number|PerformanceMark, customAttributes?: object}} [options] An object used to control the way the measure API operates
229
229
  * @returns {{start: number, end: number, duration: number, customAttributes: object}} Measurement details
230
230
  */
231
231
  measure(name, options) {