@newrelic/browser-agent 1.258.0 → 1.258.1
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 +11 -0
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/deny-list/deny-list.js +6 -8
- package/dist/cjs/common/vitals/time-to-first-byte.js +9 -1
- package/dist/cjs/features/ajax/instrument/index.js +11 -8
- package/dist/cjs/features/jserrors/aggregate/index.js +6 -2
- package/dist/cjs/features/jserrors/instrument/index.js +4 -87
- package/dist/cjs/features/jserrors/shared/cast-error.js +66 -0
- package/dist/cjs/features/jserrors/{instrument → shared}/uncaught-error.js +4 -2
- package/dist/cjs/features/session_trace/aggregate/index.js +6 -2
- package/dist/cjs/features/spa/aggregate/index.js +11 -1
- package/dist/cjs/features/spa/instrument/index.js +9 -0
- package/dist/cjs/features/utils/instrument-base.js +1 -1
- package/dist/cjs/loaders/api/api.js +6 -14
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/deny-list/deny-list.js +5 -8
- package/dist/esm/common/vitals/time-to-first-byte.js +9 -1
- package/dist/esm/features/ajax/instrument/index.js +11 -8
- package/dist/esm/features/jserrors/aggregate/index.js +6 -2
- package/dist/esm/features/jserrors/instrument/index.js +4 -87
- package/dist/esm/features/jserrors/shared/cast-error.js +59 -0
- package/dist/esm/features/jserrors/{instrument → shared}/uncaught-error.js +5 -2
- package/dist/esm/features/session_trace/aggregate/index.js +6 -2
- package/dist/esm/features/spa/aggregate/index.js +11 -1
- package/dist/esm/features/spa/instrument/index.js +9 -0
- package/dist/esm/features/utils/instrument-base.js +1 -1
- package/dist/esm/loaders/api/api.js +6 -14
- package/dist/types/common/deny-list/deny-list.d.ts +1 -0
- package/dist/types/common/deny-list/deny-list.d.ts.map +1 -1
- package/dist/types/features/ajax/instrument/index.d.ts.map +1 -1
- package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/jserrors/instrument/index.d.ts.map +1 -1
- package/dist/types/features/jserrors/shared/cast-error.d.ts +21 -0
- package/dist/types/features/jserrors/shared/cast-error.d.ts.map +1 -0
- package/dist/types/features/jserrors/{instrument → shared}/uncaught-error.d.ts +3 -2
- package/dist/types/features/jserrors/shared/uncaught-error.d.ts.map +1 -0
- package/dist/types/features/session_trace/aggregate/index.d.ts +1 -0
- package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/spa/instrument/index.d.ts.map +1 -1
- package/dist/types/loaders/api/api.d.ts +1 -1
- package/dist/types/loaders/api/api.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/common/deny-list/deny-list.js +6 -7
- package/src/common/vitals/time-to-first-byte.js +8 -1
- package/src/features/ajax/instrument/index.js +11 -9
- package/src/features/jserrors/aggregate/index.js +7 -2
- package/src/features/jserrors/instrument/index.js +4 -99
- package/src/features/jserrors/shared/cast-error.js +69 -0
- package/src/features/jserrors/{instrument → shared}/uncaught-error.js +5 -2
- package/src/features/session_trace/aggregate/index.js +6 -2
- package/src/features/spa/aggregate/index.js +10 -1
- package/src/features/spa/instrument/index.js +3 -0
- package/src/features/utils/instrument-base.js +1 -1
- package/src/loaders/api/api.js +6 -15
- package/dist/types/features/jserrors/instrument/uncaught-error.d.ts.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
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.258.1](https://github.com/newrelic/newrelic-browser-agent/compare/v1.258.0...v1.258.1) (2024-05-07)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* Exclude data url requests as captured AJAX events ([#1012](https://github.com/newrelic/newrelic-browser-agent/issues/1012)) ([2a3fa57](https://github.com/newrelic/newrelic-browser-agent/commit/2a3fa57da1f389e6eabae2c92686e25285fc6cd3))
|
|
12
|
+
* Improve Error Casting ([#1014](https://github.com/newrelic/newrelic-browser-agent/issues/1014)) ([d1dd20c](https://github.com/newrelic/newrelic-browser-agent/commit/d1dd20ce526ddb697962f695fbb5915410474987))
|
|
13
|
+
* Remove API start()'s features param ([#1009](https://github.com/newrelic/newrelic-browser-agent/issues/1009)) ([38a502b](https://github.com/newrelic/newrelic-browser-agent/commit/38a502b08b4735259e7f5b863b0e5e5361c075b6))
|
|
14
|
+
* Run inside cross-origin iframes for firefox/safari ([#1015](https://github.com/newrelic/newrelic-browser-agent/issues/1015)) ([6a4a73d](https://github.com/newrelic/newrelic-browser-agent/commit/6a4a73d72b056177268f8619a3a3b7810a7a2c79))
|
|
15
|
+
* Session trace nodes de-duplication ([#1008](https://github.com/newrelic/newrelic-browser-agent/issues/1008)) ([44f229e](https://github.com/newrelic/newrelic-browser-agent/commit/44f229e4d35cd468bfe29b1796be8031bb9c72ff))
|
|
16
|
+
|
|
6
17
|
## [1.258.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.257.0...v1.258.0) (2024-04-29)
|
|
7
18
|
|
|
8
19
|
|
|
@@ -12,7 +12,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
|
|
|
12
12
|
/**
|
|
13
13
|
* Exposes the version of the agent
|
|
14
14
|
*/
|
|
15
|
-
const VERSION = exports.VERSION = "1.258.
|
|
15
|
+
const VERSION = exports.VERSION = "1.258.1";
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Exposes the build type of the agent
|
|
@@ -12,7 +12,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
|
|
|
12
12
|
/**
|
|
13
13
|
* Exposes the version of the agent
|
|
14
14
|
*/
|
|
15
|
-
const VERSION = exports.VERSION = "1.258.
|
|
15
|
+
const VERSION = exports.VERSION = "1.258.1";
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Exposes the build type of the agent
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
+
exports.hasUndefinedHostname = hasUndefinedHostname;
|
|
6
7
|
exports.setDenyList = setDenyList;
|
|
7
8
|
exports.shouldCollectEvent = shouldCollectEvent;
|
|
8
9
|
/** An array of filter objects {hostname, pathname} for identifying XHR events to be excluded from collection.
|
|
@@ -17,14 +18,8 @@ var denyList = [];
|
|
|
17
18
|
* @returns {boolean} `true` if request does not match any entries of {@link denyList|deny list}; else `false`
|
|
18
19
|
*/
|
|
19
20
|
function shouldCollectEvent(params) {
|
|
20
|
-
if (
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// XHR requests with an undefined hostname (e.g., data URLs) should not be collected.
|
|
25
|
-
if (params.hostname === undefined) {
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
21
|
+
if (hasUndefinedHostname(params)) return false;
|
|
22
|
+
if (denyList.length === 0) return true;
|
|
28
23
|
for (var i = 0; i < denyList.length; i++) {
|
|
29
24
|
var parsed = denyList[i];
|
|
30
25
|
if (parsed.hostname === '*') {
|
|
@@ -36,6 +31,9 @@ function shouldCollectEvent(params) {
|
|
|
36
31
|
}
|
|
37
32
|
return true;
|
|
38
33
|
}
|
|
34
|
+
function hasUndefinedHostname(params) {
|
|
35
|
+
return params.hostname === undefined; // requests with an undefined hostname (e.g., data URLs) should not be collected.
|
|
36
|
+
}
|
|
39
37
|
|
|
40
38
|
/**
|
|
41
39
|
* Initializes the {@link denyList|XHR deny list} by extracting hostname and pathname from an array of filter strings.
|
|
@@ -9,7 +9,15 @@ var _constants = require("./constants");
|
|
|
9
9
|
var _vitalMetric = require("./vital-metric");
|
|
10
10
|
var _attribution = require("web-vitals/attribution");
|
|
11
11
|
const timeToFirstByte = exports.timeToFirstByte = new _vitalMetric.VitalMetric(_constants.VITAL_NAMES.TIME_TO_FIRST_BYTE);
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* onTTFB is not supported in the following scenarios:
|
|
15
|
+
* - in a non-browser scope
|
|
16
|
+
* - in browsers that do not support PerformanceNavigationTiming API
|
|
17
|
+
* - in an iOS browser
|
|
18
|
+
* - cross-origin iframes specifically in firefox and safari
|
|
19
|
+
*/
|
|
20
|
+
if (_runtime.isBrowserScope && typeof PerformanceNavigationTiming !== 'undefined' && !_runtime.isiOS && window === window.parent) {
|
|
13
21
|
(0, _attribution.onTTFB)(_ref => {
|
|
14
22
|
let {
|
|
15
23
|
value,
|
|
@@ -19,6 +19,7 @@ var _constants = require("../constants");
|
|
|
19
19
|
var _features = require("../../../loaders/features/features");
|
|
20
20
|
var _constants2 = require("../../metrics/constants");
|
|
21
21
|
var _now = require("../../../common/timing/now");
|
|
22
|
+
var _denyList = require("../../../common/deny-list/deny-list");
|
|
22
23
|
/*
|
|
23
24
|
* Copyright 2020 New Relic Corporation. All rights reserved.
|
|
24
25
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -315,17 +316,17 @@ function subscribeToEvents(agentIdentifier, ee, handler, dt) {
|
|
|
315
316
|
// eslint-disable-next-line handle-callback-err
|
|
316
317
|
function onFetchDone(_, res) {
|
|
317
318
|
this.endTime = (0, _now.now)();
|
|
318
|
-
if (!this.params) {
|
|
319
|
-
|
|
320
|
-
|
|
319
|
+
if (!this.params) this.params = {};
|
|
320
|
+
if ((0, _denyList.hasUndefinedHostname)(this.params)) return; // don't bother with fetch to url with no hostname
|
|
321
|
+
|
|
321
322
|
this.params.status = res ? res.status : 0;
|
|
322
323
|
|
|
323
324
|
// convert rxSize to a number
|
|
324
|
-
|
|
325
|
+
let responseSize;
|
|
325
326
|
if (typeof this.rxSize === 'string' && this.rxSize.length > 0) {
|
|
326
327
|
responseSize = +this.rxSize;
|
|
327
328
|
}
|
|
328
|
-
|
|
329
|
+
const metrics = {
|
|
329
330
|
txSize: this.txSize,
|
|
330
331
|
rxSize: responseSize,
|
|
331
332
|
duration: (0, _now.now)() - this.startTime
|
|
@@ -335,14 +336,16 @@ function subscribeToEvents(agentIdentifier, ee, handler, dt) {
|
|
|
335
336
|
|
|
336
337
|
// Create report for XHR request that has finished
|
|
337
338
|
function end(xhr) {
|
|
338
|
-
|
|
339
|
-
|
|
339
|
+
const params = this.params;
|
|
340
|
+
const metrics = this.metrics;
|
|
340
341
|
if (this.ended) return;
|
|
341
342
|
this.ended = true;
|
|
342
|
-
for (
|
|
343
|
+
for (let i = 0; i < handlersLen; i++) {
|
|
343
344
|
xhr.removeEventListener(handlers[i], this.listener, false);
|
|
344
345
|
}
|
|
345
346
|
if (params.aborted) return;
|
|
347
|
+
if ((0, _denyList.hasUndefinedHostname)(params)) return; // don't bother with XHR of url with no hostname
|
|
348
|
+
|
|
346
349
|
metrics.duration = (0, _now.now)() - this.startTime;
|
|
347
350
|
if (!this.loadCaptureCalled && xhr.readyState === 4) {
|
|
348
351
|
captureXhrData(this, xhr);
|
|
@@ -199,13 +199,17 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
199
199
|
|
|
200
200
|
// Trace sends the error in its payload, and both trace & replay simply listens for any error to occur.
|
|
201
201
|
const jsErrorEvent = [type, bucketHash, params, newMetrics, customAttributes];
|
|
202
|
-
(0, _handle.handle)('
|
|
202
|
+
(0, _handle.handle)('trace-jserror', jsErrorEvent, undefined, _features.FEATURE_NAMES.sessionTrace, this.ee);
|
|
203
203
|
// still send EE events for other features such as above, but stop this one from aggregating internal data
|
|
204
204
|
if (this.blocked) return;
|
|
205
|
+
if (err.__newrelic?.[this.agentIdentifier]) {
|
|
206
|
+
params._interactionId = err.__newrelic[this.agentIdentifier].interactionId;
|
|
207
|
+
params._interactionNodeId = err.__newrelic[this.agentIdentifier].interactionNodeId;
|
|
208
|
+
}
|
|
205
209
|
const softNavInUse = Boolean((0, _nreum.getNREUMInitializedAgent)(this.agentIdentifier)?.features[_features.FEATURE_NAMES.softNav]);
|
|
206
210
|
// Note: the following are subject to potential race cond wherein if the other feature aren't fully initialized, it'll be treated as there being no associated interaction.
|
|
207
211
|
// They each will also tack on their respective properties to the params object as part of the decision flow.
|
|
208
|
-
if (softNavInUse) (0, _handle.handle)('jserror', [params, time], undefined, _features.FEATURE_NAMES.softNav, this.ee);else (0, _handle.handle)('
|
|
212
|
+
if (softNavInUse) (0, _handle.handle)('jserror', [params, time], undefined, _features.FEATURE_NAMES.softNav, this.ee);else (0, _handle.handle)('spa-jserror', jsErrorEvent, undefined, _features.FEATURE_NAMES.spa, this.ee);
|
|
209
213
|
if (params.browserInteractionId && !params._softNavFinished) {
|
|
210
214
|
// hold onto the error until the in-progress interaction is done, eithered saved or discarded
|
|
211
215
|
this.bufferedErrorsUnderSpa[params.browserInteractionId] ??= [];
|
|
@@ -10,10 +10,9 @@ var _constants = require("../constants");
|
|
|
10
10
|
var _features = require("../../../loaders/features/features");
|
|
11
11
|
var _runtime = require("../../../common/constants/runtime");
|
|
12
12
|
var _eventListenerOpts = require("../../../common/event-listener/event-listener-opts");
|
|
13
|
-
var _stringify = require("../../../common/util/stringify");
|
|
14
|
-
var _uncaughtError = require("./uncaught-error");
|
|
15
13
|
var _now = require("../../../common/timing/now");
|
|
16
14
|
var _constants2 = require("../../session_replay/constants");
|
|
15
|
+
var _castError = require("../shared/cast-error");
|
|
17
16
|
/*
|
|
18
17
|
* Copyright 2020 New Relic Corporation. All rights reserved.
|
|
19
18
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -21,7 +20,6 @@ var _constants2 = require("../../session_replay/constants");
|
|
|
21
20
|
|
|
22
21
|
class Instrument extends _instrumentBase.InstrumentBase {
|
|
23
22
|
static featureName = _constants.FEATURE_NAME;
|
|
24
|
-
#seenErrors = new Set();
|
|
25
23
|
#replayRunning = false;
|
|
26
24
|
constructor(agentIdentifier, aggregator) {
|
|
27
25
|
let auto = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
|
|
@@ -30,38 +28,22 @@ class Instrument extends _instrumentBase.InstrumentBase {
|
|
|
30
28
|
// this try-catch can be removed when IE11 is completely unsupported & gone
|
|
31
29
|
this.removeOnAbort = new AbortController();
|
|
32
30
|
} catch (e) {}
|
|
33
|
-
|
|
34
|
-
// Capture function errors early in case the spa feature is loaded
|
|
35
|
-
this.ee.on('fn-err', (args, obj, error) => {
|
|
36
|
-
if (!this.abortHandler || this.#seenErrors.has(error)) return;
|
|
37
|
-
this.#seenErrors.add(error);
|
|
38
|
-
(0, _handle.handle)('err', [this.#castError(error), (0, _now.now)()], undefined, _features.FEATURE_NAMES.jserrors, this.ee);
|
|
39
|
-
});
|
|
40
31
|
this.ee.on('internal-error', error => {
|
|
41
32
|
if (!this.abortHandler) return;
|
|
42
|
-
(0, _handle.handle)('ierr', [
|
|
33
|
+
(0, _handle.handle)('ierr', [(0, _castError.castError)(error), (0, _now.now)(), true, {}, this.#replayRunning], undefined, _features.FEATURE_NAMES.jserrors, this.ee);
|
|
43
34
|
});
|
|
44
35
|
this.ee.on(_constants2.SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, isRunning => {
|
|
45
36
|
this.#replayRunning = isRunning;
|
|
46
37
|
});
|
|
47
38
|
_runtime.globalScope.addEventListener('unhandledrejection', promiseRejectionEvent => {
|
|
48
39
|
if (!this.abortHandler) return;
|
|
49
|
-
(0, _handle.handle)('err', [
|
|
40
|
+
(0, _handle.handle)('err', [(0, _castError.castPromiseRejectionEvent)(promiseRejectionEvent), (0, _now.now)(), false, {
|
|
50
41
|
unhandledPromiseRejection: 1
|
|
51
42
|
}, this.#replayRunning], undefined, _features.FEATURE_NAMES.jserrors, this.ee);
|
|
52
43
|
}, (0, _eventListenerOpts.eventListenerOpts)(false, this.removeOnAbort?.signal));
|
|
53
44
|
_runtime.globalScope.addEventListener('error', errorEvent => {
|
|
54
45
|
if (!this.abortHandler) return;
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* If the spa feature is loaded, errors may already have been captured in the `fn-err` listener above.
|
|
58
|
-
* This ensures those errors are not captured twice.
|
|
59
|
-
*/
|
|
60
|
-
if (this.#seenErrors.has(errorEvent.error)) {
|
|
61
|
-
this.#seenErrors.delete(errorEvent.error);
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
(0, _handle.handle)('err', [this.#castErrorEvent(errorEvent), (0, _now.now)(), false, {}, this.#replayRunning], undefined, _features.FEATURE_NAMES.jserrors, this.ee);
|
|
46
|
+
(0, _handle.handle)('err', [(0, _castError.castErrorEvent)(errorEvent), (0, _now.now)(), false, {}, this.#replayRunning], undefined, _features.FEATURE_NAMES.jserrors, this.ee);
|
|
65
47
|
}, (0, _eventListenerOpts.eventListenerOpts)(false, this.removeOnAbort?.signal));
|
|
66
48
|
this.abortHandler = this.#abort; // we also use this as a flag to denote that the feature is active or on and handling errors
|
|
67
49
|
this.importAggregator();
|
|
@@ -70,72 +52,7 @@ class Instrument extends _instrumentBase.InstrumentBase {
|
|
|
70
52
|
/** Restoration and resource release tasks to be done if JS error loader is being aborted. Unwind changes to globals. */
|
|
71
53
|
#abort() {
|
|
72
54
|
this.removeOnAbort?.abort();
|
|
73
|
-
this.#seenErrors.clear();
|
|
74
55
|
this.abortHandler = undefined; // weakly allow this abort op to run only once
|
|
75
56
|
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Any value can be used with the `throw` keyword. This function ensures that the value is
|
|
79
|
-
* either a proper Error instance or attempts to convert it to an UncaughtError instance.
|
|
80
|
-
* @param {any} error The value thrown
|
|
81
|
-
* @returns {Error|UncaughtError} The converted error instance
|
|
82
|
-
*/
|
|
83
|
-
#castError(error) {
|
|
84
|
-
if (error instanceof Error) {
|
|
85
|
-
return error;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* The thrown value may contain a message property. If it does, try to treat the thrown
|
|
90
|
-
* value as an Error-like object.
|
|
91
|
-
*/
|
|
92
|
-
if (typeof error?.message !== 'undefined') {
|
|
93
|
-
return new _uncaughtError.UncaughtError(error.message, error.filename || error.sourceURL, error.lineno || error.line, error.colno || error.col);
|
|
94
|
-
}
|
|
95
|
-
return new _uncaughtError.UncaughtError(typeof error === 'string' ? error : (0, _stringify.stringify)(error));
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Attempts to convert a PromiseRejectionEvent object to an Error object
|
|
100
|
-
* @param {PromiseRejectionEvent} unhandledRejectionEvent The unhandled promise rejection event
|
|
101
|
-
* @returns {Error} An Error object with the message as the casted reason
|
|
102
|
-
*/
|
|
103
|
-
#castPromiseRejectionEvent(promiseRejectionEvent) {
|
|
104
|
-
let prefix = 'Unhandled Promise Rejection: ';
|
|
105
|
-
if (promiseRejectionEvent?.reason instanceof Error) {
|
|
106
|
-
try {
|
|
107
|
-
promiseRejectionEvent.reason.message = prefix + promiseRejectionEvent.reason.message;
|
|
108
|
-
return promiseRejectionEvent.reason;
|
|
109
|
-
} catch (e) {
|
|
110
|
-
return promiseRejectionEvent.reason;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
if (typeof promiseRejectionEvent.reason === 'undefined') return new _uncaughtError.UncaughtError(prefix);
|
|
114
|
-
const error = this.#castError(promiseRejectionEvent.reason);
|
|
115
|
-
error.message = prefix + error.message;
|
|
116
|
-
return error;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Attempts to convert an ErrorEvent object to an Error object
|
|
121
|
-
* @param {ErrorEvent} errorEvent The error event
|
|
122
|
-
* @returns {Error|UncaughtError} The error event converted to an Error object
|
|
123
|
-
*/
|
|
124
|
-
#castErrorEvent(errorEvent) {
|
|
125
|
-
if (errorEvent.error instanceof SyntaxError && !/:\d+$/.test(errorEvent.error.stack?.trim())) {
|
|
126
|
-
const error = new _uncaughtError.UncaughtError(errorEvent.message, errorEvent.filename, errorEvent.lineno, errorEvent.colno);
|
|
127
|
-
error.name = SyntaxError.name;
|
|
128
|
-
return error;
|
|
129
|
-
}
|
|
130
|
-
if (errorEvent.error instanceof Error) {
|
|
131
|
-
return errorEvent.error;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Older browsers do not contain the `error` property on the ErrorEvent instance.
|
|
136
|
-
* https://caniuse.com/mdn-api_errorevent_error
|
|
137
|
-
*/
|
|
138
|
-
return new _uncaughtError.UncaughtError(errorEvent.message, errorEvent.filename, errorEvent.lineno, errorEvent.colno);
|
|
139
|
-
}
|
|
140
57
|
}
|
|
141
58
|
exports.Instrument = Instrument;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.castError = castError;
|
|
7
|
+
exports.castErrorEvent = castErrorEvent;
|
|
8
|
+
exports.castPromiseRejectionEvent = castPromiseRejectionEvent;
|
|
9
|
+
var _uncaughtError = require("./uncaught-error");
|
|
10
|
+
/**
|
|
11
|
+
* Any value can be used with the `throw` keyword. This function ensures that the value is
|
|
12
|
+
* either a proper Error instance or attempts to convert it to an UncaughtError instance.
|
|
13
|
+
* @param {any} error The value thrown
|
|
14
|
+
* @returns {Error|UncaughtError} The converted error instance
|
|
15
|
+
*/
|
|
16
|
+
function castError(error) {
|
|
17
|
+
/** Sometimes a browser can emit an error object with no stack */
|
|
18
|
+
if (canTrustError(error)) {
|
|
19
|
+
return error;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The thrown value may contain a message property. If it does, try to treat the thrown
|
|
24
|
+
* value as an Error-like object.
|
|
25
|
+
*/
|
|
26
|
+
return new _uncaughtError.UncaughtError(error?.message !== undefined ? error.message : error, error?.filename || error?.sourceURL, error?.lineno || error?.line, error?.colno || error?.col, error?.__newrelic);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Attempts to convert a PromiseRejectionEvent object to an Error object
|
|
31
|
+
* @param {PromiseRejectionEvent} unhandledRejectionEvent The unhandled promise rejection event
|
|
32
|
+
* @returns {Error} An Error object with the message as the casted reason
|
|
33
|
+
*/
|
|
34
|
+
function castPromiseRejectionEvent(promiseRejectionEvent) {
|
|
35
|
+
let prefix = 'Unhandled Promise Rejection';
|
|
36
|
+
if (canTrustError(promiseRejectionEvent?.reason)) {
|
|
37
|
+
try {
|
|
38
|
+
promiseRejectionEvent.reason.message = prefix + ': ' + promiseRejectionEvent.reason.message;
|
|
39
|
+
return castError(promiseRejectionEvent.reason);
|
|
40
|
+
} catch (e) {
|
|
41
|
+
return castError(promiseRejectionEvent.reason);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (typeof promiseRejectionEvent.reason === 'undefined') return castError(prefix);
|
|
45
|
+
const error = castError(promiseRejectionEvent.reason);
|
|
46
|
+
error.message = prefix + ': ' + error?.message;
|
|
47
|
+
return error;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Attempts to convert an ErrorEvent object to an Error object
|
|
52
|
+
* @param {ErrorEvent} errorEvent The error event
|
|
53
|
+
* @returns {Error|UncaughtError} The error event converted to an Error object
|
|
54
|
+
*/
|
|
55
|
+
function castErrorEvent(errorEvent) {
|
|
56
|
+
if (errorEvent.error instanceof SyntaxError && !/:\d+$/.test(errorEvent.error.stack?.trim())) {
|
|
57
|
+
const error = new _uncaughtError.UncaughtError(errorEvent.message, errorEvent.filename, errorEvent.lineno, errorEvent.colno, errorEvent.error.__newrelic);
|
|
58
|
+
error.name = SyntaxError.name;
|
|
59
|
+
return error;
|
|
60
|
+
}
|
|
61
|
+
if (canTrustError(errorEvent.error)) return errorEvent.error;
|
|
62
|
+
return castError(errorEvent);
|
|
63
|
+
}
|
|
64
|
+
function canTrustError(error) {
|
|
65
|
+
return error instanceof Error && !!error.stack;
|
|
66
|
+
}
|
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.UncaughtError = void 0;
|
|
7
|
+
var _stringify = require("../../../common/util/stringify");
|
|
7
8
|
/**
|
|
8
9
|
* Represents an uncaught non Error type error. This class does
|
|
9
10
|
* not extend the Error class to prevent an invalid stack trace
|
|
@@ -11,12 +12,13 @@ exports.UncaughtError = void 0;
|
|
|
11
12
|
* do not use the Error class (strings, etc) to an object.
|
|
12
13
|
*/
|
|
13
14
|
class UncaughtError {
|
|
14
|
-
constructor(message, filename, lineno, colno) {
|
|
15
|
+
constructor(message, filename, lineno, colno, newrelic) {
|
|
15
16
|
this.name = 'UncaughtError';
|
|
16
|
-
this.message = message;
|
|
17
|
+
this.message = typeof message === 'string' ? message : (0, _stringify.stringify)(message);
|
|
17
18
|
this.sourceURL = filename;
|
|
18
19
|
this.line = lineno;
|
|
19
20
|
this.column = colno;
|
|
21
|
+
this.__newrelic = newrelic;
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
24
|
exports.UncaughtError = UncaughtError;
|
|
@@ -59,6 +59,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
59
59
|
this.trace = {};
|
|
60
60
|
this.nodeCount = 0;
|
|
61
61
|
this.sentTrace = null;
|
|
62
|
+
this.prevStoredEvents = new Set();
|
|
62
63
|
this.harvestTimeSeconds = (0, _config.getConfigurationValue)(agentIdentifier, 'session_trace.harvestTimeSeconds') || 10;
|
|
63
64
|
this.maxNodesPerHarvest = (0, _config.getConfigurationValue)(agentIdentifier, 'session_trace.maxNodesPerHarvest') || 1000;
|
|
64
65
|
/**
|
|
@@ -126,7 +127,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
126
127
|
return controlTraceOp(on);
|
|
127
128
|
}, this.featureName, this.ee);
|
|
128
129
|
} else {
|
|
129
|
-
(0, _registerHandler.registerHandler)('
|
|
130
|
+
(0, _registerHandler.registerHandler)('trace-jserror', () => {
|
|
130
131
|
seenAnError = true;
|
|
131
132
|
switchToFull();
|
|
132
133
|
}, this.featureName, this.ee);
|
|
@@ -221,7 +222,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
221
222
|
}
|
|
222
223
|
return operationalGate.settle(() => _this.storeSTN(...args));
|
|
223
224
|
}, this.featureName, this.ee);
|
|
224
|
-
(0, _registerHandler.registerHandler)('
|
|
225
|
+
(0, _registerHandler.registerHandler)('trace-jserror', function () {
|
|
225
226
|
for (var _len6 = arguments.length, args = new Array(_len6), _key6 = 0; _key6 < _len6; _key6++) {
|
|
226
227
|
args[_key6] = arguments[_key6];
|
|
227
228
|
}
|
|
@@ -270,6 +271,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
270
271
|
}
|
|
271
272
|
}
|
|
272
273
|
#prepareHarvest(options) {
|
|
274
|
+
this.prevStoredEvents.clear(); // release references to past events for GC
|
|
273
275
|
if (this.isStandalone) {
|
|
274
276
|
if (this.ptid && (0, _now.now)() >= MAX_TRACE_DURATION) {
|
|
275
277
|
// Perform a final harvest once we hit or exceed the max session trace time
|
|
@@ -336,6 +338,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
336
338
|
// Tracks the events and their listener's duration on objects wrapped by wrap-events.
|
|
337
339
|
storeEvent(currentEvent, target, start, end) {
|
|
338
340
|
if (this.shouldIgnoreEvent(currentEvent, target)) return;
|
|
341
|
+
if (this.prevStoredEvents.has(currentEvent)) return; // prevent multiple listeners of an event from creating duplicate trace nodes per occurrence. Cleared every harvest. near-zero chance for re-duplication after clearing per harvest since the timestamps of the event are considered for uniqueness.
|
|
342
|
+
this.prevStoredEvents.add(currentEvent);
|
|
339
343
|
const evt = {
|
|
340
344
|
n: this.evtName(currentEvent.type),
|
|
341
345
|
s: start,
|
|
@@ -651,7 +651,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
651
651
|
state.interactionsSent = [];
|
|
652
652
|
}
|
|
653
653
|
}
|
|
654
|
-
baseEE.on('
|
|
654
|
+
baseEE.on('spa-jserror', function (type, name, params, metrics) {
|
|
655
655
|
if (!state.currentNode) return;
|
|
656
656
|
params._interactionId = state.currentNode.interaction.id;
|
|
657
657
|
// do not capture parentNodeId when in root node
|
|
@@ -659,6 +659,16 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
659
659
|
params._interactionNodeId = state.currentNode.id;
|
|
660
660
|
}
|
|
661
661
|
});
|
|
662
|
+
(0, _registerHandler.registerHandler)('function-err', function (args, obj, error) {
|
|
663
|
+
if (!state.currentNode) return;
|
|
664
|
+
error.__newrelic ??= {};
|
|
665
|
+
error.__newrelic[agentIdentifier] = {
|
|
666
|
+
interactionId: state.currentNode.interaction.id
|
|
667
|
+
};
|
|
668
|
+
if (state.currentNode.type && state.currentNode.type !== 'interaction') {
|
|
669
|
+
error.__newrelic[agentIdentifier].interactionNodeId = state.currentNode.id;
|
|
670
|
+
}
|
|
671
|
+
}, this.featureName, baseEE);
|
|
662
672
|
baseEE.on('interaction', saveInteraction);
|
|
663
673
|
function getActionText(node) {
|
|
664
674
|
var nodeType = node.tagName.toLowerCase();
|
|
@@ -10,6 +10,7 @@ var _instrumentBase = require("../../utils/instrument-base");
|
|
|
10
10
|
var CONSTANTS = _interopRequireWildcard(require("../constants"));
|
|
11
11
|
var _runtime = require("../../../common/constants/runtime");
|
|
12
12
|
var _now = require("../../../common/timing/now");
|
|
13
|
+
var _handle = require("../../../common/event-emitter/handle");
|
|
13
14
|
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
14
15
|
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
15
16
|
/*
|
|
@@ -32,8 +33,10 @@ const {
|
|
|
32
33
|
class Instrument extends _instrumentBase.InstrumentBase {
|
|
33
34
|
static featureName = FEATURE_NAME;
|
|
34
35
|
constructor(agentIdentifier, aggregator) {
|
|
36
|
+
var _this;
|
|
35
37
|
let auto = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
|
|
36
38
|
super(agentIdentifier, aggregator, FEATURE_NAME, auto);
|
|
39
|
+
_this = this;
|
|
37
40
|
if (!_runtime.isBrowserScope) return; // SPA not supported outside web env
|
|
38
41
|
|
|
39
42
|
try {
|
|
@@ -56,6 +59,12 @@ class Instrument extends _instrumentBase.InstrumentBase {
|
|
|
56
59
|
this.ee.on(FN_END, endTimestamp);
|
|
57
60
|
promiseEE.on(CB_END, endTimestamp);
|
|
58
61
|
jsonpEE.on(CB_END, endTimestamp);
|
|
62
|
+
this.ee.on('fn-err', function () {
|
|
63
|
+
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
64
|
+
args[_key] = arguments[_key];
|
|
65
|
+
}
|
|
66
|
+
if (!args[2]?.__newrelic?.[agentIdentifier]) (0, _handle.handle)('function-err', [...args], undefined, _this.featureName, _this.ee);
|
|
67
|
+
});
|
|
59
68
|
this.ee.buffer([FN_START, FN_END, 'xhr-resolved'], this.featureName);
|
|
60
69
|
eventsEE.buffer([FN_START], this.featureName);
|
|
61
70
|
timerEE.buffer(['setTimeout' + END, 'clearTimeout' + START, FN_START], this.featureName);
|
|
@@ -57,7 +57,7 @@ class InstrumentBase extends _featureBase.FeatureBase {
|
|
|
57
57
|
if ((0, _config.getConfigurationValue)(this.agentIdentifier, "".concat(this.featureName, ".autoStart")) === false) this.auto = false;
|
|
58
58
|
/** if the feature requires opt-in (!auto-start), it will get registered once the api has been called */
|
|
59
59
|
if (this.auto) (0, _drain.registerDrain)(agentIdentifier, featureName);else {
|
|
60
|
-
this.ee.on(
|
|
60
|
+
this.ee.on('manual-start-all', (0, _invoke.single)(() => {
|
|
61
61
|
// register the feature to drain only once the API has been called, it will drain when importAggregator finishes for all the features
|
|
62
62
|
// called by the api in that cycle
|
|
63
63
|
(0, _drain.registerDrain)(this.agentIdentifier, this.featureName);
|
|
@@ -133,19 +133,10 @@ function setAPI(agentIdentifier, forceDrain) {
|
|
|
133
133
|
}
|
|
134
134
|
return appendJsAttribute('application.version', value, 'setApplicationVersion', false);
|
|
135
135
|
};
|
|
136
|
-
apiInterface.start =
|
|
136
|
+
apiInterface.start = () => {
|
|
137
137
|
try {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const featNames = Object.values(_features.FEATURE_NAMES);
|
|
141
|
-
if (features === undefined) features = featNames;else {
|
|
142
|
-
features = Array.isArray(features) && features.length ? features : [features];
|
|
143
|
-
if (features.some(f => !featNames.includes(f))) return (0, _console.warn)("Invalid feature name supplied. Acceptable feature names are: ".concat(featNames));
|
|
144
|
-
if (!features.includes(_features.FEATURE_NAMES.pageViewEvent)) features.push(_features.FEATURE_NAMES.pageViewEvent);
|
|
145
|
-
}
|
|
146
|
-
features.forEach(feature => {
|
|
147
|
-
instanceEE.emit("".concat(feature, "-opt-in"));
|
|
148
|
-
});
|
|
138
|
+
(0, _handle.handle)(_constants.SUPPORTABILITY_METRIC_CHANNEL, ['API/start/called'], undefined, _features.FEATURE_NAMES.metrics, instanceEE);
|
|
139
|
+
instanceEE.emit('manual-start-all');
|
|
149
140
|
} catch (err) {
|
|
150
141
|
(0, _console.warn)('An unexpected issue occurred', err);
|
|
151
142
|
}
|
|
@@ -176,9 +167,10 @@ function setAPI(agentIdentifier, forceDrain) {
|
|
|
176
167
|
try {
|
|
177
168
|
return cb.apply(this, arguments);
|
|
178
169
|
} catch (err) {
|
|
179
|
-
|
|
170
|
+
const error = typeof err === 'string' ? new Error(err) : err;
|
|
171
|
+
tracerEE.emit('fn-err', [arguments, this, error], contextStore);
|
|
180
172
|
// the error came from outside the agent, so don't swallow
|
|
181
|
-
throw
|
|
173
|
+
throw error;
|
|
182
174
|
} finally {
|
|
183
175
|
tracerEE.emit('fn-end', [(0, _now.now)()], contextStore);
|
|
184
176
|
}
|
|
@@ -10,14 +10,8 @@ var denyList = [];
|
|
|
10
10
|
* @returns {boolean} `true` if request does not match any entries of {@link denyList|deny list}; else `false`
|
|
11
11
|
*/
|
|
12
12
|
export function shouldCollectEvent(params) {
|
|
13
|
-
if (
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// XHR requests with an undefined hostname (e.g., data URLs) should not be collected.
|
|
18
|
-
if (params.hostname === undefined) {
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
13
|
+
if (hasUndefinedHostname(params)) return false;
|
|
14
|
+
if (denyList.length === 0) return true;
|
|
21
15
|
for (var i = 0; i < denyList.length; i++) {
|
|
22
16
|
var parsed = denyList[i];
|
|
23
17
|
if (parsed.hostname === '*') {
|
|
@@ -29,6 +23,9 @@ export function shouldCollectEvent(params) {
|
|
|
29
23
|
}
|
|
30
24
|
return true;
|
|
31
25
|
}
|
|
26
|
+
export function hasUndefinedHostname(params) {
|
|
27
|
+
return params.hostname === undefined; // requests with an undefined hostname (e.g., data URLs) should not be collected.
|
|
28
|
+
}
|
|
32
29
|
|
|
33
30
|
/**
|
|
34
31
|
* Initializes the {@link denyList|XHR deny list} by extracting hostname and pathname from an array of filter strings.
|
|
@@ -3,7 +3,15 @@ import { VITAL_NAMES } from './constants';
|
|
|
3
3
|
import { VitalMetric } from './vital-metric';
|
|
4
4
|
import { onTTFB } from 'web-vitals/attribution';
|
|
5
5
|
export const timeToFirstByte = new VitalMetric(VITAL_NAMES.TIME_TO_FIRST_BYTE);
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* onTTFB is not supported in the following scenarios:
|
|
9
|
+
* - in a non-browser scope
|
|
10
|
+
* - in browsers that do not support PerformanceNavigationTiming API
|
|
11
|
+
* - in an iOS browser
|
|
12
|
+
* - cross-origin iframes specifically in firefox and safari
|
|
13
|
+
*/
|
|
14
|
+
if (isBrowserScope && typeof PerformanceNavigationTiming !== 'undefined' && !isiOS && window === window.parent) {
|
|
7
15
|
onTTFB(_ref => {
|
|
8
16
|
let {
|
|
9
17
|
value,
|