@newrelic/browser-agent 1.309.0-rc.1 → 1.309.0-rc.2

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 (33) hide show
  1. package/dist/cjs/common/constants/env.cdn.js +1 -1
  2. package/dist/cjs/common/constants/env.npm.js +1 -1
  3. package/dist/cjs/common/util/browser-stack-matchers.js +15 -0
  4. package/dist/cjs/common/util/script-tracker.js +81 -0
  5. package/dist/cjs/features/jserrors/aggregate/compute-stack-trace.js +6 -10
  6. package/dist/cjs/interfaces/registered-entity.js +1 -0
  7. package/dist/cjs/loaders/api/register-api-types.js +6 -0
  8. package/dist/cjs/loaders/api/register.js +36 -2
  9. package/dist/esm/common/constants/env.cdn.js +1 -1
  10. package/dist/esm/common/constants/env.npm.js +1 -1
  11. package/dist/esm/common/util/browser-stack-matchers.js +9 -0
  12. package/dist/esm/common/util/script-tracker.js +76 -0
  13. package/dist/esm/features/jserrors/aggregate/compute-stack-trace.js +2 -6
  14. package/dist/esm/interfaces/registered-entity.js +1 -0
  15. package/dist/esm/loaders/api/register-api-types.js +6 -0
  16. package/dist/esm/loaders/api/register.js +36 -2
  17. package/dist/tsconfig.tsbuildinfo +1 -1
  18. package/dist/types/common/util/browser-stack-matchers.d.ts +10 -0
  19. package/dist/types/common/util/browser-stack-matchers.d.ts.map +1 -0
  20. package/dist/types/common/util/script-tracker.d.ts +10 -0
  21. package/dist/types/common/util/script-tracker.d.ts.map +1 -0
  22. package/dist/types/features/jserrors/aggregate/compute-stack-trace.d.ts.map +1 -1
  23. package/dist/types/interfaces/registered-entity.d.ts.map +1 -1
  24. package/dist/types/loaders/api/register-api-types.d.ts +10 -0
  25. package/dist/types/loaders/api/register-api-types.d.ts.map +1 -1
  26. package/dist/types/loaders/api/register.d.ts.map +1 -1
  27. package/package.json +1 -1
  28. package/src/common/util/browser-stack-matchers.js +9 -0
  29. package/src/common/util/script-tracker.js +77 -0
  30. package/src/features/jserrors/aggregate/compute-stack-trace.js +2 -7
  31. package/src/interfaces/registered-entity.js +1 -0
  32. package/src/loaders/api/register-api-types.js +6 -0
  33. package/src/loaders/api/register.js +33 -2
@@ -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-rc.1";
20
+ const VERSION = exports.VERSION = "1.309.0-rc.2";
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-rc.1";
20
+ const VERSION = exports.VERSION = "1.309.0-rc.2";
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,81 @@
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 _cleanUrl = require("../url/clean-url");
9
+ var _browserStackMatchers = require("./browser-stack-matchers");
10
+ /**
11
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
12
+ * SPDX-License-Identifier: Apache-2.0
13
+ */
14
+
15
+ /**
16
+ * Extracts URLs from stack traces using the same logic as compute-stack-trace.js
17
+ * @param {string} stack The error stack trace
18
+ * @returns {string[]} Array of cleaned URLs found in the stack trace
19
+ */
20
+ function extractUrlsFromStack(stack) {
21
+ if (!stack || typeof stack !== 'string') return [];
22
+ const urls = new Set();
23
+ const lines = stack.split('\n');
24
+ for (const line of lines) {
25
+ // Try gecko format first, then chrome
26
+ const parts = line.match(_browserStackMatchers.gecko) || line.match(_browserStackMatchers.chrome);
27
+ if (parts && parts[2]) {
28
+ urls.add((0, _cleanUrl.cleanURL)(parts[2]));
29
+ }
30
+ }
31
+ return [...urls];
32
+ }
33
+
34
+ /**
35
+ * Returns a deep stack trace by temporarily increasing the stack trace limit.
36
+ * @returns {Error.stack | undefined}
37
+ */
38
+ function getDeepStackTrace() {
39
+ let stack;
40
+ try {
41
+ const originalStackLimit = Error.stackTraceLimit;
42
+ Error.stackTraceLimit = 50;
43
+ stack = new Error().stack;
44
+ Error.stackTraceLimit = originalStackLimit;
45
+ } catch (e) {
46
+ stack = new Error().stack;
47
+ }
48
+ return stack;
49
+ }
50
+
51
+ /**
52
+ * 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.
53
+ * @returns {{fetchStart: number, fetchEnd: number, asset: string|undefined}} Object containing script fetch start and end times, and the asset URL if found
54
+ */
55
+ function findScriptTimings() {
56
+ const stack = getDeepStackTrace();
57
+ const timings = {
58
+ fetchStart: 0,
59
+ fetchEnd: 0,
60
+ asset: undefined
61
+ };
62
+ const scripts = _runtime.globalScope.performance?.getEntriesByType('resource').filter(entry => entry.initiatorType === 'script') || [];
63
+ if (scripts.length < 1 || !stack) return timings;
64
+ try {
65
+ 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
66
+ if (!mfeScriptUrl) return timings;
67
+ const match = scripts.find(script => {
68
+ const scriptUrl = (0, _cleanUrl.cleanURL)(script.name);
69
+ // Try exact match, then partial matches for different URL formats
70
+ return mfeScriptUrl === scriptUrl || mfeScriptUrl.endsWith(scriptUrl) || scriptUrl.endsWith(mfeScriptUrl);
71
+ });
72
+ if (match) {
73
+ timings.fetchStart = Math.floor(match.startTime);
74
+ timings.fetchEnd = Math.floor(match.responseEnd);
75
+ timings.asset = match.name;
76
+ }
77
+ } catch (error) {
78
+ // Don't let stack parsing errors break anything
79
+ }
80
+ return timings;
81
+ }
@@ -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
 
@@ -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
 
@@ -33,6 +33,12 @@ 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} timings - The timing metrics for the registered entity.
37
+ * @property {number} timings.registeredAt - The timestamp when the registered entity was created.
38
+ * @property {number} [timings.reportedAt] - The timestamp when the registered entity was deregistered.
39
+ * @property {number} timings.fetchStart - The timestamp when the registered entity began fetching.
40
+ * @property {number} timings.fetchEnd - The timestamp when the registered entity finished fetching.
41
+ * @property {Object} [timings.asset] - The asset path (if found) for the registered entity.
36
42
  * @property {Object} target - The options for the registered entity.
37
43
  * @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
44
  * @property {string} target.id - The ID for the registered entity.
@@ -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,11 @@ 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 = {
62
+ registeredAt: (0, _now.now)(),
63
+ reportedAt: undefined,
64
+ ...(0, _scriptTracker.findScriptTimings)()
65
+ };
59
66
  const attrs = {};
60
67
 
61
68
  // Process tags object and add to attrs, excluding protected keys
@@ -106,6 +113,7 @@ function register(agentRef, target, parent) {
106
113
  }, agentRef], target),
107
114
  deregister: () => {
108
115
  /** note: blocking this instance will disable access for all entities sharing the instance, and will invalidate it from the v2 checks */
116
+ reportTimings();
109
117
  block((0, _invoke.single)(() => (0, _console.warn)(68)));
110
118
  },
111
119
  log: (message, options = {}) => report(_log.log, [message, {
@@ -137,7 +145,8 @@ function register(agentRef, target, parent) {
137
145
  /** metadata */
138
146
  metadata: {
139
147
  customAttributes: attrs,
140
- target
148
+ target,
149
+ timings
141
150
  }
142
151
  };
143
152
 
@@ -151,7 +160,32 @@ function register(agentRef, target, parent) {
151
160
  };
152
161
 
153
162
  /** only allow registered APIs to be tracked in the agent runtime */
154
- if (!isBlocked()) registeredEntities.push(api);
163
+ if (!isBlocked()) {
164
+ registeredEntities.push(api);
165
+ (0, _pageVisibility.subscribeToPageUnload)(reportTimings);
166
+ }
167
+
168
+ /**
169
+ * Reports the gathered timings for the registered entity through a custom event to the container agent. Only reports once
170
+ * by checking for the presence of the reportedAt timing.
171
+ * @returns {void}
172
+ */
173
+ function reportTimings() {
174
+ // only ever report the timings the first time this is called
175
+ if (timings.reportedAt) return;
176
+ timings.reportedAt = (0, _now.now)();
177
+ api.recordCustomEvent('MicroFrontEndTiming', {
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.
@@ -11,7 +11,7 @@
11
11
  /**
12
12
  * Exposes the version of the agent
13
13
  */
14
- export const VERSION = "1.309.0-rc.1";
14
+ export const VERSION = "1.309.0-rc.2";
15
15
 
16
16
  /**
17
17
  * Exposes the build type of the agent
@@ -11,7 +11,7 @@
11
11
  /**
12
12
  * Exposes the version of the agent
13
13
  */
14
- export const VERSION = "1.309.0-rc.1";
14
+ export const VERSION = "1.309.0-rc.2";
15
15
 
16
16
  /**
17
17
  * Exposes the build type of the agent
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ export const classNameRegex = /function (.+?)\s*\(/;
6
+ export const chromeEval = /^\s*at .+ \(eval at \S+ \((?:(?:file|http|https):[^)]+)?\)(?:, [^:]*:\d+:\d+)?\)$/i;
7
+ export const ieEval = /^\s*at Function code \(Function code:\d+:\d+\)\s*/i;
8
+ export const chrome = /^\s*at (?:((?:\[object object\])?(?:[^(]*\([^)]*\))*[^()]*(?: \[as \S+\])?) )?\(?((?:file|http|https|chrome-extension):.*?)?:(\d+)(?::(\d+))?\)?\s*$/i;
9
+ export const gecko = /^\s*(?:([^@]*)(?:\(.*?\))?@)?((?:file|http|https|chrome|safari-extension).*?):(\d+)(?::(\d+))?\s*$/i;
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ import { globalScope } from '../constants/runtime';
7
+ import { cleanURL } from '../url/clean-url';
8
+ import { chrome, gecko } from './browser-stack-matchers';
9
+
10
+ /**
11
+ * Extracts URLs from stack traces using the same logic as compute-stack-trace.js
12
+ * @param {string} stack The error stack trace
13
+ * @returns {string[]} Array of cleaned URLs found in the stack trace
14
+ */
15
+ function extractUrlsFromStack(stack) {
16
+ if (!stack || typeof stack !== 'string') return [];
17
+ const urls = new Set();
18
+ const lines = stack.split('\n');
19
+ for (const line of lines) {
20
+ // Try gecko format first, then chrome
21
+ const parts = line.match(gecko) || line.match(chrome);
22
+ if (parts && parts[2]) {
23
+ urls.add(cleanURL(parts[2]));
24
+ }
25
+ }
26
+ return [...urls];
27
+ }
28
+
29
+ /**
30
+ * Returns a deep stack trace by temporarily increasing the stack trace limit.
31
+ * @returns {Error.stack | undefined}
32
+ */
33
+ function getDeepStackTrace() {
34
+ let stack;
35
+ try {
36
+ const originalStackLimit = Error.stackTraceLimit;
37
+ Error.stackTraceLimit = 50;
38
+ stack = new Error().stack;
39
+ Error.stackTraceLimit = originalStackLimit;
40
+ } catch (e) {
41
+ stack = new Error().stack;
42
+ }
43
+ return stack;
44
+ }
45
+
46
+ /**
47
+ * 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.
48
+ * @returns {{fetchStart: number, fetchEnd: number, asset: string|undefined}} Object containing script fetch start and end times, and the asset URL if found
49
+ */
50
+ export function findScriptTimings() {
51
+ const stack = getDeepStackTrace();
52
+ const timings = {
53
+ fetchStart: 0,
54
+ fetchEnd: 0,
55
+ asset: undefined
56
+ };
57
+ const scripts = globalScope.performance?.getEntriesByType('resource').filter(entry => entry.initiatorType === 'script') || [];
58
+ if (scripts.length < 1 || !stack) return timings;
59
+ try {
60
+ 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
61
+ if (!mfeScriptUrl) return timings;
62
+ const match = scripts.find(script => {
63
+ const scriptUrl = cleanURL(script.name);
64
+ // Try exact match, then partial matches for different URL formats
65
+ return mfeScriptUrl === scriptUrl || mfeScriptUrl.endsWith(scriptUrl) || scriptUrl.endsWith(mfeScriptUrl);
66
+ });
67
+ if (match) {
68
+ timings.fetchStart = Math.floor(match.startTime);
69
+ timings.fetchEnd = Math.floor(match.responseEnd);
70
+ timings.asset = match.name;
71
+ }
72
+ } catch (error) {
73
+ // Don't let stack parsing errors break anything
74
+ }
75
+ return timings;
76
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
 
@@ -58,12 +58,8 @@
58
58
  // ex.name = ReferenceError
59
59
  import { formatStackTrace } from './format-stack-trace';
60
60
  import { canonicalizeUrl } from '../../../common/url/canonicalize-url';
61
+ import { chrome, chromeEval, classNameRegex, gecko, ieEval } from '../../../common/util/browser-stack-matchers';
61
62
  var debug = false;
62
- var classNameRegex = /function (.+?)\s*\(/;
63
- var chrome = /^\s*at (?:((?:\[object object\])?(?:[^(]*\([^)]*\))*[^()]*(?: \[as \S+\])?) )?\(?((?:file|http|https|chrome-extension):.*?)?:(\d+)(?::(\d+))?\)?\s*$/i;
64
- var gecko = /^\s*(?:(\S*|global code)(?:\(.*?\))?@)?((?:file|http|https|chrome|safari-extension).*?):(\d+)(?::(\d+))?\s*$/i;
65
- var chromeEval = /^\s*at .+ \(eval at \S+ \((?:(?:file|http|https):[^)]+)?\)(?:, [^:]*:\d+:\d+)?\)$/i;
66
- var ieEval = /^\s*at Function code \(Function code:\d+:\d+\)\s*/i;
67
63
 
68
64
  /**
69
65
  * Represents an error with a stack trace.
@@ -21,6 +21,7 @@ export class RegisteredEntity {
21
21
  /** @type {RegisterAPIMetadata} */
22
22
  metadata = {
23
23
  target: {},
24
+ timings: {},
24
25
  customAttributes: {}
25
26
  };
26
27
 
@@ -30,6 +30,12 @@
30
30
  /**
31
31
  * @typedef {Object} RegisterAPIMetadata
32
32
  * @property {Object} customAttributes - The custom attributes for the registered entity.
33
+ * @property {Object} timings - The timing metrics for the registered entity.
34
+ * @property {number} timings.registeredAt - The timestamp when the registered entity was created.
35
+ * @property {number} [timings.reportedAt] - The timestamp when the registered entity was deregistered.
36
+ * @property {number} timings.fetchStart - The timestamp when the registered entity began fetching.
37
+ * @property {number} timings.fetchEnd - The timestamp when the registered entity finished fetching.
38
+ * @property {Object} [timings.asset] - The asset path (if found) for the registered entity.
33
39
  * @property {Object} target - The options for the registered entity.
34
40
  * @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.
35
41
  * @property {string} target.id - The ID for the registered entity.
@@ -16,6 +16,8 @@ import { noticeError } from './noticeError';
16
16
  import { single } from '../../common/util/invoke';
17
17
  import { measure } from './measure';
18
18
  import { recordCustomEvent } from './recordCustomEvent';
19
+ import { subscribeToPageUnload } from '../../common/window/page-visibility';
20
+ import { findScriptTimings } from '../../common/util/script-tracker';
19
21
 
20
22
  /**
21
23
  * @typedef {import('./register-api-types').RegisterAPI} RegisterAPI
@@ -50,6 +52,11 @@ function register(agentRef, target, parent) {
50
52
  target.blocked = false;
51
53
  target.parent = parent || {};
52
54
  if (typeof target.tags !== 'object' || target.tags === null || Array.isArray(target.tags)) target.tags = {};
55
+ const timings = {
56
+ registeredAt: now(),
57
+ reportedAt: undefined,
58
+ ...findScriptTimings()
59
+ };
53
60
  const attrs = {};
54
61
 
55
62
  // Process tags object and add to attrs, excluding protected keys
@@ -100,6 +107,7 @@ function register(agentRef, target, parent) {
100
107
  }, agentRef], target),
101
108
  deregister: () => {
102
109
  /** note: blocking this instance will disable access for all entities sharing the instance, and will invalidate it from the v2 checks */
110
+ reportTimings();
103
111
  block(single(() => warn(68)));
104
112
  },
105
113
  log: (message, options = {}) => report(log, [message, {
@@ -131,7 +139,8 @@ function register(agentRef, target, parent) {
131
139
  /** metadata */
132
140
  metadata: {
133
141
  customAttributes: attrs,
134
- target
142
+ target,
143
+ timings
135
144
  }
136
145
  };
137
146
 
@@ -145,7 +154,32 @@ function register(agentRef, target, parent) {
145
154
  };
146
155
 
147
156
  /** only allow registered APIs to be tracked in the agent runtime */
148
- if (!isBlocked()) registeredEntities.push(api);
157
+ if (!isBlocked()) {
158
+ registeredEntities.push(api);
159
+ subscribeToPageUnload(reportTimings);
160
+ }
161
+
162
+ /**
163
+ * Reports the gathered timings for the registered entity through a custom event to the container agent. Only reports once
164
+ * by checking for the presence of the reportedAt timing.
165
+ * @returns {void}
166
+ */
167
+ function reportTimings() {
168
+ // only ever report the timings the first time this is called
169
+ if (timings.reportedAt) return;
170
+ timings.reportedAt = now();
171
+ api.recordCustomEvent('MicroFrontEndTiming', {
172
+ timeToLoad: timings.registeredAt - timings.fetchStart,
173
+ // fetchStart to registeredAt
174
+ timeToBeRequested: timings.fetchStart,
175
+ // origin to fetchStart
176
+ timeToFetch: timings.fetchEnd - timings.fetchStart,
177
+ // fetchStart to fetchEnd
178
+ timeToRegister: timings.registeredAt - timings.fetchEnd,
179
+ // fetchEnd to registeredAt
180
+ timeAlive: timings.reportedAt - timings.registeredAt // registeredAt to reportedAt
181
+ });
182
+ }
149
183
 
150
184
  /**
151
185
  * Sets a value local to the registered API attrs. Will do nothing if APIs are deregistered.
@@ -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/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/util/v2.js","../src/common/util/webdriver-detection.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
+ {"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/browser-stack-matchers.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/monkey-patched.js","../src/common/util/obfuscate.js","../src/common/util/script-tracker.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/util/v2.js","../src/common/util/webdriver-detection.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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ export const classNameRegex: RegExp;
6
+ export const chromeEval: RegExp;
7
+ export const ieEval: RegExp;
8
+ export const chrome: RegExp;
9
+ export const gecko: RegExp;
10
+ //# sourceMappingURL=browser-stack-matchers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-stack-matchers.d.ts","sourceRoot":"","sources":["../../../../src/common/util/browser-stack-matchers.js"],"names":[],"mappings":"AAAA;;;GAGG;AACH,oCAAmD;AACnD,gCAA8G;AAC9G,4BAA0E;AAC1E,4BAA6K;AAC7K,2BAA0H"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * 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.
3
+ * @returns {{fetchStart: number, fetchEnd: number, asset: string|undefined}} Object containing script fetch start and end times, and the asset URL if found
4
+ */
5
+ export function findScriptTimings(): {
6
+ fetchStart: number;
7
+ fetchEnd: number;
8
+ asset: string | undefined;
9
+ };
10
+ //# sourceMappingURL=script-tracker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"script-tracker.d.ts","sourceRoot":"","sources":["../../../../src/common/util/script-tracker.js"],"names":[],"mappings":"AA+CA;;;GAGG;AACH,qCAFa;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,GAAC,SAAS,CAAA;CAAC,CA2B3E"}
@@ -1 +1 @@
1
- {"version":3,"file":"compute-stack-trace.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/aggregate/compute-stack-trace.js"],"names":[],"mappings":"AAqEA;;;;;;;;;;GAUG;AAEH;;;;GAIG;AACH,sCAHW,KAAK,GACH,SAAS,CA2CrB;;;;;;;;UAvDa,MAAM;;;;aACN,MAAM;;;;iBACN,MAAM;;;;YACN,KAAK,CAAC,MAAM,CAAC;;;;SACb,MAAM;;;;UACN,MAAM;;;;UACN,MAAM"}
1
+ {"version":3,"file":"compute-stack-trace.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/aggregate/compute-stack-trace.js"],"names":[],"mappings":"AAgEA;;;;;;;;;;GAUG;AAEH;;;;GAIG;AACH,sCAHW,KAAK,GACH,SAAS,CA2CrB;;;;;;;;UAvDa,MAAM;;;;aACN,MAAM;;;;iBACN,MAAM;;;;YACN,KAAK,CAAC,MAAM,CAAC;;;;SACb,MAAM;;;;UACN,MAAM;;;;UACN,MAAM"}
@@ -1 +1 @@
1
- {"version":3,"file":"registered-entity.d.ts","sourceRoot":"","sources":["../../../src/interfaces/registered-entity.js"],"names":[],"mappings":"AAMA;;;;GAIG;AAEH;;;;;;GAMG;AACH;IAOE;;;OAGG;IACH,kBAFW,sBAAsB,EAShC;IAjBD,kCAAkC;IAClC,UADW,mBAAmB,CAI7B;IAeD;;;;;OAKG;IACH,oBAHW,MAAM,eACN,MAAM,QAKhB;IAED;;;;;;;;OAQG;IACH,iBAHW,OAAO,mCAAmC,EAAE,sBAAsB,GACjE,OAAO,mCAAmC,EAAE,WAAW,CAIlE;IAED;;;;;;;OAOG;IACH,cAFa,IAAI,CAIhB;IAED;;;;;SAKK;IACL,6BAHa,MAAM,eACN,MAAM,QAIlB;IAED;;;;;;OAMG;IACH,cAJW,MAAM,YACN;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAC,GACtE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAC,CAIpF;IAED;;;;;;OAMG;IACH,yBAJW,MAAM,SACN,MAAM,GAAC,MAAM,GAAC,OAAO,GAAC,IAAI,YAC1B,OAAO,QAKjB;IAED;;;;;OAKG;IACH,mBAHW,KAAK,GAAC,MAAM,qBACZ,MAAM,QAKhB;IAED;;;;;OAKG;IACH,iBAHW,MAAM,GAAC,IAAI,iBACX,OAAO,QAKjB;IAED;;;;;;;OAOG;IACH,6BAJW,MAAM,GAAC,IAAI,QAOrB;IAED;;;;;MAKE;IACF,aAHW,MAAM,YACN;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,GAAC,OAAO,GAAC,OAAO,GAAC,MAAM,GAAC,MAAM,CAAA;KAAC,QAKpF;CACF;0BAlJY,OAAO,mCAAmC,EAAE,WAAW;kCACvD,OAAO,mCAAmC,EAAE,mBAAmB;qCAC/D,OAAO,mCAAmC,EAAE,sBAAsB"}
1
+ {"version":3,"file":"registered-entity.d.ts","sourceRoot":"","sources":["../../../src/interfaces/registered-entity.js"],"names":[],"mappings":"AAMA;;;;GAIG;AAEH;;;;;;GAMG;AACH;IAQE;;;OAGG;IACH,kBAFW,sBAAsB,EAShC;IAlBD,kCAAkC;IAClC,UADW,mBAAmB,CAK7B;IAeD;;;;;OAKG;IACH,oBAHW,MAAM,eACN,MAAM,QAKhB;IAED;;;;;;;;OAQG;IACH,iBAHW,OAAO,mCAAmC,EAAE,sBAAsB,GACjE,OAAO,mCAAmC,EAAE,WAAW,CAIlE;IAED;;;;;;;OAOG;IACH,cAFa,IAAI,CAIhB;IAED;;;;;SAKK;IACL,6BAHa,MAAM,eACN,MAAM,QAIlB;IAED;;;;;;OAMG;IACH,cAJW,MAAM,YACN;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAC,GACtE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAC,CAIpF;IAED;;;;;;OAMG;IACH,yBAJW,MAAM,SACN,MAAM,GAAC,MAAM,GAAC,OAAO,GAAC,IAAI,YAC1B,OAAO,QAKjB;IAED;;;;;OAKG;IACH,mBAHW,KAAK,GAAC,MAAM,qBACZ,MAAM,QAKhB;IAED;;;;;OAKG;IACH,iBAHW,MAAM,GAAC,IAAI,iBACX,OAAO,QAKjB;IAED;;;;;;;OAOG;IACH,6BAJW,MAAM,GAAC,IAAI,QAOrB;IAED;;;;;MAKE;IACF,aAHW,MAAM,YACN;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,GAAC,OAAO,GAAC,OAAO,GAAC,MAAM,GAAC,MAAM,CAAA;KAAC,QAKpF;CACF;0BAnJY,OAAO,mCAAmC,EAAE,WAAW;kCACvD,OAAO,mCAAmC,EAAE,mBAAmB;qCAC/D,OAAO,mCAAmC,EAAE,sBAAsB"}
@@ -88,6 +88,16 @@ export type RegisterAPIMetadata = {
88
88
  * - The custom attributes for the registered entity.
89
89
  */
90
90
  customAttributes: Object;
91
+ /**
92
+ * - The timing metrics for the registered entity.
93
+ */
94
+ timings: {
95
+ registeredAt: number;
96
+ reportedAt?: number | undefined;
97
+ fetchStart: number;
98
+ fetchEnd: number;
99
+ asset?: Object | undefined;
100
+ };
91
101
  /**
92
102
  * - The options for the registered entity.
93
103
  */
@@ -1 +1 @@
1
- {"version":3,"file":"register-api-types.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/register-api-types.js"],"names":[],"mappings":";;;;;;mBAOc,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,IAAI;;;;SAC3C,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAA;KAAC,KAAK,IAAI;;;;iBACxH,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,KAAK,IAAI;;;;cAC1D,CAAC,MAAM,EAAE,sBAAsB,KAAK,WAAW;;;;gBAC/C,MAAM,IAAI;;;;uBACV,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,IAAI;;;;aAChD,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAC,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAC,CAAC;;;;2BACrL,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI;;;;wBAC9B,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI;;;;eAClF,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,YAAY,CAAC,EAAE,OAAO,KAAK,IAAI;;;;cACtD,mBAAmB;;;;;;QAKnB,MAAM,GAAC,MAAM;;;;UACb,MAAM;;;;;;;;;;;;;;;;;;;;sBAQN,MAAM;;;;YAEjB;QAA2B,UAAU;QACX,EAAE,EAAjB,MAAM;QACS,IAAI,EAAnB,MAAM;QACwB,IAAI;;;QAClB,QAAQ;QACP,QAAQ;KACtC"}
1
+ {"version":3,"file":"register-api-types.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/register-api-types.js"],"names":[],"mappings":";;;;;;mBAOc,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,IAAI;;;;SAC3C,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAA;KAAC,KAAK,IAAI;;;;iBACxH,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,KAAK,IAAI;;;;cAC1D,CAAC,MAAM,EAAE,sBAAsB,KAAK,WAAW;;;;gBAC/C,MAAM,IAAI;;;;uBACV,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,IAAI;;;;aAChD,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAC,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAC,CAAC;;;;2BACrL,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI;;;;wBAC9B,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI;;;;eAClF,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,YAAY,CAAC,EAAE,OAAO,KAAK,IAAI;;;;cACtD,mBAAmB;;;;;;QAKnB,MAAM,GAAC,MAAM;;;;UACb,MAAM;;;;;;;;;;;;;;;;;;;;sBAQN,MAAM;;;;aAEjB;QAA2B,YAAY,EAA5B,MAAM;QACW,UAAU;QACX,UAAU,EAA1B,MAAM;QACU,QAAQ,EAAxB,MAAM;QACW,KAAK;KACjC;;;;YACA;QAA2B,UAAU;QACX,EAAE,EAAjB,MAAM;QACS,IAAI,EAAnB,MAAM;QACwB,IAAI;;;QAClB,QAAQ;QACP,QAAQ;KACtC"}
@@ -1 +1 @@
1
- {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/register.js"],"names":[],"mappings":"AAyBA;;;;GAIG;AACH,mDAIC;0BAdY,OAAO,sBAAsB,EAAE,WAAW"}
1
+ {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/register.js"],"names":[],"mappings":"AA2BA;;;;GAIG;AACH,mDAIC;0BAdY,OAAO,sBAAsB,EAAE,WAAW"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/browser-agent",
3
- "version": "1.309.0-rc.1",
3
+ "version": "1.309.0-rc.2",
4
4
  "private": false,
5
5
  "author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
6
6
  "description": "New Relic Browser Agent",
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ export const classNameRegex = /function (.+?)\s*\(/
6
+ export const chromeEval = /^\s*at .+ \(eval at \S+ \((?:(?:file|http|https):[^)]+)?\)(?:, [^:]*:\d+:\d+)?\)$/i
7
+ export const ieEval = /^\s*at Function code \(Function code:\d+:\d+\)\s*/i
8
+ export const chrome = /^\s*at (?:((?:\[object object\])?(?:[^(]*\([^)]*\))*[^()]*(?: \[as \S+\])?) )?\(?((?:file|http|https|chrome-extension):.*?)?:(\d+)(?::(\d+))?\)?\s*$/i
9
+ export const gecko = /^\s*(?:([^@]*)(?:\(.*?\))?@)?((?:file|http|https|chrome|safari-extension).*?):(\d+)(?::(\d+))?\s*$/i
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ import { globalScope } from '../constants/runtime'
7
+ import { cleanURL } from '../url/clean-url'
8
+ import { chrome, gecko } from './browser-stack-matchers'
9
+
10
+ /**
11
+ * Extracts URLs from stack traces using the same logic as compute-stack-trace.js
12
+ * @param {string} stack The error stack trace
13
+ * @returns {string[]} Array of cleaned URLs found in the stack trace
14
+ */
15
+ function extractUrlsFromStack (stack) {
16
+ if (!stack || typeof stack !== 'string') return []
17
+
18
+ const urls = new Set()
19
+ const lines = stack.split('\n')
20
+
21
+ for (const line of lines) {
22
+ // Try gecko format first, then chrome
23
+ const parts = line.match(gecko) || line.match(chrome)
24
+ if (parts && parts[2]) {
25
+ urls.add(cleanURL(parts[2]))
26
+ }
27
+ }
28
+ return [...urls]
29
+ }
30
+
31
+ /**
32
+ * Returns a deep stack trace by temporarily increasing the stack trace limit.
33
+ * @returns {Error.stack | undefined}
34
+ */
35
+ function getDeepStackTrace () {
36
+ let stack
37
+ try {
38
+ const originalStackLimit = Error.stackTraceLimit
39
+ Error.stackTraceLimit = 50
40
+ stack = new Error().stack
41
+ Error.stackTraceLimit = originalStackLimit
42
+ } catch (e) {
43
+ stack = new Error().stack
44
+ }
45
+ return stack
46
+ }
47
+
48
+ /**
49
+ * 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.
50
+ * @returns {{fetchStart: number, fetchEnd: number, asset: string|undefined}} Object containing script fetch start and end times, and the asset URL if found
51
+ */
52
+ export function findScriptTimings () {
53
+ const stack = getDeepStackTrace()
54
+ const timings = { fetchStart: 0, fetchEnd: 0, asset: undefined }
55
+ const scripts = globalScope.performance?.getEntriesByType('resource').filter(entry => entry.initiatorType === 'script') || []
56
+ if (scripts.length < 1 || !stack) return timings
57
+
58
+ try {
59
+ 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
60
+ if (!mfeScriptUrl) return timings
61
+ const match = scripts.find(script => {
62
+ const scriptUrl = cleanURL(script.name)
63
+ // Try exact match, then partial matches for different URL formats
64
+ return mfeScriptUrl === scriptUrl || mfeScriptUrl.endsWith(scriptUrl) || scriptUrl.endsWith(mfeScriptUrl)
65
+ })
66
+
67
+ if (match) {
68
+ timings.fetchStart = Math.floor(match.startTime)
69
+ timings.fetchEnd = Math.floor(match.responseEnd)
70
+ timings.asset = match.name
71
+ }
72
+ } catch (error) {
73
+ // Don't let stack parsing errors break anything
74
+ }
75
+
76
+ return timings
77
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
 
@@ -58,15 +58,10 @@
58
58
  // ex.name = ReferenceError
59
59
  import { formatStackTrace } from './format-stack-trace'
60
60
  import { canonicalizeUrl } from '../../../common/url/canonicalize-url'
61
+ import { chrome, chromeEval, classNameRegex, gecko, ieEval } from '../../../common/util/browser-stack-matchers'
61
62
 
62
63
  var debug = false
63
64
 
64
- var classNameRegex = /function (.+?)\s*\(/
65
- var chrome = /^\s*at (?:((?:\[object object\])?(?:[^(]*\([^)]*\))*[^()]*(?: \[as \S+\])?) )?\(?((?:file|http|https|chrome-extension):.*?)?:(\d+)(?::(\d+))?\)?\s*$/i
66
- var gecko = /^\s*(?:(\S*|global code)(?:\(.*?\))?@)?((?:file|http|https|chrome|safari-extension).*?):(\d+)(?::(\d+))?\s*$/i
67
- var chromeEval = /^\s*at .+ \(eval at \S+ \((?:(?:file|http|https):[^)]+)?\)(?:, [^:]*:\d+:\d+)?\)$/i
68
- var ieEval = /^\s*at Function code \(Function code:\d+:\d+\)\s*/i
69
-
70
65
  /**
71
66
  * Represents an error with a stack trace.
72
67
  * @typedef {Object} StackInfo
@@ -21,6 +21,7 @@ export class RegisteredEntity {
21
21
  /** @type {RegisterAPIMetadata} */
22
22
  metadata = {
23
23
  target: {},
24
+ timings: {},
24
25
  customAttributes: {}
25
26
  }
26
27
 
@@ -30,6 +30,12 @@
30
30
  /**
31
31
  * @typedef {Object} RegisterAPIMetadata
32
32
  * @property {Object} customAttributes - The custom attributes for the registered entity.
33
+ * @property {Object} timings - The timing metrics for the registered entity.
34
+ * @property {number} timings.registeredAt - The timestamp when the registered entity was created.
35
+ * @property {number} [timings.reportedAt] - The timestamp when the registered entity was deregistered.
36
+ * @property {number} timings.fetchStart - The timestamp when the registered entity began fetching.
37
+ * @property {number} timings.fetchEnd - The timestamp when the registered entity finished fetching.
38
+ * @property {Object} [timings.asset] - The asset path (if found) for the registered entity.
33
39
  * @property {Object} target - The options for the registered entity.
34
40
  * @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.
35
41
  * @property {string} target.id - The ID for the registered entity.
@@ -16,6 +16,8 @@ import { noticeError } from './noticeError'
16
16
  import { single } from '../../common/util/invoke'
17
17
  import { measure } from './measure'
18
18
  import { recordCustomEvent } from './recordCustomEvent'
19
+ import { subscribeToPageUnload } from '../../common/window/page-visibility'
20
+ import { findScriptTimings } from '../../common/util/script-tracker'
19
21
 
20
22
  /**
21
23
  * @typedef {import('./register-api-types').RegisterAPI} RegisterAPI
@@ -52,6 +54,12 @@ function register (agentRef, target, parent) {
52
54
  target.parent = parent || {}
53
55
  if (typeof target.tags !== 'object' || target.tags === null || Array.isArray(target.tags)) target.tags = {}
54
56
 
57
+ const timings = {
58
+ registeredAt: now(),
59
+ reportedAt: undefined,
60
+ ...findScriptTimings()
61
+ }
62
+
55
63
  const attrs = {}
56
64
 
57
65
  // Process tags object and add to attrs, excluding protected keys
@@ -96,6 +104,7 @@ function register (agentRef, target, parent) {
96
104
  addPageAction: (name, attributes = {}) => report(addPageAction, [name, { ...attrs, ...attributes }, agentRef], target),
97
105
  deregister: () => {
98
106
  /** note: blocking this instance will disable access for all entities sharing the instance, and will invalidate it from the v2 checks */
107
+ reportTimings()
99
108
  block(single(() => warn(68)))
100
109
  },
101
110
  log: (message, options = {}) => report(log, [message, { ...options, customAttributes: { ...attrs, ...(options.customAttributes || {}) } }, agentRef], target),
@@ -109,7 +118,8 @@ function register (agentRef, target, parent) {
109
118
  /** metadata */
110
119
  metadata: {
111
120
  customAttributes: attrs,
112
- target
121
+ target,
122
+ timings
113
123
  }
114
124
  }
115
125
 
@@ -123,7 +133,28 @@ function register (agentRef, target, parent) {
123
133
  }
124
134
 
125
135
  /** only allow registered APIs to be tracked in the agent runtime */
126
- if (!isBlocked()) registeredEntities.push(api)
136
+ if (!isBlocked()) {
137
+ registeredEntities.push(api)
138
+ subscribeToPageUnload(reportTimings)
139
+ }
140
+
141
+ /**
142
+ * Reports the gathered timings for the registered entity through a custom event to the container agent. Only reports once
143
+ * by checking for the presence of the reportedAt timing.
144
+ * @returns {void}
145
+ */
146
+ function reportTimings () {
147
+ // only ever report the timings the first time this is called
148
+ if (timings.reportedAt) return
149
+ timings.reportedAt = now()
150
+ api.recordCustomEvent('MicroFrontEndTiming', {
151
+ timeToLoad: timings.registeredAt - timings.fetchStart, // fetchStart to registeredAt
152
+ timeToBeRequested: timings.fetchStart, // origin to fetchStart
153
+ timeToFetch: timings.fetchEnd - timings.fetchStart, // fetchStart to fetchEnd
154
+ timeToRegister: timings.registeredAt - timings.fetchEnd, // fetchEnd to registeredAt
155
+ timeAlive: timings.reportedAt - timings.registeredAt // registeredAt to reportedAt
156
+ })
157
+ }
127
158
 
128
159
  /**
129
160
  * Sets a value local to the registered API attrs. Will do nothing if APIs are deregistered.