@newrelic/browser-agent 1.251.0 → 1.251.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,14 @@
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.251.1](https://github.com/newrelic/newrelic-browser-agent/compare/v1.251.0...v1.251.1) (2024-01-29)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * 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))
12
+ * 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))
13
+
6
14
  ## [1.251.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.250.0...v1.251.0) (2024-01-24)
7
15
 
8
16
 
@@ -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.251.1";
16
16
 
17
17
  /**
18
18
  * Exposes the build type of the agent
@@ -12,7 +12,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
12
12
  /**
13
13
  * Exposes the version of the agent
14
14
  */
15
- const VERSION = exports.VERSION = "1.251.0";
15
+ const VERSION = exports.VERSION = "1.251.1";
16
16
 
17
17
  /**
18
18
  * Exposes the build type of the agent
@@ -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) {
@@ -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
  });
@@ -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
@@ -92,6 +93,11 @@ class Aggregate extends _aggregateBase.AggregateBase {
92
93
  getPayload: this.prepareHarvest.bind(this),
93
94
  raw: true
94
95
  }, this);
96
+ if (this.recorder?.getEvents().type === 'preloaded') {
97
+ this.prepUtils().then(() => {
98
+ this.scheduler.runHarvest();
99
+ });
100
+ }
95
101
  (0, _registerHandler.registerHandler)('recordReplay', () => {
96
102
  // if it has aborted or BCS returned bad entitlements, do not allow
97
103
  if (this.blocked || !this.entitled) return;
@@ -196,24 +202,29 @@ class Aggregate extends _aggregateBase.AggregateBase {
196
202
  // We only report (harvest) in FULL mode
197
203
  this.scheduler.startTimer(this.harvestTimeSeconds);
198
204
  }
205
+ await this.prepUtils();
206
+ if (!this.recorder.recording) this.recorder.startRecording();
207
+ this.syncWithSessionManager({
208
+ sessionReplayMode: this.mode
209
+ });
210
+ }
211
+ async prepUtils() {
199
212
  try {
200
213
  // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
201
214
  const {
202
215
  gzipSync,
203
216
  strToU8
204
217
  } = await Promise.resolve().then(() => _interopRequireWildcard(require( /* webpackChunkName: "compressor" */'fflate')));
205
- gzipper = gzipSync;
206
- u8 = strToU8;
218
+ this.gzipper = gzipSync;
219
+ this.u8 = strToU8;
207
220
  } catch (err) {
208
221
  // compressor failed to load, but we can still record without compression as a last ditch effort
209
- this.shouldCompress = false;
210
222
  }
211
- if (!this.recorder.recording) this.recorder.startRecording();
212
- this.syncWithSessionManager({
213
- sessionReplayMode: this.mode
214
- });
215
223
  }
216
224
  prepareHarvest() {
225
+ let {
226
+ opts
227
+ } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
217
228
  if (!this.recorder) return;
218
229
  const recorderEvents = this.recorder.getEvents();
219
230
  // get the event type and use that to trigger another harvest if needed
@@ -224,8 +235,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
224
235
  return;
225
236
  }
226
237
  let len = 0;
227
- if (this.shouldCompress) {
228
- payload.body = gzipper(u8("[".concat(payload.body.map(e => e.__serialized).join(','), "]")));
238
+ if (!!this.gzipper && !!this.u8) {
239
+ payload.body = this.gzipper(this.u8("[".concat(payload.body.map(e => e.__serialized).join(','), "]")));
229
240
  len = payload.body.length;
230
241
  this.scheduler.opts.gzip = true;
231
242
  } else {
@@ -251,7 +262,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
251
262
  sessionReplaySentFirstChunk: true
252
263
  });
253
264
  this.recorder.clearBuffer();
254
- if (recorderEvents.type === 'preloaded') this.scheduler.runHarvest();
265
+ if (recorderEvents.type === 'preloaded') this.scheduler.runHarvest(opts);
255
266
  return [payload];
256
267
  }
257
268
  getHarvestContents(recorderEvents) {
@@ -282,7 +293,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
282
293
  const relativeNow = (0, _now.now)();
283
294
  const firstEventTimestamp = events[0]?.timestamp; // from rrweb node
284
295
  const lastEventTimestamp = events[events.length - 1]?.timestamp; // from rrweb node
285
- const firstTimestamp = firstEventTimestamp || recorderEvents.cycleTimestamp;
296
+ const firstTimestamp = firstEventTimestamp || recorderEvents.cycleTimestamp; // from rrweb node || from when the harvest cycle started
286
297
  const lastTimestamp = lastEventTimestamp || agentOffset + relativeNow;
287
298
  return {
288
299
  qs: {
@@ -293,7 +304,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
293
304
  attributes: (0, _encode.obj)({
294
305
  // this section of attributes must be controllable and stay below the query param padding limit -- see QUERY_PARAM_PADDING
295
306
  // if not, data could be lost to truncation at time of sending, potentially breaking parsing / API behavior in NR1
296
- ...(this.shouldCompress && {
307
+ ...(!!this.gzipper && !!this.u8 && {
297
308
  content_encoding: 'gzip'
298
309
  }),
299
310
  'replay.firstTimestamp': firstTimestamp,
@@ -194,7 +194,7 @@ class Recorder {
194
194
  * https://staging.onenr.io/037jbJWxbjy
195
195
  * */
196
196
  estimateCompression(data) {
197
- if (this.shouldCompress) return data * _constants.AVG_COMPRESSION;
197
+ if (!!this.parent.gzipper && !!this.parent.u8) return data * _constants.AVG_COMPRESSION;
198
198
  return data;
199
199
  }
200
200
  }
@@ -60,33 +60,38 @@ 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 = true;
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 = true;
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
- });
94
+ // failed to fetch
90
95
  this.failedToFix = true;
91
96
  }
92
97
  }
@@ -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.251.1";
10
10
 
11
11
  /**
12
12
  * Exposes the build type of the agent
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Exposes the version of the agent
8
8
  */
9
- export const VERSION = "1.251.0";
9
+ export const VERSION = "1.251.1";
10
10
 
11
11
  /**
12
12
  * Exposes the build type of the agent
@@ -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) {
@@ -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
  });
@@ -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
@@ -87,6 +88,11 @@ export class Aggregate extends AggregateBase {
87
88
  getPayload: this.prepareHarvest.bind(this),
88
89
  raw: true
89
90
  }, this);
91
+ if (this.recorder?.getEvents().type === 'preloaded') {
92
+ this.prepUtils().then(() => {
93
+ this.scheduler.runHarvest();
94
+ });
95
+ }
90
96
  registerHandler('recordReplay', () => {
91
97
  // if it has aborted or BCS returned bad entitlements, do not allow
92
98
  if (this.blocked || !this.entitled) return;
@@ -191,24 +197,29 @@ export class Aggregate extends AggregateBase {
191
197
  // We only report (harvest) in FULL mode
192
198
  this.scheduler.startTimer(this.harvestTimeSeconds);
193
199
  }
200
+ await this.prepUtils();
201
+ if (!this.recorder.recording) this.recorder.startRecording();
202
+ this.syncWithSessionManager({
203
+ sessionReplayMode: this.mode
204
+ });
205
+ }
206
+ async prepUtils() {
194
207
  try {
195
208
  // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
196
209
  const {
197
210
  gzipSync,
198
211
  strToU8
199
212
  } = await import( /* webpackChunkName: "compressor" */'fflate');
200
- gzipper = gzipSync;
201
- u8 = strToU8;
213
+ this.gzipper = gzipSync;
214
+ this.u8 = strToU8;
202
215
  } catch (err) {
203
216
  // compressor failed to load, but we can still record without compression as a last ditch effort
204
- this.shouldCompress = false;
205
217
  }
206
- if (!this.recorder.recording) this.recorder.startRecording();
207
- this.syncWithSessionManager({
208
- sessionReplayMode: this.mode
209
- });
210
218
  }
211
219
  prepareHarvest() {
220
+ let {
221
+ opts
222
+ } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
212
223
  if (!this.recorder) return;
213
224
  const recorderEvents = this.recorder.getEvents();
214
225
  // get the event type and use that to trigger another harvest if needed
@@ -219,8 +230,8 @@ export class Aggregate extends AggregateBase {
219
230
  return;
220
231
  }
221
232
  let len = 0;
222
- if (this.shouldCompress) {
223
- payload.body = gzipper(u8("[".concat(payload.body.map(e => e.__serialized).join(','), "]")));
233
+ if (!!this.gzipper && !!this.u8) {
234
+ payload.body = this.gzipper(this.u8("[".concat(payload.body.map(e => e.__serialized).join(','), "]")));
224
235
  len = payload.body.length;
225
236
  this.scheduler.opts.gzip = true;
226
237
  } else {
@@ -246,7 +257,7 @@ export class Aggregate extends AggregateBase {
246
257
  sessionReplaySentFirstChunk: true
247
258
  });
248
259
  this.recorder.clearBuffer();
249
- if (recorderEvents.type === 'preloaded') this.scheduler.runHarvest();
260
+ if (recorderEvents.type === 'preloaded') this.scheduler.runHarvest(opts);
250
261
  return [payload];
251
262
  }
252
263
  getHarvestContents(recorderEvents) {
@@ -277,7 +288,7 @@ export class Aggregate extends AggregateBase {
277
288
  const relativeNow = now();
278
289
  const firstEventTimestamp = events[0]?.timestamp; // from rrweb node
279
290
  const lastEventTimestamp = events[events.length - 1]?.timestamp; // from rrweb node
280
- const firstTimestamp = firstEventTimestamp || recorderEvents.cycleTimestamp;
291
+ const firstTimestamp = firstEventTimestamp || recorderEvents.cycleTimestamp; // from rrweb node || from when the harvest cycle started
281
292
  const lastTimestamp = lastEventTimestamp || agentOffset + relativeNow;
282
293
  return {
283
294
  qs: {
@@ -288,7 +299,7 @@ export class Aggregate extends AggregateBase {
288
299
  attributes: encodeObj({
289
300
  // this section of attributes must be controllable and stay below the query param padding limit -- see QUERY_PARAM_PADDING
290
301
  // if not, data could be lost to truncation at time of sending, potentially breaking parsing / API behavior in NR1
291
- ...(this.shouldCompress && {
302
+ ...(!!this.gzipper && !!this.u8 && {
292
303
  content_encoding: 'gzip'
293
304
  }),
294
305
  'replay.firstTimestamp': firstTimestamp,
@@ -188,7 +188,7 @@ export class Recorder {
188
188
  * https://staging.onenr.io/037jbJWxbjy
189
189
  * */
190
190
  estimateCompression(data) {
191
- if (this.shouldCompress) return data * AVG_COMPRESSION;
191
+ if (!!this.parent.gzipper && !!this.parent.u8) return data * AVG_COMPRESSION;
192
192
  return data;
193
193
  }
194
194
  }
@@ -54,33 +54,38 @@ 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 = true;
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 = true;
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
- });
88
+ // failed to fetch
84
89
  this.failedToFix = true;
85
90
  }
86
91
  }
@@ -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,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 +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,qBAAmB;IAEnB;;;OAGG;IACH,mBAmBC;IAED;;;OAGG;IACH,oBAMC;;CAsCF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/browser-agent",
3
- "version": "1.251.0",
3
+ "version": "1.251.1",
4
4
  "private": false,
5
5
  "author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
6
6
  "description": "New Relic Browser Agent",
@@ -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) {
@@ -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
 
@@ -28,8 +28,6 @@ import { MODE, SESSION_EVENTS, SESSION_EVENT_TYPES } from '../../../common/sessi
28
28
  import { stringify } from '../../../common/util/stringify'
29
29
  import { stylesheetEvaluator } from '../shared/stylesheet-evaluator'
30
30
 
31
- let gzipper, u8
32
-
33
31
  export class Aggregate extends AggregateBase {
34
32
  static featureName = FEATURE_NAME
35
33
  // pass the recorder into the aggregator
@@ -41,8 +39,10 @@ export class Aggregate extends AggregateBase {
41
39
  this.initialized = false
42
40
  /** Set once the feature has been "aborted" to prevent other side-effects from continuing */
43
41
  this.blocked = false
44
- /** can shut off efforts to compress the data */
45
- this.shouldCompress = true
42
+ /** populated with the gzipper lib async */
43
+ this.gzipper = undefined
44
+ /** populated with the u8 string lib async */
45
+ this.u8 = undefined
46
46
  /** the mode to start in. Defaults to off */
47
47
  const { session } = getRuntime(this.agentIdentifier)
48
48
  this.mode = session.state.sessionReplayMode || MODE.OFF
@@ -91,6 +91,12 @@ export class Aggregate extends AggregateBase {
91
91
  raw: true
92
92
  }, this)
93
93
 
94
+ if (this.recorder?.getEvents().type === 'preloaded') {
95
+ this.prepUtils().then(() => {
96
+ this.scheduler.runHarvest()
97
+ })
98
+ }
99
+
94
100
  registerHandler('recordReplay', () => {
95
101
  // if it has aborted or BCS returned bad entitlements, do not allow
96
102
  if (this.blocked || !this.entitled) return
@@ -197,21 +203,25 @@ export class Aggregate extends AggregateBase {
197
203
  this.scheduler.startTimer(this.harvestTimeSeconds)
198
204
  }
199
205
 
206
+ await this.prepUtils()
207
+
208
+ if (!this.recorder.recording) this.recorder.startRecording()
209
+
210
+ this.syncWithSessionManager({ sessionReplayMode: this.mode })
211
+ }
212
+
213
+ async prepUtils () {
200
214
  try {
201
215
  // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
202
216
  const { gzipSync, strToU8 } = await import(/* webpackChunkName: "compressor" */'fflate')
203
- gzipper = gzipSync
204
- u8 = strToU8
217
+ this.gzipper = gzipSync
218
+ this.u8 = strToU8
205
219
  } catch (err) {
206
220
  // compressor failed to load, but we can still record without compression as a last ditch effort
207
- this.shouldCompress = false
208
221
  }
209
- if (!this.recorder.recording) this.recorder.startRecording()
210
-
211
- this.syncWithSessionManager({ sessionReplayMode: this.mode })
212
222
  }
213
223
 
214
- prepareHarvest () {
224
+ prepareHarvest ({ opts } = {}) {
215
225
  if (!this.recorder) return
216
226
  const recorderEvents = this.recorder.getEvents()
217
227
  // get the event type and use that to trigger another harvest if needed
@@ -224,8 +234,8 @@ export class Aggregate extends AggregateBase {
224
234
  }
225
235
 
226
236
  let len = 0
227
- if (this.shouldCompress) {
228
- payload.body = gzipper(u8(`[${payload.body.map(e => e.__serialized).join(',')}]`))
237
+ if (!!this.gzipper && !!this.u8) {
238
+ payload.body = this.gzipper(this.u8(`[${payload.body.map(e => e.__serialized).join(',')}]`))
229
239
  len = payload.body.length
230
240
  this.scheduler.opts.gzip = true
231
241
  } else {
@@ -242,7 +252,7 @@ export class Aggregate extends AggregateBase {
242
252
  const { session } = getRuntime(this.agentIdentifier)
243
253
  if (!session.state.sessionReplaySentFirstChunk) this.syncWithSessionManager({ sessionReplaySentFirstChunk: true })
244
254
  this.recorder.clearBuffer()
245
- if (recorderEvents.type === 'preloaded') this.scheduler.runHarvest()
255
+ if (recorderEvents.type === 'preloaded') this.scheduler.runHarvest(opts)
246
256
  return [payload]
247
257
  }
248
258
 
@@ -276,7 +286,7 @@ export class Aggregate extends AggregateBase {
276
286
 
277
287
  const firstEventTimestamp = events[0]?.timestamp // from rrweb node
278
288
  const lastEventTimestamp = events[events.length - 1]?.timestamp // from rrweb node
279
- const firstTimestamp = firstEventTimestamp || recorderEvents.cycleTimestamp
289
+ const firstTimestamp = firstEventTimestamp || recorderEvents.cycleTimestamp // from rrweb node || from when the harvest cycle started
280
290
  const lastTimestamp = lastEventTimestamp || agentOffset + relativeNow
281
291
 
282
292
  return {
@@ -288,7 +298,7 @@ export class Aggregate extends AggregateBase {
288
298
  attributes: encodeObj({
289
299
  // this section of attributes must be controllable and stay below the query param padding limit -- see QUERY_PARAM_PADDING
290
300
  // if not, data could be lost to truncation at time of sending, potentially breaking parsing / API behavior in NR1
291
- ...(this.shouldCompress && { content_encoding: 'gzip' }),
301
+ ...(!!this.gzipper && !!this.u8 && { content_encoding: 'gzip' }),
292
302
  'replay.firstTimestamp': firstTimestamp,
293
303
  'replay.firstTimestampOffset': firstTimestamp - agentOffset,
294
304
  'replay.lastTimestamp': lastTimestamp,
@@ -184,7 +184,7 @@ export class Recorder {
184
184
  * https://staging.onenr.io/037jbJWxbjy
185
185
  * */
186
186
  estimateCompression (data) {
187
- if (this.shouldCompress) return data * AVG_COMPRESSION
187
+ if (!!this.parent.gzipper && !!this.parent.u8) return data * AVG_COMPRESSION
188
188
  return data
189
189
  }
190
190
  }
@@ -55,27 +55,32 @@ class StylesheetEvaluator {
55
55
  * @returns {Promise}
56
56
  */
57
57
  async #fetchAndOverride (target, href) {
58
- const stylesheetContents = await originals.FETCH.bind(window)(href)
59
- if (!stylesheetContents.ok) {
60
- this.failedToFix = true
61
- return
62
- }
63
- const stylesheetText = await stylesheetContents.text()
64
58
  try {
65
- const cssSheet = new CSSStyleSheet()
66
- await cssSheet.replace(stylesheetText)
67
- Object.defineProperty(target, 'cssRules', {
68
- get () { return cssSheet.cssRules }
69
- })
70
- Object.defineProperty(target, 'rules', {
71
- get () { return cssSheet.rules }
72
- })
73
- } catch (err) {
59
+ const stylesheetContents = await originals.FETCH.bind(window)(href)
60
+ if (!stylesheetContents.ok) {
61
+ this.failedToFix = true
62
+ return
63
+ }
64
+ const stylesheetText = await stylesheetContents.text()
65
+ try {
66
+ const cssSheet = new CSSStyleSheet()
67
+ await cssSheet.replace(stylesheetText)
68
+ Object.defineProperty(target, 'cssRules', {
69
+ get () { return cssSheet.cssRules }
70
+ })
71
+ Object.defineProperty(target, 'rules', {
72
+ get () { return cssSheet.rules }
73
+ })
74
+ } catch (err) {
74
75
  // cant make new dynamic stylesheets, browser likely doesn't support `.replace()`...
75
76
  // this is appended in prep of forking rrweb
76
- Object.defineProperty(target, 'cssText', {
77
- get () { return stylesheetText }
78
- })
77
+ Object.defineProperty(target, 'cssText', {
78
+ get () { return stylesheetText }
79
+ })
80
+ this.failedToFix = true
81
+ }
82
+ } catch (err) {
83
+ // failed to fetch
79
84
  this.failedToFix = true
80
85
  }
81
86
  }