@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.
Files changed (59) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +3 -4
  3. package/dist/cjs/common/constants/env.cdn.js +1 -1
  4. package/dist/cjs/common/constants/env.npm.js +1 -1
  5. package/dist/cjs/common/deny-list/deny-list.js +6 -8
  6. package/dist/cjs/common/vitals/time-to-first-byte.js +9 -1
  7. package/dist/cjs/features/ajax/instrument/index.js +11 -8
  8. package/dist/cjs/features/jserrors/aggregate/index.js +7 -2
  9. package/dist/cjs/features/jserrors/instrument/index.js +4 -87
  10. package/dist/cjs/features/jserrors/shared/cast-error.js +66 -0
  11. package/dist/cjs/features/jserrors/{instrument → shared}/uncaught-error.js +4 -2
  12. package/dist/cjs/features/session_trace/aggregate/index.js +6 -2
  13. package/dist/cjs/features/spa/aggregate/index.js +11 -1
  14. package/dist/cjs/features/spa/instrument/index.js +9 -0
  15. package/dist/cjs/features/utils/instrument-base.js +1 -1
  16. package/dist/cjs/loaders/api/api.js +6 -14
  17. package/dist/esm/common/constants/env.cdn.js +1 -1
  18. package/dist/esm/common/constants/env.npm.js +1 -1
  19. package/dist/esm/common/deny-list/deny-list.js +5 -8
  20. package/dist/esm/common/vitals/time-to-first-byte.js +9 -1
  21. package/dist/esm/features/ajax/instrument/index.js +11 -8
  22. package/dist/esm/features/jserrors/aggregate/index.js +7 -2
  23. package/dist/esm/features/jserrors/instrument/index.js +4 -87
  24. package/dist/esm/features/jserrors/shared/cast-error.js +59 -0
  25. package/dist/esm/features/jserrors/{instrument → shared}/uncaught-error.js +5 -2
  26. package/dist/esm/features/session_trace/aggregate/index.js +6 -2
  27. package/dist/esm/features/spa/aggregate/index.js +11 -1
  28. package/dist/esm/features/spa/instrument/index.js +9 -0
  29. package/dist/esm/features/utils/instrument-base.js +1 -1
  30. package/dist/esm/loaders/api/api.js +6 -14
  31. package/dist/types/common/deny-list/deny-list.d.ts +1 -0
  32. package/dist/types/common/deny-list/deny-list.d.ts.map +1 -1
  33. package/dist/types/features/ajax/instrument/index.d.ts.map +1 -1
  34. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  35. package/dist/types/features/jserrors/instrument/index.d.ts.map +1 -1
  36. package/dist/types/features/jserrors/shared/cast-error.d.ts +21 -0
  37. package/dist/types/features/jserrors/shared/cast-error.d.ts.map +1 -0
  38. package/dist/types/features/jserrors/{instrument → shared}/uncaught-error.d.ts +3 -2
  39. package/dist/types/features/jserrors/shared/uncaught-error.d.ts.map +1 -0
  40. package/dist/types/features/session_trace/aggregate/index.d.ts +1 -0
  41. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  42. package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
  43. package/dist/types/features/spa/instrument/index.d.ts.map +1 -1
  44. package/dist/types/loaders/api/api.d.ts +1 -1
  45. package/dist/types/loaders/api/api.d.ts.map +1 -1
  46. package/package.json +1 -1
  47. package/src/common/deny-list/deny-list.js +6 -7
  48. package/src/common/vitals/time-to-first-byte.js +8 -1
  49. package/src/features/ajax/instrument/index.js +11 -9
  50. package/src/features/jserrors/aggregate/index.js +8 -2
  51. package/src/features/jserrors/instrument/index.js +4 -99
  52. package/src/features/jserrors/shared/cast-error.js +69 -0
  53. package/src/features/jserrors/{instrument → shared}/uncaught-error.js +5 -2
  54. package/src/features/session_trace/aggregate/index.js +6 -2
  55. package/src/features/spa/aggregate/index.js +10 -1
  56. package/src/features/spa/instrument/index.js +3 -0
  57. package/src/features/utils/instrument-base.js +1 -1
  58. package/src/loaders/api/api.js +6 -15
  59. 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
- this.params = {};
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
- var responseSize;
317
+ let responseSize;
317
318
  if (typeof this.rxSize === 'string' && this.rxSize.length > 0) {
318
319
  responseSize = +this.rxSize;
319
320
  }
320
- var metrics = {
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
- var params = this.params;
331
- var metrics = this.metrics;
331
+ const params = this.params;
332
+ const metrics = this.metrics;
332
333
  if (this.ended) return;
333
334
  this.ended = true;
334
- for (var i = 0; i < handlersLen; i++) {
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('errorAgg', jsErrorEvent, undefined, FEATURE_NAMES.sessionTrace, this.ee);
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('errorAgg', jsErrorEvent, undefined, FEATURE_NAMES.spa, this.ee);
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', [this.#castError(error), now(), true, {}, this.#replayRunning], undefined, FEATURE_NAMES.jserrors, this.ee);
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', [this.#castPromiseRejectionEvent(promiseRejectionEvent), now(), false, {
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('errorAgg', () => {
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('errorAgg', function () {
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('errorAgg', function (type, name, params, metrics) {
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("".concat(this.featureName, "-opt-in"), single(() => {
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 = features => {
128
+ apiInterface.start = () => {
129
129
  try {
130
- const smTag = !features ? 'undefined' : 'defined';
131
- handle(SUPPORTABILITY_METRIC_CHANNEL, ["API/start/".concat(smTag, "/called")], undefined, FEATURE_NAMES.metrics, instanceEE);
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
- tracerEE.emit('fn-err', [arguments, this, err], contextStore);
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 err;
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,CA0BnB;AAED;;;GAGG;AACH,4CAFW,MAAM,EAAE,QAgClB"}
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":"AA0BA;IACE,2BAAiC;IACjC,mEAkCC;IA/BC,OAAiC;IAEjC,8DAAkF;CA8BrF;+BAjD8B,6BAA6B;mBAFzC,uBAAuB"}
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,4FAsFC;IA4BD,yDA6BC;IAED,qFAOC;;CAwBF;wBAjSY,OAAO,0BAA0B,EAAE,SAAS;8BAN3B,4BAA4B"}
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":"AAgBA;IACE,2BAAiC;IAKjC,mEA+CC;IA1CG,2CAA0C;IAwC5C,yBAA+B;;CAoFlC;+BAjJ8B,6BAA6B"}
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: any;
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"}
@@ -7,6 +7,7 @@ export class Aggregate extends AggregateBase {
7
7
  trace: {};
8
8
  nodeCount: number;
9
9
  sentTrace: {} | null;
10
+ prevStoredEvents: Set<any>;
10
11
  harvestTimeSeconds: any;
11
12
  maxNodesPerHarvest: any;
12
13
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_trace/aggregate/index.js"],"names":[],"mappings":"AAkCA;IACE,2BAAiC;IAGjC,iEAwIC;IAtIC,kBAA+C;IAC/C,sBAAiD;IACjD,aAAc;IACd,UAAe;IACf,kBAAkB;IAClB,qBAAqB;IACrB,wBAA0G;IAC1G,wBAA4G;IAC5G;;4EAEwE;IACxE,sBAAyB;IAGzB,8BAAsC;IA0HxC,sEAcC;IA6CD,oDAOC;IAGD,oCAwBC;IAGD,uEAkBC;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;8BA3gB6B,4BAA4B;6BAF7B,2BAA2B"}
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,mDAktBC;IA9sBC;;;;;;;;;;;;;;;;;MAkBC;IAGD,uBAAsC;CA0rBzC;8BAnuB6B,4BAA4B;2BAJ/B,cAAc"}
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":"AAiBA;IACE,2BAAiC;IACjC,mEAoFC;IA/EG,2CAA0C;;CAsF/C;+BAtG8B,6BAA6B"}
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(features: any): void;
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;;;;;EA0GvB"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/browser-agent",
3
- "version": "1.258.0",
3
+ "version": "1.258.2",
4
4
  "private": false,
5
5
  "author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
6
6
  "description": "New Relic Browser Agent",
@@ -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 (denyList.length === 0) {
14
- return true
15
- }
13
+ if (hasUndefinedHostname(params)) return false
16
14
 
17
- // XHR requests with an undefined hostname (e.g., data URLs) should not be collected.
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
- if (isBrowserScope && typeof PerformanceNavigationTiming !== 'undefined' && !isiOS) {
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 } })