@newrelic/browser-agent 1.265.1 → 1.267.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 (68) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/cjs/common/config/init.js +1 -4
  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/timing/time-keeper.js +5 -12
  6. package/dist/cjs/common/util/stringify.js +2 -0
  7. package/dist/cjs/common/vitals/constants.js +0 -1
  8. package/dist/cjs/common/vitals/interaction-to-next-paint.js +11 -3
  9. package/dist/cjs/common/vitals/largest-contentful-paint.js +3 -1
  10. package/dist/cjs/features/metrics/aggregate/index.js +1 -3
  11. package/dist/cjs/features/page_view_event/aggregate/index.js +9 -9
  12. package/dist/cjs/features/page_view_timing/aggregate/index.js +0 -2
  13. package/dist/cjs/features/session_replay/aggregate/index.js +1 -3
  14. package/dist/cjs/features/session_replay/shared/recorder.js +22 -14
  15. package/dist/cjs/features/session_replay/shared/stylesheet-evaluator.js +5 -4
  16. package/dist/cjs/features/utils/event-buffer.js +3 -2
  17. package/dist/cjs/loaders/browser-agent.js +2 -1
  18. package/dist/esm/common/config/init.js +1 -4
  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/timing/time-keeper.js +5 -11
  22. package/dist/esm/common/util/stringify.js +2 -0
  23. package/dist/esm/common/vitals/constants.js +0 -1
  24. package/dist/esm/common/vitals/interaction-to-next-paint.js +11 -3
  25. package/dist/esm/common/vitals/largest-contentful-paint.js +3 -1
  26. package/dist/esm/features/metrics/aggregate/index.js +1 -3
  27. package/dist/esm/features/page_view_event/aggregate/index.js +9 -9
  28. package/dist/esm/features/page_view_timing/aggregate/index.js +0 -2
  29. package/dist/esm/features/session_replay/aggregate/index.js +1 -3
  30. package/dist/esm/features/session_replay/shared/recorder.js +22 -14
  31. package/dist/esm/features/session_replay/shared/stylesheet-evaluator.js +5 -4
  32. package/dist/esm/features/utils/event-buffer.js +3 -2
  33. package/dist/esm/loaders/browser-agent.js +2 -1
  34. package/dist/types/common/config/init.d.ts.map +1 -1
  35. package/dist/types/common/timing/time-keeper.d.ts +2 -1
  36. package/dist/types/common/timing/time-keeper.d.ts.map +1 -1
  37. package/dist/types/common/util/stringify.d.ts.map +1 -1
  38. package/dist/types/common/vitals/constants.d.ts +0 -1
  39. package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
  40. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  41. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  42. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  43. package/dist/types/features/session_replay/shared/recorder.d.ts +2 -2
  44. package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
  45. package/dist/types/features/session_replay/shared/stylesheet-evaluator.d.ts.map +1 -1
  46. package/dist/types/features/utils/event-buffer.d.ts +2 -1
  47. package/dist/types/features/utils/event-buffer.d.ts.map +1 -1
  48. package/dist/types/loaders/browser-agent.d.ts.map +1 -1
  49. package/package.json +5 -2
  50. package/src/common/config/init.js +2 -3
  51. package/src/common/timing/time-keeper.js +5 -12
  52. package/src/common/util/stringify.js +2 -0
  53. package/src/common/vitals/constants.js +0 -1
  54. package/src/common/vitals/interaction-to-next-paint.js +9 -3
  55. package/src/common/vitals/largest-contentful-paint.js +2 -1
  56. package/src/features/metrics/aggregate/index.js +1 -2
  57. package/src/features/page_view_event/aggregate/index.js +9 -11
  58. package/src/features/page_view_timing/aggregate/index.js +0 -3
  59. package/src/features/session_replay/aggregate/index.js +2 -3
  60. package/src/features/session_replay/shared/recorder.js +23 -14
  61. package/src/features/session_replay/shared/stylesheet-evaluator.js +5 -4
  62. package/src/features/utils/event-buffer.js +3 -2
  63. package/src/loaders/browser-agent.js +2 -0
  64. package/dist/cjs/common/vitals/long-task.js +0 -61
  65. package/dist/esm/common/vitals/long-task.js +0 -55
  66. package/dist/types/common/vitals/long-task.d.ts +0 -3
  67. package/dist/types/common/vitals/long-task.d.ts.map +0 -1
  68. package/src/common/vitals/long-task.js +0 -51
@@ -103,7 +103,6 @@ export class Aggregate extends AggregateBase {
103
103
  block_selector,
104
104
  mask_text_selector,
105
105
  mask_all_inputs,
106
- inline_stylesheet,
107
106
  inline_images,
108
107
  collect_fonts
109
108
  } = getConfigurationValue(this.agentIdentifier, 'session_replay');
@@ -130,7 +129,6 @@ export class Aggregate extends AggregateBase {
130
129
  /** 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 */
131
130
  if (!autoStart) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/AutoStart/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
132
131
  if (collect_fonts === true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/CollectFonts/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
133
- if (inline_stylesheet !== true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineStylesheet/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
134
132
  if (inline_images === true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineImages/Modifed'], undefined, FEATURE_NAMES.metrics, this.ee);
135
133
  if (mask_all_inputs !== true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/MaskAllInputs/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
136
134
  if (block_selector !== '[data-nr-block]') handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/BlockSelector/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
@@ -248,7 +246,7 @@ export class Aggregate extends AggregateBase {
248
246
  prepareHarvest({
249
247
  opts
250
248
  } = {}) {
251
- if (!this.recorder || !this.timeKeeper?.ready) return;
249
+ if (!this.recorder || !this.timeKeeper?.ready || !this.recorder.hasSeenSnapshot) return;
252
250
  const recorderEvents = this.recorder.getEvents();
253
251
  // get the event type and use that to trigger another harvest if needed
254
252
  if (!recorderEvents.events.length || this.mode !== MODE.FULL || this.blocked) return;
@@ -27,14 +27,14 @@ export class Recorder {
27
27
  this.recording = false;
28
28
  /** The pointer to the current bucket holding rrweb events */
29
29
  this.currentBufferTarget = this.#events;
30
+ /** Only set to true once a snapshot node has been processed. Used to block preload harvests from sending before we know we have a snapshot */
31
+ this.hasSeenSnapshot = false;
30
32
  /** Hold on to the last meta node, so that it can be re-inserted if the meta and snapshot nodes are broken up due to harvesting */
31
33
  this.lastMeta = false;
32
34
  /** The parent class that instantiated the recorder */
33
35
  this.parent = parent;
34
- /** Config to inform to inline stylesheet contents (true default) */
35
- this.shouldInlineStylesheets = getConfigurationValue(this.parent.agentIdentifier, 'session_replay.inline_stylesheet');
36
36
  /** A flag that can be set to false by failing conversions to stop the fetching process */
37
- this.shouldFix = this.shouldInlineStylesheets && getConfigurationValue(this.parent.agentIdentifier, 'session_replay.fix_stylesheets');
37
+ this.shouldFix = getConfigurationValue(this.parent.agentIdentifier, 'session_replay.fix_stylesheets');
38
38
  /** The method to stop recording. This defaults to a noop, but is overwritten once the recording library is imported and initialized */
39
39
  this.stopRecording = () => {/* no-op until set by rrweb initializer */};
40
40
  }
@@ -76,13 +76,16 @@ export class Recorder {
76
76
  mask_input_options,
77
77
  mask_text_selector,
78
78
  mask_all_inputs,
79
- inline_stylesheet,
80
79
  inline_images,
81
80
  collect_fonts
82
81
  } = getConfigurationValue(this.parent.agentIdentifier, 'session_replay');
83
82
  const customMasker = (text, element) => {
84
- if (element?.type?.toLowerCase() !== 'password' && (element?.dataset.nrUnmask !== undefined || element?.classList.contains('nr-unmask'))) return text;
85
- return '*'.repeat(text.length);
83
+ try {
84
+ if (typeof element?.type === 'string' && element.type.toLowerCase() !== 'password' && (element?.dataset?.nrUnmask !== undefined || element?.classList?.contains('nr-unmask'))) return text;
85
+ } catch (err) {
86
+ // likely an element was passed to this handler that was invalid and was missing attributes or methods
87
+ }
88
+ return '*'.repeat(text?.length || 0);
86
89
  };
87
90
  // set up rrweb configurations for maximum privacy --
88
91
  // https://newrelic.atlassian.net/wiki/spaces/O11Y/pages/2792293280/2023+02+28+Browser+-+Session+Replay#Configuration-options
@@ -97,7 +100,7 @@ export class Recorder {
97
100
  maskTextFn: customMasker,
98
101
  maskAllInputs: mask_all_inputs,
99
102
  maskInputFn: customMasker,
100
- inlineStylesheet: inline_stylesheet,
103
+ inlineStylesheet: true,
101
104
  inlineImages: inline_images,
102
105
  collectFonts: collect_fonts,
103
106
  checkoutEveryNms: CHECKOUT_MS[this.parent.mode],
@@ -124,13 +127,17 @@ export class Recorder {
124
127
  * @param {*} isCheckout - Flag indicating if the payload was triggered as a checkout
125
128
  */
126
129
  audit(event, isCheckout) {
127
- /** only run the audit if inline_stylesheets is configured as on (default behavior) */
128
- if (this.shouldInlineStylesheets === false || !this.shouldFix) {
129
- this.currentBufferTarget.inlinedAllStylesheets = false;
130
- return this.store(event, isCheckout);
131
- }
132
130
  /** An count of stylesheet objects that were blocked from accessing contents via JS */
133
131
  const incompletes = stylesheetEvaluator.evaluate();
132
+ const missingInlineSMTag = 'SessionReplay/Payload/Missing-Inline-Css/';
133
+ /** only run the full fixing behavior (more costly) if fix_stylesheets is configured as on (default behavior) */
134
+ if (!this.shouldFix) {
135
+ if (incompletes > 0) {
136
+ this.currentBufferTarget.inlinedAllStylesheets = false;
137
+ handle(SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Skipped', incompletes], undefined, FEATURE_NAMES.metrics, this.parent.ee);
138
+ }
139
+ return this.store(event, isCheckout);
140
+ }
134
141
  /** 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) */
135
142
  if (!incompletes && this.#fixing && event.type === RRWEB_EVENT_TYPES.Meta) this.#fixing = false;
136
143
  if (incompletes > 0) {
@@ -140,8 +147,8 @@ export class Recorder {
140
147
  this.currentBufferTarget.inlinedAllStylesheets = false;
141
148
  this.shouldFix = false;
142
149
  }
143
- handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css/Failed', failedToFix], undefined, FEATURE_NAMES.metrics, this.parent.ee);
144
- handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css/Fixed', incompletes - failedToFix], undefined, FEATURE_NAMES.metrics, this.parent.ee);
150
+ handle(SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Failed', failedToFix], undefined, FEATURE_NAMES.metrics, this.parent.ee);
151
+ handle(SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Fixed', incompletes - failedToFix], undefined, FEATURE_NAMES.metrics, this.parent.ee);
145
152
  this.takeFullSnapshot();
146
153
  });
147
154
  /** Only start ignoring data if got a faulty snapshot */
@@ -184,6 +191,7 @@ export class Recorder {
184
191
  // snapshot event
185
192
  if (event.type === RRWEB_EVENT_TYPES.FullSnapshot) {
186
193
  this.currentBufferTarget.hasSnapshot = true;
194
+ this.hasSeenSnapshot = true;
187
195
  }
188
196
  this.currentBufferTarget.add(event);
189
197
 
@@ -2,7 +2,7 @@ import { gosNREUMOriginals } from '../../../common/window/nreum';
2
2
  import { isBrowserScope } from '../../../common/constants/runtime';
3
3
  class StylesheetEvaluator {
4
4
  #evaluated = new WeakSet();
5
- #fetchProms = [];
5
+ #brokenSheets = [];
6
6
  /**
7
7
  * Flipped to true if stylesheets that cannot be natively inlined are detected by the stylesheetEvaluator class
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
@@ -16,6 +16,7 @@ class StylesheetEvaluator {
16
16
  */
17
17
  evaluate() {
18
18
  let incompletes = 0;
19
+ this.#brokenSheets = [];
19
20
  if (isBrowserScope) {
20
21
  for (let i = 0; i < Object.keys(document.styleSheets).length; i++) {
21
22
  if (!this.#evaluated.has(document.styleSheets[i])) {
@@ -26,7 +27,7 @@ class StylesheetEvaluator {
26
27
  } catch (err) {
27
28
  if (!document.styleSheets[i].href) return;
28
29
  incompletes++;
29
- this.#fetchProms.push(this.#fetchAndOverride(document.styleSheets[i]));
30
+ this.#brokenSheets.push(document.styleSheets[i]);
30
31
  }
31
32
  }
32
33
  }
@@ -40,8 +41,8 @@ class StylesheetEvaluator {
40
41
  * @returns {Promise}
41
42
  */
42
43
  async fix() {
43
- await Promise.all(this.#fetchProms);
44
- this.#fetchProms = [];
44
+ await Promise.all(this.#brokenSheets.map(sheet => this.#fetchAndOverride(sheet)));
45
+ this.#brokenSheets = [];
45
46
  const failedToFix = this.failedToFix;
46
47
  this.failedToFix = 0;
47
48
  return failedToFix;
@@ -45,7 +45,7 @@ export class EventBuffer {
45
45
  * held is another event buffer
46
46
  */
47
47
  get held() {
48
- this.#held ??= new EventBuffer(this.maxPayloadSize);
48
+ if (!this.#held) this.#held = new EventBuffer(this.maxPayloadSize);
49
49
  return this.#held;
50
50
  }
51
51
 
@@ -58,7 +58,8 @@ export class EventBuffer {
58
58
  }
59
59
 
60
60
  /**
61
- * Adds an event object to the buffer while tallying size
61
+ * Adds an event object to the buffer while tallying size. Only adds the event if it is valid
62
+ * and would not make the event buffer exceed the maxPayloadSize.
62
63
  * @param {Object} event the event object to add to the buffer
63
64
  * @returns {EventBuffer} returns the event buffer for chaining
64
65
  */
@@ -9,6 +9,7 @@ import { Instrument as InstrumentSpa } from '../features/spa/instrument';
9
9
  import { Instrument as InstrumentSessionReplay } from '../features/session_replay/instrument';
10
10
  import { Instrument as InstrumentGenericEvents } from '../features/generic_events/instrument';
11
11
  import { Instrument as InstrumentLogs } from '../features/logging/instrument';
12
+ import { Instrument as InstrumentSoftNav } from '../features/soft_navigations/instrument';
12
13
 
13
14
  /**
14
15
  * An agent class with all feature modules available. Features may be disabled and enabled via runtime configuration.
@@ -18,7 +19,7 @@ export class BrowserAgent extends Agent {
18
19
  constructor(args) {
19
20
  super({
20
21
  ...args,
21
- features: [InstrumentXhr, InstrumentPageViewEvent, InstrumentPageViewTiming, InstrumentSessionTrace, InstrumentMetrics, InstrumentErrors, InstrumentSpa, InstrumentSessionReplay, InstrumentGenericEvents, InstrumentLogs],
22
+ features: [InstrumentXhr, InstrumentPageViewEvent, InstrumentPageViewTiming, InstrumentSessionTrace, InstrumentMetrics, InstrumentErrors, InstrumentSpa, InstrumentSoftNav, InstrumentSessionReplay, InstrumentGenericEvents, InstrumentLogs],
22
23
  loaderType: 'browser-agent'
23
24
  });
24
25
  }
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../src/common/config/init.js"],"names":[],"mappings":"AAkHA,+CAIC;AAED,0DAKC;AAED,+DAYC"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../src/common/config/init.js"],"names":[],"mappings":"AAiHA,+CAIC;AAED,0DAKC;AAED,+DAYC"}
@@ -13,8 +13,9 @@ export class TimeKeeper {
13
13
  * @param rumRequest {XMLHttpRequest} The xhr for the rum request
14
14
  * @param startTime {number} The start time of the RUM request
15
15
  * @param endTime {number} The end time of the RUM request
16
+ * @param nrServerTime {number} the unix number value of the NR server time in MS, returned in the RUM request body
16
17
  */
17
- processRumRequest(rumRequest: XMLHttpRequest, startTime: number, endTime: number): void;
18
+ processRumRequest(rumRequest: XMLHttpRequest, startTime: number, endTime: number, nrServerTime: number): void;
18
19
  /**
19
20
  * Converts a page origin relative time to an absolute timestamp
20
21
  * using the user's local clock.
@@ -1 +1 @@
1
- {"version":3,"file":"time-keeper.d.ts","sourceRoot":"","sources":["../../../../src/common/timing/time-keeper.js"],"names":[],"mappings":"AAKA;;;;GAIG;AACH;IA2BE,kCAGC;IAED,qBAEC;IAED,kCAEC;IAED,4BAEC;IAED;;;;;OAKG;IACH,8BAJsB,cAAc,aACf,MAAM,WACR,MAAM,QA2BxB;IAED;;;;;OAKG;IACH,uCAHwB,MAAM,GACjB,MAAM,CAIlB;IAED;;;;;OAKG;IACH,0CAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,oCAHqB,MAAM,GACf,MAAM,CAIjB;IAED,+FAA+F;IAC/F,0BASC;;CACF"}
1
+ {"version":3,"file":"time-keeper.d.ts","sourceRoot":"","sources":["../../../../src/common/timing/time-keeper.js"],"names":[],"mappings":"AAGA;;;;GAIG;AACH;IA2BE,kCAGC;IAED,qBAEC;IAED,kCAEC;IAED,4BAEC;IAED;;;;;;OAMG;IACH,8BALsB,cAAc,aACf,MAAM,WACR,MAAM,gBACD,MAAM,QAqB7B;IAED;;;;;OAKG;IACH,uCAHwB,MAAM,GACjB,MAAM,CAIlB;IAED;;;;;OAKG;IACH,0CAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,oCAHqB,MAAM,GACf,MAAM,CAIjB;IAED,+FAA+F;IAC/F,0BASC;;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"stringify.d.ts","sourceRoot":"","sources":["../../../../src/common/util/stringify.js"],"names":[],"mappings":"AAyBA;;;;;GAKG;AACH,+BAHW,GAAC,GACC,MAAM,CAYlB"}
1
+ {"version":3,"file":"stringify.d.ts","sourceRoot":"","sources":["../../../../src/common/util/stringify.js"],"names":[],"mappings":"AAyBA;;;;;GAKG;AACH,+BAHW,GAAC,GACC,MAAM,CAclB"}
@@ -5,7 +5,6 @@ export namespace VITAL_NAMES {
5
5
  let LARGEST_CONTENTFUL_PAINT: string;
6
6
  let CUMULATIVE_LAYOUT_SHIFT: string;
7
7
  let INTERACTION_TO_NEXT_PAINT: string;
8
- let LONG_TASK: string;
9
8
  let TIME_TO_FIRST_BYTE: string;
10
9
  }
11
10
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/metrics/aggregate/index.js"],"names":[],"mappings":"AAeA;IACE,2BAAiC;IACjC,mDAsBC;IAED,wDAKC;IAED,iDAKC;IAED,qBAsEC;IAED,0BAOC;IAED,eAkCC;IA/BG,mCAAyB;CAgC9B;8BAjK6B,4BAA4B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/metrics/aggregate/index.js"],"names":[],"mappings":"AAeA;IACE,2BAAiC;IACjC,mDAsBC;IAED,wDAKC;IAED,iDAKC;IAED,qBAqEC;IAED,0BAOC;IAED,eAkCC;IA/BG,mCAAyB;CAgC9B;8BAhK6B,4BAA4B"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_event/aggregate/index.js"],"names":[],"mappings":"AAkBA;IACE,2BAA2C;IAC3C,mDA0BC;IAvBC,wBAAwB;IACxB,8BAA8B;IAC9B,8BAA8B;IAC9B,uBAAsD;IAsBxD,gBAqGC;CACF;8BA5I6B,4BAA4B;2BAK/B,oCAAoC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_event/aggregate/index.js"],"names":[],"mappings":"AAkBA;IACE,2BAA2C;IAC3C,mDA0BC;IAvBC,wBAAwB;IACxB,8BAA8B;IAC9B,8BAA8B;IAC9B,uBAAsD;IAsBxD,gBAmGC;CACF;8BA1I6B,4BAA4B;2BAK/B,oCAAoC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_timing/aggregate/index.js"],"names":[],"mappings":"AA0BA;IACE,2BAAiC;IAMjC,mDAyCC;IAtCC,qBAAgC;IAChC,4BAA+B;IAuCjC;;;OAGG;IACH,6BAFW,MAAM,QAOhB;IAED;;OAEG;IACH,uCAUC;IAED,mDAuBC;IAED,qCAGC;IAED,gDAWC;IAGD;;;;kBAUC;IAGD,8BAuBC;;CACF;8BA1K6B,4BAA4B;4BAW9B,0BAA0B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_timing/aggregate/index.js"],"names":[],"mappings":"AAyBA;IACE,2BAAiC;IAMjC,mDAuCC;IApCC,qBAAgC;IAChC,4BAA+B;IAqCjC;;;OAGG;IACH,6BAFW,MAAM,QAOhB;IAED;;OAEG;IACH,uCAUC;IAED,mDAuBC;IAED,qCAGC;IAED,gDAWC;IAGD;;;;kBAUC;IAGD,8BAuBC;;CACF;8BAvK6B,4BAA4B;4BAU9B,0BAA0B"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"AA+BA;IACE,2BAAiC;IAIjC,8DAuGC;IA1GD,aAAe;IAKb,8GAA8G;IAC9G,wBAAgH;IAChH,iFAAiF;IACjF,qBAAwB;IAGxB,2CAA2C;IAC3C,sDAAwB;IACxB,6CAA6C;IAC7C,gDAAmB;IAEnB,0BAA0B;IAC1B,kBAAqB;IACrB,6CAA6C;IAC7C,gBAA2B;IAE3B,cAA8B;IAC9B,kBAA+C;IAoC/C,4BAKQ;IA6CV,0BAMC;IAED,qBAWC;IAED;;;;;;;OAOG;IACH,gDAHW,OAAO,GACL,IAAI,CA8DhB;IAED,2BASC;IAED;;;;;;;;;;;;oBAiDC;IAED,sCAIC;IAED;;;;;;;;;;MA2EC;IAED,qCAOC;IAED;;;;OAIG;IACH,mCAKC;IAED,yDAAyD;IACzD,+CAUC;IAED,yCAGC;CACF;8BAjZ6B,4BAA4B;iCALzB,2CAA2C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"AA+BA;IACE,2BAAiC;IAIjC,8DAsGC;IAzGD,aAAe;IAKb,8GAA8G;IAC9G,wBAAgH;IAChH,iFAAiF;IACjF,qBAAwB;IAGxB,2CAA2C;IAC3C,sDAAwB;IACxB,6CAA6C;IAC7C,gDAAmB;IAEnB,0BAA0B;IAC1B,kBAAqB;IACrB,6CAA6C;IAC7C,gBAA2B;IAE3B,cAA8B;IAC9B,kBAA+C;IAoC/C,4BAKQ;IA4CV,0BAMC;IAED,qBAWC;IAED;;;;;;;OAOG;IACH,gDAHW,OAAO,GACL,IAAI,CA8DhB;IAED,2BASC;IAED;;;;;;;;;;;;oBAiDC;IAED,sCAIC;IAED;;;;;;;;;;MA2EC;IAED,qCAOC;IAED;;;;OAIG;IACH,mCAKC;IAED,yDAAyD;IACzD,+CAUC;IAED,yCAGC;CACF;8BAhZ6B,4BAA4B;iCALzB,2CAA2C"}
@@ -4,12 +4,12 @@ export class Recorder {
4
4
  recording: boolean;
5
5
  /** The pointer to the current bucket holding rrweb events */
6
6
  currentBufferTarget: RecorderEvents;
7
+ /** Only set to true once a snapshot node has been processed. Used to block preload harvests from sending before we know we have a snapshot */
8
+ hasSeenSnapshot: boolean;
7
9
  /** Hold on to the last meta node, so that it can be re-inserted if the meta and snapshot nodes are broken up due to harvesting */
8
10
  lastMeta: boolean;
9
11
  /** The parent class that instantiated the recorder */
10
12
  parent: any;
11
- /** Config to inform to inline stylesheet contents (true default) */
12
- shouldInlineStylesheets: any;
13
13
  /** A flag that can be set to false by failing conversions to stop the fetching process */
14
14
  shouldFix: any;
15
15
  /** The method to stop recording. This defaults to a noop, but is overwritten once the recording library is imported and initialized */
@@ -1 +1 @@
1
- {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/recorder.js"],"names":[],"mappings":"AAaA;IAUE,yBAkBC;IAdC,iEAAiE;IACjE,mBAAsB;IACtB,6DAA6D;IAC7D,oCAAuC;IACvC,kIAAkI;IAClI,kBAAqB;IACrB,sDAAsD;IACtD,YAAoB;IACpB,oEAAoE;IACpE,6BAAqH;IACrH,0FAA0F;IAC1F,eAAqI;IACrI,uIAAuI;IACvI,0BAAyE;IAG3E;;;;;;;;;MAmBC;IAED,mFAAmF;IACnF,oBAKC;IAED,qDAAqD;IACrD,uBAuCC;IAED;;;;;OAKG;IACH,aAHW,GAAC,cACD,GAAC,QA4BX;IAED,0HAA0H;IAC1H,yCAmDC;IAzCG,8BAAoB;IA2CxB,0HAA0H;IAC1H,yBAOC;IAED,wBAEC;IAED,gCAAgC;IAChC,uCAGC;IAED;;;SAGK;IACL,oCAGC;;CACF;+BAlO8B,mBAAmB"}
1
+ {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/recorder.js"],"names":[],"mappings":"AAaA;IAUE,yBAkBC;IAdC,iEAAiE;IACjE,mBAAsB;IACtB,6DAA6D;IAC7D,oCAAuC;IACvC,+IAA+I;IAC/I,yBAA4B;IAC5B,kIAAkI;IAClI,kBAAqB;IACrB,sDAAsD;IACtD,YAAoB;IACpB,0FAA0F;IAC1F,eAAqG;IACrG,uIAAuI;IACvI,0BAAyE;IAG3E;;;;;;;;;MAmBC;IAED,mFAAmF;IACnF,oBAKC;IAED,qDAAqD;IACrD,uBA2CC;IAED;;;;;OAKG;IACH,aAHW,GAAC,cACD,GAAC,QAgCX;IAED,0HAA0H;IAC1H,yCAoDC;IA1CG,8BAAoB;IA4CxB,0HAA0H;IAC1H,yBAOC;IAED,wBAEC;IAED,gCAAgC;IAChC,uCAGC;IAED;;;SAGK;IACL,oCAGC;;CACF;+BA3O8B,mBAAmB"}
@@ -1 +1 @@
1
- {"version":3,"file":"stylesheet-evaluator.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/stylesheet-evaluator.js"],"names":[],"mappings":"AAyFA,sDAA4D;AAtF5D;IAGE;;;QAGI;IACJ,oCAAkC;IAClC,oBAAe;IAEf;;;OAGG;IACH,mBAmBC;IAED;;;OAGG;IACH,oBAMC;;CAuCF"}
1
+ {"version":3,"file":"stylesheet-evaluator.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/stylesheet-evaluator.js"],"names":[],"mappings":"AA0FA,sDAA4D;AAvF5D;IAGE;;;QAGI;IACJ,oCAAkC;IAClC,oBAAe;IAEf;;;OAGG;IACH,mBAoBC;IAED;;;OAGG;IACH,oBAMC;;CAuCF"}
@@ -32,7 +32,8 @@ export class EventBuffer {
32
32
  */
33
33
  get hasData(): boolean;
34
34
  /**
35
- * Adds an event object to the buffer while tallying size
35
+ * Adds an event object to the buffer while tallying size. Only adds the event if it is valid
36
+ * and would not make the event buffer exceed the maxPayloadSize.
36
37
  * @param {Object} event the event object to add to the buffer
37
38
  * @returns {EventBuffer} returns the event buffer for chaining
38
39
  */
@@ -1 +1 @@
1
- {"version":3,"file":"event-buffer.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/event-buffer.js"],"names":[],"mappings":"AAGA;;;;;GAKG;AAEH;;GAEG;AACH;IAQE;;;OAGG;IACH,6BAFW,MAAM,YAAC,EAIjB;IADC,uBAAoC;IAGtC;;OAEG;IACH,uBAEC;IAED;;OAEG;IACH,oBAEC;IAED;;OAEG;IACH,wBAGC;IAED;;;OAGG;IACH,uBAEC;IAED;;;;OAIG;IACH,WAHW,MAAM,GACJ,WAAW,CAQvB;IAED;;;OAGG;IACH,SAFa,WAAW,CAMvB;IAED;;;;OAIG;IACH,QAFa,WAAW,CAMvB;IAED;;;;OAIG;IACH,UAFa,WAAW,CAMvB;IAED;;;;;OAKG;IACH,kCAHW,OAAO,GACL,WAAW,CAOvB;IAED;;;;OAIG;IACH,eAHW,MAAM,GACJ,OAAO,CAInB;;CACF"}
1
+ {"version":3,"file":"event-buffer.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/event-buffer.js"],"names":[],"mappings":"AAGA;;;;;GAKG;AAEH;;GAEG;AACH;IAQE;;;OAGG;IACH,6BAFW,MAAM,YAAC,EAIjB;IADC,uBAAoC;IAGtC;;OAEG;IACH,uBAEC;IAED;;OAEG;IACH,oBAEC;IAED;;OAEG;IACH,wBAGC;IAED;;;OAGG;IACH,uBAEC;IAED;;;;;OAKG;IACH,WAHW,MAAM,GACJ,WAAW,CAQvB;IAED;;;OAGG;IACH,SAFa,WAAW,CAMvB;IAED;;;;OAIG;IACH,QAFa,WAAW,CAMvB;IAED;;;;OAIG;IACH,UAFa,WAAW,CAMvB;IAED;;;;;OAKG;IACH,kCAHW,OAAO,GACL,WAAW,CAOvB;IAED;;;;OAIG;IACH,eAHW,MAAM,GACJ,OAAO,CAInB;;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"browser-agent.d.ts","sourceRoot":"","sources":["../../../src/loaders/browser-agent.js"],"names":[],"mappings":"AAaA;;;GAGG;AACH;IACE,uBAiBC;CACF;sBApCqB,SAAS"}
1
+ {"version":3,"file":"browser-agent.d.ts","sourceRoot":"","sources":["../../../src/loaders/browser-agent.js"],"names":[],"mappings":"AAcA;;;GAGG;AACH;IACE,uBAkBC;CACF;sBAtCqB,SAAS"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/browser-agent",
3
- "version": "1.265.1",
3
+ "version": "1.267.0",
4
4
  "private": false,
5
5
  "author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
6
6
  "description": "New Relic Browser Agent",
@@ -59,6 +59,9 @@
59
59
  "features/session_replay": [
60
60
  "dist/types/features/session_replay/instrument/index.d.ts"
61
61
  ],
62
+ "features/soft_navigations": [
63
+ "dist/types/features/soft_navigations/instrument/index.d.ts"
64
+ ],
62
65
  "features/spa": [
63
66
  "dist/types/features/spa/instrument/index.d.ts"
64
67
  ]
@@ -193,7 +196,7 @@
193
196
  "dependencies": {
194
197
  "fflate": "0.7.4",
195
198
  "rrweb": "2.0.0-alpha.12",
196
- "web-vitals": "3.5.2"
199
+ "web-vitals": "4.2.3"
197
200
  },
198
201
  "devDependencies": {
199
202
  "@babel/cli": "^7.23.4",
@@ -49,7 +49,7 @@ const model = () => {
49
49
  obfuscate: undefined,
50
50
  page_action: { enabled: true },
51
51
  page_view_event: { enabled: true, autoStart: true },
52
- page_view_timing: { enabled: true, harvestTimeSeconds: 30, long_task: false, autoStart: true },
52
+ page_view_timing: { enabled: true, harvestTimeSeconds: 30, autoStart: true },
53
53
  privacy: { cookies_enabled: true }, // *cli - per discussion, default should be true
54
54
  proxy: {
55
55
  assets: undefined, // if this value is set, it will be used to overwrite the webpack asset path used to fetch assets
@@ -69,8 +69,7 @@ const model = () => {
69
69
  error_sampling_rate: 100, // float from 0 - 100
70
70
  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
71
71
  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
72
- inline_stylesheet: true, // serialize css for collection without public asset url
73
- fix_stylesheets: true, // fetch missing stylesheet resources for inlining, only works if 'inline_stylesheet' is also true
72
+ fix_stylesheets: true, // fetch missing stylesheet resources for inlining
74
73
  // recording config settings
75
74
  mask_all_inputs: true,
76
75
  // this has a getter/setter to facilitate validation of the selectors
@@ -1,8 +1,6 @@
1
1
  import { originTime } from '../constants/runtime'
2
2
  import { getRuntime } from '../config/runtime'
3
3
 
4
- const rfc2616Regex = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), ([0-3][0-9]) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ([0-9]{4}) ([01][0-9]|2[0-3])(:[0-5][0-9]){2} GMT$/
5
-
6
4
  /**
7
5
  * Class used to adjust the timestamp of harvested data to New Relic server time. This
8
6
  * is done by tracking the performance timings of the RUM call and applying a calculation
@@ -57,28 +55,23 @@ export class TimeKeeper {
57
55
  * @param rumRequest {XMLHttpRequest} The xhr for the rum request
58
56
  * @param startTime {number} The start time of the RUM request
59
57
  * @param endTime {number} The end time of the RUM request
58
+ * @param nrServerTime {number} the unix number value of the NR server time in MS, returned in the RUM request body
60
59
  */
61
- processRumRequest (rumRequest, startTime, endTime) {
60
+ processRumRequest (rumRequest, startTime, endTime, nrServerTime) {
62
61
  this.processStoredDiff() // Check session entity for stored time diff
63
62
  if (this.#ready) return // Server time calculated from session entity
64
63
 
65
- const responseDateHeader = rumRequest.getResponseHeader('Date')
66
- if (!responseDateHeader) {
67
- throw new Error('Missing date header on rum response.')
68
- }
69
- if (!rfc2616Regex.test(responseDateHeader)) {
70
- throw new Error('Date header invalid format.')
71
- }
64
+ if (!nrServerTime) throw new Error('nrServerTime not found')
72
65
 
73
66
  const medianRumOffset = (endTime - startTime) / 2
74
67
  const serverOffset = startTime + medianRumOffset
75
68
 
76
69
  // Corrected page origin time
77
- this.#correctedOriginTime = Math.floor(Date.parse(responseDateHeader) - serverOffset)
70
+ this.#correctedOriginTime = Math.floor(nrServerTime - serverOffset)
78
71
  this.#localTimeDiff = originTime - this.#correctedOriginTime
79
72
 
80
73
  if (isNaN(this.#correctedOriginTime)) {
81
- throw new Error('Date header invalid format.')
74
+ throw new Error('Failed to correct browser time to server time')
82
75
  }
83
76
 
84
77
  this.#session?.write({ serverTimeDiff: this.#localTimeDiff })
@@ -38,5 +38,7 @@ export function stringify (val) {
38
38
  } catch (err) {
39
39
  // do nothing
40
40
  }
41
+ // return a string so that downstream users of the method do not throw errors
42
+ return ''
41
43
  }
42
44
  }
@@ -5,6 +5,5 @@ export const VITAL_NAMES = {
5
5
  LARGEST_CONTENTFUL_PAINT: 'lcp',
6
6
  CUMULATIVE_LAYOUT_SHIFT: 'cls',
7
7
  INTERACTION_TO_NEXT_PAINT: 'inp',
8
- LONG_TASK: 'lt',
9
8
  TIME_TO_FIRST_BYTE: 'ttfb'
10
9
  }
@@ -10,9 +10,15 @@ if (isBrowserScope) {
10
10
  onINP(({ value, attribution, id }) => {
11
11
  const attrs = {
12
12
  metricId: id,
13
- eventTarget: attribution.eventTarget,
14
- eventType: attribution.eventType,
15
- eventTime: attribution.eventTime,
13
+ eventTarget: attribution.interactionTarget, // event* attrs deprecated in v4, kept for NR backwards compatibility
14
+ eventTime: attribution.interactionTime, // event* attrs deprecated in v4, kept for NR backwards compatibility
15
+ interactionTarget: attribution.interactionTarget,
16
+ interactionTime: attribution.interactionTime,
17
+ interactionType: attribution.interactionType,
18
+ inputDelay: attribution.inputDelay,
19
+ nextPaintTime: attribution.nextPaintTime,
20
+ processingDuration: attribution.processingDuration,
21
+ presentationDelay: attribution.presentationDelay,
16
22
  loadState: attribution.loadState
17
23
  }
18
24
  interactionToNextPaint.update({ value, attrs })
@@ -20,7 +20,8 @@ if (isBrowserScope) {
20
20
  element: attribution.element,
21
21
  timeToFirstByte: attribution.timeToFirstByte,
22
22
  resourceLoadDelay: attribution.resourceLoadDelay,
23
- resourceLoadTime: attribution.resourceLoadTime,
23
+ resourceLoadDuration: attribution.resourceLoadDuration,
24
+ resourceLoadTime: attribution.resourceLoadDuration, // kept for NR backwards compatibility, deprecated in v3->v4
24
25
  elementRenderDelay: attribution.elementRenderDelay
25
26
  }
26
27
  if (attribution.url) attrs.elUrl = cleanURL(attribution.url)
@@ -56,7 +56,7 @@ export class Aggregate extends AggregateBase {
56
56
  singleChecks () {
57
57
  // report loaderType
58
58
  const { distMethod, loaderType } = getRuntime(this.agentIdentifier)
59
- const { proxy, privacy, page_view_timing } = getConfiguration(this.agentIdentifier)
59
+ const { proxy, privacy } = getConfiguration(this.agentIdentifier)
60
60
 
61
61
  if (loaderType) this.storeSupportabilityMetrics(`Generic/LoaderType/${loaderType}/Detected`)
62
62
  if (distMethod) this.storeSupportabilityMetrics(`Generic/DistMethod/${distMethod}/Detected`)
@@ -77,7 +77,6 @@ export class Aggregate extends AggregateBase {
77
77
  })
78
78
 
79
79
  if (!privacy.cookies_enabled) this.storeSupportabilityMetrics('Config/SessionTracking/Disabled')
80
- if (page_view_timing.long_task) this.storeSupportabilityMetrics('Config/LongTask/Enabled')
81
80
  } else if (isWorkerScope) {
82
81
  this.storeSupportabilityMetrics('Generic/Runtime/Worker/Detected')
83
82
  } else {
@@ -125,19 +125,17 @@ export class Aggregate extends AggregateBase {
125
125
  return
126
126
  }
127
127
 
128
- try {
129
- this.timeKeeper.processRumRequest(xhr, rumStartTime, rumEndTime)
130
- if (!this.timeKeeper.ready) throw new Error('TimeKeeper not ready')
131
-
132
- agentRuntime.timeKeeper = this.timeKeeper
133
- } catch (error) {
134
- this.ee.abort()
135
- warn(17, error)
136
- return
137
- }
138
-
139
128
  try {
140
129
  const { app, ...flags } = JSON.parse(responseText)
130
+ try {
131
+ this.timeKeeper.processRumRequest(xhr, rumStartTime, rumEndTime, app.nrServerTime)
132
+ if (!this.timeKeeper.ready) throw new Error('TimeKeeper not ready')
133
+ agentRuntime.timeKeeper = this.timeKeeper
134
+ } catch (error) {
135
+ this.ee.abort()
136
+ warn(17, error)
137
+ return
138
+ }
141
139
  agentRuntime.appMetadata = app
142
140
  activateFeatures(flags, this.agentIdentifier)
143
141
  this.drain()
@@ -19,7 +19,6 @@ import { firstPaint } from '../../../common/vitals/first-paint'
19
19
  import { interactionToNextPaint } from '../../../common/vitals/interaction-to-next-paint'
20
20
  import { largestContentfulPaint } from '../../../common/vitals/largest-contentful-paint'
21
21
  import { timeToFirstByte } from '../../../common/vitals/time-to-first-byte'
22
- import { longTask } from '../../../common/vitals/long-task'
23
22
  import { subscribeToVisibilityChange } from '../../../common/window/page-visibility'
24
23
  import { VITAL_NAMES } from '../../../common/vitals/constants'
25
24
  import { EventBuffer } from '../../utils/event-buffer'
@@ -37,8 +36,6 @@ export class Aggregate extends AggregateBase {
37
36
  this.timings = new EventBuffer()
38
37
  this.curSessEndRecorded = false
39
38
 
40
- if (getConfigurationValue(this.agentIdentifier, 'page_view_timing.long_task') === true) longTask.subscribe(this.#handleVitalMetric)
41
-
42
39
  registerHandler('docHidden', msTimestamp => this.endCurrentSession(msTimestamp), this.featureName, this.ee)
43
40
  registerHandler('winPagehide', msTimestamp => this.recordPageUnload(msTimestamp), this.featureName, this.ee)
44
41
 
@@ -104,7 +104,7 @@ export class Aggregate extends AggregateBase {
104
104
  this.handleError(e)
105
105
  }, this.featureName, this.ee)
106
106
 
107
- const { error_sampling_rate, sampling_rate, autoStart, block_selector, mask_text_selector, mask_all_inputs, inline_stylesheet, inline_images, collect_fonts } = getConfigurationValue(this.agentIdentifier, 'session_replay')
107
+ const { error_sampling_rate, sampling_rate, autoStart, block_selector, mask_text_selector, mask_all_inputs, inline_images, collect_fonts } = getConfigurationValue(this.agentIdentifier, 'session_replay')
108
108
 
109
109
  this.waitForFlags(['srs', 'sr']).then(([srMode, entitled]) => {
110
110
  this.entitled = !!entitled
@@ -129,7 +129,6 @@ export class Aggregate extends AggregateBase {
129
129
  /** 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 */
130
130
  if (!autoStart) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/AutoStart/Modified'], undefined, FEATURE_NAMES.metrics, this.ee)
131
131
  if (collect_fonts === true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/CollectFonts/Modified'], undefined, FEATURE_NAMES.metrics, this.ee)
132
- if (inline_stylesheet !== true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineStylesheet/Modified'], undefined, FEATURE_NAMES.metrics, this.ee)
133
132
  if (inline_images === true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineImages/Modifed'], undefined, FEATURE_NAMES.metrics, this.ee)
134
133
  if (mask_all_inputs !== true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/MaskAllInputs/Modified'], undefined, FEATURE_NAMES.metrics, this.ee)
135
134
  if (block_selector !== '[data-nr-block]') handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/BlockSelector/Modified'], undefined, FEATURE_NAMES.metrics, this.ee)
@@ -242,7 +241,7 @@ export class Aggregate extends AggregateBase {
242
241
  }
243
242
 
244
243
  prepareHarvest ({ opts } = {}) {
245
- if (!this.recorder || !this.timeKeeper?.ready) return
244
+ if (!this.recorder || !this.timeKeeper?.ready || !this.recorder.hasSeenSnapshot) return
246
245
  const recorderEvents = this.recorder.getEvents()
247
246
  // get the event type and use that to trigger another harvest if needed
248
247
  if (!recorderEvents.events.length || (this.mode !== MODE.FULL) || this.blocked) return
@@ -29,14 +29,14 @@ export class Recorder {
29
29
  this.recording = false
30
30
  /** The pointer to the current bucket holding rrweb events */
31
31
  this.currentBufferTarget = this.#events
32
+ /** Only set to true once a snapshot node has been processed. Used to block preload harvests from sending before we know we have a snapshot */
33
+ this.hasSeenSnapshot = false
32
34
  /** Hold on to the last meta node, so that it can be re-inserted if the meta and snapshot nodes are broken up due to harvesting */
33
35
  this.lastMeta = false
34
36
  /** The parent class that instantiated the recorder */
35
37
  this.parent = parent
36
- /** Config to inform to inline stylesheet contents (true default) */
37
- this.shouldInlineStylesheets = getConfigurationValue(this.parent.agentIdentifier, 'session_replay.inline_stylesheet')
38
38
  /** A flag that can be set to false by failing conversions to stop the fetching process */
39
- this.shouldFix = this.shouldInlineStylesheets && getConfigurationValue(this.parent.agentIdentifier, 'session_replay.fix_stylesheets')
39
+ this.shouldFix = getConfigurationValue(this.parent.agentIdentifier, 'session_replay.fix_stylesheets')
40
40
  /** The method to stop recording. This defaults to a noop, but is overwritten once the recording library is imported and initialized */
41
41
  this.stopRecording = () => { /* no-op until set by rrweb initializer */ }
42
42
  }
@@ -73,10 +73,14 @@ export class Recorder {
73
73
  /** Begin recording using configured recording lib */
74
74
  startRecording () {
75
75
  this.recording = true
76
- const { block_class, ignore_class, mask_text_class, block_selector, mask_input_options, mask_text_selector, mask_all_inputs, inline_stylesheet, inline_images, collect_fonts } = getConfigurationValue(this.parent.agentIdentifier, 'session_replay')
76
+ const { block_class, ignore_class, mask_text_class, block_selector, mask_input_options, mask_text_selector, mask_all_inputs, inline_images, collect_fonts } = getConfigurationValue(this.parent.agentIdentifier, 'session_replay')
77
77
  const customMasker = (text, element) => {
78
- if (element?.type?.toLowerCase() !== 'password' && (element?.dataset.nrUnmask !== undefined || element?.classList.contains('nr-unmask'))) return text
79
- return '*'.repeat(text.length)
78
+ try {
79
+ if (typeof element?.type === 'string' && element.type.toLowerCase() !== 'password' && (element?.dataset?.nrUnmask !== undefined || element?.classList?.contains('nr-unmask'))) return text
80
+ } catch (err) {
81
+ // likely an element was passed to this handler that was invalid and was missing attributes or methods
82
+ }
83
+ return '*'.repeat(text?.length || 0)
80
84
  }
81
85
  // set up rrweb configurations for maximum privacy --
82
86
  // https://newrelic.atlassian.net/wiki/spaces/O11Y/pages/2792293280/2023+02+28+Browser+-+Session+Replay#Configuration-options
@@ -91,7 +95,7 @@ export class Recorder {
91
95
  maskTextFn: customMasker,
92
96
  maskAllInputs: mask_all_inputs,
93
97
  maskInputFn: customMasker,
94
- inlineStylesheet: inline_stylesheet,
98
+ inlineStylesheet: true,
95
99
  inlineImages: inline_images,
96
100
  collectFonts: collect_fonts,
97
101
  checkoutEveryNms: CHECKOUT_MS[this.parent.mode],
@@ -119,13 +123,17 @@ export class Recorder {
119
123
  * @param {*} isCheckout - Flag indicating if the payload was triggered as a checkout
120
124
  */
121
125
  audit (event, isCheckout) {
122
- /** only run the audit if inline_stylesheets is configured as on (default behavior) */
123
- if (this.shouldInlineStylesheets === false || !this.shouldFix) {
124
- this.currentBufferTarget.inlinedAllStylesheets = false
125
- return this.store(event, isCheckout)
126
- }
127
126
  /** An count of stylesheet objects that were blocked from accessing contents via JS */
128
127
  const incompletes = stylesheetEvaluator.evaluate()
128
+ const missingInlineSMTag = 'SessionReplay/Payload/Missing-Inline-Css/'
129
+ /** only run the full fixing behavior (more costly) if fix_stylesheets is configured as on (default behavior) */
130
+ if (!this.shouldFix) {
131
+ if (incompletes > 0) {
132
+ this.currentBufferTarget.inlinedAllStylesheets = false
133
+ handle(SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Skipped', incompletes], undefined, FEATURE_NAMES.metrics, this.parent.ee)
134
+ }
135
+ return this.store(event, isCheckout)
136
+ }
129
137
  /** 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) */
130
138
  if (!incompletes && this.#fixing && event.type === RRWEB_EVENT_TYPES.Meta) this.#fixing = false
131
139
  if (incompletes > 0) {
@@ -135,8 +143,8 @@ export class Recorder {
135
143
  this.currentBufferTarget.inlinedAllStylesheets = false
136
144
  this.shouldFix = false
137
145
  }
138
- handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css/Failed', failedToFix], undefined, FEATURE_NAMES.metrics, this.parent.ee)
139
- handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css/Fixed', incompletes - failedToFix], undefined, FEATURE_NAMES.metrics, this.parent.ee)
146
+ handle(SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Failed', failedToFix], undefined, FEATURE_NAMES.metrics, this.parent.ee)
147
+ handle(SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Fixed', incompletes - failedToFix], undefined, FEATURE_NAMES.metrics, this.parent.ee)
140
148
  this.takeFullSnapshot()
141
149
  })
142
150
  /** Only start ignoring data if got a faulty snapshot */
@@ -184,6 +192,7 @@ export class Recorder {
184
192
  // snapshot event
185
193
  if (event.type === RRWEB_EVENT_TYPES.FullSnapshot) {
186
194
  this.currentBufferTarget.hasSnapshot = true
195
+ this.hasSeenSnapshot = true
187
196
  }
188
197
  this.currentBufferTarget.add(event)
189
198