@newrelic/browser-agent 1.319.0 → 1.320.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/cjs/common/config/init.js +17 -2
- package/dist/cjs/common/config/runtime.js +2 -1
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/constants/iframe-constants.js +16 -0
- package/dist/cjs/common/url/add-url.js +23 -0
- package/dist/cjs/common/util/console.js +40 -33
- package/dist/cjs/common/v2/mfe-vitals.js +9 -2
- package/dist/cjs/common/v2/script-correlation.js +2 -0
- package/dist/cjs/common/v2/script-tracker-constants.js +13 -0
- package/dist/cjs/common/v2/script-tracker.js +54 -29
- package/dist/cjs/common/v2/timing-factory.js +18 -0
- package/dist/cjs/common/v2/utils.js +100 -6
- package/dist/cjs/features/ajax/instrument/index.js +4 -14
- package/dist/cjs/features/utils/instrument-base.js +13 -0
- package/dist/cjs/interfaces/registered-iframe-entity.js +527 -0
- package/dist/cjs/loaders/api/register.js +10 -6
- package/dist/cjs/loaders/configure/configure.js +14 -0
- package/dist/cjs/loaders/configure/iframe-message-handler.js +270 -0
- package/dist/esm/common/config/init.js +17 -2
- package/dist/esm/common/config/runtime.js +2 -1
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/constants/iframe-constants.js +10 -0
- package/dist/esm/common/url/add-url.js +16 -0
- package/dist/esm/common/util/console.js +40 -33
- package/dist/esm/common/v2/mfe-vitals.js +9 -2
- package/dist/esm/common/v2/script-correlation.js +2 -0
- package/dist/esm/common/v2/script-tracker-constants.js +7 -0
- package/dist/esm/common/v2/script-tracker.js +54 -29
- package/dist/esm/common/v2/timing-factory.js +12 -0
- package/dist/esm/common/v2/utils.js +97 -6
- package/dist/esm/features/ajax/instrument/index.js +1 -11
- package/dist/esm/features/utils/instrument-base.js +13 -0
- package/dist/esm/interfaces/registered-iframe-entity.js +518 -0
- package/dist/esm/loaders/api/register.js +10 -6
- package/dist/esm/loaders/configure/configure.js +15 -1
- package/dist/esm/loaders/configure/iframe-message-handler.js +263 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/common/config/init.d.ts.map +1 -1
- package/dist/types/common/config/runtime.d.ts.map +1 -1
- package/dist/types/common/constants/iframe-constants.d.ts +11 -0
- package/dist/types/common/constants/iframe-constants.d.ts.map +1 -0
- package/dist/types/common/url/add-url.d.ts +2 -0
- package/dist/types/common/url/add-url.d.ts.map +1 -0
- package/dist/types/common/util/console.d.ts +40 -33
- package/dist/types/common/util/console.d.ts.map +1 -1
- package/dist/types/common/v2/mfe-vitals.d.ts.map +1 -1
- package/dist/types/common/v2/script-correlation.d.ts +2 -0
- package/dist/types/common/v2/script-correlation.d.ts.map +1 -1
- package/dist/types/common/v2/script-tracker-constants.d.ts +7 -0
- package/dist/types/common/v2/script-tracker-constants.d.ts.map +1 -0
- package/dist/types/common/v2/script-tracker.d.ts +4 -1
- package/dist/types/common/v2/script-tracker.d.ts.map +1 -1
- package/dist/types/common/v2/timing-factory.d.ts +9 -0
- package/dist/types/common/v2/timing-factory.d.ts.map +1 -0
- package/dist/types/common/v2/utils.d.ts +37 -0
- package/dist/types/common/v2/utils.d.ts.map +1 -1
- package/dist/types/features/ajax/instrument/index.d.ts.map +1 -1
- package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
- package/dist/types/interfaces/registered-iframe-entity.d.ts +116 -0
- package/dist/types/interfaces/registered-iframe-entity.d.ts.map +1 -0
- package/dist/types/loaders/configure/configure.d.ts.map +1 -1
- package/dist/types/loaders/configure/iframe-message-handler.d.ts +17 -0
- package/dist/types/loaders/configure/iframe-message-handler.d.ts.map +1 -0
- package/package.json +9 -1
- package/src/common/config/init.js +13 -2
- package/src/common/config/runtime.js +2 -1
- package/src/common/constants/iframe-constants.js +11 -0
- package/src/common/url/add-url.js +18 -0
- package/src/common/util/console.js +40 -33
- package/src/common/v2/mfe-vitals.js +7 -4
- package/src/common/v2/script-correlation.js +2 -0
- package/src/common/v2/script-tracker-constants.js +7 -0
- package/src/common/v2/script-tracker.js +65 -26
- package/src/common/v2/timing-factory.js +10 -0
- package/src/common/v2/utils.js +102 -6
- package/src/features/ajax/instrument/index.js +1 -13
- package/src/features/utils/instrument-base.js +12 -0
- package/src/interfaces/registered-iframe-entity.js +482 -0
- package/src/loaders/api/register.js +9 -5
- package/src/loaders/configure/configure.js +15 -1
- package/src/loaders/configure/iframe-message-handler.js +244 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,19 @@
|
|
|
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.320.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.319.0...v1.320.0) (2026-08-07)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* Improve ability to apply reasonable caps to MFE vitals ([#1823](https://github.com/newrelic/newrelic-browser-agent/issues/1823)) ([df72604](https://github.com/newrelic/newrelic-browser-agent/commit/df7260404dee51cf3706311235ab9b0d785f8789))
|
|
12
|
+
* MFE iframe support - Beta Access ([#1752](https://github.com/newrelic/newrelic-browser-agent/issues/1752)) ([46e36f7](https://github.com/newrelic/newrelic-browser-agent/commit/46e36f77abbad70155f20755cd4554799125e334))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* Deduplicate auto-instrumented MFE events ([#1820](https://github.com/newrelic/newrelic-browser-agent/issues/1820)) ([ba60ac6](https://github.com/newrelic/newrelic-browser-agent/commit/ba60ac6550d93feb466af845a8f3f3fdb03b4970))
|
|
18
|
+
|
|
6
19
|
## [1.319.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.318.0...v1.319.0) (2026-07-28)
|
|
7
20
|
|
|
8
21
|
|
|
@@ -29,7 +29,9 @@ const InitModelFn = () => {
|
|
|
29
29
|
feature_flags: [],
|
|
30
30
|
experimental: {
|
|
31
31
|
register: false,
|
|
32
|
-
resources: false
|
|
32
|
+
resources: false,
|
|
33
|
+
iframe_bridge: false,
|
|
34
|
+
iframe_domains: []
|
|
33
35
|
},
|
|
34
36
|
mask_selector: '*',
|
|
35
37
|
block_selector: '[data-nr-block]',
|
|
@@ -69,7 +71,20 @@ const InitModelFn = () => {
|
|
|
69
71
|
set enabled(val) {
|
|
70
72
|
hiddenState.experimental.register = val;
|
|
71
73
|
},
|
|
72
|
-
duplicate_data_to_container: false
|
|
74
|
+
duplicate_data_to_container: false,
|
|
75
|
+
// experimental iframe bridge feature
|
|
76
|
+
get allow_iframe_bridge() {
|
|
77
|
+
return hiddenState.feature_flags.includes(_constants.FEATURE_FLAGS.IFRAME_BRIDGE) || hiddenState.experimental.iframe_bridge;
|
|
78
|
+
},
|
|
79
|
+
set allow_iframe_bridge(val) {
|
|
80
|
+
hiddenState.experimental.iframe_bridge = val;
|
|
81
|
+
},
|
|
82
|
+
get iframe_domains() {
|
|
83
|
+
return hiddenState.experimental.iframe_domains;
|
|
84
|
+
},
|
|
85
|
+
set iframe_domains(val) {
|
|
86
|
+
if (Array.isArray(val)) hiddenState.experimental.iframe_domains = val;else (0, _console.warn)(1, val);
|
|
87
|
+
}
|
|
73
88
|
}
|
|
74
89
|
},
|
|
75
90
|
browser_consent_mode: {
|
|
@@ -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.
|
|
20
|
+
const VERSION = exports.VERSION = "1.320.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.
|
|
20
|
+
const VERSION = exports.VERSION = "1.320.0";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Exposes the build type of the agent
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.prefix = exports.IFRAME_VITALS_UPDATE = exports.IFRAME_TIMING_UPDATE = exports.IFRAME_API_RESPONSE = exports.IFRAME_API = exports.IFRAME_AJAX = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
9
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
10
|
+
*/
|
|
11
|
+
const prefix = exports.prefix = 'newrelic-iframe-';
|
|
12
|
+
const IFRAME_TIMING_UPDATE = exports.IFRAME_TIMING_UPDATE = prefix + 'timing-update';
|
|
13
|
+
const IFRAME_API = exports.IFRAME_API = prefix + 'api';
|
|
14
|
+
const IFRAME_API_RESPONSE = exports.IFRAME_API_RESPONSE = prefix + 'api-response';
|
|
15
|
+
const IFRAME_VITALS_UPDATE = exports.IFRAME_VITALS_UPDATE = prefix + 'vitals-update';
|
|
16
|
+
const IFRAME_AJAX = exports.IFRAME_AJAX = prefix + 'ajax';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.addUrl = addUrl;
|
|
7
|
+
var _parseUrl = require("./parse-url.js");
|
|
8
|
+
/**
|
|
9
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
10
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
function addUrl(ctx, url) {
|
|
14
|
+
var parsed = (0, _parseUrl.parseUrl)(url);
|
|
15
|
+
var params = ctx.params || ctx;
|
|
16
|
+
params.hostname = parsed.hostname;
|
|
17
|
+
params.port = parsed.port;
|
|
18
|
+
params.protocol = parsed.protocol;
|
|
19
|
+
params.host = parsed.hostname + ':' + parsed.port;
|
|
20
|
+
params.pathname = parsed.pathname;
|
|
21
|
+
ctx.parsedOrigin = parsed;
|
|
22
|
+
ctx.sameOrigin = parsed.sameOrigin;
|
|
23
|
+
}
|
|
@@ -21,39 +21,39 @@ var _globalEvent = require("../dispatch/global-event");
|
|
|
21
21
|
*
|
|
22
22
|
* | Code | Message |
|
|
23
23
|
* |------|---------|
|
|
24
|
-
* | 1 | An error occurred while setting a property of a Configurable |
|
|
25
|
-
* | 2 | An error occurred while setting a Configurable |
|
|
26
|
-
* | 3 | Setting a Configurable requires an object as input |
|
|
27
|
-
* | 4 | Setting a Configurable requires a model to set its initial properties |
|
|
24
|
+
* | 1 | An error occurred while setting a property of a Configurable. |
|
|
25
|
+
* | 2 | An error occurred while setting a Configurable. |
|
|
26
|
+
* | 3 | Setting a Configurable requires an object as input. |
|
|
27
|
+
* | 4 | Setting a Configurable requires a model to set its initial properties. |
|
|
28
28
|
* | 5 | An invalid session_replay.mask_selector was provided. \* will be used. |
|
|
29
|
-
* | 6 | An invalid session_replay.block_selector was provided and will not be used |
|
|
30
|
-
* | 7 | An invalid session_replay.mask_input_option was provided and will not be used |
|
|
31
|
-
* | 8 | Shared context requires an object as input |
|
|
32
|
-
* | 9 | An error occurred while setting SharedContext |
|
|
33
|
-
* | 10 | Failed to read from storage API |
|
|
34
|
-
* | 11 | Failed to write to the storage API |
|
|
29
|
+
* | 6 | An invalid session_replay.block_selector was provided and will not be used. |
|
|
30
|
+
* | 7 | An invalid session_replay.mask_input_option was provided and will not be used. |
|
|
31
|
+
* | 8 | Shared context requires an object as input. |
|
|
32
|
+
* | 9 | An error occurred while setting SharedContext. |
|
|
33
|
+
* | 10 | Failed to read from storage API. |
|
|
34
|
+
* | 11 | Failed to write to the storage API. |
|
|
35
35
|
* | 12 | An obfuscation replacement rule was detected missing a "regex" value. |
|
|
36
|
-
* | 13 | An obfuscation replacement rule contains a "regex" value with an invalid type (must be a string or RegExp) |
|
|
37
|
-
* | 14 | An obfuscation replacement rule contains a "replacement" value with an invalid type (must be a string) |
|
|
38
|
-
* | 15 | An error occurred while intercepting XHR |
|
|
39
|
-
* | 16 | Could not cast log message to string |
|
|
36
|
+
* | 13 | An obfuscation replacement rule contains a "regex" value with an invalid type (must be a string or RegExp). |
|
|
37
|
+
* | 14 | An obfuscation replacement rule contains a "replacement" value with an invalid type (must be a string). |
|
|
38
|
+
* | 15 | An error occurred while intercepting XHR. |
|
|
39
|
+
* | 16 | Could not cast log message to string. |
|
|
40
40
|
* | 17 | Could not calculate New Relic server time. Agent shutting down. |
|
|
41
41
|
* | 18 | RUM call failed. Agent shutting down. |
|
|
42
42
|
* | 19 | SPA scheduler is not initialized. Saved interaction is not sent! |
|
|
43
43
|
* | 20 | A problem occurred when starting up session manager. This page will not start or extend any session. |
|
|
44
44
|
* | 21 | Failed to initialize the agent. Could not determine the runtime environment. |
|
|
45
45
|
* | 22 | Failed to initialize all enabled instrument classes (agent aborted) - |
|
|
46
|
-
* | 23 | An unexpected issue occurred |
|
|
46
|
+
* | 23 | An unexpected issue occurred. |
|
|
47
47
|
* | 24 | Something prevented the agent from instrumenting. |
|
|
48
48
|
* | 25 | Something prevented the agent from being downloaded. |
|
|
49
49
|
* | 26 | Failed to initialize instrument classes. |
|
|
50
50
|
* | 27 | Downloading runtime APIs failed... |
|
|
51
51
|
* | 28 | The Browser Agent is attempting to send a very large payload. This is usually tied to large amounts of custom attributes. Please check your configurations. |
|
|
52
|
-
* | 29 | Failed to wrap logger: invalid argument(s) |
|
|
53
|
-
* | 30 | Invalid log level |
|
|
54
|
-
* | 31 | Ignored log: Log is larger than maximum payload size |
|
|
55
|
-
* | 32 | Ignored log: Invalid message |
|
|
56
|
-
* | 33 | Session Replay Aborted |
|
|
52
|
+
* | 29 | Failed to wrap logger: invalid argument(s). |
|
|
53
|
+
* | 30 | Invalid log level. |
|
|
54
|
+
* | 31 | Ignored log: Log is larger than maximum payload size. |
|
|
55
|
+
* | 32 | Ignored log: Invalid message. |
|
|
56
|
+
* | 33 | Session Replay Aborted. |
|
|
57
57
|
* | 34 | Downloading and initializing a feature failed... |
|
|
58
58
|
* | 35 | Call to agent api failed. The API is not currently initialized. |
|
|
59
59
|
* | 36 | A feature is enabled but one or more dependent features have not been initialized. This may cause unintended consequences or missing data... |
|
|
@@ -62,7 +62,7 @@ var _globalEvent = require("../dispatch/global-event");
|
|
|
62
62
|
* | 39 | Failed to execute setCustomAttribute. Name must be a string type. |
|
|
63
63
|
* | 40 | Failed to execute setCustomAttribute. Non-null value must be a string, number or boolean type. |
|
|
64
64
|
* | 41 | Failed to execute setUserId. Non-null value must be a string type. |
|
|
65
|
-
* | 42 | Failed to execute setApplicationVersion. Expected <String \| null
|
|
65
|
+
* | 42 | Failed to execute setApplicationVersion. Expected <String \| null>. |
|
|
66
66
|
* | 43 | Agent not configured properly. |
|
|
67
67
|
* | 44 | Invalid object passed to generic event aggregate. Missing "eventType". |
|
|
68
68
|
* | 45 | An internal agent process failed to execute. |
|
|
@@ -71,27 +71,34 @@ var _globalEvent = require("../dispatch/global-event");
|
|
|
71
71
|
* | 48 | Supplied an invalid API target. Must be an <Object> that contains valid (string) id and name properties. |
|
|
72
72
|
* | 49 | Supplied API target is missing an entityGuid. Some APIs may not behave correctly without a valid entityGuid (ex. logs). |
|
|
73
73
|
* | 50 | Failed to connect. Cannot allow registered API. |
|
|
74
|
-
* | 51 | Container agent is not available to register with. Can not connect |
|
|
74
|
+
* | 51 | Container agent is not available to register with. Can not connect. |
|
|
75
75
|
* | 52 | Unexpected problem encountered. There should be at least one app for harvest! |
|
|
76
|
-
* | 53 | Did not receive a valid entityGuid from connection response |
|
|
77
|
-
* | 54 | An experimental feature is being used. Support can not be offered for issues |
|
|
78
|
-
* | 55 | Register API has been disabled on the container agent |
|
|
79
|
-
* | 56 | Could not find a matching entity to store data |
|
|
76
|
+
* | 53 | Did not receive a valid entityGuid from connection response. |
|
|
77
|
+
* | 54 | An experimental feature is being used. Support can not be offered for issues. |
|
|
78
|
+
* | 55 | Register API has been disabled on the container agent. |
|
|
79
|
+
* | 56 | Could not find a matching entity to store data. |
|
|
80
80
|
* | 57 | Failed to execute measure. Arguments must have valid types. |
|
|
81
81
|
* | 58 | Failed to execute measure. Resulting duration must be non-negative. |
|
|
82
|
-
* | 59 | Session replay harvested before a session trace payload could be sent. This could be problematic for replays that rely on a trace |
|
|
83
|
-
* | 60 | Session trace aborted |
|
|
82
|
+
* | 59 | Session replay harvested before a session trace payload could be sent. This could be problematic for replays that rely on a trace. |
|
|
83
|
+
* | 60 | Session trace aborted. |
|
|
84
84
|
* | 61 | Timestamps must be non-negative and end time cannot be before start time. |
|
|
85
|
-
* | 62 | Timestamp must be a unix timestamp greater than the page origin time |
|
|
86
|
-
* | 63 | A single event was larger than the maximum allowed payload size |
|
|
85
|
+
* | 62 | Timestamp must be a unix timestamp greater than the page origin time. |
|
|
86
|
+
* | 63 | A single event was larger than the maximum allowed payload size. |
|
|
87
87
|
* | 64 | Required globals have been mutated before being accessed by the browser agent. This can cause issues and should be avoided. |
|
|
88
|
-
* | 65 | Consent API argument must be boolean or undefined |
|
|
89
|
-
* | 66 | A new agent session has started |
|
|
88
|
+
* | 65 | Consent API argument must be boolean or undefined. |
|
|
89
|
+
* | 66 | A new agent session has started. |
|
|
90
90
|
* | 67 | The "spa" feature has been deprecated and disabled. Please use/import "soft_navigations" instead for tracking of BrowserInteraction data. |
|
|
91
91
|
* | 68 | API has been deregistered and can no longer be used. Call "register" API again with credentials to start over. |
|
|
92
|
-
* | 69 | More than one Browser agent is running on the page |
|
|
92
|
+
* | 69 | More than one Browser agent is running on the page. |
|
|
93
93
|
* | 70 | A session replay payload failed to send and is being retried. Recording is paused during the retry period, and will resume when a successful harvest is made. Some replay activity may be missed during retry phases. |
|
|
94
94
|
* | 71 | An invalid feature mode was detected and set to "off". |
|
|
95
|
+
* | 72 | RegisteredIframeEntity failed to transmit API data from an iframe to window context. |
|
|
96
|
+
* | 73 | RegisteredIframeEntity failed to register with window context. |
|
|
97
|
+
* | 74 | RegisteredIframeEntity rejected message from unauthorized origin. |
|
|
98
|
+
* | 75 | RegisteredIframeEntity rejected message with mismatched iframeInterfaceId. |
|
|
99
|
+
* | 76 | Agent rejected post message, could not match with existing entity. |
|
|
100
|
+
* | 77 | Agent rejected post message, could not validate origin. |
|
|
101
|
+
* | 78 | RegisteredIframeEntity could not determine parent origin and will not register, to avoid trusting messages from any origin. |
|
|
95
102
|
*
|
|
96
103
|
* @param {number} code The warning code to emit, which will be used to link to the warning code documentation
|
|
97
104
|
* @param {*} [secondary] Secondary data to include, usually an extra message, error or object
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.trackMFEVitals = trackMFEVitals;
|
|
7
7
|
var _runtime = require("../constants/runtime");
|
|
8
8
|
var _now = require("../timing/now");
|
|
9
|
+
var _scriptTrackerConstants = require("./script-tracker-constants");
|
|
9
10
|
/**
|
|
10
11
|
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
11
12
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -139,11 +140,17 @@ function trackMFEVitals(target, timings) {
|
|
|
139
140
|
fcp: {
|
|
140
141
|
get value() {
|
|
141
142
|
return getTimeRelativeToScriptStart(fcpObservedAt);
|
|
143
|
+
},
|
|
144
|
+
set value(v) {
|
|
145
|
+
fcpObservedAt = v;
|
|
142
146
|
}
|
|
143
147
|
},
|
|
144
148
|
lcp: {
|
|
145
149
|
get value() {
|
|
146
150
|
return getTimeRelativeToScriptStart(lcpObservedAt);
|
|
151
|
+
},
|
|
152
|
+
set value(v) {
|
|
153
|
+
lcpObservedAt = v;
|
|
147
154
|
}
|
|
148
155
|
},
|
|
149
156
|
cls: {
|
|
@@ -157,11 +164,11 @@ function trackMFEVitals(target, timings) {
|
|
|
157
164
|
if (!target || !_runtime.isBrowserScope || !_runtime.globalScope.MutationObserver || !_runtime.globalScope.PerformanceObserver) return vitals;
|
|
158
165
|
const observers = [];
|
|
159
166
|
|
|
160
|
-
// If FCP hasn't been observed within
|
|
167
|
+
// If FCP hasn't been observed within this window, give up and shut down all observers.
|
|
161
168
|
// Once FCP is observed, the other vitals are left to record until their natural lifespan ends.
|
|
162
169
|
setTimeout(() => {
|
|
163
170
|
if (!fcpObservedAt) vitals.disconnect();
|
|
164
|
-
},
|
|
171
|
+
}, _scriptTrackerConstants.CORRELATION_STALE_THRESHOLD_MS);
|
|
165
172
|
const populateVitalMinimums = () => {
|
|
166
173
|
fcpObservedAt ??= (0, _now.now)();
|
|
167
174
|
lcpObservedAt ??= (0, _now.now)();
|
|
@@ -24,6 +24,8 @@ class ScriptCorrelation {
|
|
|
24
24
|
constructor(url) {
|
|
25
25
|
/** @type {string} The cleaned URL of the script */
|
|
26
26
|
this.url = url;
|
|
27
|
+
/** @type {Set<string>} MFE target ids that have already claimed this correlation via a completed `findScriptTimings()` call. Keyed per target rather than a single flag so one script that registers multiple distinct MFEs (each with its own id) doesn't have the second MFE's registration mistaken for a stale reuse of the first's. */
|
|
28
|
+
this.claimedBy = new Set();
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
/**
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.CORRELATION_STALE_THRESHOLD_MS = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
9
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** @type {number} - Correlations are keyed only by script URL and never cleared per-registration, so a script registered more than once in a session (SPA remount, shared bundle, etc.) can reuse a correlation from a much earlier load. If the correlation's recorded start predates `registeredAt` by more than this, treat it as stale and fall back to `registeredAt`. */
|
|
13
|
+
const CORRELATION_STALE_THRESHOLD_MS = exports.CORRELATION_STALE_THRESHOLD_MS = 10000;
|
|
@@ -12,6 +12,8 @@ var _now = require("../timing/now");
|
|
|
12
12
|
var _cleanUrl = require("../url/clean-url");
|
|
13
13
|
var _browserStackMatchers = require("../util/browser-stack-matchers");
|
|
14
14
|
var _scriptCorrelation = require("./script-correlation");
|
|
15
|
+
var _scriptTrackerConstants = require("./script-tracker-constants");
|
|
16
|
+
var _timingFactory = require("./timing-factory");
|
|
15
17
|
/**
|
|
16
18
|
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
17
19
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -19,6 +21,7 @@ var _scriptCorrelation = require("./script-correlation");
|
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
23
|
* @typedef {import('./register-api-types').RegisterAPITimings} RegisterAPITimings
|
|
24
|
+
* @typedef {import('../../loaders/api/register-api-types').RegisterAPITarget} RegisterAPITarget
|
|
22
25
|
*/
|
|
23
26
|
|
|
24
27
|
/** export for testing purposes */
|
|
@@ -30,7 +33,7 @@ try {
|
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
/** @type {(entry: PerformanceEntry) => boolean} - A shared function to determine if a performance entry is a valid script or link resource for evaluation */
|
|
33
|
-
const validEntryCriteria = entry => entry.initiatorType === 'script' || ['link', 'fetch'].includes(entry.initiatorType) && entry.name.endsWith('.js');
|
|
36
|
+
const validEntryCriteria = entry => entry.initiatorType === 'script' || ['link', 'fetch'].includes(entry.initiatorType) && (0, _cleanUrl.cleanURL)(entry.name).endsWith('.js');
|
|
34
37
|
|
|
35
38
|
/** @type {Map<string, ScriptCorrelation>} - Central registry for script correlations containing both DOM and Performance data */
|
|
36
39
|
const scriptCorrelations = exports.scriptCorrelations = new Map();
|
|
@@ -205,11 +208,32 @@ function applyPerformanceEntry(timings, entry) {
|
|
|
205
208
|
timings.type = entry.initiatorType;
|
|
206
209
|
}
|
|
207
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Subscribes to late resource timing emissions for a script URL.
|
|
213
|
+
* @param {RegisterAPITimings} timings - The timings object to update
|
|
214
|
+
* @param {string} mfeScriptUrl - The script URL to match
|
|
215
|
+
*/
|
|
216
|
+
function subscribeToLatePerformanceEntry(timings, mfeScriptUrl) {
|
|
217
|
+
if (!_runtime.globalScope.PerformanceObserver?.supportedEntryTypes?.includes('resource')) return;
|
|
218
|
+
poSubscribers.push({
|
|
219
|
+
addedAt: (0, _now.now)(),
|
|
220
|
+
test: entry => {
|
|
221
|
+
if (entryMatchesUrl(entry, mfeScriptUrl)) {
|
|
222
|
+
applyPerformanceEntry(timings, entry);
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
208
230
|
/**
|
|
209
231
|
* 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.
|
|
232
|
+
* @param {RegisterAPITarget} [target] - The MFE target being registered. Its id is used to scope stale-correlation detection per-MFE rather than per-script-URL, so one script registering multiple distinct MFEs doesn't misclassify a later MFE's registration as a stale reuse of an earlier one's.
|
|
210
233
|
* @returns {RegisterAPITimings} Object containing script fetch start and end times, and the asset URL if found
|
|
211
234
|
*/
|
|
212
|
-
function findScriptTimings() {
|
|
235
|
+
function findScriptTimings(target) {
|
|
236
|
+
const mfeId = target?.id;
|
|
213
237
|
const timings = {
|
|
214
238
|
registeredAt: (0, _now.now)(),
|
|
215
239
|
reportedAt: undefined,
|
|
@@ -239,38 +263,39 @@ function findScriptTimings() {
|
|
|
239
263
|
// Get correlation data
|
|
240
264
|
timings.correlation = findCorrelation(mfeScriptUrl);
|
|
241
265
|
|
|
242
|
-
// Use correlation's performance entry if available, otherwise check live performance API
|
|
243
|
-
const performanceEntry = timings.correlation?.performance.value || performance
|
|
266
|
+
// Use correlation's performance entry if available, otherwise check the live performance API before falling back to the buffered observer.
|
|
267
|
+
const performanceEntry = timings.correlation?.performance.value || _runtime.globalScope.performance?.getEntriesByType('resource')?.find(e => entryMatchesUrl(e, mfeScriptUrl));
|
|
244
268
|
if (performanceEntry) {
|
|
245
269
|
applyPerformanceEntry(timings, performanceEntry);
|
|
246
|
-
} else
|
|
247
|
-
|
|
248
|
-
timings.asset = mfeScriptUrl;
|
|
249
|
-
timings.type = 'preload';
|
|
270
|
+
} else {
|
|
271
|
+
const isPreloaded = wasPreloaded(mfeScriptUrl);
|
|
250
272
|
|
|
251
|
-
//
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
return true;
|
|
258
|
-
}
|
|
259
|
-
return false;
|
|
260
|
-
}
|
|
261
|
-
});
|
|
273
|
+
// Handle preloaded scripts and any late resource emissions through the shared buffered observer.
|
|
274
|
+
if (isPreloaded) {
|
|
275
|
+
timings.asset = mfeScriptUrl;
|
|
276
|
+
timings.type = 'preload';
|
|
277
|
+
}
|
|
278
|
+
subscribeToLatePerformanceEntry(timings, mfeScriptUrl);
|
|
262
279
|
}
|
|
263
280
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
281
|
+
// A correlation can be reused across multiple `register()` calls for the same script URL (e.g. an SPA
|
|
282
|
+
// remounting the same MFE without the script actually reloading). When that happens, its dom/performance
|
|
283
|
+
// timings still describe the *original* load, not this one. Detect that case so scriptStart/scriptEnd
|
|
284
|
+
// below can ignore the stale data instead of reporting it as if it were fresh.
|
|
285
|
+
const correlation = timings.correlation;
|
|
286
|
+
const alreadyClaimedByThisMFE = !!mfeId && !!correlation?.claimedBy.has(mfeId);
|
|
287
|
+
if (correlation && mfeId) correlation.claimedBy.add(mfeId);
|
|
288
|
+
const isCorrelationStale = () => {
|
|
289
|
+
const correlationStart = correlation?.script.start;
|
|
290
|
+
if (!alreadyClaimedByThisMFE || !correlationStart) return false;
|
|
291
|
+
const staleness = timings.registeredAt - correlationStart;
|
|
292
|
+
return staleness > _scriptTrackerConstants.CORRELATION_STALE_THRESHOLD_MS;
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
// Use getters here because the correlation data may arrive after this function returns the timing object, and we want to provide the most up-to-date timing information possible when the getters are accessed at harvest time.
|
|
296
|
+
// Non-stale: fall back to fetchEnd if correlation data isn't available yet (our best approximation for script execution start). Stale: fall back straight to registeredAt — fetchEnd would be derived from the same stale correlation, so it can't be trusted either.
|
|
297
|
+
Object.defineProperty(timings, 'scriptStart', (0, _timingFactory.timingFactory)(() => isCorrelationStale() ? timings.registeredAt : correlation?.script.start ?? timings.fetchEnd));
|
|
298
|
+
Object.defineProperty(timings, 'scriptEnd', (0, _timingFactory.timingFactory)(() => isCorrelationStale() ? timings.registeredAt : correlation?.script.end ?? timings.registeredAt));
|
|
274
299
|
} catch (error) {
|
|
275
300
|
// Don't let stack parsing errors break anything
|
|
276
301
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.timingFactory = timingFactory;
|
|
7
|
+
/**
|
|
8
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
9
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
10
|
+
*/
|
|
11
|
+
function timingFactory(getLazyValue = () => {}, override) {
|
|
12
|
+
return {
|
|
13
|
+
get: () => override !== undefined ? override : getLazyValue(),
|
|
14
|
+
set: newValue => {
|
|
15
|
+
override = newValue;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -4,7 +4,10 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.V2_TYPES = void 0;
|
|
7
|
+
exports.dedupeRegisteredEntitiesByAsset = dedupeRegisteredEntitiesByAsset;
|
|
8
|
+
exports.dedupeTargetsByInstance = dedupeTargetsByInstance;
|
|
7
9
|
exports.findTargetsFromStackTrace = findTargetsFromStackTrace;
|
|
10
|
+
exports.getRegisteredEntityByIframeInterfaceId = getRegisteredEntityByIframeInterfaceId;
|
|
8
11
|
exports.getRegisteredTargetsFromFilename = getRegisteredTargetsFromFilename;
|
|
9
12
|
exports.getRegisteredTargetsFromId = getRegisteredTargetsFromId;
|
|
10
13
|
exports.getVersion2Attributes = getVersion2Attributes;
|
|
@@ -27,6 +30,18 @@ const V2_TYPES = exports.V2_TYPES = {
|
|
|
27
30
|
BA: 'BA'
|
|
28
31
|
};
|
|
29
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Returns a single registered entity associated with a given iframe interface ID. Returns undefined if no entity is found.
|
|
35
|
+
* @param {string} iframeInterfaceId
|
|
36
|
+
* @param {*} agentRef the agent reference
|
|
37
|
+
* @returns {import("../../loaders/api/register-api-types").RegisterAPI|undefined}
|
|
38
|
+
*/
|
|
39
|
+
function getRegisteredEntityByIframeInterfaceId(iframeInterfaceId, agentRef) {
|
|
40
|
+
if (!isValid(iframeInterfaceId, agentRef)) return undefined;
|
|
41
|
+
const registeredEntities = agentRef.runtime.registeredEntities;
|
|
42
|
+
return registeredEntities?.find(entity => entity.metadata.target.iframeInterfaceId === iframeInterfaceId);
|
|
43
|
+
}
|
|
44
|
+
|
|
30
45
|
/**
|
|
31
46
|
* Returns the registered target associated with a given ID. Returns an empty array if no target is found.
|
|
32
47
|
* @param {string|number} id
|
|
@@ -34,21 +49,69 @@ const V2_TYPES = exports.V2_TYPES = {
|
|
|
34
49
|
* @returns {import("../../interfaces/registered-entity").RegisterAPIMetadataTarget[]}
|
|
35
50
|
*/
|
|
36
51
|
function getRegisteredTargetsFromId(id, agentRef) {
|
|
37
|
-
if (!id
|
|
52
|
+
if (!isValid(id, agentRef)) return [];
|
|
38
53
|
const registeredEntities = agentRef.runtime.registeredEntities;
|
|
39
54
|
return registeredEntities?.filter(entity => String(entity.metadata.target.id) === String(id)).map(entity => entity.metadata.target) || [];
|
|
40
55
|
}
|
|
41
56
|
|
|
42
57
|
/**
|
|
43
58
|
* Returns the registered target(s) associated with a given filename if found in the resource timing API during registration. Returns an empty array if no target is found.
|
|
59
|
+
* Multiple registrations that resolve to the same underlying script asset AND represent the same logical MFE (i.e.
|
|
60
|
+
* share the same customer-supplied `target.id`) are collapsed to a single target via
|
|
61
|
+
* {@link dedupeRegisteredEntitiesByAsset}, since auto-instrumented events (AJAX, JS errors, logs, WebSockets) should
|
|
62
|
+
* only be reported once per real occurrence when the same MFE was registered many times over -- not once per
|
|
63
|
+
* duplicate registration. Distinct MFEs (different `target.id`) that happen to share a script (e.g. two different
|
|
64
|
+
* MFEs both registered from the same inline `<script>` block) are intentionally NOT collapsed, since each is a
|
|
65
|
+
* genuinely different entity that should still receive its own copy of the matched event.
|
|
44
66
|
* @param {string} filename
|
|
45
67
|
* @param {*} agentRef
|
|
46
68
|
* @returns {import("../../interfaces/registered-entity").RegisterAPIMetadataTarget[]}
|
|
47
69
|
*/
|
|
48
70
|
function getRegisteredTargetsFromFilename(filename, agentRef) {
|
|
49
|
-
if (!filename
|
|
71
|
+
if (!isValid(filename, agentRef)) return [];
|
|
50
72
|
const registeredEntities = agentRef.runtime.registeredEntities;
|
|
51
|
-
|
|
73
|
+
const matches = registeredEntities?.filter(entity => entity.metadata.timings?.asset?.endsWith(filename));
|
|
74
|
+
return dedupeRegisteredEntitiesByAsset(matches).map(entity => entity.metadata.target);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Collapses a list of registered entities down to one canonical entity per unique, defined `metadata.timings.asset` +
|
|
79
|
+
* `target.id` combination. Entities whose asset could not be resolved (undefined -- e.g. inline scripts or scripts
|
|
80
|
+
* not found in the resource timing buffer) are never deduped against each other or against resolved entities, since
|
|
81
|
+
* there is no signal that they represent the same underlying script. Entities that share an asset but have different
|
|
82
|
+
* `target.id`s are never deduped against each other either, since a differing id means the customer registered
|
|
83
|
+
* genuinely distinct MFEs (not duplicate registrations of the same one).
|
|
84
|
+
*
|
|
85
|
+
* Canonical selection per asset+id: prefer an entity whose target has not been deregistered (`target.blocked ===
|
|
86
|
+
* false`) over one that has; otherwise the first-encountered entity wins, for determinism.
|
|
87
|
+
* @param {Array} entities registered entities (each with `metadata.timings.asset` and `metadata.target`)
|
|
88
|
+
* @returns {Array} deduped list of entities, preserving relative order of first occurrence
|
|
89
|
+
*/
|
|
90
|
+
function dedupeRegisteredEntitiesByAsset(entities) {
|
|
91
|
+
if (!entities?.length) return entities || [];
|
|
92
|
+
const byKey = new Map(); // `${asset}::${id}` -> canonical entity
|
|
93
|
+
const result = [];
|
|
94
|
+
for (const entity of entities) {
|
|
95
|
+
const asset = entity.metadata?.timings?.asset;
|
|
96
|
+
if (!asset) {
|
|
97
|
+
// can't safely dedupe unresolved-asset entities -- always keep as-is
|
|
98
|
+
result.push(entity);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const key = "".concat(asset, "::").concat(entity.metadata?.target?.id);
|
|
102
|
+
const existing = byKey.get(key);
|
|
103
|
+
if (!existing) {
|
|
104
|
+
byKey.set(key, entity);
|
|
105
|
+
result.push(entity);
|
|
106
|
+
} else if (existing.metadata.target?.blocked && !entity.metadata.target?.blocked) {
|
|
107
|
+
// swap in a non-deregistered target as the canonical one for this asset+id
|
|
108
|
+
const idx = result.indexOf(existing);
|
|
109
|
+
if (idx !== -1) result[idx] = entity;
|
|
110
|
+
byKey.set(key, entity);
|
|
111
|
+
}
|
|
112
|
+
// else: existing canonical entity wins (already non-blocked, or both blocked -- first wins); drop this duplicate
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
52
115
|
}
|
|
53
116
|
|
|
54
117
|
/**
|
|
@@ -107,7 +170,7 @@ function shouldDuplicate(target, aggregateInstance) {
|
|
|
107
170
|
* @returns {Array} An array of targets found from the stack trace. If no targets are found or allowed, returns an array with undefined.
|
|
108
171
|
*/
|
|
109
172
|
function findTargetsFromStackTrace(agentRef) {
|
|
110
|
-
if (!agentRef
|
|
173
|
+
if (!isValid(true, agentRef) || !agentRef?.runtime?.registeredEntities?.length) return [undefined];
|
|
111
174
|
const targets = [];
|
|
112
175
|
try {
|
|
113
176
|
var urls = (0, _scriptTracker.extractUrlsFromStack)((0, _scriptTracker.getDeepStackTrace)());
|
|
@@ -118,8 +181,29 @@ function findTargetsFromStackTrace(agentRef) {
|
|
|
118
181
|
} catch (err) {
|
|
119
182
|
// Silent catch to prevent errors from propagating
|
|
120
183
|
}
|
|
121
|
-
|
|
122
|
-
return
|
|
184
|
+
const deduped = dedupeTargetsByInstance(targets);
|
|
185
|
+
if (!deduped.length) deduped.push(undefined); // if we can't find any targets from the stack trace, return an array with undefined to signify the container agent is the target
|
|
186
|
+
return deduped;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Removes duplicate targets from an array, keyed by `target.instance`. This guards against the same canonical
|
|
191
|
+
* target re-entering the array via multiple matched stack-frame URLs (e.g. a recursive call whose stack contains the
|
|
192
|
+
* same file at multiple depths). Entries with no `instance` (i.e. `undefined`, meaning "the container agent")
|
|
193
|
+
* naturally collapse to a single entry too, which is the desired behavior.
|
|
194
|
+
* @param {Array} targets
|
|
195
|
+
* @returns {Array} deduped list of targets, preserving relative order of first occurrence
|
|
196
|
+
*/
|
|
197
|
+
function dedupeTargetsByInstance(targets) {
|
|
198
|
+
const seen = new Set();
|
|
199
|
+
const result = [];
|
|
200
|
+
for (const target of targets) {
|
|
201
|
+
const key = target?.instance;
|
|
202
|
+
if (seen.has(key)) continue;
|
|
203
|
+
seen.add(key);
|
|
204
|
+
result.push(target);
|
|
205
|
+
}
|
|
206
|
+
return result;
|
|
123
207
|
}
|
|
124
208
|
|
|
125
209
|
/**
|
|
@@ -131,4 +215,14 @@ function findTargetsFromStackTrace(agentRef) {
|
|
|
131
215
|
*/
|
|
132
216
|
function supportsV2(aggregateInstance) {
|
|
133
217
|
return aggregateInstance?.harvestEndpointVersion === 2;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Determines if the given identifier and agent reference are valid for use for entity lookups and other operations in the utils methods. This is a common check that is used across multiple methods in this module to ensure that the necessary data is present and that the register API is enabled before attempting to perform operations that depend on those things.
|
|
222
|
+
* @param {*} identifier The identifier to check.
|
|
223
|
+
* @param {*} agentRef The agent reference to check.
|
|
224
|
+
* @returns {boolean} Returns true if the identifier and agent reference are valid, false otherwise.
|
|
225
|
+
*/
|
|
226
|
+
function isValid(identifier, agentRef) {
|
|
227
|
+
return !!identifier && !!agentRef?.init.api.register.enabled;
|
|
134
228
|
}
|