@newrelic/browser-agent 1.258.0 → 1.258.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +3 -4
- 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 +7 -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 +7 -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 +8 -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
|
@@ -17,6 +17,7 @@ import { FEATURE_NAME } from '../constants';
|
|
|
17
17
|
import { FEATURE_NAMES } from '../../../loaders/features/features';
|
|
18
18
|
import { SUPPORTABILITY_METRIC } from '../../metrics/constants';
|
|
19
19
|
import { now } from '../../../common/timing/now';
|
|
20
|
+
import { hasUndefinedHostname } from '../../../common/deny-list/deny-list';
|
|
20
21
|
var handlers = ['load', 'error', 'abort', 'timeout'];
|
|
21
22
|
var handlersLen = handlers.length;
|
|
22
23
|
var origRequest = originals.REQ;
|
|
@@ -307,17 +308,17 @@ function subscribeToEvents(agentIdentifier, ee, handler, dt) {
|
|
|
307
308
|
// eslint-disable-next-line handle-callback-err
|
|
308
309
|
function onFetchDone(_, res) {
|
|
309
310
|
this.endTime = now();
|
|
310
|
-
if (!this.params) {
|
|
311
|
-
|
|
312
|
-
|
|
311
|
+
if (!this.params) this.params = {};
|
|
312
|
+
if (hasUndefinedHostname(this.params)) return; // don't bother with fetch to url with no hostname
|
|
313
|
+
|
|
313
314
|
this.params.status = res ? res.status : 0;
|
|
314
315
|
|
|
315
316
|
// convert rxSize to a number
|
|
316
|
-
|
|
317
|
+
let responseSize;
|
|
317
318
|
if (typeof this.rxSize === 'string' && this.rxSize.length > 0) {
|
|
318
319
|
responseSize = +this.rxSize;
|
|
319
320
|
}
|
|
320
|
-
|
|
321
|
+
const metrics = {
|
|
321
322
|
txSize: this.txSize,
|
|
322
323
|
rxSize: responseSize,
|
|
323
324
|
duration: now() - this.startTime
|
|
@@ -327,14 +328,16 @@ function subscribeToEvents(agentIdentifier, ee, handler, dt) {
|
|
|
327
328
|
|
|
328
329
|
// Create report for XHR request that has finished
|
|
329
330
|
function end(xhr) {
|
|
330
|
-
|
|
331
|
-
|
|
331
|
+
const params = this.params;
|
|
332
|
+
const metrics = this.metrics;
|
|
332
333
|
if (this.ended) return;
|
|
333
334
|
this.ended = true;
|
|
334
|
-
for (
|
|
335
|
+
for (let i = 0; i < handlersLen; i++) {
|
|
335
336
|
xhr.removeEventListener(handlers[i], this.listener, false);
|
|
336
337
|
}
|
|
337
338
|
if (params.aborted) return;
|
|
339
|
+
if (hasUndefinedHostname(params)) return; // don't bother with XHR of url with no hostname
|
|
340
|
+
|
|
338
341
|
metrics.duration = now() - this.startTime;
|
|
339
342
|
if (!this.loadCaptureCalled && xhr.readyState === 4) {
|
|
340
343
|
captureXhrData(this, xhr);
|
|
@@ -135,6 +135,7 @@ export class Aggregate extends AggregateBase {
|
|
|
135
135
|
return canonicalStackString;
|
|
136
136
|
}
|
|
137
137
|
storeError(err, time, internal, customAttributes, hasReplay) {
|
|
138
|
+
if (!err) return;
|
|
138
139
|
// are we in an interaction
|
|
139
140
|
time = time || now();
|
|
140
141
|
const agentRuntime = getRuntime(this.agentIdentifier);
|
|
@@ -194,13 +195,17 @@ export class Aggregate extends AggregateBase {
|
|
|
194
195
|
|
|
195
196
|
// Trace sends the error in its payload, and both trace & replay simply listens for any error to occur.
|
|
196
197
|
const jsErrorEvent = [type, bucketHash, params, newMetrics, customAttributes];
|
|
197
|
-
handle('
|
|
198
|
+
handle('trace-jserror', jsErrorEvent, undefined, FEATURE_NAMES.sessionTrace, this.ee);
|
|
198
199
|
// still send EE events for other features such as above, but stop this one from aggregating internal data
|
|
199
200
|
if (this.blocked) return;
|
|
201
|
+
if (err?.__newrelic?.[this.agentIdentifier]) {
|
|
202
|
+
params._interactionId = err.__newrelic[this.agentIdentifier].interactionId;
|
|
203
|
+
params._interactionNodeId = err.__newrelic[this.agentIdentifier].interactionNodeId;
|
|
204
|
+
}
|
|
200
205
|
const softNavInUse = Boolean(getNREUMInitializedAgent(this.agentIdentifier)?.features[FEATURE_NAMES.softNav]);
|
|
201
206
|
// 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.
|
|
202
207
|
// They each will also tack on their respective properties to the params object as part of the decision flow.
|
|
203
|
-
if (softNavInUse) handle('jserror', [params, time], undefined, FEATURE_NAMES.softNav, this.ee);else handle('
|
|
208
|
+
if (softNavInUse) handle('jserror', [params, time], undefined, FEATURE_NAMES.softNav, this.ee);else handle('spa-jserror', jsErrorEvent, undefined, FEATURE_NAMES.spa, this.ee);
|
|
204
209
|
if (params.browserInteractionId && !params._softNavFinished) {
|
|
205
210
|
// hold onto the error until the in-progress interaction is done, eithered saved or discarded
|
|
206
211
|
this.bufferedErrorsUnderSpa[params.browserInteractionId] ??= [];
|
|
@@ -9,13 +9,11 @@ import { FEATURE_NAME } from '../constants';
|
|
|
9
9
|
import { FEATURE_NAMES } from '../../../loaders/features/features';
|
|
10
10
|
import { globalScope } from '../../../common/constants/runtime';
|
|
11
11
|
import { eventListenerOpts } from '../../../common/event-listener/event-listener-opts';
|
|
12
|
-
import { stringify } from '../../../common/util/stringify';
|
|
13
|
-
import { UncaughtError } from './uncaught-error';
|
|
14
12
|
import { now } from '../../../common/timing/now';
|
|
15
13
|
import { SR_EVENT_EMITTER_TYPES } from '../../session_replay/constants';
|
|
14
|
+
import { castError, castErrorEvent, castPromiseRejectionEvent } from '../shared/cast-error';
|
|
16
15
|
export class Instrument extends InstrumentBase {
|
|
17
16
|
static featureName = FEATURE_NAME;
|
|
18
|
-
#seenErrors = new Set();
|
|
19
17
|
#replayRunning = false;
|
|
20
18
|
constructor(agentIdentifier, aggregator) {
|
|
21
19
|
let auto = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
|
|
@@ -24,38 +22,22 @@ export class Instrument extends InstrumentBase {
|
|
|
24
22
|
// this try-catch can be removed when IE11 is completely unsupported & gone
|
|
25
23
|
this.removeOnAbort = new AbortController();
|
|
26
24
|
} catch (e) {}
|
|
27
|
-
|
|
28
|
-
// Capture function errors early in case the spa feature is loaded
|
|
29
|
-
this.ee.on('fn-err', (args, obj, error) => {
|
|
30
|
-
if (!this.abortHandler || this.#seenErrors.has(error)) return;
|
|
31
|
-
this.#seenErrors.add(error);
|
|
32
|
-
handle('err', [this.#castError(error), now()], undefined, FEATURE_NAMES.jserrors, this.ee);
|
|
33
|
-
});
|
|
34
25
|
this.ee.on('internal-error', error => {
|
|
35
26
|
if (!this.abortHandler) return;
|
|
36
|
-
handle('ierr', [
|
|
27
|
+
handle('ierr', [castError(error), now(), true, {}, this.#replayRunning], undefined, FEATURE_NAMES.jserrors, this.ee);
|
|
37
28
|
});
|
|
38
29
|
this.ee.on(SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, isRunning => {
|
|
39
30
|
this.#replayRunning = isRunning;
|
|
40
31
|
});
|
|
41
32
|
globalScope.addEventListener('unhandledrejection', promiseRejectionEvent => {
|
|
42
33
|
if (!this.abortHandler) return;
|
|
43
|
-
handle('err', [
|
|
34
|
+
handle('err', [castPromiseRejectionEvent(promiseRejectionEvent), now(), false, {
|
|
44
35
|
unhandledPromiseRejection: 1
|
|
45
36
|
}, this.#replayRunning], undefined, FEATURE_NAMES.jserrors, this.ee);
|
|
46
37
|
}, eventListenerOpts(false, this.removeOnAbort?.signal));
|
|
47
38
|
globalScope.addEventListener('error', errorEvent => {
|
|
48
39
|
if (!this.abortHandler) return;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* If the spa feature is loaded, errors may already have been captured in the `fn-err` listener above.
|
|
52
|
-
* This ensures those errors are not captured twice.
|
|
53
|
-
*/
|
|
54
|
-
if (this.#seenErrors.has(errorEvent.error)) {
|
|
55
|
-
this.#seenErrors.delete(errorEvent.error);
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
handle('err', [this.#castErrorEvent(errorEvent), now(), false, {}, this.#replayRunning], undefined, FEATURE_NAMES.jserrors, this.ee);
|
|
40
|
+
handle('err', [castErrorEvent(errorEvent), now(), false, {}, this.#replayRunning], undefined, FEATURE_NAMES.jserrors, this.ee);
|
|
59
41
|
}, eventListenerOpts(false, this.removeOnAbort?.signal));
|
|
60
42
|
this.abortHandler = this.#abort; // we also use this as a flag to denote that the feature is active or on and handling errors
|
|
61
43
|
this.importAggregator();
|
|
@@ -64,71 +46,6 @@ export class Instrument extends InstrumentBase {
|
|
|
64
46
|
/** Restoration and resource release tasks to be done if JS error loader is being aborted. Unwind changes to globals. */
|
|
65
47
|
#abort() {
|
|
66
48
|
this.removeOnAbort?.abort();
|
|
67
|
-
this.#seenErrors.clear();
|
|
68
49
|
this.abortHandler = undefined; // weakly allow this abort op to run only once
|
|
69
50
|
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Any value can be used with the `throw` keyword. This function ensures that the value is
|
|
73
|
-
* either a proper Error instance or attempts to convert it to an UncaughtError instance.
|
|
74
|
-
* @param {any} error The value thrown
|
|
75
|
-
* @returns {Error|UncaughtError} The converted error instance
|
|
76
|
-
*/
|
|
77
|
-
#castError(error) {
|
|
78
|
-
if (error instanceof Error) {
|
|
79
|
-
return error;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* The thrown value may contain a message property. If it does, try to treat the thrown
|
|
84
|
-
* value as an Error-like object.
|
|
85
|
-
*/
|
|
86
|
-
if (typeof error?.message !== 'undefined') {
|
|
87
|
-
return new UncaughtError(error.message, error.filename || error.sourceURL, error.lineno || error.line, error.colno || error.col);
|
|
88
|
-
}
|
|
89
|
-
return new UncaughtError(typeof error === 'string' ? error : stringify(error));
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Attempts to convert a PromiseRejectionEvent object to an Error object
|
|
94
|
-
* @param {PromiseRejectionEvent} unhandledRejectionEvent The unhandled promise rejection event
|
|
95
|
-
* @returns {Error} An Error object with the message as the casted reason
|
|
96
|
-
*/
|
|
97
|
-
#castPromiseRejectionEvent(promiseRejectionEvent) {
|
|
98
|
-
let prefix = 'Unhandled Promise Rejection: ';
|
|
99
|
-
if (promiseRejectionEvent?.reason instanceof Error) {
|
|
100
|
-
try {
|
|
101
|
-
promiseRejectionEvent.reason.message = prefix + promiseRejectionEvent.reason.message;
|
|
102
|
-
return promiseRejectionEvent.reason;
|
|
103
|
-
} catch (e) {
|
|
104
|
-
return promiseRejectionEvent.reason;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
if (typeof promiseRejectionEvent.reason === 'undefined') return new UncaughtError(prefix);
|
|
108
|
-
const error = this.#castError(promiseRejectionEvent.reason);
|
|
109
|
-
error.message = prefix + error.message;
|
|
110
|
-
return error;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Attempts to convert an ErrorEvent object to an Error object
|
|
115
|
-
* @param {ErrorEvent} errorEvent The error event
|
|
116
|
-
* @returns {Error|UncaughtError} The error event converted to an Error object
|
|
117
|
-
*/
|
|
118
|
-
#castErrorEvent(errorEvent) {
|
|
119
|
-
if (errorEvent.error instanceof SyntaxError && !/:\d+$/.test(errorEvent.error.stack?.trim())) {
|
|
120
|
-
const error = new UncaughtError(errorEvent.message, errorEvent.filename, errorEvent.lineno, errorEvent.colno);
|
|
121
|
-
error.name = SyntaxError.name;
|
|
122
|
-
return error;
|
|
123
|
-
}
|
|
124
|
-
if (errorEvent.error instanceof Error) {
|
|
125
|
-
return errorEvent.error;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Older browsers do not contain the `error` property on the ErrorEvent instance.
|
|
130
|
-
* https://caniuse.com/mdn-api_errorevent_error
|
|
131
|
-
*/
|
|
132
|
-
return new UncaughtError(errorEvent.message, errorEvent.filename, errorEvent.lineno, errorEvent.colno);
|
|
133
|
-
}
|
|
134
51
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { UncaughtError } from './uncaught-error';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Any value can be used with the `throw` keyword. This function ensures that the value is
|
|
5
|
+
* either a proper Error instance or attempts to convert it to an UncaughtError instance.
|
|
6
|
+
* @param {any} error The value thrown
|
|
7
|
+
* @returns {Error|UncaughtError} The converted error instance
|
|
8
|
+
*/
|
|
9
|
+
export function castError(error) {
|
|
10
|
+
/** Sometimes a browser can emit an error object with no stack */
|
|
11
|
+
if (canTrustError(error)) {
|
|
12
|
+
return error;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The thrown value may contain a message property. If it does, try to treat the thrown
|
|
17
|
+
* value as an Error-like object.
|
|
18
|
+
*/
|
|
19
|
+
return new UncaughtError(error?.message !== undefined ? error.message : error, error?.filename || error?.sourceURL, error?.lineno || error?.line, error?.colno || error?.col, error?.__newrelic);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Attempts to convert a PromiseRejectionEvent object to an Error object
|
|
24
|
+
* @param {PromiseRejectionEvent} unhandledRejectionEvent The unhandled promise rejection event
|
|
25
|
+
* @returns {Error} An Error object with the message as the casted reason
|
|
26
|
+
*/
|
|
27
|
+
export function castPromiseRejectionEvent(promiseRejectionEvent) {
|
|
28
|
+
let prefix = 'Unhandled Promise Rejection';
|
|
29
|
+
if (canTrustError(promiseRejectionEvent?.reason)) {
|
|
30
|
+
try {
|
|
31
|
+
promiseRejectionEvent.reason.message = prefix + ': ' + promiseRejectionEvent.reason.message;
|
|
32
|
+
return castError(promiseRejectionEvent.reason);
|
|
33
|
+
} catch (e) {
|
|
34
|
+
return castError(promiseRejectionEvent.reason);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (typeof promiseRejectionEvent.reason === 'undefined') return castError(prefix);
|
|
38
|
+
const error = castError(promiseRejectionEvent.reason);
|
|
39
|
+
error.message = prefix + ': ' + error?.message;
|
|
40
|
+
return error;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Attempts to convert an ErrorEvent object to an Error object
|
|
45
|
+
* @param {ErrorEvent} errorEvent The error event
|
|
46
|
+
* @returns {Error|UncaughtError} The error event converted to an Error object
|
|
47
|
+
*/
|
|
48
|
+
export function castErrorEvent(errorEvent) {
|
|
49
|
+
if (errorEvent.error instanceof SyntaxError && !/:\d+$/.test(errorEvent.error.stack?.trim())) {
|
|
50
|
+
const error = new UncaughtError(errorEvent.message, errorEvent.filename, errorEvent.lineno, errorEvent.colno, errorEvent.error.__newrelic);
|
|
51
|
+
error.name = SyntaxError.name;
|
|
52
|
+
return error;
|
|
53
|
+
}
|
|
54
|
+
if (canTrustError(errorEvent.error)) return errorEvent.error;
|
|
55
|
+
return castError(errorEvent);
|
|
56
|
+
}
|
|
57
|
+
function canTrustError(error) {
|
|
58
|
+
return error instanceof Error && !!error.stack;
|
|
59
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { stringify } from '../../../common/util/stringify';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Represents an uncaught non Error type error. This class does
|
|
3
5
|
* not extend the Error class to prevent an invalid stack trace
|
|
@@ -5,11 +7,12 @@
|
|
|
5
7
|
* do not use the Error class (strings, etc) to an object.
|
|
6
8
|
*/
|
|
7
9
|
export class UncaughtError {
|
|
8
|
-
constructor(message, filename, lineno, colno) {
|
|
10
|
+
constructor(message, filename, lineno, colno, newrelic) {
|
|
9
11
|
this.name = 'UncaughtError';
|
|
10
|
-
this.message = message;
|
|
12
|
+
this.message = typeof message === 'string' ? message : stringify(message);
|
|
11
13
|
this.sourceURL = filename;
|
|
12
14
|
this.line = lineno;
|
|
13
15
|
this.column = colno;
|
|
16
|
+
this.__newrelic = newrelic;
|
|
14
17
|
}
|
|
15
18
|
}
|
|
@@ -52,6 +52,7 @@ export class Aggregate extends AggregateBase {
|
|
|
52
52
|
this.trace = {};
|
|
53
53
|
this.nodeCount = 0;
|
|
54
54
|
this.sentTrace = null;
|
|
55
|
+
this.prevStoredEvents = new Set();
|
|
55
56
|
this.harvestTimeSeconds = getConfigurationValue(agentIdentifier, 'session_trace.harvestTimeSeconds') || 10;
|
|
56
57
|
this.maxNodesPerHarvest = getConfigurationValue(agentIdentifier, 'session_trace.maxNodesPerHarvest') || 1000;
|
|
57
58
|
/**
|
|
@@ -119,7 +120,7 @@ export class Aggregate extends AggregateBase {
|
|
|
119
120
|
return controlTraceOp(on);
|
|
120
121
|
}, this.featureName, this.ee);
|
|
121
122
|
} else {
|
|
122
|
-
registerHandler('
|
|
123
|
+
registerHandler('trace-jserror', () => {
|
|
123
124
|
seenAnError = true;
|
|
124
125
|
switchToFull();
|
|
125
126
|
}, this.featureName, this.ee);
|
|
@@ -214,7 +215,7 @@ export class Aggregate extends AggregateBase {
|
|
|
214
215
|
}
|
|
215
216
|
return operationalGate.settle(() => _this.storeSTN(...args));
|
|
216
217
|
}, this.featureName, this.ee);
|
|
217
|
-
registerHandler('
|
|
218
|
+
registerHandler('trace-jserror', function () {
|
|
218
219
|
for (var _len6 = arguments.length, args = new Array(_len6), _key6 = 0; _key6 < _len6; _key6++) {
|
|
219
220
|
args[_key6] = arguments[_key6];
|
|
220
221
|
}
|
|
@@ -263,6 +264,7 @@ export class Aggregate extends AggregateBase {
|
|
|
263
264
|
}
|
|
264
265
|
}
|
|
265
266
|
#prepareHarvest(options) {
|
|
267
|
+
this.prevStoredEvents.clear(); // release references to past events for GC
|
|
266
268
|
if (this.isStandalone) {
|
|
267
269
|
if (this.ptid && now() >= MAX_TRACE_DURATION) {
|
|
268
270
|
// Perform a final harvest once we hit or exceed the max session trace time
|
|
@@ -329,6 +331,8 @@ export class Aggregate extends AggregateBase {
|
|
|
329
331
|
// Tracks the events and their listener's duration on objects wrapped by wrap-events.
|
|
330
332
|
storeEvent(currentEvent, target, start, end) {
|
|
331
333
|
if (this.shouldIgnoreEvent(currentEvent, target)) return;
|
|
334
|
+
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.
|
|
335
|
+
this.prevStoredEvents.add(currentEvent);
|
|
332
336
|
const evt = {
|
|
333
337
|
n: this.evtName(currentEvent.type),
|
|
334
338
|
s: start,
|
|
@@ -642,7 +642,7 @@ export class Aggregate extends AggregateBase {
|
|
|
642
642
|
state.interactionsSent = [];
|
|
643
643
|
}
|
|
644
644
|
}
|
|
645
|
-
baseEE.on('
|
|
645
|
+
baseEE.on('spa-jserror', function (type, name, params, metrics) {
|
|
646
646
|
if (!state.currentNode) return;
|
|
647
647
|
params._interactionId = state.currentNode.interaction.id;
|
|
648
648
|
// do not capture parentNodeId when in root node
|
|
@@ -650,6 +650,16 @@ export class Aggregate extends AggregateBase {
|
|
|
650
650
|
params._interactionNodeId = state.currentNode.id;
|
|
651
651
|
}
|
|
652
652
|
});
|
|
653
|
+
register('function-err', function (args, obj, error) {
|
|
654
|
+
if (!state.currentNode) return;
|
|
655
|
+
error.__newrelic ??= {};
|
|
656
|
+
error.__newrelic[agentIdentifier] = {
|
|
657
|
+
interactionId: state.currentNode.interaction.id
|
|
658
|
+
};
|
|
659
|
+
if (state.currentNode.type && state.currentNode.type !== 'interaction') {
|
|
660
|
+
error.__newrelic[agentIdentifier].interactionNodeId = state.currentNode.id;
|
|
661
|
+
}
|
|
662
|
+
}, this.featureName, baseEE);
|
|
653
663
|
baseEE.on('interaction', saveInteraction);
|
|
654
664
|
function getActionText(node) {
|
|
655
665
|
var nodeType = node.tagName.toLowerCase();
|
|
@@ -8,6 +8,7 @@ import { InstrumentBase } from '../../utils/instrument-base';
|
|
|
8
8
|
import * as CONSTANTS from '../constants';
|
|
9
9
|
import { isBrowserScope } from '../../../common/constants/runtime';
|
|
10
10
|
import { now } from '../../../common/timing/now';
|
|
11
|
+
import { handle } from '../../../common/event-emitter/handle';
|
|
11
12
|
const {
|
|
12
13
|
FEATURE_NAME,
|
|
13
14
|
START,
|
|
@@ -23,8 +24,10 @@ const {
|
|
|
23
24
|
export class Instrument extends InstrumentBase {
|
|
24
25
|
static featureName = FEATURE_NAME;
|
|
25
26
|
constructor(agentIdentifier, aggregator) {
|
|
27
|
+
var _this;
|
|
26
28
|
let auto = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
|
|
27
29
|
super(agentIdentifier, aggregator, FEATURE_NAME, auto);
|
|
30
|
+
_this = this;
|
|
28
31
|
if (!isBrowserScope) return; // SPA not supported outside web env
|
|
29
32
|
|
|
30
33
|
try {
|
|
@@ -47,6 +50,12 @@ export class Instrument extends InstrumentBase {
|
|
|
47
50
|
this.ee.on(FN_END, endTimestamp);
|
|
48
51
|
promiseEE.on(CB_END, endTimestamp);
|
|
49
52
|
jsonpEE.on(CB_END, endTimestamp);
|
|
53
|
+
this.ee.on('fn-err', function () {
|
|
54
|
+
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
55
|
+
args[_key] = arguments[_key];
|
|
56
|
+
}
|
|
57
|
+
if (!args[2]?.__newrelic?.[agentIdentifier]) handle('function-err', [...args], undefined, _this.featureName, _this.ee);
|
|
58
|
+
});
|
|
50
59
|
this.ee.buffer([FN_START, FN_END, 'xhr-resolved'], this.featureName);
|
|
51
60
|
eventsEE.buffer([FN_START], this.featureName);
|
|
52
61
|
timerEE.buffer(['setTimeout' + END, 'clearTimeout' + START, FN_START], this.featureName);
|
|
@@ -52,7 +52,7 @@ export class InstrumentBase extends FeatureBase {
|
|
|
52
52
|
if (getConfigurationValue(this.agentIdentifier, "".concat(this.featureName, ".autoStart")) === false) this.auto = false;
|
|
53
53
|
/** if the feature requires opt-in (!auto-start), it will get registered once the api has been called */
|
|
54
54
|
if (this.auto) registerDrain(agentIdentifier, featureName);else {
|
|
55
|
-
this.ee.on(
|
|
55
|
+
this.ee.on('manual-start-all', single(() => {
|
|
56
56
|
// register the feature to drain only once the API has been called, it will drain when importAggregator finishes for all the features
|
|
57
57
|
// called by the api in that cycle
|
|
58
58
|
registerDrain(this.agentIdentifier, this.featureName);
|
|
@@ -125,19 +125,10 @@ export function setAPI(agentIdentifier, forceDrain) {
|
|
|
125
125
|
}
|
|
126
126
|
return appendJsAttribute('application.version', value, 'setApplicationVersion', false);
|
|
127
127
|
};
|
|
128
|
-
apiInterface.start =
|
|
128
|
+
apiInterface.start = () => {
|
|
129
129
|
try {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const featNames = Object.values(FEATURE_NAMES);
|
|
133
|
-
if (features === undefined) features = featNames;else {
|
|
134
|
-
features = Array.isArray(features) && features.length ? features : [features];
|
|
135
|
-
if (features.some(f => !featNames.includes(f))) return warn("Invalid feature name supplied. Acceptable feature names are: ".concat(featNames));
|
|
136
|
-
if (!features.includes(FEATURE_NAMES.pageViewEvent)) features.push(FEATURE_NAMES.pageViewEvent);
|
|
137
|
-
}
|
|
138
|
-
features.forEach(feature => {
|
|
139
|
-
instanceEE.emit("".concat(feature, "-opt-in"));
|
|
140
|
-
});
|
|
130
|
+
handle(SUPPORTABILITY_METRIC_CHANNEL, ['API/start/called'], undefined, FEATURE_NAMES.metrics, instanceEE);
|
|
131
|
+
instanceEE.emit('manual-start-all');
|
|
141
132
|
} catch (err) {
|
|
142
133
|
warn('An unexpected issue occurred', err);
|
|
143
134
|
}
|
|
@@ -168,9 +159,10 @@ export function setAPI(agentIdentifier, forceDrain) {
|
|
|
168
159
|
try {
|
|
169
160
|
return cb.apply(this, arguments);
|
|
170
161
|
} catch (err) {
|
|
171
|
-
|
|
162
|
+
const error = typeof err === 'string' ? new Error(err) : err;
|
|
163
|
+
tracerEE.emit('fn-err', [arguments, this, error], contextStore);
|
|
172
164
|
// the error came from outside the agent, so don't swallow
|
|
173
|
-
throw
|
|
165
|
+
throw error;
|
|
174
166
|
} finally {
|
|
175
167
|
tracerEE.emit('fn-end', [now()], contextStore);
|
|
176
168
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* @returns {boolean} `true` if request does not match any entries of {@link denyList|deny list}; else `false`
|
|
5
5
|
*/
|
|
6
6
|
export function shouldCollectEvent(params: Object): boolean;
|
|
7
|
+
export function hasUndefinedHostname(params: any): boolean;
|
|
7
8
|
/**
|
|
8
9
|
* Initializes the {@link denyList|XHR deny list} by extracting hostname and pathname from an array of filter strings.
|
|
9
10
|
* @param {string[]} denyListConfig - array of URL filters to identify XHR requests to be excluded from collection
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deny-list.d.ts","sourceRoot":"","sources":["../../../../src/common/deny-list/deny-list.js"],"names":[],"mappings":"AAMA;;;;GAIG;AACH,2CAHW,MAAM,GACJ,OAAO,
|
|
1
|
+
{"version":3,"file":"deny-list.d.ts","sourceRoot":"","sources":["../../../../src/common/deny-list/deny-list.js"],"names":[],"mappings":"AAMA;;;;GAIG;AACH,2CAHW,MAAM,GACJ,OAAO,CAqBnB;AAED,2DAEC;AAED;;;GAGG;AACH,4CAFW,MAAM,EAAE,QAgClB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/ajax/instrument/index.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/ajax/instrument/index.js"],"names":[],"mappings":"AA2BA;IACE,2BAAiC;IACjC,mEAkCC;IA/BC,OAAiC;IAEjC,8DAAkF;CA8BrF;+BAlD8B,6BAA6B;mBAFzC,uBAAuB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/aggregate/index.js"],"names":[],"mappings":"AAyBA;;GAEG;AAEH;IACE,2BAAiC;IACjC,mDAgCC;IA7BC,kBAAuB;IACvB,eAAoB;IACpB,qBAA0B;IAC1B,2BAAgC;IAChC,mCAA4B;IAC5B,qBAAwB;IA0B1B;;;MAwBC;IAED,qCAWC;IAED,8BAEC;IAED,oEAMC;IAED;;;;;;OAMG;IACH,qCAHW,SAAS,GACP,MAAM,CAgBlB;IAED,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/aggregate/index.js"],"names":[],"mappings":"AAyBA;;GAEG;AAEH;IACE,2BAAiC;IACjC,mDAgCC;IA7BC,kBAAuB;IACvB,eAAoB;IACpB,qBAA0B;IAC1B,2BAAgC;IAChC,mCAA4B;IAC5B,qBAAwB;IA0B1B;;;MAwBC;IAED,qCAWC;IAED,8BAEC;IAED,oEAMC;IAED;;;;;;OAMG;IACH,qCAHW,SAAS,GACP,MAAM,CAgBlB;IAED,4FA4FC;IA4BD,yDA6BC;IAED,qFAOC;;CAwBF;wBAvSY,OAAO,0BAA0B,EAAE,SAAS;8BAN3B,4BAA4B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/instrument/index.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/instrument/index.js"],"names":[],"mappings":"AAeA;IACE,2BAAiC;IAIjC,mEA6BC;IAxBG,2CAA0C;IAsB5C,yBAA+B;;CASlC;+BAlD8B,6BAA6B"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Any value can be used with the `throw` keyword. This function ensures that the value is
|
|
3
|
+
* either a proper Error instance or attempts to convert it to an UncaughtError instance.
|
|
4
|
+
* @param {any} error The value thrown
|
|
5
|
+
* @returns {Error|UncaughtError} The converted error instance
|
|
6
|
+
*/
|
|
7
|
+
export function castError(error: any): Error | UncaughtError;
|
|
8
|
+
/**
|
|
9
|
+
* Attempts to convert a PromiseRejectionEvent object to an Error object
|
|
10
|
+
* @param {PromiseRejectionEvent} unhandledRejectionEvent The unhandled promise rejection event
|
|
11
|
+
* @returns {Error} An Error object with the message as the casted reason
|
|
12
|
+
*/
|
|
13
|
+
export function castPromiseRejectionEvent(promiseRejectionEvent: any): Error;
|
|
14
|
+
/**
|
|
15
|
+
* Attempts to convert an ErrorEvent object to an Error object
|
|
16
|
+
* @param {ErrorEvent} errorEvent The error event
|
|
17
|
+
* @returns {Error|UncaughtError} The error event converted to an Error object
|
|
18
|
+
*/
|
|
19
|
+
export function castErrorEvent(errorEvent: ErrorEvent): Error | UncaughtError;
|
|
20
|
+
import { UncaughtError } from './uncaught-error';
|
|
21
|
+
//# sourceMappingURL=cast-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cast-error.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/shared/cast-error.js"],"names":[],"mappings":"AAEA;;;;;KAKK;AACL,iCAHa,GAAG,GACD,KAAK,GAAC,aAAa,CAmBjC;AAED;;;;KAIK;AACL,uEAFe,KAAK,CAmBnB;AAED;;;;KAIK;AACL,2CAHa,UAAU,GACR,KAAK,GAAC,aAAa,CAUjC;8BAhE6B,kBAAkB"}
|
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
* do not use the Error class (strings, etc) to an object.
|
|
6
6
|
*/
|
|
7
7
|
export class UncaughtError {
|
|
8
|
-
constructor(message: any, filename: any, lineno: any, colno: any);
|
|
8
|
+
constructor(message: any, filename: any, lineno: any, colno: any, newrelic: any);
|
|
9
9
|
name: string;
|
|
10
|
-
message:
|
|
10
|
+
message: string;
|
|
11
11
|
sourceURL: any;
|
|
12
12
|
line: any;
|
|
13
13
|
column: any;
|
|
14
|
+
__newrelic: any;
|
|
14
15
|
}
|
|
15
16
|
//# sourceMappingURL=uncaught-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uncaught-error.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/shared/uncaught-error.js"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH;IACE,iFAOC;IANC,aAA2B;IAC3B,gBAAyE;IACzE,eAAyB;IACzB,UAAkB;IAClB,YAAmB;IACnB,gBAA0B;CAE7B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_trace/aggregate/index.js"],"names":[],"mappings":"AAkCA;IACE,2BAAiC;IAGjC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_trace/aggregate/index.js"],"names":[],"mappings":"AAkCA;IACE,2BAAiC;IAGjC,iEAyIC;IAvIC,kBAA+C;IAC/C,sBAAiD;IACjD,aAAc;IACd,UAAe;IACf,kBAAkB;IAClB,qBAAqB;IACrB,2BAAiC;IACjC,wBAA0G;IAC1G,wBAA4G;IAC5G;;4EAEwE;IACxE,sBAAyB;IAGzB,8BAAsC;IA0HxC,sEAcC;IA8CD,oDAOC;IAGD,oCAwBC;IAGD,uEAoBC;IAED,oDAKC;IAED,wBAwBC;IAED,uCAuBC;IAGD,gDASC;IAID,qCAkBC;IAGD,qEAUC;IAGD,mEAUC;IAGD,yBAeC;IAED;;;;OAIG;IACH,2BAHW,MAAM,GACJ,MAAM,CAsBlB;IAGD;;;;;;;YAkCM;0GAC8F;;YAE9F;0FAC8E;;YAE9E,4IAA4I;;;;;;MAMjJ;IAED,mEA6BC;;CACF;8BA/gB6B,4BAA4B;6BAF7B,2BAA2B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/spa/aggregate/index.js"],"names":[],"mappings":"AAiCA;IACE,2BAAiC;IACjC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/spa/aggregate/index.js"],"names":[],"mappings":"AAiCA;IACE,2BAAiC;IACjC,mDA2tBC;IAvtBC;;;;;;;;;;;;;;;;;MAkBC;IAGD,uBAAsC;CAmsBzC;8BA5uB6B,4BAA4B;2BAJ/B,cAAc"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/spa/instrument/index.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/spa/instrument/index.js"],"names":[],"mappings":"AAkBA;IACE,2BAAiC;IACjC,mEAsFC;IAjFG,2CAA0C;;CAwF/C;+BAzG8B,6BAA6B"}
|
|
@@ -15,7 +15,7 @@ export function setAPI(agentIdentifier: any, forceDrain: any, runSoftNavOverSpa?
|
|
|
15
15
|
* @returns @see apiCall
|
|
16
16
|
*/
|
|
17
17
|
setApplicationVersion(value: string | null): any;
|
|
18
|
-
start(
|
|
18
|
+
start(): void;
|
|
19
19
|
interaction(options: any): any;
|
|
20
20
|
setCurrentRouteName: (...args: any[]) => any;
|
|
21
21
|
noticeError(err: any, customAttributes: any): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/api.js"],"names":[],"mappings":"AAmBA,2CAeC;AAID;;;;IAuDE;;;;OAIG;qBAFQ,MAAM;IAWjB;;;;OAIG;iCAFQ,MAAM,GAAC,IAAI;;;;;
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/api.js"],"names":[],"mappings":"AAmBA,2CAeC;AAID;;;;IAuDE;;;;OAIG;qBAFQ,MAAM;IAWjB;;;;OAIG;iCAFQ,MAAM,GAAC,IAAI;;;;;EAiGvB"}
|
package/package.json
CHANGED
|
@@ -10,14 +10,9 @@ 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
|
-
return true
|
|
15
|
-
}
|
|
13
|
+
if (hasUndefinedHostname(params)) return false
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
if (params.hostname === undefined) {
|
|
19
|
-
return false
|
|
20
|
-
}
|
|
15
|
+
if (denyList.length === 0) return true
|
|
21
16
|
|
|
22
17
|
for (var i = 0; i < denyList.length; i++) {
|
|
23
18
|
var parsed = denyList[i]
|
|
@@ -35,6 +30,10 @@ export function shouldCollectEvent (params) {
|
|
|
35
30
|
return true
|
|
36
31
|
}
|
|
37
32
|
|
|
33
|
+
export function hasUndefinedHostname (params) {
|
|
34
|
+
return (params.hostname === undefined) // requests with an undefined hostname (e.g., data URLs) should not be collected.
|
|
35
|
+
}
|
|
36
|
+
|
|
38
37
|
/**
|
|
39
38
|
* Initializes the {@link denyList|XHR deny list} by extracting hostname and pathname from an array of filter strings.
|
|
40
39
|
* @param {string[]} denyListConfig - array of URL filters to identify XHR requests to be excluded from collection
|
|
@@ -5,7 +5,14 @@ import { onTTFB } from 'web-vitals/attribution'
|
|
|
5
5
|
|
|
6
6
|
export const timeToFirstByte = new VitalMetric(VITAL_NAMES.TIME_TO_FIRST_BYTE)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* onTTFB is not supported in the following scenarios:
|
|
10
|
+
* - in a non-browser scope
|
|
11
|
+
* - in browsers that do not support PerformanceNavigationTiming API
|
|
12
|
+
* - in an iOS browser
|
|
13
|
+
* - cross-origin iframes specifically in firefox and safari
|
|
14
|
+
*/
|
|
15
|
+
if (isBrowserScope && typeof PerformanceNavigationTiming !== 'undefined' && !isiOS && window === window.parent) {
|
|
9
16
|
onTTFB(({ value, attribution }) => {
|
|
10
17
|
if (timeToFirstByte.isValid) return
|
|
11
18
|
timeToFirstByte.update({ value, attrs: { navigationEntry: attribution.navigationEntry } })
|