@newrelic/browser-agent 1.251.1 → 1.252.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +5 -5
  3. package/dist/cjs/common/config/state/init.js +2 -2
  4. package/dist/cjs/common/constants/env.cdn.js +1 -1
  5. package/dist/cjs/common/constants/env.npm.js +1 -1
  6. package/dist/cjs/common/drain/drain.js +3 -1
  7. package/dist/cjs/common/event-emitter/contextual-ee.js +7 -1
  8. package/dist/cjs/common/harvest/harvest.js +3 -2
  9. package/dist/cjs/common/session/session-entity.js +3 -1
  10. package/dist/cjs/features/ajax/instrument/index.js +2 -0
  11. package/dist/cjs/features/jserrors/instrument/index.js +5 -0
  12. package/dist/cjs/features/page_view_event/aggregate/index.js +1 -1
  13. package/dist/cjs/features/session_replay/aggregate/index.js +27 -2
  14. package/dist/cjs/features/session_replay/shared/recorder.js +10 -4
  15. package/dist/cjs/features/session_replay/shared/stylesheet-evaluator.js +13 -12
  16. package/dist/cjs/features/utils/instrument-base.js +1 -1
  17. package/dist/cjs/loaders/api/api-methods.js +8 -0
  18. package/dist/cjs/loaders/api/api.js +7 -8
  19. package/dist/cjs/loaders/configure/configure.js +1 -1
  20. package/dist/esm/common/config/state/init.js +2 -2
  21. package/dist/esm/common/constants/env.cdn.js +1 -1
  22. package/dist/esm/common/constants/env.npm.js +1 -1
  23. package/dist/esm/common/drain/drain.js +3 -1
  24. package/dist/esm/common/event-emitter/contextual-ee.js +7 -1
  25. package/dist/esm/common/harvest/harvest.js +3 -2
  26. package/dist/esm/common/session/session-entity.js +3 -1
  27. package/dist/esm/features/ajax/instrument/index.js +2 -0
  28. package/dist/esm/features/jserrors/instrument/index.js +5 -0
  29. package/dist/esm/features/page_view_event/aggregate/index.js +1 -1
  30. package/dist/esm/features/session_replay/aggregate/index.js +27 -2
  31. package/dist/esm/features/session_replay/shared/recorder.js +10 -4
  32. package/dist/esm/features/session_replay/shared/stylesheet-evaluator.js +13 -12
  33. package/dist/esm/features/utils/instrument-base.js +1 -1
  34. package/dist/esm/loaders/api/api-methods.js +2 -0
  35. package/dist/esm/loaders/api/api.js +7 -7
  36. package/dist/esm/loaders/configure/configure.js +1 -1
  37. package/dist/types/common/drain/drain.d.ts +2 -1
  38. package/dist/types/common/drain/drain.d.ts.map +1 -1
  39. package/dist/types/common/event-emitter/contextual-ee.d.ts.map +1 -1
  40. package/dist/types/common/harvest/harvest.d.ts.map +1 -1
  41. package/dist/types/common/session/session-entity.d.ts +0 -1
  42. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  43. package/dist/types/features/ajax/instrument/index.d.ts.map +1 -1
  44. package/dist/types/features/jserrors/instrument/index.d.ts.map +1 -1
  45. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  46. package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
  47. package/dist/types/features/session_replay/shared/stylesheet-evaluator.d.ts +1 -1
  48. package/dist/types/features/session_replay/shared/stylesheet-evaluator.d.ts.map +1 -1
  49. package/dist/types/loaders/api/api-methods.d.ts +3 -0
  50. package/dist/types/loaders/api/api-methods.d.ts.map +1 -0
  51. package/dist/types/loaders/api/api.d.ts +0 -1
  52. package/dist/types/loaders/api/api.d.ts.map +1 -1
  53. package/package.json +4 -1
  54. package/src/common/config/state/init.js +2 -2
  55. package/src/common/drain/drain.js +3 -2
  56. package/src/common/event-emitter/contextual-ee.js +7 -1
  57. package/src/common/harvest/harvest.js +3 -2
  58. package/src/common/session/session-entity.js +3 -1
  59. package/src/features/ajax/instrument/index.js +2 -0
  60. package/src/features/jserrors/instrument/index.js +6 -0
  61. package/src/features/page_view_event/aggregate/index.js +1 -1
  62. package/src/features/session_replay/aggregate/index.js +21 -3
  63. package/src/features/session_replay/shared/recorder.js +10 -4
  64. package/src/features/session_replay/shared/stylesheet-evaluator.js +13 -12
  65. package/src/features/utils/instrument-base.js +1 -1
  66. package/src/loaders/api/api-methods.js +9 -0
  67. package/src/loaders/api/api.js +7 -17
package/CHANGELOG.md CHANGED
@@ -3,6 +3,31 @@
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.252.1](https://github.com/newrelic/newrelic-browser-agent/compare/v1.252.0...v1.252.1) (2024-02-29)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * Fix webpack imports in npm package ([#905](https://github.com/newrelic/newrelic-browser-agent/issues/905)) ([35810a8](https://github.com/newrelic/newrelic-browser-agent/commit/35810a895b7f61ab60ea5c24adfa49c4a3956191))
12
+ * Page load after session timeouts don't start new session ([#899](https://github.com/newrelic/newrelic-browser-agent/issues/899)) ([5c952a0](https://github.com/newrelic/newrelic-browser-agent/commit/5c952a0a212922d84dfd7e8eb388fdbd566b6c00))
13
+
14
+ ## [1.252.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.251.1...v1.252.0) (2024-02-12)
15
+
16
+
17
+ ### Features
18
+
19
+ * add types mappings for esm distribution ([#887](https://github.com/newrelic/newrelic-browser-agent/issues/887)) ([811ed41](https://github.com/newrelic/newrelic-browser-agent/commit/811ed418b74dcb8f25544da79521c384b9fd498a))
20
+ * align browser reported uncaught syntax errors ([#881](https://github.com/newrelic/newrelic-browser-agent/issues/881)) ([d4a0f30](https://github.com/newrelic/newrelic-browser-agent/commit/d4a0f30e0ab4d8edbdb17bf4ebdf282626761045))
21
+ * Capture Internal Metrics for Session Replay Configurations ([#879](https://github.com/newrelic/newrelic-browser-agent/issues/879)) ([f60e7f1](https://github.com/newrelic/newrelic-browser-agent/commit/f60e7f155bb95087ea4af8864b652878f08ccaff))
22
+ * Create more granular metrics about stylesheet fix success ([#882](https://github.com/newrelic/newrelic-browser-agent/issues/882)) ([697f13e](https://github.com/newrelic/newrelic-browser-agent/commit/697f13e6ea5ba0738ffd74dfd214751ab98adf8d))
23
+ * Report config changes away from default state for UX improvement ([#885](https://github.com/newrelic/newrelic-browser-agent/issues/885)) ([aa19a9c](https://github.com/newrelic/newrelic-browser-agent/commit/aa19a9c0737c175c011656f3da3f327dc6442f04))
24
+
25
+
26
+ ### Bug Fixes
27
+
28
+ * Add safe logic to snapshots ([#884](https://github.com/newrelic/newrelic-browser-agent/issues/884)) ([1fcdd8d](https://github.com/newrelic/newrelic-browser-agent/commit/1fcdd8d9a20819911ba7e7350354085a57f1b187))
29
+ * Fix adblock memory leak ([#877](https://github.com/newrelic/newrelic-browser-agent/issues/877)) ([695415b](https://github.com/newrelic/newrelic-browser-agent/commit/695415b0fcaa8b41496fc6556a38ec76dd357539))
30
+
6
31
  ## [1.251.1](https://github.com/newrelic/newrelic-browser-agent/compare/v1.251.0...v1.251.1) (2024-01-29)
7
32
 
8
33
 
package/README.md CHANGED
@@ -24,12 +24,12 @@ $ npm install @newrelic/browser-agent --save
24
24
  $ yarn add @newrelic/browser-agent
25
25
  ```
26
26
 
27
- ## Creating an app in New Relic One
27
+ ## Creating an app in New Relic
28
28
 
29
- Before instrumenting your app using the NPM package, a Browser App should be configured in New Relic One. This may be done with or without a corresponding APM agent. Once the app has been created, the Copy/Paste JavaScript code on the app's *Application settings* page will contain the configuration values needed to define options when instantiating the agent via the NPM package.
29
+ Before instrumenting your app using the NPM package, a Browser App should be configured in New Relic. This may be done with or without a corresponding APM agent. Once the app has been created, the Copy/Paste JavaScript code on the app's *Application settings* page will contain the configuration values needed to define options when instantiating the agent via the NPM package.
30
30
 
31
31
  1. If a browser app does not already exist, create one:
32
- - From the *New Relic One* navigation panel, click *Add Data*.
32
+ - From the *New Relic* navigation panel, click *Add Data*.
33
33
  - Select the *Browser monitoring* data source.
34
34
  - Choose the *APM* or *Copy/Paste* method.
35
35
  - Select or name your app and click *Enable*.
@@ -41,7 +41,7 @@ Before instrumenting your app using the NPM package, a Browser App should be con
41
41
 
42
42
  For best results, import and instantiate the `BrowserAgent` class as close to the top of the `head` element of your app's HTML output as possible. The specific location and method will vary based on your application's architecture or framework. See [Library Support](#library-support) for more information.
43
43
 
44
- Populate the `options` parameter using configuration values found in the the *Copy/Paste JavaScript* box in your browser app's *Application settings* page in New Relic One.
44
+ Populate the `options` parameter using configuration values found in the the *Copy/Paste JavaScript* box in your browser app's *Application settings* page in New Relic.
45
45
 
46
46
  ```javascript
47
47
  import { BrowserAgent } from '@newrelic/browser-agent/loaders/browser-agent'
@@ -137,7 +137,7 @@ The examples above use the `Agent` class at their core, which is ideal for most
137
137
 
138
138
  Using the `MicroAgent` class, it is possible to skip the "auto" instrumentation phases of the other loader types, and provide a *very small* agent designed for capturing data in a controlled manner via the API interfaces. The `MicroAgent` captures a distinct `PageView` event when instantiated, and additional `PageAction` and `JavaScriptError` events may be captured by calling the `noticeError` and `addPageAction` methods.
139
139
 
140
- Because it does not wrap the page-level globals in the same way as the base `Agent` class, the `MicroAgent` is not only smaller but can easily be instantiated multiple times on a single page with low overhead, with each instance configured to report to a different Browser App entity in New Relic One if desired. This accommodates specialized use cases, such as segmented UI designs (e.g., the micro front-end pattern) or applications requiring subsets of manually-handled data to be reported to different application entities.
140
+ Because it does not wrap the page-level globals in the same way as the base `Agent` class, the `MicroAgent` is not only smaller but can easily be instantiated multiple times on a single page with low overhead, with each instance configured to report to a different Browser App entity in New Relic if desired. This accommodates specialized use cases, such as segmented UI designs (e.g., the micro front-end pattern) or applications requiring subsets of manually-handled data to be reported to different application entities.
141
141
 
142
142
  The example below illustrates how to instantiate and interact with two separate `MicroAgent` instances on one page.
143
143
 
@@ -106,9 +106,9 @@ const model = () => {
106
106
  autoStart: true,
107
107
  enabled: false,
108
108
  harvestTimeSeconds: 60,
109
- sampling_rate: 50,
109
+ sampling_rate: 10,
110
110
  // float from 0 - 100
111
- error_sampling_rate: 50,
111
+ error_sampling_rate: 100,
112
112
  // float from 0 - 100
113
113
  collect_fonts: false,
114
114
  // serialize fonts for collection without public asset url, this is currently broken in RRWeb -- https://github.com/rrweb-io/rrweb/issues/1304. When fixed, revisit with test cases
@@ -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.251.1";
15
+ const VERSION = exports.VERSION = "1.252.1";
16
16
 
17
17
  /**
18
18
  * Exposes the build type of the agent
@@ -12,7 +12,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
12
12
  /**
13
13
  * Exposes the version of the agent
14
14
  */
15
- const VERSION = exports.VERSION = "1.251.1";
15
+ const VERSION = exports.VERSION = "1.252.1";
16
16
 
17
17
  /**
18
18
  * Exposes the build type of the agent
@@ -50,15 +50,17 @@ function curateRegistry(agentIdentifier) {
50
50
  * its own named group explicitly, when ready.
51
51
  * @param {string} agentIdentifier - A unique 16 character ID corresponding to an instantiated agent.
52
52
  * @param {string} featureName - A named group into which the feature's buffered events are bucketed.
53
+ * @param {boolean} force - Whether to force the drain to occur immediately, bypassing the registry and staging logic.
53
54
  */
54
55
  function drain() {
55
56
  let agentIdentifier = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
56
57
  let featureName = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'feature';
58
+ let force = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
57
59
  curateRegistry(agentIdentifier);
58
60
  // If the feature for the specified agent is not in the registry, that means the instrument file was bypassed.
59
61
  // This could happen in tests, or loaders that directly import the aggregator. In these cases it is safe to
60
62
  // drain the feature group immediately rather than waiting to drain all at once.
61
- if (!agentIdentifier || !registry[agentIdentifier].get(featureName)) return drainGroup(featureName);
63
+ if (!agentIdentifier || !registry[agentIdentifier].get(featureName) || force) return drainGroup(featureName);
62
64
 
63
65
  // When `drain` is called, this feature is ready to drain (staged).
64
66
  registry[agentIdentifier].get(featureName).staged = true;
@@ -135,5 +135,11 @@ function ee(old, debugId) {
135
135
  }
136
136
  function abort() {
137
137
  globalInstance.aborted = true;
138
- globalInstance.backlog = {};
138
+ // The global backlog can be referenced directly by other emitters,
139
+ // so we need to delete its contents as opposed to replacing it.
140
+ // Otherwise, these references to the old backlog would still exist
141
+ // and the keys will not be garbage collected.
142
+ Object.keys(globalInstance.backlog).forEach(key => {
143
+ delete globalInstance.backlog[key];
144
+ });
139
145
  }
@@ -180,10 +180,11 @@ class Harvest extends _sharedContext.SharedContext {
180
180
  });
181
181
  if (!opts.unload && cbFinished && submitMethod === submitData.xhr) {
182
182
  const harvestScope = this;
183
- result.addEventListener('load', function () {
183
+ result.addEventListener('loadend', function () {
184
184
  // `this` refers to the XHR object in this scope, do not change this to a fat arrow
185
+ // status 0 refers to a local error, such as CORS or network failure, or a blocked request by the browser (e.g. adblocker)
185
186
  const cbResult = {
186
- sent: true,
187
+ sent: this.status !== 0,
187
188
  status: this.status
188
189
  };
189
190
  if (this.status === 429) {
@@ -135,7 +135,9 @@ class SessionEntity {
135
135
 
136
136
  // The fact that the session is "new" or pre-existing is used in some places in the agent. Session Replay and Trace
137
137
  // can use this info to inform whether to trust a new sampling decision vs continue a previous tracking effort.
138
- this.isNew = !Object.keys(initialRead).length;
138
+ /* [NR-230914] 02/2024 - the logical OR assignment is used so that isNew remains 'true' if it was already set as such. This fixes the expires and inactive timestamps timing out in localStorage
139
+ while no page for a given domain is in-use and the session resetting upon user returning to the page as part of a fresh session. */
140
+ this.isNew ||= !Object.keys(initialRead).length;
139
141
  // if its a "new" session, we write to storage API with the default values. These values may change over the lifespan of the agent run.
140
142
  // we can use a modeled object here to help us know and manage what values are being used. -- see "model" above
141
143
  if (this.isNew) this.write((0, _configurable.getModeledObject)(this.state, model), true);else this.sync(initialRead);
@@ -18,6 +18,7 @@ var _responseSize = require("./response-size");
18
18
  var _instrumentBase = require("../../utils/instrument-base");
19
19
  var _constants = require("../constants");
20
20
  var _features = require("../../../loaders/features/features");
21
+ var _constants2 = require("../../metrics/constants");
21
22
  /*
22
23
  * Copyright 2020 New Relic Corporation. All rights reserved.
23
24
  * SPDX-License-Identifier: Apache-2.0
@@ -363,6 +364,7 @@ function subscribeToEvents(agentIdentifier, ee, handler, dt) {
363
364
  if (ctx.sameOrigin) {
364
365
  var header = xhr.getResponseHeader('X-NewRelic-App-Data');
365
366
  if (header) {
367
+ (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC, ['Ajax/CrossApplicationTracing/Header/Seen'], undefined, _features.FEATURE_NAMES.metrics, ee);
366
368
  ctx.params.cat = header.split(', ').pop();
367
369
  }
368
370
  }
@@ -117,6 +117,11 @@ class Instrument extends _instrumentBase.InstrumentBase {
117
117
  * @returns {Error|UncaughtError} The error event converted to an Error object
118
118
  */
119
119
  #castErrorEvent(errorEvent) {
120
+ if (errorEvent.error instanceof SyntaxError && !/:\d+$/.test(errorEvent.error.stack?.trim())) {
121
+ const error = new _uncaughtError.UncaughtError(errorEvent.message, errorEvent.filename, errorEvent.lineno, errorEvent.colno);
122
+ error.name = SyntaxError.name;
123
+ return error;
124
+ }
120
125
  if (errorEvent.error instanceof Error) {
121
126
  return errorEvent.error;
122
127
  }
@@ -127,7 +127,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
127
127
  status,
128
128
  responseText
129
129
  } = _ref3;
130
- if (status >= 400) {
130
+ if (status >= 400 || status === 0) {
131
131
  // Adding retry logic for the rum call will be a separate change
132
132
  this.ee.abort();
133
133
  return;
@@ -57,6 +57,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
57
57
  this.entitled = false;
58
58
  this.recorder = args?.recorder;
59
59
  if (this.recorder) this.recorder.parent = this;
60
+ (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/Enabled'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
60
61
  const shouldSetup = (0, _config.getConfigurationValue)(agentIdentifier, 'privacy.cookies_enabled') === true && (0, _config.getConfigurationValue)(agentIdentifier, 'session_trace.enabled') === true;
61
62
  if (shouldSetup) {
62
63
  // The SessionEntity class can emit a message indicating the session was cleared and reset (expiry, inactivity). This feature must abort and never resume if that occurs.
@@ -121,13 +122,37 @@ class Aggregate extends _aggregateBase.AggregateBase {
121
122
  this.switchToFull();
122
123
  }
123
124
  }, this.featureName, this.ee);
125
+ const {
126
+ error_sampling_rate,
127
+ sampling_rate,
128
+ autoStart,
129
+ block_selector,
130
+ mask_text_selector,
131
+ mask_all_inputs,
132
+ inline_stylesheet,
133
+ inline_images,
134
+ collect_fonts
135
+ } = (0, _config.getConfigurationValue)(this.agentIdentifier, 'session_replay');
124
136
  this.waitForFlags(['sr']).then(_ref => {
125
137
  let [flagOn] = _ref;
126
138
  this.entitled = flagOn;
127
- if (!this.entitled && this.recorder?.recording) this.recorder.abort(_constants.ABORT_REASONS.ENTITLEMENTS);
128
- this.initializeRecording(Math.random() * 100 < (0, _config.getConfigurationValue)(this.agentIdentifier, 'session_replay.error_sampling_rate'), Math.random() * 100 < (0, _config.getConfigurationValue)(this.agentIdentifier, 'session_replay.sampling_rate'));
139
+ if (!this.entitled && this.recorder?.recording) {
140
+ this.recorder.abort(_constants.ABORT_REASONS.ENTITLEMENTS);
141
+ (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/EnabledNotEntitled/Detected'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
142
+ }
143
+ this.initializeRecording(Math.random() * 100 < error_sampling_rate, Math.random() * 100 < sampling_rate);
129
144
  }).then(() => _sharedChannel.sharedChannel.onReplayReady(this.mode)); // notify watchers that replay started with the mode
130
145
 
146
+ /** Detect if the default configs have been altered and report a SM. This is useful to evaluate what the reasonable defaults are across a customer base over time */
147
+ if (!autoStart) (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/AutoStart/Modified'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
148
+ if (collect_fonts === true) (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/CollectFonts/Modified'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
149
+ if (inline_stylesheet !== true) (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineStylesheet/Modified'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
150
+ if (inline_images === true) (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineImages/Modifed'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
151
+ if (mask_all_inputs !== true) (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/MaskAllInputs/Modified'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
152
+ if (block_selector !== '[data-nr-block]') (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/BlockSelector/Modified'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
153
+ if (mask_text_selector !== '*') (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/MaskTextSelector/Modified'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
154
+ (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/SamplingRate/Value', sampling_rate], undefined, _features.FEATURE_NAMES.metrics, this.ee);
155
+ (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/ErrorSamplingRate/Value', error_sampling_rate], undefined, _features.FEATURE_NAMES.metrics, this.ee);
131
156
  this.drain();
132
157
  }
133
158
  }
@@ -115,14 +115,15 @@ class Recorder {
115
115
  const incompletes = _stylesheetEvaluator.stylesheetEvaluator.evaluate();
116
116
  /** Only stop ignoring data if already ignoring and a new valid snapshap is taking place (0 incompletes and we get a meta node for the snap) */
117
117
  if (!incompletes && this.#fixing && event.type === _constants.RRWEB_EVENT_TYPES.Meta) this.#fixing = false;
118
- if (incompletes) {
119
- (0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css', incompletes], undefined, _features.FEATURE_NAMES.metrics, this.parent.ee);
118
+ if (incompletes > 0) {
120
119
  /** wait for the evaluator to download/replace the incompletes' src code and then take a new snap */
121
120
  _stylesheetEvaluator.stylesheetEvaluator.fix().then(failedToFix => {
122
- if (failedToFix) {
121
+ if (failedToFix > 0) {
123
122
  this.currentBufferTarget.inlinedAllStylesheets = false;
124
123
  this.shouldFix = false;
125
124
  }
125
+ (0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css/Failed', failedToFix], undefined, _features.FEATURE_NAMES.metrics, this.parent.ee);
126
+ (0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css/Fixed', incompletes - failedToFix], undefined, _features.FEATURE_NAMES.metrics, this.parent.ee);
126
127
  this.takeFullSnapshot();
127
128
  });
128
129
  /** Only start ignoring data if got a faulty snapshot */
@@ -176,7 +177,12 @@ class Recorder {
176
177
 
177
178
  /** force the recording lib to take a full DOM snapshot. This needs to occur in certain cases, like visibility changes */
178
179
  takeFullSnapshot() {
179
- _rrweb.record.takeFullSnapshot();
180
+ try {
181
+ if (!this.recording) return;
182
+ _rrweb.record.takeFullSnapshot();
183
+ } catch (err) {
184
+ // in the off chance we think we are recording, but rrweb does not, rrweb's lib will throw an error. This catch is just a precaution
185
+ }
180
186
  }
181
187
  clearTimestamps() {
182
188
  this.currentBufferTarget.cycleTimestamp = undefined;
@@ -14,7 +14,7 @@ class StylesheetEvaluator {
14
14
  * Used at harvest time to denote that all subsequent payloads are subject to this and customers should be advised to handle crossorigin decoration
15
15
  * */
16
16
  invalidStylesheetsDetected = false;
17
- failedToFix = false;
17
+ failedToFix = 0;
18
18
 
19
19
  /**
20
20
  * this works by checking (only ever once) each cssRules obj in the style sheets array. The try/catch will catch an error if the cssRules obj blocks access, triggering the module to try to "fix" the asset`. Returns the count of incomplete assets discovered.
@@ -24,15 +24,15 @@ class StylesheetEvaluator {
24
24
  let incompletes = 0;
25
25
  if (_runtime.isBrowserScope) {
26
26
  for (let i = 0; i < Object.keys(document.styleSheets).length; i++) {
27
- const ss = document.styleSheets[i];
28
- if (!this.#evaluated.has(ss)) {
29
- this.#evaluated.add(ss);
27
+ if (!this.#evaluated.has(document.styleSheets[i])) {
28
+ this.#evaluated.add(document.styleSheets[i]);
30
29
  try {
31
30
  // eslint-disable-next-line
32
- const temp = ss.cssRules;
31
+ const temp = document.styleSheets[i].cssRules;
33
32
  } catch (err) {
33
+ if (!document.styleSheets[i].href) return;
34
34
  incompletes++;
35
- this.#fetchProms.push(this.#fetchAndOverride(document.styleSheets[i], ss.href));
35
+ this.#fetchProms.push(this.#fetchAndOverride(document.styleSheets[i]));
36
36
  }
37
37
  }
38
38
  }
@@ -49,7 +49,7 @@ class StylesheetEvaluator {
49
49
  await Promise.all(this.#fetchProms);
50
50
  this.#fetchProms = [];
51
51
  const failedToFix = this.failedToFix;
52
- this.failedToFix = false;
52
+ this.failedToFix = 0;
53
53
  return failedToFix;
54
54
  }
55
55
 
@@ -59,11 +59,12 @@ class StylesheetEvaluator {
59
59
  * @param {*} href - The asset href to fetch
60
60
  * @returns {Promise}
61
61
  */
62
- async #fetchAndOverride(target, href) {
62
+ async #fetchAndOverride(target) {
63
+ if (!target?.href) return;
63
64
  try {
64
- const stylesheetContents = await _config.originals.FETCH.bind(window)(href);
65
+ const stylesheetContents = await _config.originals.FETCH.bind(window)(target.href);
65
66
  if (!stylesheetContents.ok) {
66
- this.failedToFix = true;
67
+ this.failedToFix++;
67
68
  return;
68
69
  }
69
70
  const stylesheetText = await stylesheetContents.text();
@@ -88,11 +89,11 @@ class StylesheetEvaluator {
88
89
  return stylesheetText;
89
90
  }
90
91
  });
91
- this.failedToFix = true;
92
+ this.failedToFix++;
92
93
  }
93
94
  } catch (err) {
94
95
  // failed to fetch
95
- this.failedToFix = true;
96
+ this.failedToFix++;
96
97
  }
97
98
  }
98
99
  }
@@ -119,7 +119,7 @@ class InstrumentBase extends _featureBase.FeatureBase {
119
119
  (0, _console.warn)("Downloading and initializing ".concat(this.featureName, " failed..."), e);
120
120
  this.abortHandler?.(); // undo any important alterations made to the page
121
121
  // not supported yet but nice to do: "abort" this agent's EE for this feature specifically
122
- (0, _drain.drain)(this.agentIdentifier, this.featureName);
122
+ (0, _drain.drain)(this.agentIdentifier, this.featureName, true);
123
123
  loadedSuccessfully(false);
124
124
  }
125
125
  };
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.asyncApiMethods = exports.apiMethods = void 0;
7
+ const apiMethods = exports.apiMethods = ['setErrorHandler', 'finished', 'addToTrace', 'addRelease', 'addPageAction', 'setCurrentRouteName', 'setPageViewName', 'setCustomAttribute', 'interaction', 'noticeError', 'setUserId', 'setApplicationVersion', 'start', 'recordReplay', 'pauseReplay'];
8
+ const asyncApiMethods = exports.asyncApiMethods = ['setErrorHandler', 'finished', 'addToTrace', 'addRelease'];
@@ -3,7 +3,6 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.CUSTOM_ATTR_GROUP = void 0;
7
6
  exports.setAPI = setAPI;
8
7
  exports.setTopLevelCallers = setTopLevelCallers;
9
8
  var _features = require("../features/features");
@@ -17,17 +16,15 @@ var _runtime = require("../../common/constants/runtime");
17
16
  var _console = require("../../common/util/console");
18
17
  var _constants = require("../../features/metrics/constants");
19
18
  var _nreum = require("../../common/window/nreum");
19
+ var _apiMethods = require("./api-methods");
20
20
  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); }
21
21
  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; } /*
22
22
  * Copyright 2020 New Relic Corporation. All rights reserved.
23
23
  * SPDX-License-Identifier: Apache-2.0
24
24
  */
25
- const CUSTOM_ATTR_GROUP = exports.CUSTOM_ATTR_GROUP = 'CUSTOM/'; // the subgroup items should be stored under in storage API
26
-
27
25
  function setTopLevelCallers() {
28
26
  const nr = (0, _nreum.gosCDN)();
29
- const funcs = ['setErrorHandler', 'finished', 'addToTrace', 'addRelease', 'addPageAction', 'setCurrentRouteName', 'setPageViewName', 'setCustomAttribute', 'interaction', 'noticeError', 'setUserId', 'setApplicationVersion', 'start', 'recordReplay', 'pauseReplay'];
30
- funcs.forEach(f => {
27
+ _apiMethods.apiMethods.forEach(f => {
31
28
  nr[f] = function () {
32
29
  for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
33
30
  args[_key] = arguments[_key];
@@ -53,12 +50,11 @@ function setAPI(agentIdentifier, forceDrain) {
53
50
  const apiInterface = {};
54
51
  var instanceEE = _contextualEe.ee.get(agentIdentifier);
55
52
  var tracerEE = instanceEE.get('tracer');
56
- var asyncApiFns = ['setErrorHandler', 'finished', 'addToTrace', 'addRelease'];
57
53
  var prefix = 'api-';
58
54
  var spaPrefix = prefix + 'ixn-';
59
55
 
60
56
  // Setup stub functions that queue calls for later processing.
61
- asyncApiFns.forEach(fnName => {
57
+ _apiMethods.asyncApiMethods.forEach(fnName => {
62
58
  apiInterface[fnName] = apiCall(prefix, fnName, true, 'api');
63
59
  });
64
60
  apiInterface.addPageAction = apiCall(prefix, 'addPageAction', true, _features.FEATURE_NAMES.pageAction);
@@ -209,7 +205,10 @@ function setAPI(agentIdentifier, forceDrain) {
209
205
  } = _ref;
210
206
  setAPI(agentIdentifier);
211
207
  (0, _drain.drain)(agentIdentifier, 'api');
212
- }).catch(() => (0, _console.warn)('Downloading runtime APIs failed...'));
208
+ }).catch(() => {
209
+ (0, _console.warn)('Downloading runtime APIs failed...');
210
+ (0, _drain.drain)(agentIdentifier, 'api', true);
211
+ });
213
212
  }
214
213
  return apiInterface;
215
214
  }
@@ -9,7 +9,7 @@ var _nreum = require("../../common/window/nreum");
9
9
  var _config = require("../../common/config/config");
10
10
  var _featureFlags = require("../../common/util/feature-flags");
11
11
  var _runtime = require("../../common/constants/runtime");
12
- var _publicPath = require("./public-path");
12
+ var _publicPath = require("./public-path.npm");
13
13
  let alreadySetOnce = false; // the configure() function can run multiple times in agent lifecycle
14
14
 
15
15
  /**
@@ -98,9 +98,9 @@ const model = () => {
98
98
  autoStart: true,
99
99
  enabled: false,
100
100
  harvestTimeSeconds: 60,
101
- sampling_rate: 50,
101
+ sampling_rate: 10,
102
102
  // float from 0 - 100
103
- error_sampling_rate: 50,
103
+ error_sampling_rate: 100,
104
104
  // float from 0 - 100
105
105
  collect_fonts: false,
106
106
  // serialize fonts for collection without public asset url, this is currently broken in RRWeb -- https://github.com/rrweb-io/rrweb/issues/1304. When fixed, revisit with test cases
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Exposes the version of the agent
8
8
  */
9
- export const VERSION = "1.251.1";
9
+ export const VERSION = "1.252.1";
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.251.1";
9
+ export const VERSION = "1.252.1";
10
10
 
11
11
  /**
12
12
  * Exposes the build type of the agent
@@ -43,15 +43,17 @@ function curateRegistry(agentIdentifier) {
43
43
  * its own named group explicitly, when ready.
44
44
  * @param {string} agentIdentifier - A unique 16 character ID corresponding to an instantiated agent.
45
45
  * @param {string} featureName - A named group into which the feature's buffered events are bucketed.
46
+ * @param {boolean} force - Whether to force the drain to occur immediately, bypassing the registry and staging logic.
46
47
  */
47
48
  export function drain() {
48
49
  let agentIdentifier = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
49
50
  let featureName = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'feature';
51
+ let force = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
50
52
  curateRegistry(agentIdentifier);
51
53
  // If the feature for the specified agent is not in the registry, that means the instrument file was bypassed.
52
54
  // This could happen in tests, or loaders that directly import the aggregator. In these cases it is safe to
53
55
  // drain the feature group immediately rather than waiting to drain all at once.
54
- if (!agentIdentifier || !registry[agentIdentifier].get(featureName)) return drainGroup(featureName);
56
+ if (!agentIdentifier || !registry[agentIdentifier].get(featureName) || force) return drainGroup(featureName);
55
57
 
56
58
  // When `drain` is called, this feature is ready to drain (staged).
57
59
  registry[agentIdentifier].get(featureName).staged = true;
@@ -131,5 +131,11 @@ function ee(old, debugId) {
131
131
  }
132
132
  function abort() {
133
133
  globalInstance.aborted = true;
134
- globalInstance.backlog = {};
134
+ // The global backlog can be referenced directly by other emitters,
135
+ // so we need to delete its contents as opposed to replacing it.
136
+ // Otherwise, these references to the old backlog would still exist
137
+ // and the keys will not be garbage collected.
138
+ Object.keys(globalInstance.backlog).forEach(key => {
139
+ delete globalInstance.backlog[key];
140
+ });
135
141
  }
@@ -172,10 +172,11 @@ export class Harvest extends SharedContext {
172
172
  });
173
173
  if (!opts.unload && cbFinished && submitMethod === submitData.xhr) {
174
174
  const harvestScope = this;
175
- result.addEventListener('load', function () {
175
+ result.addEventListener('loadend', function () {
176
176
  // `this` refers to the XHR object in this scope, do not change this to a fat arrow
177
+ // status 0 refers to a local error, such as CORS or network failure, or a blocked request by the browser (e.g. adblocker)
177
178
  const cbResult = {
178
- sent: true,
179
+ sent: this.status !== 0,
179
180
  status: this.status
180
181
  };
181
182
  if (this.status === 429) {
@@ -130,7 +130,9 @@ export class SessionEntity {
130
130
 
131
131
  // The fact that the session is "new" or pre-existing is used in some places in the agent. Session Replay and Trace
132
132
  // can use this info to inform whether to trust a new sampling decision vs continue a previous tracking effort.
133
- this.isNew = !Object.keys(initialRead).length;
133
+ /* [NR-230914] 02/2024 - the logical OR assignment is used so that isNew remains 'true' if it was already set as such. This fixes the expires and inactive timestamps timing out in localStorage
134
+ while no page for a given domain is in-use and the session resetting upon user returning to the page as part of a fresh session. */
135
+ this.isNew ||= !Object.keys(initialRead).length;
134
136
  // if its a "new" session, we write to storage API with the default values. These values may change over the lifespan of the agent run.
135
137
  // we can use a modeled object here to help us know and manage what values are being used. -- see "model" above
136
138
  if (this.isNew) this.write(getModeledObject(this.state, model), true);else this.sync(initialRead);
@@ -16,6 +16,7 @@ import { responseSizeFromXhr } from './response-size';
16
16
  import { InstrumentBase } from '../../utils/instrument-base';
17
17
  import { FEATURE_NAME } from '../constants';
18
18
  import { FEATURE_NAMES } from '../../../loaders/features/features';
19
+ import { SUPPORTABILITY_METRIC } from '../../metrics/constants';
19
20
  var handlers = ['load', 'error', 'abort', 'timeout'];
20
21
  var handlersLen = handlers.length;
21
22
  var origRequest = originals.REQ;
@@ -355,6 +356,7 @@ function subscribeToEvents(agentIdentifier, ee, handler, dt) {
355
356
  if (ctx.sameOrigin) {
356
357
  var header = xhr.getResponseHeader('X-NewRelic-App-Data');
357
358
  if (header) {
359
+ handle(SUPPORTABILITY_METRIC, ['Ajax/CrossApplicationTracing/Header/Seen'], undefined, FEATURE_NAMES.metrics, ee);
358
360
  ctx.params.cat = header.split(', ').pop();
359
361
  }
360
362
  }
@@ -111,6 +111,11 @@ export class Instrument extends InstrumentBase {
111
111
  * @returns {Error|UncaughtError} The error event converted to an Error object
112
112
  */
113
113
  #castErrorEvent(errorEvent) {
114
+ if (errorEvent.error instanceof SyntaxError && !/:\d+$/.test(errorEvent.error.stack?.trim())) {
115
+ const error = new UncaughtError(errorEvent.message, errorEvent.filename, errorEvent.lineno, errorEvent.colno);
116
+ error.name = SyntaxError.name;
117
+ return error;
118
+ }
114
119
  if (errorEvent.error instanceof Error) {
115
120
  return errorEvent.error;
116
121
  }
@@ -119,7 +119,7 @@ export class Aggregate extends AggregateBase {
119
119
  status,
120
120
  responseText
121
121
  } = _ref3;
122
- if (status >= 400) {
122
+ if (status >= 400 || status === 0) {
123
123
  // Adding retry logic for the rum call will be a separate change
124
124
  this.ee.abort();
125
125
  return;