@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
|
@@ -22,6 +22,7 @@ var _constants2 = require("../../metrics/constants");
|
|
|
22
22
|
var _now = require("../../../common/timing/now");
|
|
23
23
|
var _denyList = require("../../../common/deny-list/deny-list");
|
|
24
24
|
var _extractUrl = require("../../../common/url/extract-url");
|
|
25
|
+
var _addUrl = require("../../../common/url/add-url");
|
|
25
26
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } /**
|
|
26
27
|
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
27
28
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -63,7 +64,7 @@ class Instrument extends _instrumentBase.InstrumentBase {
|
|
|
63
64
|
duration: Math.floor(resource.duration),
|
|
64
65
|
cbTime: 0
|
|
65
66
|
};
|
|
66
|
-
addUrl(params, resource.name);
|
|
67
|
+
(0, _addUrl.addUrl)(params, resource.name);
|
|
67
68
|
this.handler('xhr', [params, metrics, resource.startTime, resource.responseEnd, initiators[resource.initiatorType]], undefined, _features.FEATURE_NAMES.ajax);
|
|
68
69
|
}
|
|
69
70
|
});
|
|
@@ -129,7 +130,7 @@ function subscribeToEvents(agentRef, ee, handler, dt) {
|
|
|
129
130
|
this.params = {
|
|
130
131
|
method: args[0]
|
|
131
132
|
};
|
|
132
|
-
addUrl(this, args[1]);
|
|
133
|
+
(0, _addUrl.addUrl)(this, args[1]);
|
|
133
134
|
this.metrics = {};
|
|
134
135
|
}
|
|
135
136
|
function onOpenXhrEnd(args, xhr) {
|
|
@@ -305,7 +306,7 @@ function subscribeToEvents(agentRef, ee, handler, dt) {
|
|
|
305
306
|
this.startTime = (0, _now.now)();
|
|
306
307
|
this.dt = dtPayload;
|
|
307
308
|
let [target, opts = {}] = fetchArguments;
|
|
308
|
-
addUrl(this, (0, _extractUrl.extractUrl)(target));
|
|
309
|
+
(0, _addUrl.addUrl)(this, (0, _extractUrl.extractUrl)(target));
|
|
309
310
|
const method = ('' + (target && target instanceof origRequest && target.method || opts.method || 'GET')).toUpperCase();
|
|
310
311
|
this.params.method = method;
|
|
311
312
|
this.txSize = (0, _dataSize.dataSize)(opts.body || target?.body) || 0;
|
|
@@ -441,17 +442,6 @@ function subscribeToEvents(agentRef, ee, handler, dt) {
|
|
|
441
442
|
ctx.loadCaptureCalled = true;
|
|
442
443
|
}
|
|
443
444
|
}
|
|
444
|
-
function addUrl(ctx, url) {
|
|
445
|
-
var parsed = (0, _parseUrl.parseUrl)(url);
|
|
446
|
-
var params = ctx.params || ctx;
|
|
447
|
-
params.hostname = parsed.hostname;
|
|
448
|
-
params.port = parsed.port;
|
|
449
|
-
params.protocol = parsed.protocol;
|
|
450
|
-
params.host = parsed.hostname + ':' + parsed.port;
|
|
451
|
-
params.pathname = parsed.pathname;
|
|
452
|
-
ctx.parsedOrigin = parsed;
|
|
453
|
-
ctx.sameOrigin = parsed.sameOrigin;
|
|
454
|
-
}
|
|
455
445
|
function parseResponseHeaders(headerStr) {
|
|
456
446
|
const headers = {};
|
|
457
447
|
if (!headerStr) return headers;
|
|
@@ -116,6 +116,19 @@ class InstrumentBase extends _featureBase.FeatureBase {
|
|
|
116
116
|
this.ee.emit('internal-error', [e]);
|
|
117
117
|
(0, _handle.handle)(_agentConstants.SESSION_ERROR, [e], undefined, this.featureName, this.ee);
|
|
118
118
|
}
|
|
119
|
+
if (agentRef.init.api.register.allow_iframe_bridge) {
|
|
120
|
+
try {
|
|
121
|
+
// This chunk doesn't exist in the lite build (see webpack IgnorePlugin config) since none
|
|
122
|
+
// of lite's features wire up agent.register -- guard against that rather than letting an
|
|
123
|
+
// unhandled rejection surface if this flag is ever set on a lite page.
|
|
124
|
+
const {
|
|
125
|
+
setupIframeMFEMessageListener
|
|
126
|
+
} = await Promise.resolve().then(() => _interopRequireWildcard(require(/* webpackChunkName: "iframe-message-handler" */'../../loaders/configure/iframe-message-handler')));
|
|
127
|
+
setupIframeMFEMessageListener(agentRef);
|
|
128
|
+
} catch (e) {
|
|
129
|
+
(0, _console.warn)(23, e);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
119
132
|
|
|
120
133
|
/**
|
|
121
134
|
* Note this try-catch differs from the one in Agent.run() in that it's placed later in a page's lifecycle and
|
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.RegisteredIframeEntity = void 0;
|
|
7
|
+
var _webVitals = require("web-vitals");
|
|
8
|
+
var _runtime = require("../common/constants/runtime");
|
|
9
|
+
var _iframe = require("../common/dom/iframe");
|
|
10
|
+
var _now = require("../common/timing/now");
|
|
11
|
+
var _console = require("../common/util/console");
|
|
12
|
+
var _scriptTracker = require("../common/v2/script-tracker");
|
|
13
|
+
var _addUrl = require("../common/url/add-url");
|
|
14
|
+
var _uniqueId = require("../common/ids/unique-id");
|
|
15
|
+
var _iframeConstants = require("../common/constants/iframe-constants");
|
|
16
|
+
var _castError = require("../features/jserrors/shared/cast-error");
|
|
17
|
+
/**
|
|
18
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
19
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
// libraries
|
|
23
|
+
|
|
24
|
+
// internal
|
|
25
|
+
|
|
26
|
+
const REGISTER = 'register'; // define it here to prevent importing the full list of constants for build size.
|
|
27
|
+
const VITALS = [[_webVitals.onCLS, 'cls'], [_webVitals.onLCP, 'lcp'], [_webVitals.onFCP, 'fcp'], [_webVitals.onINP, 'inp']];
|
|
28
|
+
const AJAX_INITIATOR_TYPES = {
|
|
29
|
+
xmlhttprequest: 'xhr',
|
|
30
|
+
fetch: 'fetch',
|
|
31
|
+
beacon: 'beacon'
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @typedef {import('../loaders/api/register-api-types').RegisterAPI} RegisterAPI
|
|
36
|
+
* @typedef {import('../loaders/api/register-api-types').RegisterAPIMetadata} RegisterAPIMetadata
|
|
37
|
+
* @typedef {import('../loaders/api/register-api-types').RegisterAPIConstructor} RegisterAPIConstructor
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @experimental
|
|
42
|
+
* IMPORTANT: This feature is being developed for use internally and is not in a public-facing production-ready state.
|
|
43
|
+
* It is not recommended for use in production environments and will not receive support for issues.
|
|
44
|
+
*
|
|
45
|
+
* An interface for registering an external caller to report through the base agent to a different target than the base agent.
|
|
46
|
+
*/
|
|
47
|
+
class RegisteredIframeEntity {
|
|
48
|
+
/** @type {RegisterAPIMetadata} */
|
|
49
|
+
metadata = {
|
|
50
|
+
target: {},
|
|
51
|
+
timings: {},
|
|
52
|
+
customAttributes: {},
|
|
53
|
+
vitals: {
|
|
54
|
+
cls: {
|
|
55
|
+
value: null
|
|
56
|
+
},
|
|
57
|
+
lcp: {
|
|
58
|
+
value: null
|
|
59
|
+
},
|
|
60
|
+
fcp: {
|
|
61
|
+
value: null
|
|
62
|
+
},
|
|
63
|
+
inp: {
|
|
64
|
+
value: null
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/** @private Map to store pending promise resolvers keyed by message ID */
|
|
70
|
+
#pendingMessages = new Map();
|
|
71
|
+
/** @private Unique ID for this iframe interface instance to correlate messages */
|
|
72
|
+
#iframeInterfaceId = (0, _uniqueId.generateUuid)();
|
|
73
|
+
/** @private Counter for generating unique message IDs */
|
|
74
|
+
#messageIdCounter = 0;
|
|
75
|
+
/** @private Promise that resolves when registration with parent completes */
|
|
76
|
+
#registrationPromise = null;
|
|
77
|
+
/** @private Resource timing observer used to seed AJAX state */
|
|
78
|
+
#resourceObserver = null;
|
|
79
|
+
/** @private Original target descriptor (serializable) for postMessage */
|
|
80
|
+
#targetDescriptor = null;
|
|
81
|
+
/**
|
|
82
|
+
* Whether this entity is blocked from sending further calls to the container. Backed by
|
|
83
|
+
* `this.metadata.target.blocked` (rather than an independent field) so that this always
|
|
84
|
+
* reflects the container's blocked state too, including future syncs from response metadata --
|
|
85
|
+
* there is only ever one source of truth for "blocked", not one flag per side.
|
|
86
|
+
* @returns {boolean}
|
|
87
|
+
*/
|
|
88
|
+
get blocked() {
|
|
89
|
+
return !!this.metadata.target.blocked;
|
|
90
|
+
}
|
|
91
|
+
set blocked(value) {
|
|
92
|
+
this.metadata.target.blocked = value;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** @private Parent window origin for secure postMessage */
|
|
96
|
+
#parentOrigin = (() => {
|
|
97
|
+
try {
|
|
98
|
+
return _runtime.globalScope?.location?.ancestorOrigins?.[0] || (_runtime.globalScope?.document?.referrer ? new URL(_runtime.globalScope.document.referrer).origin : '*');
|
|
99
|
+
} catch (e) {
|
|
100
|
+
return '*';
|
|
101
|
+
}
|
|
102
|
+
})();
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
*
|
|
106
|
+
* @param {RegisterAPIConstructor} opts The options for setting up the registered iframe entity.
|
|
107
|
+
*/
|
|
108
|
+
constructor(opts) {
|
|
109
|
+
(0, _console.warn)(54);
|
|
110
|
+
// Store original descriptor for postMessage (before any function merging)
|
|
111
|
+
this.metadata.target = this.#targetDescriptor = opts;
|
|
112
|
+
if (!_runtime.isBrowserScope || !(0, _iframe.isIFrameWindow)(_runtime.globalScope)) {
|
|
113
|
+
(0, _console.warn)(72);
|
|
114
|
+
this.blocked = true;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (this.#parentOrigin === '*') {
|
|
118
|
+
// If the parent's origin cannot be determined, fail closed rather than allow postMessage
|
|
119
|
+
// traffic to/from any origin
|
|
120
|
+
(0, _console.warn)(78);
|
|
121
|
+
this.blocked = true;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Store the registration promise so other methods can wait for it
|
|
126
|
+
this.#registrationPromise = this.#register(opts);
|
|
127
|
+
this.#registrationPromise.then(() => {
|
|
128
|
+
const timings = (0, _scriptTracker.findScriptTimings)();
|
|
129
|
+
// Send initial timing values, skipping ones still at their "not yet known" default
|
|
130
|
+
// (e.g. reportedAt: undefined, fetchStart: 0) to avoid pointless postMessage calls
|
|
131
|
+
for (const [key, value] of Object.entries(timings)) {
|
|
132
|
+
if (key !== 'correlation' && value) {
|
|
133
|
+
this.#postTimingToAgent(key, value);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Proxy the timings object to watch for updates to fetchStart, fetchEnd, asset, type
|
|
137
|
+
this.metadata.timings = new Proxy(timings, {
|
|
138
|
+
set: (target, key, value) => {
|
|
139
|
+
const changed = target[key] !== value;
|
|
140
|
+
target[key] = value;
|
|
141
|
+
|
|
142
|
+
// Send updates for these 4 properties when they change
|
|
143
|
+
if (changed && this.metadata.target.id && key !== 'correlation') {
|
|
144
|
+
this.#postTimingToAgent(key, value);
|
|
145
|
+
}
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}).catch(() => {});
|
|
150
|
+
this.#setupErrorListeners();
|
|
151
|
+
this.#setupVitalsListeners();
|
|
152
|
+
this.#setupAjaxObserver();
|
|
153
|
+
this.#setupResponseListener();
|
|
154
|
+
this.#bindPublicMethods();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Registers this entity with the parent agent, then seeds initial timings and FCP.
|
|
159
|
+
* @private
|
|
160
|
+
* @param {RegisterAPIConstructor} opts
|
|
161
|
+
* @returns {Promise<void>}
|
|
162
|
+
*/
|
|
163
|
+
async #register(opts) {
|
|
164
|
+
try {
|
|
165
|
+
const response = await this.#postMethodToAgent(REGISTER, [opts]);
|
|
166
|
+
if (response.metadata) Object.assign(this.metadata, response.metadata);
|
|
167
|
+
return response;
|
|
168
|
+
} catch (err) {
|
|
169
|
+
(0, _console.warn)(73, err);
|
|
170
|
+
this.blocked = true;
|
|
171
|
+
throw err;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Wires up global error/rejection listeners that funnel into noticeError.
|
|
177
|
+
* @private
|
|
178
|
+
*/
|
|
179
|
+
#setupErrorListeners() {
|
|
180
|
+
_runtime.globalScope.addEventListener('error', err => {
|
|
181
|
+
this.noticeError((0, _castError.castErrorEvent)(err));
|
|
182
|
+
});
|
|
183
|
+
_runtime.globalScope.addEventListener('unhandledrejection', event => {
|
|
184
|
+
this.noticeError((0, _castError.castPromiseRejectionEvent)(event));
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Wires up web-vitals callbacks to report vitals updates to the parent.
|
|
190
|
+
* @private
|
|
191
|
+
*/
|
|
192
|
+
#setupVitalsListeners() {
|
|
193
|
+
VITALS.forEach(([vitalFn, property]) => {
|
|
194
|
+
vitalFn(({
|
|
195
|
+
value
|
|
196
|
+
}) => {
|
|
197
|
+
this.metadata.vitals[property].value = value;
|
|
198
|
+
this.#postMessageToParent(_iframeConstants.IFRAME_VITALS_UPDATE, {
|
|
199
|
+
entries: [{
|
|
200
|
+
property,
|
|
201
|
+
value
|
|
202
|
+
}]
|
|
203
|
+
});
|
|
204
|
+
}, {
|
|
205
|
+
reportAllChanges: property === 'cls' || property === 'inp'
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Instruments ajax using buffered resource timing so pre-registration entries are included.
|
|
212
|
+
* @private
|
|
213
|
+
*/
|
|
214
|
+
#setupAjaxObserver() {
|
|
215
|
+
if (!_runtime.globalScope.PerformanceObserver?.supportedEntryTypes?.includes('resource')) return;
|
|
216
|
+
this.#resourceObserver = new _runtime.globalScope.PerformanceObserver(list => {
|
|
217
|
+
// Batched into a single postMessage per observer callback rather than one per entry --
|
|
218
|
+
// with buffered: true this callback can fire with dozens of pre-existing entries at once,
|
|
219
|
+
// and each postMessage carries its own await/registration/dispatch overhead independent of
|
|
220
|
+
// payload size, so sending them individually multiplies that overhead for no benefit.
|
|
221
|
+
const entries = list.getEntries().map(resource => this.#buildAjaxPayload(resource)).filter(Boolean);
|
|
222
|
+
if (!entries.length) return;
|
|
223
|
+
this.#postMessageToParent(_iframeConstants.IFRAME_AJAX, {
|
|
224
|
+
entries
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
this.#resourceObserver.observe({
|
|
228
|
+
type: 'resource',
|
|
229
|
+
buffered: true
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Builds the AJAX event payload for a single resource timing entry, if it looks like a network request.
|
|
235
|
+
* @private
|
|
236
|
+
* @param {PerformanceResourceTiming} resource
|
|
237
|
+
* @returns {object|undefined}
|
|
238
|
+
*/
|
|
239
|
+
#buildAjaxPayload(resource) {
|
|
240
|
+
if (!(resource.initiatorType in AJAX_INITIATOR_TYPES)) return;
|
|
241
|
+
// Cross-origin requests without a Timing-Allow-Origin response header report responseStatus (and
|
|
242
|
+
// transferSize) as 0 per spec -- that's a browser privacy restriction, not evidence the request
|
|
243
|
+
// failed, so it should still be reported rather than dropped. status: 0 is already the established
|
|
244
|
+
// "unknown" convention elsewhere in the ajax feature (see features/ajax/instrument/index.js).
|
|
245
|
+
const params = {
|
|
246
|
+
status: resource.responseStatus
|
|
247
|
+
};
|
|
248
|
+
const metrics = {
|
|
249
|
+
rxSize: resource.transferSize,
|
|
250
|
+
duration: Math.floor(resource.duration),
|
|
251
|
+
cbTime: 0
|
|
252
|
+
};
|
|
253
|
+
(0, _addUrl.addUrl)(params, resource.name);
|
|
254
|
+
return {
|
|
255
|
+
params,
|
|
256
|
+
metrics,
|
|
257
|
+
start: resource.startTime,
|
|
258
|
+
end: resource.responseEnd,
|
|
259
|
+
initiatorType: AJAX_INITIATOR_TYPES[resource.initiatorType]
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Listens for postMessage responses from the parent window and routes them to pending resolvers.
|
|
265
|
+
* @private
|
|
266
|
+
*/
|
|
267
|
+
#setupResponseListener() {
|
|
268
|
+
_runtime.globalScope.addEventListener('message', event => {
|
|
269
|
+
if (this.blocked) return;
|
|
270
|
+
// Validate message structure
|
|
271
|
+
if (event.data?.type !== _iframeConstants.IFRAME_API_RESPONSE) return;
|
|
272
|
+
|
|
273
|
+
// Validate iframeInterfaceId first to confirm this message actually claims to be addressed
|
|
274
|
+
// to this instance -- messageIds are only unique per-instance, so we must not act on one
|
|
275
|
+
// (including rejecting) until we know it's actually meant for us.
|
|
276
|
+
if (event.data.iframeInterfaceId !== this.#iframeInterfaceId) {
|
|
277
|
+
(0, _console.warn)(75);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Validate origin now that we know the message claims to be for us. Reject the pending
|
|
282
|
+
// call immediately rather than leaving it to time out, since we know exactly which
|
|
283
|
+
// messageId this response was for.
|
|
284
|
+
if (event.origin !== this.#parentOrigin) {
|
|
285
|
+
(0, _console.warn)(74, event.origin);
|
|
286
|
+
this.#closePending({
|
|
287
|
+
messageId: event.data.messageId,
|
|
288
|
+
error: 'Rejected message from unauthorized origin'
|
|
289
|
+
});
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
this.#closePending(event.data);
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Explicitly binds API methods as own properties for better console visibility.
|
|
298
|
+
* @private
|
|
299
|
+
*/
|
|
300
|
+
#bindPublicMethods() {
|
|
301
|
+
this.addPageAction = this.addPageAction.bind(this);
|
|
302
|
+
this.deregister = this.deregister.bind(this);
|
|
303
|
+
this.recordCustomEvent = this.recordCustomEvent.bind(this);
|
|
304
|
+
this.measure = this.measure.bind(this);
|
|
305
|
+
this.setCustomAttribute = this.setCustomAttribute.bind(this);
|
|
306
|
+
this.noticeError = this.noticeError.bind(this);
|
|
307
|
+
this.setUserId = this.setUserId.bind(this);
|
|
308
|
+
this.setApplicationVersion = this.setApplicationVersion.bind(this);
|
|
309
|
+
this.log = this.log.bind(this);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Low-level helper to send postMessage to parent window with error handling
|
|
314
|
+
* @private
|
|
315
|
+
* @param {string} type - The message type to send
|
|
316
|
+
* @param {object} data - The message payload to send
|
|
317
|
+
* @param {boolean} [bypassRegistration=false] - Whether to bypass waiting for registration
|
|
318
|
+
* @param {boolean} [needsResponse=false] - Whether to wait for a response from the parent
|
|
319
|
+
* @returns {Promise<void>}
|
|
320
|
+
*/
|
|
321
|
+
async #postMessageToParent(type, data, bypassRegistration = false, needsResponse = false) {
|
|
322
|
+
if (this.blocked) return;
|
|
323
|
+
const timestamp = (0, _now.now)();
|
|
324
|
+
try {
|
|
325
|
+
await (bypassRegistration ? Promise.resolve() : this.#registrationPromise);
|
|
326
|
+
const messageId = ++this.#messageIdCounter;
|
|
327
|
+
const pending = needsResponse ? this.#openPending(messageId) : Promise.resolve();
|
|
328
|
+
_runtime.globalScope.parent.postMessage({
|
|
329
|
+
type,
|
|
330
|
+
target: this.#targetDescriptor,
|
|
331
|
+
timestamp,
|
|
332
|
+
iframeInterfaceId: this.#iframeInterfaceId,
|
|
333
|
+
messageId,
|
|
334
|
+
...data
|
|
335
|
+
}, this.#parentOrigin);
|
|
336
|
+
return await pending;
|
|
337
|
+
} catch (err) {
|
|
338
|
+
// If the postMessage never responded with a new message, it will surface here -- but, this could be expected, as many messages are sent to the parent that don't require a response.
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Sends a timing property update message to the parent window
|
|
344
|
+
* @private
|
|
345
|
+
* @param {string} property - The property name that changed
|
|
346
|
+
* @param {*} value - The new value
|
|
347
|
+
*/
|
|
348
|
+
#postTimingToAgent(property, value) {
|
|
349
|
+
this.#postMessageToParent(_iframeConstants.IFRAME_TIMING_UPDATE, {
|
|
350
|
+
entries: [{
|
|
351
|
+
property,
|
|
352
|
+
value
|
|
353
|
+
}]
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Sends a message to the parent window's agent using postMessage API
|
|
359
|
+
* @private
|
|
360
|
+
* @param {string} method The API method name to invoke
|
|
361
|
+
* @param {Array} args The arguments to pass to the method
|
|
362
|
+
* @returns {Promise<any>} Promise that resolves with the response from the agent
|
|
363
|
+
*/
|
|
364
|
+
async #postMethodToAgent(method, args) {
|
|
365
|
+
return await this.#postMessageToParent(_iframeConstants.IFRAME_API, {
|
|
366
|
+
entries: [{
|
|
367
|
+
method,
|
|
368
|
+
args
|
|
369
|
+
}]
|
|
370
|
+
}, method === REGISTER, true);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Opens a pending entry for a message awaiting a response, auto-rejecting after a timeout.
|
|
375
|
+
* @private
|
|
376
|
+
* @param {number} messageId
|
|
377
|
+
* @returns {Promise<any>}
|
|
378
|
+
*/
|
|
379
|
+
#openPending(messageId) {
|
|
380
|
+
const resolvers = {};
|
|
381
|
+
const pending = new Promise((resolve, reject) => {
|
|
382
|
+
resolvers.resolve = resolve;
|
|
383
|
+
resolvers.reject = reject;
|
|
384
|
+
});
|
|
385
|
+
this.#pendingMessages.set(messageId, resolvers);
|
|
386
|
+
// Timeout after 10 seconds
|
|
387
|
+
setTimeout(() => this.#closePending({
|
|
388
|
+
messageId,
|
|
389
|
+
error: 'Timed out'
|
|
390
|
+
}), 10000);
|
|
391
|
+
return pending;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Resolves or rejects a pending message by ID, based on an incoming response (or timeout).
|
|
396
|
+
* @private
|
|
397
|
+
* @param {{messageId: number, error?: string, result?: any, metadata?: object}} event
|
|
398
|
+
*/
|
|
399
|
+
#closePending(event = {}) {
|
|
400
|
+
const {
|
|
401
|
+
messageId,
|
|
402
|
+
error,
|
|
403
|
+
result,
|
|
404
|
+
metadata
|
|
405
|
+
} = event;
|
|
406
|
+
const pending = this.#pendingMessages.get(messageId);
|
|
407
|
+
if (pending) {
|
|
408
|
+
if (error) pending.reject(new Error(error));else pending.resolve({
|
|
409
|
+
result,
|
|
410
|
+
metadata
|
|
411
|
+
});
|
|
412
|
+
this.#pendingMessages.delete(messageId);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// ---------------------------------------------------------------------------
|
|
417
|
+
// Public API
|
|
418
|
+
// ---------------------------------------------------------------------------
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Reports a browser PageAction event along with a name and optional attributes to the registered target.
|
|
422
|
+
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/addpageaction/}
|
|
423
|
+
* @param {string} name Name or category of the action. Reported as the actionName attribute.
|
|
424
|
+
* @param {object} [attributes] JSON object with one or more key/value pairs. For example: {key:"value"}. The key is reported as its own PageAction attribute with the specified values.
|
|
425
|
+
*/
|
|
426
|
+
addPageAction(name, attributes) {
|
|
427
|
+
this.#postMethodToAgent('addPageAction', [name, attributes]);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* @experimental
|
|
432
|
+
* IMPORTANT: This feature is being developed for use internally and is not in a public-facing production-ready state.
|
|
433
|
+
* It is not recommended for use in production environments and will not receive support for issues.
|
|
434
|
+
*
|
|
435
|
+
* Deregister the registered entity (this), which blocks its use and captures end of life timings.
|
|
436
|
+
* @returns {Promise<void>}
|
|
437
|
+
*/
|
|
438
|
+
async deregister() {
|
|
439
|
+
try {
|
|
440
|
+
this.#resourceObserver?.disconnect();
|
|
441
|
+
} catch (err) {}
|
|
442
|
+
this.#resourceObserver = null;
|
|
443
|
+
try {
|
|
444
|
+
const response = await this.#postMethodToAgent('deregister', []);
|
|
445
|
+
if (response?.metadata) Object.assign(this.metadata, response.metadata);
|
|
446
|
+
} finally {
|
|
447
|
+
// Always end up blocked locally, even if the round trip to the container never resolved --
|
|
448
|
+
// deregistering is a one-way decision and further local calls should stop regardless.
|
|
449
|
+
this.blocked = true;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Records a custom event with a specified eventType and attributes.
|
|
455
|
+
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/recordCustomEvent/}
|
|
456
|
+
* @param {string} eventType The eventType to store the event as.
|
|
457
|
+
* @param {Object} [attributes] JSON object with one or more key/value pairs. For example: {key:"value"}.
|
|
458
|
+
*/
|
|
459
|
+
recordCustomEvent(eventType, attributes) {
|
|
460
|
+
this.#postMethodToAgent('recordCustomEvent', [eventType, attributes]);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Measures a task that is recorded as a BrowserPerformance event.
|
|
465
|
+
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/measure/}
|
|
466
|
+
* @param {string} name The name of the task
|
|
467
|
+
* @param {{start?: number|PerformanceMark, end?: number|PerformanceMark, customAttributes?: object}} [options] An object used to control the way the measure API operates
|
|
468
|
+
* @returns {Promise<{start: number, end: number, duration: number, customAttributes: object}>} Measurement details
|
|
469
|
+
*/
|
|
470
|
+
async measure(name, options) {
|
|
471
|
+
return (await this.#postMethodToAgent('measure', [name, options])).result;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Adds a user-defined attribute name and value to subsequent events on the page for the registered target. Note -- the persist flag does not work with the register API.
|
|
476
|
+
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/setcustomattribute/}
|
|
477
|
+
* @param {string} name Name of the attribute. Appears as column in the PageView event. It will also appear as a column in the PageAction event if you are using it.
|
|
478
|
+
* @param {string|number|boolean|null} value Value of the attribute. Appears as the value in the named attribute column in the PageView event. It will appear as a column in the PageAction event if you are using it. Custom attribute values cannot be complex objects, only simple types such as Strings, Integers and Booleans. Passing a null value unsets any existing attribute of the same name.
|
|
479
|
+
* @param {boolean} [persist] Default false. If set to true, the name-value pair will also be set into the browser's storage API. Then on the following instrumented pages that load within the same session, the pair will be re-applied as a custom attribute.
|
|
480
|
+
*/
|
|
481
|
+
setCustomAttribute(name, value, persist) {
|
|
482
|
+
this.#postMethodToAgent('setCustomAttribute', [name, value, persist]);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Identifies a browser error without disrupting your app's operations for the registered target.
|
|
487
|
+
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/noticeerror/}
|
|
488
|
+
* @param {Error|string} error Provide a meaningful error message that you can use when analyzing data on browser's JavaScript errors page.
|
|
489
|
+
* @param {object} [customAttributes] An object containing name/value pairs representing custom attributes.
|
|
490
|
+
*/
|
|
491
|
+
noticeError(error, customAttributes) {
|
|
492
|
+
this.#postMethodToAgent('noticeError', [(0, _castError.castError)(error), customAttributes]);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Adds a user-defined identifier string to subsequent events on the page for the registered target.
|
|
497
|
+
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/setuserid/}
|
|
498
|
+
* @param {string|null} value A string identifier for the end-user, useful for tying all browser events to specific users. The value parameter does not have to be unique. If IDs should be unique, the caller is responsible for that validation. Passing a null value unsets any existing user ID.
|
|
499
|
+
* @param {boolean} [resetSession=false] Optional param. Should not be used from a registered entity context. To reset a session when updating user id, must be initiated by the main agent.
|
|
500
|
+
*/
|
|
501
|
+
setUserId(value, resetSession = false) {
|
|
502
|
+
this.#postMethodToAgent('setUserId', [value, resetSession]);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Adds a user-defined application version string to subsequent events on the page for the registered target.
|
|
507
|
+
* This decorates all payloads with an attribute of `application.version` which is queryable in NR1.
|
|
508
|
+
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/setapplicationversion/}
|
|
509
|
+
* @param {string|null} value A string identifier for the application version, useful for
|
|
510
|
+
* tying all browser events to a specific release tag. The value parameter does not
|
|
511
|
+
* have to be unique. Passing a null value unsets any existing value.
|
|
512
|
+
*/
|
|
513
|
+
setApplicationVersion(value) {
|
|
514
|
+
this.#postMethodToAgent('setApplicationVersion', [value]);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Capture a single log for the registered target.
|
|
519
|
+
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/log/}
|
|
520
|
+
* @param {string} message String to be captured as log message
|
|
521
|
+
* @param {{customAttributes?: object, level?: 'ERROR'|'TRACE'|'DEBUG'|'INFO'|'WARN'}} [options] customAttributes defaults to `{}` if not assigned, level defaults to `info` if not assigned.
|
|
522
|
+
*/
|
|
523
|
+
log(message, options) {
|
|
524
|
+
this.#postMethodToAgent('log', [message, options]);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
exports.RegisteredIframeEntity = RegisteredIframeEntity;
|
|
@@ -79,12 +79,12 @@ function register(agentRef, target) {
|
|
|
79
79
|
if (typeof target.tags !== 'object' || target.tags === null || Array.isArray(target.tags)) target.tags = {};
|
|
80
80
|
target.parent ??= {
|
|
81
81
|
get id() {
|
|
82
|
-
return agentRef.runtime.appMetadata.agents[0].entityGuid;
|
|
82
|
+
return agentRef.runtime.appMetadata.agents?.[0].entityGuid;
|
|
83
83
|
},
|
|
84
|
-
// getter because this is
|
|
84
|
+
// getter because this is asynchronously set
|
|
85
85
|
type: _utils.V2_TYPES.BA
|
|
86
86
|
};
|
|
87
|
-
const timings = (0, _scriptTracker.findScriptTimings)();
|
|
87
|
+
const timings = (0, _scriptTracker.findScriptTimings)(target);
|
|
88
88
|
|
|
89
89
|
// Track MFE vitals for this entity
|
|
90
90
|
const vitals = (0, _mfeVitals.trackMFEVitals)(target, timings);
|
|
@@ -177,7 +177,10 @@ function register(agentRef, target) {
|
|
|
177
177
|
},
|
|
178
178
|
target,
|
|
179
179
|
timings,
|
|
180
|
-
vitals
|
|
180
|
+
vitals,
|
|
181
|
+
events: {
|
|
182
|
+
latestTimestamp: undefined
|
|
183
|
+
}
|
|
181
184
|
}
|
|
182
185
|
};
|
|
183
186
|
|
|
@@ -274,8 +277,9 @@ function register(agentRef, target) {
|
|
|
274
277
|
const report = (methodToCall, args, target) => {
|
|
275
278
|
/** Even if we are blocked, if registering we should still return a child register API so nested API calls do not throw errors */
|
|
276
279
|
if (isBlocked() && methodToCall !== register) return;
|
|
277
|
-
/**
|
|
278
|
-
const timestamp = (0, _now.now)();
|
|
280
|
+
/** use the timestamp captured inside the iframe for this call, if one was supplied (see iframe-message-handler.js); otherwise fall back to now(). Consume it immediately so a stale value can't leak into a later call that isn't preceded by a fresh iframe message (e.g. deregister() via page unload) */
|
|
281
|
+
const timestamp = api.metadata.events.latestTimestamp ?? (0, _now.now)();
|
|
282
|
+
api.metadata.events.latestTimestamp = undefined;
|
|
279
283
|
const methodName = METHOD_NAMES.get(methodToCall) || 'unknown';
|
|
280
284
|
(0, _handle.handle)(_constants.SUPPORTABILITY_METRIC_CHANNEL, ["API/register/".concat(methodName, "/called")], undefined, _features.FEATURE_NAMES.metrics, agentRef.ee);
|
|
281
285
|
try {
|
|
@@ -14,6 +14,8 @@ var _publicPath = require("./public-path");
|
|
|
14
14
|
var _contextualEe = require("../../common/event-emitter/contextual-ee");
|
|
15
15
|
var _globalEvent = require("../../common/dispatch/global-event");
|
|
16
16
|
var _loaderConfig = require("../../common/config/loader-config");
|
|
17
|
+
var _handle = require("../../common/event-emitter/handle");
|
|
18
|
+
var _iframeConstants = require("../../common/constants/iframe-constants");
|
|
17
19
|
/**
|
|
18
20
|
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
19
21
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -80,6 +82,18 @@ function configure(agent, opts = {}, loaderType, forceDrain) {
|
|
|
80
82
|
feature: undefined,
|
|
81
83
|
data: agent.config
|
|
82
84
|
});
|
|
85
|
+
|
|
86
|
+
// Set up iframe postMessage listener for registered entities
|
|
87
|
+
if (agent.init.api.register.allow_iframe_bridge) {
|
|
88
|
+
_runtime2.globalScope.addEventListener('message', event => {
|
|
89
|
+
// Pre-filter here rather than in setupIframeMFEMessageListener, so unrelated MessageEvents
|
|
90
|
+
// (there can be many, from any script on the page) aren't buffered/held onto in memory
|
|
91
|
+
// while waiting for the iframe bridge's lazy chunk to load and drain the buffer.
|
|
92
|
+
if (typeof event.data?.type === 'string' && event.data.type.startsWith(_iframeConstants.prefix)) {
|
|
93
|
+
(0, _handle.handle)('iframe-message', [event], undefined, 'IFRAME', agent.ee);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
83
97
|
agent.runtime.configured = true;
|
|
84
98
|
}
|
|
85
99
|
}
|