@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
@@ -27,7 +27,6 @@ import { now } from '../../../common/timing/now';
27
27
  import { MODE, SESSION_EVENTS, SESSION_EVENT_TYPES } from '../../../common/session/constants';
28
28
  import { stringify } from '../../../common/util/stringify';
29
29
  import { stylesheetEvaluator } from '../shared/stylesheet-evaluator';
30
- let gzipper, u8;
31
30
  export class Aggregate extends AggregateBase {
32
31
  static featureName = FEATURE_NAME;
33
32
  // pass the recorder into the aggregator
@@ -39,8 +38,10 @@ export class Aggregate extends AggregateBase {
39
38
  this.initialized = false;
40
39
  /** Set once the feature has been "aborted" to prevent other side-effects from continuing */
41
40
  this.blocked = false;
42
- /** can shut off efforts to compress the data */
43
- this.shouldCompress = true;
41
+ /** populated with the gzipper lib async */
42
+ this.gzipper = undefined;
43
+ /** populated with the u8 string lib async */
44
+ this.u8 = undefined;
44
45
  /** the mode to start in. Defaults to off */
45
46
  const {
46
47
  session
@@ -51,6 +52,7 @@ export class Aggregate extends AggregateBase {
51
52
  this.entitled = false;
52
53
  this.recorder = args?.recorder;
53
54
  if (this.recorder) this.recorder.parent = this;
55
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/Enabled'], undefined, FEATURE_NAMES.metrics, this.ee);
54
56
  const shouldSetup = getConfigurationValue(agentIdentifier, 'privacy.cookies_enabled') === true && getConfigurationValue(agentIdentifier, 'session_trace.enabled') === true;
55
57
  if (shouldSetup) {
56
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.
@@ -87,6 +89,11 @@ export class Aggregate extends AggregateBase {
87
89
  getPayload: this.prepareHarvest.bind(this),
88
90
  raw: true
89
91
  }, this);
92
+ if (this.recorder?.getEvents().type === 'preloaded') {
93
+ this.prepUtils().then(() => {
94
+ this.scheduler.runHarvest();
95
+ });
96
+ }
90
97
  registerHandler('recordReplay', () => {
91
98
  // if it has aborted or BCS returned bad entitlements, do not allow
92
99
  if (this.blocked || !this.entitled) return;
@@ -110,13 +117,37 @@ export class Aggregate extends AggregateBase {
110
117
  this.switchToFull();
111
118
  }
112
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');
113
131
  this.waitForFlags(['sr']).then(_ref => {
114
132
  let [flagOn] = _ref;
115
133
  this.entitled = flagOn;
116
- if (!this.entitled && this.recorder?.recording) this.recorder.abort(ABORT_REASONS.ENTITLEMENTS);
117
- 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);
118
139
  }).then(() => sharedChannel.onReplayReady(this.mode)); // notify watchers that replay started with the mode
119
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);
120
151
  this.drain();
121
152
  }
122
153
  }
@@ -191,24 +222,29 @@ export class Aggregate extends AggregateBase {
191
222
  // We only report (harvest) in FULL mode
192
223
  this.scheduler.startTimer(this.harvestTimeSeconds);
193
224
  }
225
+ await this.prepUtils();
226
+ if (!this.recorder.recording) this.recorder.startRecording();
227
+ this.syncWithSessionManager({
228
+ sessionReplayMode: this.mode
229
+ });
230
+ }
231
+ async prepUtils() {
194
232
  try {
195
233
  // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
196
234
  const {
197
235
  gzipSync,
198
236
  strToU8
199
237
  } = await import( /* webpackChunkName: "compressor" */'fflate');
200
- gzipper = gzipSync;
201
- u8 = strToU8;
238
+ this.gzipper = gzipSync;
239
+ this.u8 = strToU8;
202
240
  } catch (err) {
203
241
  // compressor failed to load, but we can still record without compression as a last ditch effort
204
- this.shouldCompress = false;
205
242
  }
206
- if (!this.recorder.recording) this.recorder.startRecording();
207
- this.syncWithSessionManager({
208
- sessionReplayMode: this.mode
209
- });
210
243
  }
211
244
  prepareHarvest() {
245
+ let {
246
+ opts
247
+ } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
212
248
  if (!this.recorder) return;
213
249
  const recorderEvents = this.recorder.getEvents();
214
250
  // get the event type and use that to trigger another harvest if needed
@@ -219,8 +255,8 @@ export class Aggregate extends AggregateBase {
219
255
  return;
220
256
  }
221
257
  let len = 0;
222
- if (this.shouldCompress) {
223
- payload.body = gzipper(u8("[".concat(payload.body.map(e => e.__serialized).join(','), "]")));
258
+ if (!!this.gzipper && !!this.u8) {
259
+ payload.body = this.gzipper(this.u8("[".concat(payload.body.map(e => e.__serialized).join(','), "]")));
224
260
  len = payload.body.length;
225
261
  this.scheduler.opts.gzip = true;
226
262
  } else {
@@ -246,7 +282,7 @@ export class Aggregate extends AggregateBase {
246
282
  sessionReplaySentFirstChunk: true
247
283
  });
248
284
  this.recorder.clearBuffer();
249
- if (recorderEvents.type === 'preloaded') this.scheduler.runHarvest();
285
+ if (recorderEvents.type === 'preloaded') this.scheduler.runHarvest(opts);
250
286
  return [payload];
251
287
  }
252
288
  getHarvestContents(recorderEvents) {
@@ -277,7 +313,7 @@ export class Aggregate extends AggregateBase {
277
313
  const relativeNow = now();
278
314
  const firstEventTimestamp = events[0]?.timestamp; // from rrweb node
279
315
  const lastEventTimestamp = events[events.length - 1]?.timestamp; // from rrweb node
280
- const firstTimestamp = firstEventTimestamp || recorderEvents.cycleTimestamp;
316
+ const firstTimestamp = firstEventTimestamp || recorderEvents.cycleTimestamp; // from rrweb node || from when the harvest cycle started
281
317
  const lastTimestamp = lastEventTimestamp || agentOffset + relativeNow;
282
318
  return {
283
319
  qs: {
@@ -288,7 +324,7 @@ export class Aggregate extends AggregateBase {
288
324
  attributes: encodeObj({
289
325
  // this section of attributes must be controllable and stay below the query param padding limit -- see QUERY_PARAM_PADDING
290
326
  // if not, data could be lost to truncation at time of sending, potentially breaking parsing / API behavior in NR1
291
- ...(this.shouldCompress && {
327
+ ...(!!this.gzipper && !!this.u8 && {
292
328
  content_encoding: 'gzip'
293
329
  }),
294
330
  'replay.firstTimestamp': firstTimestamp,
@@ -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;
@@ -188,7 +193,7 @@ export class Recorder {
188
193
  * https://staging.onenr.io/037jbJWxbjy
189
194
  * */
190
195
  estimateCompression(data) {
191
- if (this.shouldCompress) return data * AVG_COMPRESSION;
196
+ if (!!this.parent.gzipper && !!this.parent.u8) return data * AVG_COMPRESSION;
192
197
  return data;
193
198
  }
194
199
  }
@@ -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
 
@@ -54,34 +54,39 @@ class StylesheetEvaluator {
54
54
  * @returns {Promise}
55
55
  */
56
56
  async #fetchAndOverride(target, href) {
57
- const stylesheetContents = await originals.FETCH.bind(window)(href);
58
- if (!stylesheetContents.ok) {
59
- this.failedToFix = true;
60
- return;
61
- }
62
- const stylesheetText = await stylesheetContents.text();
63
57
  try {
64
- const cssSheet = new CSSStyleSheet();
65
- await cssSheet.replace(stylesheetText);
66
- Object.defineProperty(target, 'cssRules', {
67
- get() {
68
- return cssSheet.cssRules;
69
- }
70
- });
71
- Object.defineProperty(target, 'rules', {
72
- get() {
73
- return cssSheet.rules;
74
- }
75
- });
58
+ const stylesheetContents = await originals.FETCH.bind(window)(href);
59
+ if (!stylesheetContents.ok) {
60
+ this.failedToFix++;
61
+ return;
62
+ }
63
+ const stylesheetText = await stylesheetContents.text();
64
+ try {
65
+ const cssSheet = new CSSStyleSheet();
66
+ await cssSheet.replace(stylesheetText);
67
+ Object.defineProperty(target, 'cssRules', {
68
+ get() {
69
+ return cssSheet.cssRules;
70
+ }
71
+ });
72
+ Object.defineProperty(target, 'rules', {
73
+ get() {
74
+ return cssSheet.rules;
75
+ }
76
+ });
77
+ } catch (err) {
78
+ // cant make new dynamic stylesheets, browser likely doesn't support `.replace()`...
79
+ // this is appended in prep of forking rrweb
80
+ Object.defineProperty(target, 'cssText', {
81
+ get() {
82
+ return stylesheetText;
83
+ }
84
+ });
85
+ this.failedToFix++;
86
+ }
76
87
  } catch (err) {
77
- // cant make new dynamic stylesheets, browser likely doesn't support `.replace()`...
78
- // this is appended in prep of forking rrweb
79
- Object.defineProperty(target, 'cssText', {
80
- get() {
81
- return stylesheetText;
82
- }
83
- });
84
- this.failedToFix = true;
88
+ // failed to fetch
89
+ this.failedToFix++;
85
90
  }
86
91
  }
87
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"}
@@ -5,8 +5,10 @@ export class Aggregate extends AggregateBase {
5
5
  harvestTimeSeconds: any;
6
6
  /** Set once the recorder has fully initialized after flag checks and sampling */
7
7
  initialized: boolean;
8
- /** can shut off efforts to compress the data */
9
- shouldCompress: boolean;
8
+ /** populated with the gzipper lib async */
9
+ gzipper: typeof import("fflate").gzipSync | undefined;
10
+ /** populated with the u8 string lib async */
11
+ u8: typeof import("fflate").strToU8 | undefined;
10
12
  mode: any;
11
13
  /** set by BCS response */
12
14
  entitled: boolean;
@@ -23,7 +25,10 @@ export class Aggregate extends AggregateBase {
23
25
  * @returns {void}
24
26
  */
25
27
  initializeRecording(errorSample: boolean, fullSample: boolean, ignoreSession: boolean): void;
26
- prepareHarvest(): {
28
+ prepUtils(): Promise<void>;
29
+ prepareHarvest({ opts }?: {
30
+ opts: any;
31
+ }): {
27
32
  qs: {
28
33
  browser_monitoring_key: any;
29
34
  type: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"AAgCA;IACE,2BAAiC;IAEjC,8DA8FC;IA5FC,8GAA8G;IAC9G,wBAAgH;IAChH,iFAAiF;IACjF,qBAAwB;IAGxB,gDAAgD;IAChD,wBAA0B;IAG1B,UAAuD;IAEvD,0BAA0B;IAC1B,kBAAqB;IAErB,cAA8B;IAkC5B,wCAKQ;IAmBN,sBAAwB;IAqB9B,qBAWC;IAED;;;;;;;OAOG;IACH,iCALW,OAAO,cACP,OAAO,iBACP,OAAO,GACL,IAAI,CA6DhB;IAED;;;;;;;;;oBAiCC;IAED;;;;;;;;;MAmEC;IAED,qCAOC;IAED;;;;OAIG;IACH,mCAKC;IAED,yDAAyD;IACzD,yBAUC;IAED,yCAGC;CACF;8BAnV6B,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":"AAmFA,sDAA4D;AAhF5D;IAGE;;;QAGI;IACJ,oCAAkC;IAClC,qBAAmB;IAEnB;;;OAGG;IACH,mBAmBC;IAED;;;OAGG;IACH,oBAMC;;CAiCF"}
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.0",
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
  }
@@ -102,7 +102,7 @@ export class HarvestScheduler extends SharedContext {
102
102
  if (!submitMethod) return false
103
103
 
104
104
  const retry = !opts?.unload && submitMethod === submitData.xhr
105
- payload = this.opts.getPayload({ retry })
105
+ payload = this.opts.getPayload({ retry, opts })
106
106
 
107
107
  if (!payload) {
108
108
  if (this.started) {
@@ -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
  }
@@ -102,7 +102,7 @@ export class Aggregate extends AggregateBase {
102
102
 
103
103
  // [Temporary] Report restores from BFCache to NR1 while feature flag is in place in lieu of sending pageshow events.
104
104
  windowAddEventListener('pageshow', (evt) => {
105
- if (evt.persisted) { this.storeSupportabilityMetrics('Generic/BFCache/PageRestored') }
105
+ if (evt?.persisted) { this.storeSupportabilityMetrics('Generic/BFCache/PageRestored') }
106
106
  })
107
107
  }
108
108
 
@@ -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