@newrelic/browser-agent 1.251.0 → 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 (60) hide show
  1. package/CHANGELOG.md +25 -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-scheduler.js +2 -1
  8. package/dist/cjs/common/harvest/harvest.js +3 -2
  9. package/dist/cjs/features/ajax/instrument/index.js +2 -0
  10. package/dist/cjs/features/jserrors/instrument/index.js +5 -0
  11. package/dist/cjs/features/metrics/aggregate/index.js +1 -1
  12. package/dist/cjs/features/page_view_event/aggregate/index.js +1 -1
  13. package/dist/cjs/features/session_replay/aggregate/index.js +53 -17
  14. package/dist/cjs/features/session_replay/shared/recorder.js +9 -4
  15. package/dist/cjs/features/session_replay/shared/stylesheet-evaluator.js +33 -28
  16. package/dist/cjs/features/utils/instrument-base.js +1 -1
  17. package/dist/cjs/loaders/api/api.js +4 -1
  18. package/dist/esm/common/config/state/init.js +2 -2
  19. package/dist/esm/common/constants/env.cdn.js +1 -1
  20. package/dist/esm/common/constants/env.npm.js +1 -1
  21. package/dist/esm/common/drain/drain.js +3 -1
  22. package/dist/esm/common/event-emitter/contextual-ee.js +7 -1
  23. package/dist/esm/common/harvest/harvest-scheduler.js +2 -1
  24. package/dist/esm/common/harvest/harvest.js +3 -2
  25. package/dist/esm/features/ajax/instrument/index.js +2 -0
  26. package/dist/esm/features/jserrors/instrument/index.js +5 -0
  27. package/dist/esm/features/metrics/aggregate/index.js +1 -1
  28. package/dist/esm/features/page_view_event/aggregate/index.js +1 -1
  29. package/dist/esm/features/session_replay/aggregate/index.js +53 -17
  30. package/dist/esm/features/session_replay/shared/recorder.js +9 -4
  31. package/dist/esm/features/session_replay/shared/stylesheet-evaluator.js +33 -28
  32. package/dist/esm/features/utils/instrument-base.js +1 -1
  33. package/dist/esm/loaders/api/api.js +4 -1
  34. package/dist/types/common/drain/drain.d.ts +2 -1
  35. package/dist/types/common/drain/drain.d.ts.map +1 -1
  36. package/dist/types/common/event-emitter/contextual-ee.d.ts.map +1 -1
  37. package/dist/types/common/harvest/harvest.d.ts.map +1 -1
  38. package/dist/types/features/ajax/instrument/index.d.ts.map +1 -1
  39. package/dist/types/features/jserrors/instrument/index.d.ts.map +1 -1
  40. package/dist/types/features/session_replay/aggregate/index.d.ts +8 -3
  41. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  42. package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
  43. package/dist/types/features/session_replay/shared/stylesheet-evaluator.d.ts +1 -1
  44. package/dist/types/features/session_replay/shared/stylesheet-evaluator.d.ts.map +1 -1
  45. package/dist/types/loaders/api/api.d.ts.map +1 -1
  46. package/package.json +4 -1
  47. package/src/common/config/state/init.js +2 -2
  48. package/src/common/drain/drain.js +3 -2
  49. package/src/common/event-emitter/contextual-ee.js +7 -1
  50. package/src/common/harvest/harvest-scheduler.js +1 -1
  51. package/src/common/harvest/harvest.js +3 -2
  52. package/src/features/ajax/instrument/index.js +2 -0
  53. package/src/features/jserrors/instrument/index.js +6 -0
  54. package/src/features/metrics/aggregate/index.js +1 -1
  55. package/src/features/page_view_event/aggregate/index.js +1 -1
  56. package/src/features/session_replay/aggregate/index.js +47 -19
  57. package/src/features/session_replay/shared/recorder.js +9 -4
  58. package/src/features/session_replay/shared/stylesheet-evaluator.js +26 -21
  59. package/src/features/utils/instrument-base.js +1 -1
  60. package/src/loaders/api/api.js +4 -1
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.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
+
23
+ ## [1.251.1](https://github.com/newrelic/newrelic-browser-agent/compare/v1.251.0...v1.251.1) (2024-01-29)
24
+
25
+
26
+ ### Bug Fixes
27
+
28
+ * Fix deferred Session Replay payloads ([#868](https://github.com/newrelic/newrelic-browser-agent/issues/868)) ([f69e4b0](https://github.com/newrelic/newrelic-browser-agent/commit/f69e4b0eba5a54f4e67316f5e6a30090cf7360cc))
29
+ * Pass unload options to simultaneous harvests in Session Replay ([#870](https://github.com/newrelic/newrelic-browser-agent/issues/870)) ([655aa5d](https://github.com/newrelic/newrelic-browser-agent/commit/655aa5d261d03f71086d3cfc73cb72db51cb28c7))
30
+
6
31
  ## [1.251.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.250.0...v1.251.0) (2024-01-24)
7
32
 
8
33
 
@@ -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.0";
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.0";
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
  }
@@ -109,7 +109,8 @@ class HarvestScheduler extends _sharedContext.SharedContext {
109
109
  if (!submitMethod) return false;
110
110
  const retry = !opts?.unload && submitMethod === submitData.xhr;
111
111
  payload = this.opts.getPayload({
112
- retry
112
+ retry,
113
+ opts
113
114
  });
114
115
  if (!payload) {
115
116
  if (this.started) {
@@ -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
  }
@@ -113,7 +113,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
113
113
 
114
114
  // [Temporary] Report restores from BFCache to NR1 while feature flag is in place in lieu of sending pageshow events.
115
115
  (0, _eventListenerOpts.windowAddEventListener)('pageshow', evt => {
116
- if (evt.persisted) {
116
+ if (evt?.persisted) {
117
117
  this.storeSupportabilityMetrics('Generic/BFCache/PageRestored');
118
118
  }
119
119
  });
@@ -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;
@@ -32,7 +32,6 @@ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e;
32
32
  * It is not production ready, and is not intended to be imported or implemented in any build of the browser agent until
33
33
  * functionality is validated and a full user experience is curated.
34
34
  */
35
- let gzipper, u8;
36
35
  class Aggregate extends _aggregateBase.AggregateBase {
37
36
  static featureName = _constants.FEATURE_NAME;
38
37
  // pass the recorder into the aggregator
@@ -44,8 +43,10 @@ class Aggregate extends _aggregateBase.AggregateBase {
44
43
  this.initialized = false;
45
44
  /** Set once the feature has been "aborted" to prevent other side-effects from continuing */
46
45
  this.blocked = false;
47
- /** can shut off efforts to compress the data */
48
- this.shouldCompress = true;
46
+ /** populated with the gzipper lib async */
47
+ this.gzipper = undefined;
48
+ /** populated with the u8 string lib async */
49
+ this.u8 = undefined;
49
50
  /** the mode to start in. Defaults to off */
50
51
  const {
51
52
  session
@@ -56,6 +57,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
56
57
  this.entitled = false;
57
58
  this.recorder = args?.recorder;
58
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);
59
61
  const shouldSetup = (0, _config.getConfigurationValue)(agentIdentifier, 'privacy.cookies_enabled') === true && (0, _config.getConfigurationValue)(agentIdentifier, 'session_trace.enabled') === true;
60
62
  if (shouldSetup) {
61
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.
@@ -92,6 +94,11 @@ class Aggregate extends _aggregateBase.AggregateBase {
92
94
  getPayload: this.prepareHarvest.bind(this),
93
95
  raw: true
94
96
  }, this);
97
+ if (this.recorder?.getEvents().type === 'preloaded') {
98
+ this.prepUtils().then(() => {
99
+ this.scheduler.runHarvest();
100
+ });
101
+ }
95
102
  (0, _registerHandler.registerHandler)('recordReplay', () => {
96
103
  // if it has aborted or BCS returned bad entitlements, do not allow
97
104
  if (this.blocked || !this.entitled) return;
@@ -115,13 +122,37 @@ class Aggregate extends _aggregateBase.AggregateBase {
115
122
  this.switchToFull();
116
123
  }
117
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');
118
136
  this.waitForFlags(['sr']).then(_ref => {
119
137
  let [flagOn] = _ref;
120
138
  this.entitled = flagOn;
121
- if (!this.entitled && this.recorder?.recording) this.recorder.abort(_constants.ABORT_REASONS.ENTITLEMENTS);
122
- 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);
123
144
  }).then(() => _sharedChannel.sharedChannel.onReplayReady(this.mode)); // notify watchers that replay started with the mode
124
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);
125
156
  this.drain();
126
157
  }
127
158
  }
@@ -196,24 +227,29 @@ class Aggregate extends _aggregateBase.AggregateBase {
196
227
  // We only report (harvest) in FULL mode
197
228
  this.scheduler.startTimer(this.harvestTimeSeconds);
198
229
  }
230
+ await this.prepUtils();
231
+ if (!this.recorder.recording) this.recorder.startRecording();
232
+ this.syncWithSessionManager({
233
+ sessionReplayMode: this.mode
234
+ });
235
+ }
236
+ async prepUtils() {
199
237
  try {
200
238
  // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
201
239
  const {
202
240
  gzipSync,
203
241
  strToU8
204
242
  } = await Promise.resolve().then(() => _interopRequireWildcard(require( /* webpackChunkName: "compressor" */'fflate')));
205
- gzipper = gzipSync;
206
- u8 = strToU8;
243
+ this.gzipper = gzipSync;
244
+ this.u8 = strToU8;
207
245
  } catch (err) {
208
246
  // compressor failed to load, but we can still record without compression as a last ditch effort
209
- this.shouldCompress = false;
210
247
  }
211
- if (!this.recorder.recording) this.recorder.startRecording();
212
- this.syncWithSessionManager({
213
- sessionReplayMode: this.mode
214
- });
215
248
  }
216
249
  prepareHarvest() {
250
+ let {
251
+ opts
252
+ } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
217
253
  if (!this.recorder) return;
218
254
  const recorderEvents = this.recorder.getEvents();
219
255
  // get the event type and use that to trigger another harvest if needed
@@ -224,8 +260,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
224
260
  return;
225
261
  }
226
262
  let len = 0;
227
- if (this.shouldCompress) {
228
- payload.body = gzipper(u8("[".concat(payload.body.map(e => e.__serialized).join(','), "]")));
263
+ if (!!this.gzipper && !!this.u8) {
264
+ payload.body = this.gzipper(this.u8("[".concat(payload.body.map(e => e.__serialized).join(','), "]")));
229
265
  len = payload.body.length;
230
266
  this.scheduler.opts.gzip = true;
231
267
  } else {
@@ -251,7 +287,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
251
287
  sessionReplaySentFirstChunk: true
252
288
  });
253
289
  this.recorder.clearBuffer();
254
- if (recorderEvents.type === 'preloaded') this.scheduler.runHarvest();
290
+ if (recorderEvents.type === 'preloaded') this.scheduler.runHarvest(opts);
255
291
  return [payload];
256
292
  }
257
293
  getHarvestContents(recorderEvents) {
@@ -282,7 +318,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
282
318
  const relativeNow = (0, _now.now)();
283
319
  const firstEventTimestamp = events[0]?.timestamp; // from rrweb node
284
320
  const lastEventTimestamp = events[events.length - 1]?.timestamp; // from rrweb node
285
- const firstTimestamp = firstEventTimestamp || recorderEvents.cycleTimestamp;
321
+ const firstTimestamp = firstEventTimestamp || recorderEvents.cycleTimestamp; // from rrweb node || from when the harvest cycle started
286
322
  const lastTimestamp = lastEventTimestamp || agentOffset + relativeNow;
287
323
  return {
288
324
  qs: {
@@ -293,7 +329,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
293
329
  attributes: (0, _encode.obj)({
294
330
  // this section of attributes must be controllable and stay below the query param padding limit -- see QUERY_PARAM_PADDING
295
331
  // if not, data could be lost to truncation at time of sending, potentially breaking parsing / API behavior in NR1
296
- ...(this.shouldCompress && {
332
+ ...(!!this.gzipper && !!this.u8 && {
297
333
  content_encoding: 'gzip'
298
334
  }),
299
335
  'replay.firstTimestamp': firstTimestamp,
@@ -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;
@@ -194,7 +199,7 @@ class Recorder {
194
199
  * https://staging.onenr.io/037jbJWxbjy
195
200
  * */
196
201
  estimateCompression(data) {
197
- if (this.shouldCompress) return data * _constants.AVG_COMPRESSION;
202
+ if (!!this.parent.gzipper && !!this.parent.u8) return data * _constants.AVG_COMPRESSION;
198
203
  return data;
199
204
  }
200
205
  }
@@ -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
 
@@ -60,34 +60,39 @@ class StylesheetEvaluator {
60
60
  * @returns {Promise}
61
61
  */
62
62
  async #fetchAndOverride(target, href) {
63
- const stylesheetContents = await _config.originals.FETCH.bind(window)(href);
64
- if (!stylesheetContents.ok) {
65
- this.failedToFix = true;
66
- return;
67
- }
68
- const stylesheetText = await stylesheetContents.text();
69
63
  try {
70
- const cssSheet = new CSSStyleSheet();
71
- await cssSheet.replace(stylesheetText);
72
- Object.defineProperty(target, 'cssRules', {
73
- get() {
74
- return cssSheet.cssRules;
75
- }
76
- });
77
- Object.defineProperty(target, 'rules', {
78
- get() {
79
- return cssSheet.rules;
80
- }
81
- });
64
+ const stylesheetContents = await _config.originals.FETCH.bind(window)(href);
65
+ if (!stylesheetContents.ok) {
66
+ this.failedToFix++;
67
+ return;
68
+ }
69
+ const stylesheetText = await stylesheetContents.text();
70
+ try {
71
+ const cssSheet = new CSSStyleSheet();
72
+ await cssSheet.replace(stylesheetText);
73
+ Object.defineProperty(target, 'cssRules', {
74
+ get() {
75
+ return cssSheet.cssRules;
76
+ }
77
+ });
78
+ Object.defineProperty(target, 'rules', {
79
+ get() {
80
+ return cssSheet.rules;
81
+ }
82
+ });
83
+ } catch (err) {
84
+ // cant make new dynamic stylesheets, browser likely doesn't support `.replace()`...
85
+ // this is appended in prep of forking rrweb
86
+ Object.defineProperty(target, 'cssText', {
87
+ get() {
88
+ return stylesheetText;
89
+ }
90
+ });
91
+ this.failedToFix++;
92
+ }
82
93
  } catch (err) {
83
- // cant make new dynamic stylesheets, browser likely doesn't support `.replace()`...
84
- // this is appended in prep of forking rrweb
85
- Object.defineProperty(target, 'cssText', {
86
- get() {
87
- return stylesheetText;
88
- }
89
- });
90
- this.failedToFix = true;
94
+ // failed to fetch
95
+ this.failedToFix++;
91
96
  }
92
97
  }
93
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.0";
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.0";
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
  }
@@ -102,7 +102,8 @@ export class HarvestScheduler extends SharedContext {
102
102
  if (!submitMethod) return false;
103
103
  const retry = !opts?.unload && submitMethod === submitData.xhr;
104
104
  payload = this.opts.getPayload({
105
- retry
105
+ retry,
106
+ opts
106
107
  });
107
108
  if (!payload) {
108
109
  if (this.started) {
@@ -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
  }
@@ -107,7 +107,7 @@ export class Aggregate extends AggregateBase {
107
107
 
108
108
  // [Temporary] Report restores from BFCache to NR1 while feature flag is in place in lieu of sending pageshow events.
109
109
  windowAddEventListener('pageshow', evt => {
110
- if (evt.persisted) {
110
+ if (evt?.persisted) {
111
111
  this.storeSupportabilityMetrics('Generic/BFCache/PageRestored');
112
112
  }
113
113
  });
@@ -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;