@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
package/CHANGELOG.md CHANGED
@@ -3,6 +3,24 @@
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.2](https://github.com/newrelic/newrelic-browser-agent/compare/v1.258.1...v1.258.2) (2024-05-07)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * Prevent noticeError() API from running if not given an argument ([#1021](https://github.com/newrelic/newrelic-browser-agent/issues/1021)) ([c023a53](https://github.com/newrelic/newrelic-browser-agent/commit/c023a53c20a9f0f2472e1ba5ff78eb7210a906fa))
12
+
13
+ ## [1.258.1](https://github.com/newrelic/newrelic-browser-agent/compare/v1.258.0...v1.258.1) (2024-05-07)
14
+
15
+
16
+ ### Bug Fixes
17
+
18
+ * 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))
19
+ * Improve Error Casting ([#1014](https://github.com/newrelic/newrelic-browser-agent/issues/1014)) ([d1dd20c](https://github.com/newrelic/newrelic-browser-agent/commit/d1dd20ce526ddb697962f695fbb5915410474987))
20
+ * 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))
21
+ * 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))
22
+ * 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))
23
+
6
24
  ## [1.258.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.257.0...v1.258.0) (2024-04-29)
7
25
 
8
26
 
package/README.md CHANGED
@@ -82,6 +82,7 @@ The following features may be disabled by adding `init` entries as shown above.
82
82
  - `metrics`
83
83
  - `page_action`
84
84
  - `page_view_timing`
85
+ - `session_replay`
85
86
  - `session_trace`
86
87
  - `spa`
87
88
 
@@ -186,9 +187,7 @@ Please see our [official documentation](https://docs.newrelic.com/docs/browser/n
186
187
  ```
187
188
 
188
189
  ## Session Replay
189
- The Session Replay feature is currently in Limited Preview and only functional for customers participating in the early access program. To request access, please visit [this link](https://newrelic.com/platform/session-replay-early-access).
190
-
191
- Due to the sensitive nature of the feature, Session Replay has many configuration options, which are configurable in each browser application's *Application Settings* page on the New Relic site. These settings will only be accessible if you are participating in the limited preview. Additionally, you can control the sampling rates, obfuscation conditions and triggering rules of Session Replay.
190
+ The Session Replay feature is now available for limited free use by all customers. The data collected by this feature will become billable starting May 15th, 2024. Please see the [Session Replay documentation](https://docs.newrelic.com/docs/browser/browser-monitoring/browser-pro-features/session-replay/) to get started using this new feature.
192
191
 
193
192
  ## Supported browsers
194
193
 
@@ -239,7 +238,7 @@ A lot of new frameworks support the concept of server-side rendering the pages o
239
238
 
240
239
  ## Disclaimers
241
240
 
242
- The session replay library shipping with this version of the browser agent is in *limited preview* and is not turned on by default. To use the feature, users will need to be part of the limited preview customer group and configure their browser application settings in the UI.
241
+ The session replay library shipping as part of the browser agent is not turned on by default. For information on the use of this feature, see [Session Replay](#session-replay)
243
242
 
244
243
  ## Support
245
244
 
@@ -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.0";
15
+ const VERSION = exports.VERSION = "1.258.2";
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.0";
15
+ const VERSION = exports.VERSION = "1.258.2";
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 (denyList.length === 0) {
21
- return true;
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
- if (_runtime.isBrowserScope && typeof PerformanceNavigationTiming !== 'undefined' && !_runtime.isiOS) {
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
- this.params = {};
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
- var responseSize;
325
+ let responseSize;
325
326
  if (typeof this.rxSize === 'string' && this.rxSize.length > 0) {
326
327
  responseSize = +this.rxSize;
327
328
  }
328
- var metrics = {
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
- var params = this.params;
339
- var metrics = this.metrics;
339
+ const params = this.params;
340
+ const metrics = this.metrics;
340
341
  if (this.ended) return;
341
342
  this.ended = true;
342
- for (var i = 0; i < handlersLen; i++) {
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);
@@ -140,6 +140,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
140
140
  return canonicalStackString;
141
141
  }
142
142
  storeError(err, time, internal, customAttributes, hasReplay) {
143
+ if (!err) return;
143
144
  // are we in an interaction
144
145
  time = time || (0, _now.now)();
145
146
  const agentRuntime = (0, _config.getRuntime)(this.agentIdentifier);
@@ -199,13 +200,17 @@ class Aggregate extends _aggregateBase.AggregateBase {
199
200
 
200
201
  // Trace sends the error in its payload, and both trace & replay simply listens for any error to occur.
201
202
  const jsErrorEvent = [type, bucketHash, params, newMetrics, customAttributes];
202
- (0, _handle.handle)('errorAgg', jsErrorEvent, undefined, _features.FEATURE_NAMES.sessionTrace, this.ee);
203
+ (0, _handle.handle)('trace-jserror', jsErrorEvent, undefined, _features.FEATURE_NAMES.sessionTrace, this.ee);
203
204
  // still send EE events for other features such as above, but stop this one from aggregating internal data
204
205
  if (this.blocked) return;
206
+ if (err?.__newrelic?.[this.agentIdentifier]) {
207
+ params._interactionId = err.__newrelic[this.agentIdentifier].interactionId;
208
+ params._interactionNodeId = err.__newrelic[this.agentIdentifier].interactionNodeId;
209
+ }
205
210
  const softNavInUse = Boolean((0, _nreum.getNREUMInitializedAgent)(this.agentIdentifier)?.features[_features.FEATURE_NAMES.softNav]);
206
211
  // 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
212
  // 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)('errorAgg', jsErrorEvent, undefined, _features.FEATURE_NAMES.spa, this.ee);
213
+ 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
214
  if (params.browserInteractionId && !params._softNavFinished) {
210
215
  // hold onto the error until the in-progress interaction is done, eithered saved or discarded
211
216
  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', [this.#castError(error), (0, _now.now)(), true, {}, this.#replayRunning], undefined, _features.FEATURE_NAMES.jserrors, this.ee);
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', [this.#castPromiseRejectionEvent(promiseRejectionEvent), (0, _now.now)(), false, {
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)('errorAgg', () => {
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)('errorAgg', function () {
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('errorAgg', function (type, name, params, metrics) {
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("".concat(this.featureName, "-opt-in"), (0, _invoke.single)(() => {
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 = features => {
136
+ apiInterface.start = () => {
137
137
  try {
138
- const smTag = !features ? 'undefined' : 'defined';
139
- (0, _handle.handle)(_constants.SUPPORTABILITY_METRIC_CHANNEL, ["API/start/".concat(smTag, "/called")], undefined, _features.FEATURE_NAMES.metrics, instanceEE);
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
- tracerEE.emit('fn-err', [arguments, this, err], contextStore);
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 err;
173
+ throw error;
182
174
  } finally {
183
175
  tracerEE.emit('fn-end', [(0, _now.now)()], contextStore);
184
176
  }
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Exposes the version of the agent
8
8
  */
9
- export const VERSION = "1.258.0";
9
+ export const VERSION = "1.258.2";
10
10
 
11
11
  /**
12
12
  * Exposes the build type of the agent
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Exposes the version of the agent
8
8
  */
9
- export const VERSION = "1.258.0";
9
+ export const VERSION = "1.258.2";
10
10
 
11
11
  /**
12
12
  * Exposes the build type of the agent
@@ -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 (denyList.length === 0) {
14
- return true;
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
- if (isBrowserScope && typeof PerformanceNavigationTiming !== 'undefined' && !isiOS) {
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,