@newrelic/browser-agent 1.251.1 → 1.252.0

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 (53) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/cjs/common/config/state/init.js +2 -2
  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/drain/drain.js +3 -1
  6. package/dist/cjs/common/event-emitter/contextual-ee.js +7 -1
  7. package/dist/cjs/common/harvest/harvest.js +3 -2
  8. package/dist/cjs/features/ajax/instrument/index.js +2 -0
  9. package/dist/cjs/features/jserrors/instrument/index.js +5 -0
  10. package/dist/cjs/features/page_view_event/aggregate/index.js +1 -1
  11. package/dist/cjs/features/session_replay/aggregate/index.js +27 -2
  12. package/dist/cjs/features/session_replay/shared/recorder.js +8 -3
  13. package/dist/cjs/features/session_replay/shared/stylesheet-evaluator.js +5 -5
  14. package/dist/cjs/features/utils/instrument-base.js +1 -1
  15. package/dist/cjs/loaders/api/api.js +4 -1
  16. package/dist/esm/common/config/state/init.js +2 -2
  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/drain/drain.js +3 -1
  20. package/dist/esm/common/event-emitter/contextual-ee.js +7 -1
  21. package/dist/esm/common/harvest/harvest.js +3 -2
  22. package/dist/esm/features/ajax/instrument/index.js +2 -0
  23. package/dist/esm/features/jserrors/instrument/index.js +5 -0
  24. package/dist/esm/features/page_view_event/aggregate/index.js +1 -1
  25. package/dist/esm/features/session_replay/aggregate/index.js +27 -2
  26. package/dist/esm/features/session_replay/shared/recorder.js +8 -3
  27. package/dist/esm/features/session_replay/shared/stylesheet-evaluator.js +5 -5
  28. package/dist/esm/features/utils/instrument-base.js +1 -1
  29. package/dist/esm/loaders/api/api.js +4 -1
  30. package/dist/types/common/drain/drain.d.ts +2 -1
  31. package/dist/types/common/drain/drain.d.ts.map +1 -1
  32. package/dist/types/common/event-emitter/contextual-ee.d.ts.map +1 -1
  33. package/dist/types/common/harvest/harvest.d.ts.map +1 -1
  34. package/dist/types/features/ajax/instrument/index.d.ts.map +1 -1
  35. package/dist/types/features/jserrors/instrument/index.d.ts.map +1 -1
  36. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  37. package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
  38. package/dist/types/features/session_replay/shared/stylesheet-evaluator.d.ts +1 -1
  39. package/dist/types/features/session_replay/shared/stylesheet-evaluator.d.ts.map +1 -1
  40. package/dist/types/loaders/api/api.d.ts.map +1 -1
  41. package/package.json +4 -1
  42. package/src/common/config/state/init.js +2 -2
  43. package/src/common/drain/drain.js +3 -2
  44. package/src/common/event-emitter/contextual-ee.js +7 -1
  45. package/src/common/harvest/harvest.js +3 -2
  46. package/src/features/ajax/instrument/index.js +2 -0
  47. package/src/features/jserrors/instrument/index.js +6 -0
  48. package/src/features/page_view_event/aggregate/index.js +1 -1
  49. package/src/features/session_replay/aggregate/index.js +21 -3
  50. package/src/features/session_replay/shared/recorder.js +8 -3
  51. package/src/features/session_replay/shared/stylesheet-evaluator.js +5 -5
  52. package/src/features/utils/instrument-base.js +1 -1
  53. package/src/loaders/api/api.js +4 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,23 @@
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.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.251.1...v1.252.0) (2024-02-12)
7
+
8
+
9
+ ### Features
10
+
11
+ * 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))
12
+ * 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))
13
+ * 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))
14
+ * 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))
15
+ * 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))
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * 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))
21
+ * Fix adblock memory leak ([#877](https://github.com/newrelic/newrelic-browser-agent/issues/877)) ([695415b](https://github.com/newrelic/newrelic-browser-agent/commit/695415b0fcaa8b41496fc6556a38ec76dd357539))
22
+
6
23
  ## [1.251.1](https://github.com/newrelic/newrelic-browser-agent/compare/v1.251.0...v1.251.1) (2024-01-29)
7
24
 
8
25
 
@@ -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.0";
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.0";
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) {
@@ -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
  }
@@ -116,13 +116,13 @@ class Recorder {
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
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);
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
121
  if (failedToFix) {
123
122
  this.currentBufferTarget.inlinedAllStylesheets = false;
124
123
  this.shouldFix = false;
125
- }
124
+ (0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css/Failed', failedToFix], undefined, _features.FEATURE_NAMES.metrics, this.parent.ee);
125
+ } else (0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css/Fixed', incompletes - failedToFix], undefined, _features.FEATURE_NAMES.metrics, this.parent.ee);
126
126
  this.takeFullSnapshot();
127
127
  });
128
128
  /** Only start ignoring data if got a faulty snapshot */
@@ -176,7 +176,12 @@ class Recorder {
176
176
 
177
177
  /** force the recording lib to take a full DOM snapshot. This needs to occur in certain cases, like visibility changes */
178
178
  takeFullSnapshot() {
179
- _rrweb.record.takeFullSnapshot();
179
+ try {
180
+ if (!this.recording) return;
181
+ _rrweb.record.takeFullSnapshot();
182
+ } catch (err) {
183
+ // 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
184
+ }
180
185
  }
181
186
  clearTimestamps() {
182
187
  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.
@@ -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
 
@@ -63,7 +63,7 @@ class StylesheetEvaluator {
63
63
  try {
64
64
  const stylesheetContents = await _config.originals.FETCH.bind(window)(href);
65
65
  if (!stylesheetContents.ok) {
66
- this.failedToFix = true;
66
+ this.failedToFix++;
67
67
  return;
68
68
  }
69
69
  const stylesheetText = await stylesheetContents.text();
@@ -88,11 +88,11 @@ class StylesheetEvaluator {
88
88
  return stylesheetText;
89
89
  }
90
90
  });
91
- this.failedToFix = true;
91
+ this.failedToFix++;
92
92
  }
93
93
  } catch (err) {
94
94
  // failed to fetch
95
- this.failedToFix = true;
95
+ this.failedToFix++;
96
96
  }
97
97
  }
98
98
  }
@@ -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
  };
@@ -209,7 +209,10 @@ function setAPI(agentIdentifier, forceDrain) {
209
209
  } = _ref;
210
210
  setAPI(agentIdentifier);
211
211
  (0, _drain.drain)(agentIdentifier, 'api');
212
- }).catch(() => (0, _console.warn)('Downloading runtime APIs failed...'));
212
+ }).catch(() => {
213
+ (0, _console.warn)('Downloading runtime APIs failed...');
214
+ (0, _drain.drain)(agentIdentifier, 'api', true);
215
+ });
213
216
  }
214
217
  return apiInterface;
215
218
  }
@@ -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.0";
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.0";
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) {
@@ -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;
@@ -52,6 +52,7 @@ export class Aggregate extends AggregateBase {
52
52
  this.entitled = false;
53
53
  this.recorder = args?.recorder;
54
54
  if (this.recorder) this.recorder.parent = this;
55
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/Enabled'], undefined, FEATURE_NAMES.metrics, this.ee);
55
56
  const shouldSetup = getConfigurationValue(agentIdentifier, 'privacy.cookies_enabled') === true && getConfigurationValue(agentIdentifier, 'session_trace.enabled') === true;
56
57
  if (shouldSetup) {
57
58
  // 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.
@@ -116,13 +117,37 @@ export class Aggregate extends AggregateBase {
116
117
  this.switchToFull();
117
118
  }
118
119
  }, this.featureName, this.ee);
120
+ const {
121
+ error_sampling_rate,
122
+ sampling_rate,
123
+ autoStart,
124
+ block_selector,
125
+ mask_text_selector,
126
+ mask_all_inputs,
127
+ inline_stylesheet,
128
+ inline_images,
129
+ collect_fonts
130
+ } = getConfigurationValue(this.agentIdentifier, 'session_replay');
119
131
  this.waitForFlags(['sr']).then(_ref => {
120
132
  let [flagOn] = _ref;
121
133
  this.entitled = flagOn;
122
- if (!this.entitled && this.recorder?.recording) this.recorder.abort(ABORT_REASONS.ENTITLEMENTS);
123
- this.initializeRecording(Math.random() * 100 < getConfigurationValue(this.agentIdentifier, 'session_replay.error_sampling_rate'), Math.random() * 100 < getConfigurationValue(this.agentIdentifier, 'session_replay.sampling_rate'));
134
+ if (!this.entitled && this.recorder?.recording) {
135
+ this.recorder.abort(ABORT_REASONS.ENTITLEMENTS);
136
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/EnabledNotEntitled/Detected'], undefined, FEATURE_NAMES.metrics, this.ee);
137
+ }
138
+ this.initializeRecording(Math.random() * 100 < error_sampling_rate, Math.random() * 100 < sampling_rate);
124
139
  }).then(() => sharedChannel.onReplayReady(this.mode)); // notify watchers that replay started with the mode
125
140
 
141
+ /** 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 */
142
+ if (!autoStart) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/AutoStart/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
143
+ if (collect_fonts === true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/CollectFonts/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
144
+ if (inline_stylesheet !== true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineStylesheet/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
145
+ if (inline_images === true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineImages/Modifed'], undefined, FEATURE_NAMES.metrics, this.ee);
146
+ if (mask_all_inputs !== true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/MaskAllInputs/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
147
+ if (block_selector !== '[data-nr-block]') handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/BlockSelector/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
148
+ if (mask_text_selector !== '*') handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/MaskTextSelector/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
149
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/SamplingRate/Value', sampling_rate], undefined, FEATURE_NAMES.metrics, this.ee);
150
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/ErrorSamplingRate/Value', error_sampling_rate], undefined, FEATURE_NAMES.metrics, this.ee);
126
151
  this.drain();
127
152
  }
128
153
  }
@@ -110,13 +110,13 @@ export class Recorder {
110
110
  /** 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) */
111
111
  if (!incompletes && this.#fixing && event.type === RRWEB_EVENT_TYPES.Meta) this.#fixing = false;
112
112
  if (incompletes) {
113
- handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css', incompletes], undefined, FEATURE_NAMES.metrics, this.parent.ee);
114
113
  /** wait for the evaluator to download/replace the incompletes' src code and then take a new snap */
115
114
  stylesheetEvaluator.fix().then(failedToFix => {
116
115
  if (failedToFix) {
117
116
  this.currentBufferTarget.inlinedAllStylesheets = false;
118
117
  this.shouldFix = false;
119
- }
118
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css/Failed', failedToFix], undefined, FEATURE_NAMES.metrics, this.parent.ee);
119
+ } else handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css/Fixed', incompletes - failedToFix], undefined, FEATURE_NAMES.metrics, this.parent.ee);
120
120
  this.takeFullSnapshot();
121
121
  });
122
122
  /** Only start ignoring data if got a faulty snapshot */
@@ -170,7 +170,12 @@ export class Recorder {
170
170
 
171
171
  /** force the recording lib to take a full DOM snapshot. This needs to occur in certain cases, like visibility changes */
172
172
  takeFullSnapshot() {
173
- recorder.takeFullSnapshot();
173
+ try {
174
+ if (!this.recording) return;
175
+ recorder.takeFullSnapshot();
176
+ } catch (err) {
177
+ // 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
178
+ }
174
179
  }
175
180
  clearTimestamps() {
176
181
  this.currentBufferTarget.cycleTimestamp = undefined;
@@ -8,7 +8,7 @@ class StylesheetEvaluator {
8
8
  * Used at harvest time to denote that all subsequent payloads are subject to this and customers should be advised to handle crossorigin decoration
9
9
  * */
10
10
  invalidStylesheetsDetected = false;
11
- failedToFix = false;
11
+ failedToFix = 0;
12
12
 
13
13
  /**
14
14
  * 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.
@@ -43,7 +43,7 @@ class StylesheetEvaluator {
43
43
  await Promise.all(this.#fetchProms);
44
44
  this.#fetchProms = [];
45
45
  const failedToFix = this.failedToFix;
46
- this.failedToFix = false;
46
+ this.failedToFix = 0;
47
47
  return failedToFix;
48
48
  }
49
49
 
@@ -57,7 +57,7 @@ class StylesheetEvaluator {
57
57
  try {
58
58
  const stylesheetContents = await originals.FETCH.bind(window)(href);
59
59
  if (!stylesheetContents.ok) {
60
- this.failedToFix = true;
60
+ this.failedToFix++;
61
61
  return;
62
62
  }
63
63
  const stylesheetText = await stylesheetContents.text();
@@ -82,11 +82,11 @@ class StylesheetEvaluator {
82
82
  return stylesheetText;
83
83
  }
84
84
  });
85
- this.failedToFix = true;
85
+ this.failedToFix++;
86
86
  }
87
87
  } catch (err) {
88
88
  // failed to fetch
89
- this.failedToFix = true;
89
+ this.failedToFix++;
90
90
  }
91
91
  }
92
92
  }
@@ -114,7 +114,7 @@ export class InstrumentBase extends FeatureBase {
114
114
  warn("Downloading and initializing ".concat(this.featureName, " failed..."), e);
115
115
  this.abortHandler?.(); // undo any important alterations made to the page
116
116
  // not supported yet but nice to do: "abort" this agent's EE for this feature specifically
117
- drain(this.agentIdentifier, this.featureName);
117
+ drain(this.agentIdentifier, this.featureName, true);
118
118
  loadedSuccessfully(false);
119
119
  }
120
120
  };
@@ -200,7 +200,10 @@ export function setAPI(agentIdentifier, forceDrain) {
200
200
  } = _ref;
201
201
  setAPI(agentIdentifier);
202
202
  drain(agentIdentifier, 'api');
203
- }).catch(() => warn('Downloading runtime APIs failed...'));
203
+ }).catch(() => {
204
+ warn('Downloading runtime APIs failed...');
205
+ drain(agentIdentifier, 'api', true);
206
+ });
204
207
  }
205
208
  return apiInterface;
206
209
  }
@@ -12,6 +12,7 @@ export function registerDrain(agentIdentifier: string, group: string): void;
12
12
  * its own named group explicitly, when ready.
13
13
  * @param {string} agentIdentifier - A unique 16 character ID corresponding to an instantiated agent.
14
14
  * @param {string} featureName - A named group into which the feature's buffered events are bucketed.
15
+ * @param {boolean} force - Whether to force the drain to occur immediately, bypassing the registry and staging logic.
15
16
  */
16
- export function drain(agentIdentifier?: string, featureName?: string): void;
17
+ export function drain(agentIdentifier?: string, featureName?: string, force?: boolean): void;
17
18
  //# sourceMappingURL=drain.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"drain.d.ts","sourceRoot":"","sources":["../../../../src/common/drain/drain.js"],"names":[],"mappings":"AAYA;;;;;;;GAOG;AACH,+CAHW,MAAM,SACN,MAAM,QAQhB;AAYD;;;;;GAKG;AACH,wCAHW,MAAM,gBACN,MAAM,QAuDhB"}
1
+ {"version":3,"file":"drain.d.ts","sourceRoot":"","sources":["../../../../src/common/drain/drain.js"],"names":[],"mappings":"AAYA;;;;;;;GAOG;AACH,+CAHW,MAAM,SACN,MAAM,QAQhB;AAYD;;;;;;GAMG;AACH,wCAJW,MAAM,gBACN,MAAM,UACN,OAAO,QAuDjB"}
@@ -1 +1 @@
1
- {"version":3,"file":"contextual-ee.d.ts","sourceRoot":"","sources":["../../../../src/common/event-emitter/contextual-ee.js"],"names":[],"mappings":";;;;;;;;;;;;;;;AAYA,+BAA0C;AA0DxC,+FAsBC;AAqBD,6CAEC;AAND,2CAEC;AAMD,mEAaC;AAcH,+BAGC;AAfC,iDAGC"}
1
+ {"version":3,"file":"contextual-ee.d.ts","sourceRoot":"","sources":["../../../../src/common/event-emitter/contextual-ee.js"],"names":[],"mappings":";;;;;;;;;;;;;;;AAYA,+BAA0C;AA0DxC,+FAsBC;AAqBD,6CAEC;AAND,2CAEC;AAMD,mEAaC;AAcH,+BASC;AArBC,iDAGC"}
@@ -1 +1 @@
1
- {"version":3,"file":"harvest.d.ts","sourceRoot":"","sources":["../../../../src/common/harvest/harvest.js"],"names":[],"mappings":"AAsBA;;;;;;GAMG;AACH;IAII,0BAA2H;IAC3H,uBAAoD;IAEpD,YAAiB;IAGnB;;;;;OAKG;IACH,aAFW,eAAe,WAWzB;IAED;;;OAGG;IACH,YAFW,eAAe,WAMzB;IAED;;;OAGG;IACH,wBAFW,eAAe,WAMzB;IAED;;;;;;OAMG;IACH,gGAJW,eAAe,GACb,OAAO,CAkFnB;IAGD,iCAoBC;IAED;;;;;;;OAOG;IACH,wBALW,yBAAyB,WACzB,6BAA6B,GAE3B,cAAc,CA2B1B;IAED;;;;;;;OAOG;IACH,uBAHW,cAAc,GACZ,cAAc,CAuB1B;IAED;;;;;OAKG;IACH,aAHW,yBAAyB,YACzB,sBAAsB,QAQhC;CACF;8BAnPY,OAAO,YAAY,EAAE,eAAe;wCACpC,OAAO,YAAY,EAAE,yBAAyB;6BAC9C,OAAO,YAAY,EAAE,cAAc;qCACnC,OAAO,YAAY,EAAE,sBAAsB;4CAC3C,OAAO,YAAY,EAAE,6BAA6B;8BAZjC,2BAA2B;2BAF9B,mBAAmB"}
1
+ {"version":3,"file":"harvest.d.ts","sourceRoot":"","sources":["../../../../src/common/harvest/harvest.js"],"names":[],"mappings":"AAsBA;;;;;;GAMG;AACH;IAII,0BAA2H;IAC3H,uBAAoD;IAEpD,YAAiB;IAGnB;;;;;OAKG;IACH,aAFW,eAAe,WAWzB;IAED;;;OAGG;IACH,YAFW,eAAe,WAMzB;IAED;;;OAGG;IACH,wBAFW,eAAe,WAMzB;IAED;;;;;;OAMG;IACH,gGAJW,eAAe,GACb,OAAO,CAmFnB;IAGD,iCAoBC;IAED;;;;;;;OAOG;IACH,wBALW,yBAAyB,WACzB,6BAA6B,GAE3B,cAAc,CA2B1B;IAED;;;;;;;OAOG;IACH,uBAHW,cAAc,GACZ,cAAc,CAuB1B;IAED;;;;;OAKG;IACH,aAHW,yBAAyB,YACzB,sBAAsB,QAQhC;CACF;8BApPY,OAAO,YAAY,EAAE,eAAe;wCACpC,OAAO,YAAY,EAAE,yBAAyB;6BAC9C,OAAO,YAAY,EAAE,cAAc;qCACnC,OAAO,YAAY,EAAE,sBAAsB;4CAC3C,OAAO,YAAY,EAAE,6BAA6B;8BAZjC,2BAA2B;2BAF9B,mBAAmB"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/ajax/instrument/index.js"],"names":[],"mappings":"AAyBA;IACE,2BAAiC;IACjC,mEAqCC;IA/BC,mBAAiC;IAEjC,4EAAkF;CA8BrF;+BAlD8B,6BAA6B;mBAFzC,uBAAuB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/ajax/instrument/index.js"],"names":[],"mappings":"AA0BA;IACE,2BAAiC;IACjC,mEAqCC;IA/BC,mBAAiC;IAEjC,4EAAkF;CA8BrF;+BAnD8B,6BAA6B;mBAFzC,uBAAuB"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/instrument/index.js"],"names":[],"mappings":"AAeA;IACE,2BAAiC;IAIjC,mEA4CC;IAvCG,2CAA0C;IAqC5C,yBAA+B;;CA8ElC;+BArI8B,6BAA6B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/instrument/index.js"],"names":[],"mappings":"AAeA;IACE,2BAAiC;IAIjC,mEA4CC;IAvCG,2CAA0C;IAqC5C,yBAA+B;;CAoFlC;+BA3I8B,6BAA6B"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"AA8BA;IACE,2BAAiC;IAEjC,8DAsGC;IApGC,8GAA8G;IAC9G,wBAAgH;IAChH,iFAAiF;IACjF,qBAAwB;IAGxB,2CAA2C;IAC3C,sDAAwB;IACxB,6CAA6C;IAC7C,gDAAmB;IAGnB,UAAuD;IAEvD,0BAA0B;IAC1B,kBAAqB;IAErB,cAA8B;IAkC5B,wCAKQ;IAyBN,sBAAwB;IAqB9B,qBAWC;IAED;;;;;;;OAOG;IACH,iCALW,OAAO,cACP,OAAO,iBACP,OAAO,GACL,IAAI,CAsDhB;IAED,2BASC;IAED;;;;;;;;;;;oBAiCC;IAED;;;;;;;;;MAmEC;IAED,qCAOC;IAED;;;;OAIG;IACH,mCAKC;IAED,yDAAyD;IACzD,yBAUC;IAED,yCAGC;CACF;8BA7V6B,4BAA4B;iCAHzB,2CAA2C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"AA8BA;IACE,2BAAiC;IAEjC,8DAwHC;IAtHC,8GAA8G;IAC9G,wBAAgH;IAChH,iFAAiF;IACjF,qBAAwB;IAGxB,2CAA2C;IAC3C,sDAAwB;IACxB,6CAA6C;IAC7C,gDAAmB;IAGnB,UAAuD;IAEvD,0BAA0B;IAC1B,kBAAqB;IAErB,cAA8B;IAoC5B,wCAKQ;IAyBN,sBAAwB;IAqC9B,qBAWC;IAED;;;;;;;OAOG;IACH,iCALW,OAAO,cACP,OAAO,iBACP,OAAO,GACL,IAAI,CAsDhB;IAED,2BASC;IAED;;;;;;;;;;;oBAiCC;IAED;;;;;;;;;MAmEC;IAED,qCAOC;IAED;;;;OAIG;IACH,mCAKC;IAED,yDAAyD;IACzD,yBAUC;IAED,yCAGC;CACF;8BA/W6B,4BAA4B;iCAHzB,2CAA2C"}
@@ -1 +1 @@
1
- {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/recorder.js"],"names":[],"mappings":"AAWA;IAUE,yBAeC;IAdC,iEAAiE;IACjE,mBAAsB;IACtB,6DAA6D;IAC7D,oCAAuC;IACvC,kIAAkI;IAClI,kBAAqB;IACrB,sDAAsD;IACtD,YAAoB;IACpB,oEAAoE;IACpE,6BAAqH;IACrH,0FAA0F;IAC1F,eAA6C;IAC7C,uIAAuI;IACvI,0BAAyE;IAG3E;;;;;;;;;MAYC;IAED,mFAAmF;IACnF,oBAKC;IAED,qDAAqD;IACrD,uBAwBC;IAED;;;;;OAKG;IACH,yCAyBC;IAED,0HAA0H;IAC1H,yCA2CC;IAED,0HAA0H;IAC1H,yBAEC;IAED,wBAEC;IAED,gCAAgC;IAChC,uCAGC;IAED;;;SAGK;IACL,oCAGC;;CACF;+BAzL8B,mBAAmB"}
1
+ {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/recorder.js"],"names":[],"mappings":"AAWA;IAUE,yBAeC;IAdC,iEAAiE;IACjE,mBAAsB;IACtB,6DAA6D;IAC7D,oCAAuC;IACvC,kIAAkI;IAClI,kBAAqB;IACrB,sDAAsD;IACtD,YAAoB;IACpB,oEAAoE;IACpE,6BAAqH;IACrH,0FAA0F;IAC1F,eAA6C;IAC7C,uIAAuI;IACvI,0BAAyE;IAG3E;;;;;;;;;MAYC;IAED,mFAAmF;IACnF,oBAKC;IAED,qDAAqD;IACrD,uBAwBC;IAED;;;;;OAKG;IACH,yCAyBC;IAED,0HAA0H;IAC1H,yCA2CC;IAED,0HAA0H;IAC1H,yBAOC;IAED,wBAEC;IAED,gCAAgC;IAChC,uCAGC;IAED;;;SAGK;IACL,oCAGC;;CACF;+BA9L8B,mBAAmB"}
@@ -5,7 +5,7 @@ declare class StylesheetEvaluator {
5
5
  * Used at harvest time to denote that all subsequent payloads are subject to this and customers should be advised to handle crossorigin decoration
6
6
  * */
7
7
  invalidStylesheetsDetected: boolean;
8
- failedToFix: boolean;
8
+ failedToFix: number;
9
9
  /**
10
10
  * 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.
11
11
  * @returns {Number}
@@ -1 +1 @@
1
- {"version":3,"file":"stylesheet-evaluator.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/stylesheet-evaluator.js"],"names":[],"mappings":"AAwFA,sDAA4D;AArF5D;IAGE;;;QAGI;IACJ,oCAAkC;IAClC,qBAAmB;IAEnB;;;OAGG;IACH,mBAmBC;IAED;;;OAGG;IACH,oBAMC;;CAsCF"}
1
+ {"version":3,"file":"stylesheet-evaluator.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/stylesheet-evaluator.js"],"names":[],"mappings":"AAwFA,sDAA4D;AArF5D;IAGE;;;QAGI;IACJ,oCAAkC;IAClC,oBAAe;IAEf;;;OAGG;IACH,mBAmBC;IAED;;;OAGG;IACH,oBAMC;;CAsCF"}
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/api.js"],"names":[],"mappings":"AAkBA,2CAoBC;AAED;;;;;IAyDE;;;;OAIG;qBAFQ,MAAM;IAWjB;;;;OAIG;iCAFQ,MAAM,GAAC,IAAI;;;;;;EAqGvB;AArMD,0CAA0C"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/api.js"],"names":[],"mappings":"AAkBA,2CAoBC;AAED;;;;;IAyDE;;;;OAIG;qBAFQ,MAAM;IAWjB;;;;OAIG;iCAFQ,MAAM,GAAC,IAAI;;;;;;EAwGvB;AAxMD,0CAA0C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/browser-agent",
3
- "version": "1.251.1",
3
+ "version": "1.252.0",
4
4
  "private": false,
5
5
  "author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
6
6
  "description": "New Relic Browser Agent",
@@ -17,6 +17,9 @@
17
17
  "src/*": [
18
18
  "dist/types/*"
19
19
  ],
20
+ "dist/esm/*": [
21
+ "dist/types/*"
22
+ ],
20
23
  "loaders/agent": [
21
24
  "dist/types/loaders/agent.d.ts"
22
25
  ],
@@ -64,8 +64,8 @@ const model = () => {
64
64
  autoStart: true,
65
65
  enabled: false,
66
66
  harvestTimeSeconds: 60,
67
- sampling_rate: 50, // float from 0 - 100
68
- error_sampling_rate: 50, // float from 0 - 100
67
+ sampling_rate: 10, // float from 0 - 100
68
+ error_sampling_rate: 100, // float from 0 - 100
69
69
  collect_fonts: false, // 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
70
70
  inline_images: false, // serialize images for collection without public asset url -- right now this is only useful for testing as it easily generates payloads too large to be harvested
71
71
  inline_stylesheet: true, // serialize css for collection without public asset url
@@ -41,13 +41,14 @@ function curateRegistry (agentIdentifier) {
41
41
  * its own named group explicitly, when ready.
42
42
  * @param {string} agentIdentifier - A unique 16 character ID corresponding to an instantiated agent.
43
43
  * @param {string} featureName - A named group into which the feature's buffered events are bucketed.
44
+ * @param {boolean} force - Whether to force the drain to occur immediately, bypassing the registry and staging logic.
44
45
  */
45
- export function drain (agentIdentifier = '', featureName = 'feature') {
46
+ export function drain (agentIdentifier = '', featureName = 'feature', force = false) {
46
47
  curateRegistry(agentIdentifier)
47
48
  // If the feature for the specified agent is not in the registry, that means the instrument file was bypassed.
48
49
  // This could happen in tests, or loaders that directly import the aggregator. In these cases it is safe to
49
50
  // drain the feature group immediately rather than waiting to drain all at once.
50
- if (!agentIdentifier || !registry[agentIdentifier].get(featureName)) return drainGroup(featureName)
51
+ if (!agentIdentifier || !registry[agentIdentifier].get(featureName) || force) return drainGroup(featureName)
51
52
 
52
53
  // When `drain` is called, this feature is ready to drain (staged).
53
54
  registry[agentIdentifier].get(featureName).staged = true
@@ -144,5 +144,11 @@ function ee (old, debugId) {
144
144
 
145
145
  function abort () {
146
146
  globalInstance.aborted = true
147
- globalInstance.backlog = {}
147
+ // The global backlog can be referenced directly by other emitters,
148
+ // so we need to delete its contents as opposed to replacing it.
149
+ // Otherwise, these references to the old backlog would still exist
150
+ // and the keys will not be garbage collected.
151
+ Object.keys(globalInstance.backlog).forEach(key => {
152
+ delete globalInstance.backlog[key]
153
+ })
148
154
  }
@@ -142,9 +142,10 @@ export class Harvest extends SharedContext {
142
142
 
143
143
  if (!opts.unload && cbFinished && submitMethod === submitData.xhr) {
144
144
  const harvestScope = this
145
- result.addEventListener('load', function () {
145
+ result.addEventListener('loadend', function () {
146
146
  // `this` refers to the XHR object in this scope, do not change this to a fat arrow
147
- const cbResult = { sent: true, status: this.status }
147
+ // status 0 refers to a local error, such as CORS or network failure, or a blocked request by the browser (e.g. adblocker)
148
+ const cbResult = { sent: this.status !== 0, status: this.status }
148
149
  if (this.status === 429) {
149
150
  cbResult.retry = true
150
151
  cbResult.delay = harvestScope.tooManyRequestsDelay
@@ -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
 
20
21
  var handlers = ['load', 'error', 'abort', 'timeout']
21
22
  var handlersLen = handlers.length
@@ -395,6 +396,7 @@ function subscribeToEvents (agentIdentifier, ee, handler, dt) {
395
396
  if (ctx.sameOrigin) {
396
397
  var header = xhr.getResponseHeader('X-NewRelic-App-Data')
397
398
  if (header) {
399
+ handle(SUPPORTABILITY_METRIC, ['Ajax/CrossApplicationTracing/Header/Seen'], undefined, FEATURE_NAMES.metrics, ee)
398
400
  ctx.params.cat = header.split(', ').pop()
399
401
  }
400
402
  }
@@ -128,6 +128,12 @@ export class Instrument extends InstrumentBase {
128
128
  * @returns {Error|UncaughtError} The error event converted to an Error object
129
129
  */
130
130
  #castErrorEvent (errorEvent) {
131
+ if (errorEvent.error instanceof SyntaxError && !/:\d+$/.test(errorEvent.error.stack?.trim())) {
132
+ const error = new UncaughtError(errorEvent.message, errorEvent.filename, errorEvent.lineno, errorEvent.colno)
133
+ error.name = SyntaxError.name
134
+ return error
135
+ }
136
+
131
137
  if (errorEvent.error instanceof Error) {
132
138
  return errorEvent.error
133
139
  }
@@ -101,7 +101,7 @@ export class Aggregate extends AggregateBase {
101
101
  payload: { qs: queryParameters, body },
102
102
  opts: { needResponse: true, sendEmptyBody: true },
103
103
  cbFinished: ({ status, responseText }) => {
104
- if (status >= 400) {
104
+ if (status >= 400 || status === 0) {
105
105
  // Adding retry logic for the rum call will be a separate change
106
106
  this.ee.abort()
107
107
  return
@@ -53,6 +53,8 @@ export class Aggregate extends AggregateBase {
53
53
  this.recorder = args?.recorder
54
54
  if (this.recorder) this.recorder.parent = this
55
55
 
56
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/Enabled'], undefined, FEATURE_NAMES.metrics, this.ee)
57
+
56
58
  const shouldSetup = (
57
59
  getConfigurationValue(agentIdentifier, 'privacy.cookies_enabled') === true &&
58
60
  getConfigurationValue(agentIdentifier, 'session_trace.enabled') === true
@@ -122,15 +124,31 @@ export class Aggregate extends AggregateBase {
122
124
  }
123
125
  }, this.featureName, this.ee)
124
126
 
127
+ const { error_sampling_rate, sampling_rate, autoStart, block_selector, mask_text_selector, mask_all_inputs, inline_stylesheet, inline_images, collect_fonts } = getConfigurationValue(this.agentIdentifier, 'session_replay')
128
+
125
129
  this.waitForFlags(['sr']).then(([flagOn]) => {
126
130
  this.entitled = flagOn
127
- if (!this.entitled && this.recorder?.recording) this.recorder.abort(ABORT_REASONS.ENTITLEMENTS)
131
+ if (!this.entitled && this.recorder?.recording) {
132
+ this.recorder.abort(ABORT_REASONS.ENTITLEMENTS)
133
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/EnabledNotEntitled/Detected'], undefined, FEATURE_NAMES.metrics, this.ee)
134
+ }
128
135
  this.initializeRecording(
129
- (Math.random() * 100) < getConfigurationValue(this.agentIdentifier, 'session_replay.error_sampling_rate'),
130
- (Math.random() * 100) < getConfigurationValue(this.agentIdentifier, 'session_replay.sampling_rate')
136
+ (Math.random() * 100) < error_sampling_rate,
137
+ (Math.random() * 100) < sampling_rate
131
138
  )
132
139
  }).then(() => sharedChannel.onReplayReady(this.mode)) // notify watchers that replay started with the mode
133
140
 
141
+ /** 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 */
142
+ if (!autoStart) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/AutoStart/Modified'], undefined, FEATURE_NAMES.metrics, this.ee)
143
+ if (collect_fonts === true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/CollectFonts/Modified'], undefined, FEATURE_NAMES.metrics, this.ee)
144
+ if (inline_stylesheet !== true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineStylesheet/Modified'], undefined, FEATURE_NAMES.metrics, this.ee)
145
+ if (inline_images === true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineImages/Modifed'], undefined, FEATURE_NAMES.metrics, this.ee)
146
+ if (mask_all_inputs !== true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/MaskAllInputs/Modified'], undefined, FEATURE_NAMES.metrics, this.ee)
147
+ if (block_selector !== '[data-nr-block]') handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/BlockSelector/Modified'], undefined, FEATURE_NAMES.metrics, this.ee)
148
+ if (mask_text_selector !== '*') handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/MaskTextSelector/Modified'], undefined, FEATURE_NAMES.metrics, this.ee)
149
+
150
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/SamplingRate/Value', sampling_rate], undefined, FEATURE_NAMES.metrics, this.ee)
151
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/ErrorSamplingRate/Value', error_sampling_rate], undefined, FEATURE_NAMES.metrics, this.ee)
134
152
  this.drain()
135
153
  }
136
154
  }
@@ -102,13 +102,13 @@ export class Recorder {
102
102
  /** 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) */
103
103
  if (!incompletes && this.#fixing && event.type === RRWEB_EVENT_TYPES.Meta) this.#fixing = false
104
104
  if (incompletes) {
105
- handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css', incompletes], undefined, FEATURE_NAMES.metrics, this.parent.ee)
106
105
  /** wait for the evaluator to download/replace the incompletes' src code and then take a new snap */
107
106
  stylesheetEvaluator.fix().then((failedToFix) => {
108
107
  if (failedToFix) {
109
108
  this.currentBufferTarget.inlinedAllStylesheets = false
110
109
  this.shouldFix = false
111
- }
110
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css/Failed', failedToFix], undefined, FEATURE_NAMES.metrics, this.parent.ee)
111
+ } else handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css/Fixed', incompletes - failedToFix], undefined, FEATURE_NAMES.metrics, this.parent.ee)
112
112
  this.takeFullSnapshot()
113
113
  })
114
114
  /** Only start ignoring data if got a faulty snapshot */
@@ -166,7 +166,12 @@ export class Recorder {
166
166
 
167
167
  /** force the recording lib to take a full DOM snapshot. This needs to occur in certain cases, like visibility changes */
168
168
  takeFullSnapshot () {
169
- recorder.takeFullSnapshot()
169
+ try {
170
+ if (!this.recording) return
171
+ recorder.takeFullSnapshot()
172
+ } catch (err) {
173
+ // 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
174
+ }
170
175
  }
171
176
 
172
177
  clearTimestamps () {
@@ -9,7 +9,7 @@ class StylesheetEvaluator {
9
9
  * Used at harvest time to denote that all subsequent payloads are subject to this and customers should be advised to handle crossorigin decoration
10
10
  * */
11
11
  invalidStylesheetsDetected = false
12
- failedToFix = false
12
+ failedToFix = 0
13
13
 
14
14
  /**
15
15
  * 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.
@@ -44,7 +44,7 @@ class StylesheetEvaluator {
44
44
  await Promise.all(this.#fetchProms)
45
45
  this.#fetchProms = []
46
46
  const failedToFix = this.failedToFix
47
- this.failedToFix = false
47
+ this.failedToFix = 0
48
48
  return failedToFix
49
49
  }
50
50
 
@@ -58,7 +58,7 @@ class StylesheetEvaluator {
58
58
  try {
59
59
  const stylesheetContents = await originals.FETCH.bind(window)(href)
60
60
  if (!stylesheetContents.ok) {
61
- this.failedToFix = true
61
+ this.failedToFix++
62
62
  return
63
63
  }
64
64
  const stylesheetText = await stylesheetContents.text()
@@ -77,11 +77,11 @@ class StylesheetEvaluator {
77
77
  Object.defineProperty(target, 'cssText', {
78
78
  get () { return stylesheetText }
79
79
  })
80
- this.failedToFix = true
80
+ this.failedToFix++
81
81
  }
82
82
  } catch (err) {
83
83
  // failed to fetch
84
- this.failedToFix = true
84
+ this.failedToFix++
85
85
  }
86
86
  }
87
87
  }
@@ -108,7 +108,7 @@ export class InstrumentBase extends FeatureBase {
108
108
  warn(`Downloading and initializing ${this.featureName} failed...`, e)
109
109
  this.abortHandler?.() // undo any important alterations made to the page
110
110
  // not supported yet but nice to do: "abort" this agent's EE for this feature specifically
111
- drain(this.agentIdentifier, this.featureName)
111
+ drain(this.agentIdentifier, this.featureName, true)
112
112
  loadedSuccessfully(false)
113
113
  }
114
114
  }
@@ -207,7 +207,10 @@ export function setAPI (agentIdentifier, forceDrain) {
207
207
  import(/* webpackChunkName: "async-api" */'./apiAsync').then(({ setAPI }) => {
208
208
  setAPI(agentIdentifier)
209
209
  drain(agentIdentifier, 'api')
210
- }).catch(() => warn('Downloading runtime APIs failed...'))
210
+ }).catch(() => {
211
+ warn('Downloading runtime APIs failed...')
212
+ drain(agentIdentifier, 'api', true)
213
+ })
211
214
  }
212
215
 
213
216
  return apiInterface