@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
package/CHANGELOG.md CHANGED
@@ -3,6 +3,33 @@
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.267.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.266.0...v1.267.0) (2024-09-23)
7
+
8
+
9
+ ### Features
10
+
11
+ * add soft navigations to Browser-Agent loader ([#1191](https://github.com/newrelic/newrelic-browser-agent/issues/1191)) ([fd033a7](https://github.com/newrelic/newrelic-browser-agent/commit/fd033a7be52fed01e5a67a5f8060cf6ec080c769))
12
+ * Improve notifications of stylesheet status for session replay ([#1190](https://github.com/newrelic/newrelic-browser-agent/issues/1190)) ([a21b939](https://github.com/newrelic/newrelic-browser-agent/commit/a21b939884697fc4951e6d4cdaceecaa9b000810))
13
+ * Update TimeKeeper Source of Truth ([#1181](https://github.com/newrelic/newrelic-browser-agent/issues/1181)) ([60d63cf](https://github.com/newrelic/newrelic-browser-agent/commit/60d63cf416529d700abaa6fa36ba3e64402b35a9))
14
+ * Upgrade to web-vitals v4 ([#1193](https://github.com/newrelic/newrelic-browser-agent/issues/1193)) ([81349b8](https://github.com/newrelic/newrelic-browser-agent/commit/81349b82f5befd88f425cce1dee06f08fcea8a05))
15
+
16
+ ### Bug Fixes
17
+
18
+ * Improve reliability of customMasker ([#1197](https://github.com/newrelic/newrelic-browser-agent/issues/1197)) ([9f2ef1f](https://github.com/newrelic/newrelic-browser-agent/commit/9f2ef1f7d0e5dceaf61745a47849a3b2cc930c53))
19
+ * Stringify now returns an empty string if failed to transform ([#1198](https://github.com/newrelic/newrelic-browser-agent/issues/1198)) ([310937a](https://github.com/newrelic/newrelic-browser-agent/commit/310937ae20a004f91f3ccb4ba6b1d1b230d92632))
20
+
21
+ ## [1.266.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.265.1...v1.266.0) (2024-09-16)
22
+
23
+
24
+ ### Features
25
+
26
+ * Removing long task ([#1153](https://github.com/newrelic/newrelic-browser-agent/issues/1153)) ([304e395](https://github.com/newrelic/newrelic-browser-agent/commit/304e3954df9639d6d61e545fcaa0c761346a5016))
27
+
28
+
29
+ ### Bug Fixes
30
+
31
+ * Remove nullish coalescing inside getter ([#1180](https://github.com/newrelic/newrelic-browser-agent/issues/1180)) ([e537359](https://github.com/newrelic/newrelic-browser-agent/commit/e537359f354270fcbd607445b6853c236f0fdbfc))
32
+
6
33
  ## [1.265.1](https://github.com/newrelic/newrelic-browser-agent/compare/v1.265.0...v1.265.1) (2024-09-06)
7
34
 
8
35
 
@@ -87,7 +87,6 @@ const model = () => {
87
87
  page_view_timing: {
88
88
  enabled: true,
89
89
  harvestTimeSeconds: 30,
90
- long_task: false,
91
90
  autoStart: true
92
91
  },
93
92
  privacy: {
@@ -118,10 +117,8 @@ const model = () => {
118
117
  // 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
119
118
  inline_images: false,
120
119
  // 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
121
- inline_stylesheet: true,
122
- // serialize css for collection without public asset url
123
120
  fix_stylesheets: true,
124
- // fetch missing stylesheet resources for inlining, only works if 'inline_stylesheet' is also true
121
+ // fetch missing stylesheet resources for inlining
125
122
  // recording config settings
126
123
  mask_all_inputs: true,
127
124
  // this has a getter/setter to facilitate validation of the selectors
@@ -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.265.1";
15
+ const VERSION = exports.VERSION = "1.267.0";
16
16
 
17
17
  /**
18
18
  * Exposes the build type of the agent
@@ -12,7 +12,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
12
12
  /**
13
13
  * Exposes the version of the agent
14
14
  */
15
- const VERSION = exports.VERSION = "1.265.1";
15
+ const VERSION = exports.VERSION = "1.267.0";
16
16
 
17
17
  /**
18
18
  * Exposes the build type of the agent
@@ -6,8 +6,6 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.TimeKeeper = void 0;
7
7
  var _runtime = require("../constants/runtime");
8
8
  var _runtime2 = require("../config/runtime");
9
- 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$/;
10
-
11
9
  /**
12
10
  * Class used to adjust the timestamp of harvested data to New Relic server time. This
13
11
  * is done by tracking the performance timings of the RUM call and applying a calculation
@@ -58,26 +56,21 @@ class TimeKeeper {
58
56
  * @param rumRequest {XMLHttpRequest} The xhr for the rum request
59
57
  * @param startTime {number} The start time of the RUM request
60
58
  * @param endTime {number} The end time of the RUM request
59
+ * @param nrServerTime {number} the unix number value of the NR server time in MS, returned in the RUM request body
61
60
  */
62
- processRumRequest(rumRequest, startTime, endTime) {
61
+ processRumRequest(rumRequest, startTime, endTime, nrServerTime) {
63
62
  this.processStoredDiff(); // Check session entity for stored time diff
64
63
  if (this.#ready) return; // Server time calculated from session entity
65
64
 
66
- const responseDateHeader = rumRequest.getResponseHeader('Date');
67
- if (!responseDateHeader) {
68
- throw new Error('Missing date header on rum response.');
69
- }
70
- if (!rfc2616Regex.test(responseDateHeader)) {
71
- throw new Error('Date header invalid format.');
72
- }
65
+ if (!nrServerTime) throw new Error('nrServerTime not found');
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 = _runtime.originTime - this.#correctedOriginTime;
79
72
  if (isNaN(this.#correctedOriginTime)) {
80
- throw new Error('Date header invalid format.');
73
+ throw new Error('Failed to correct browser time to server time');
81
74
  }
82
75
  this.#session?.write({
83
76
  serverTimeDiff: this.#localTimeDiff
@@ -43,5 +43,7 @@ function stringify(val) {
43
43
  } catch (err) {
44
44
  // do nothing
45
45
  }
46
+ // return a string so that downstream users of the method do not throw errors
47
+ return '';
46
48
  }
47
49
  }
@@ -11,6 +11,5 @@ const VITAL_NAMES = exports.VITAL_NAMES = {
11
11
  LARGEST_CONTENTFUL_PAINT: 'lcp',
12
12
  CUMULATIVE_LAYOUT_SHIFT: 'cls',
13
13
  INTERACTION_TO_NEXT_PAINT: 'inp',
14
- LONG_TASK: 'lt',
15
14
  TIME_TO_FIRST_BYTE: 'ttfb'
16
15
  };
@@ -18,9 +18,17 @@ if (_runtime.isBrowserScope) {
18
18
  }) => {
19
19
  const attrs = {
20
20
  metricId: id,
21
- eventTarget: attribution.eventTarget,
22
- eventType: attribution.eventType,
23
- eventTime: attribution.eventTime,
21
+ eventTarget: attribution.interactionTarget,
22
+ // event* attrs deprecated in v4, kept for NR backwards compatibility
23
+ eventTime: attribution.interactionTime,
24
+ // event* attrs deprecated in v4, kept for NR backwards compatibility
25
+ interactionTarget: attribution.interactionTarget,
26
+ interactionTime: attribution.interactionTime,
27
+ interactionType: attribution.interactionType,
28
+ inputDelay: attribution.inputDelay,
29
+ nextPaintTime: attribution.nextPaintTime,
30
+ processingDuration: attribution.processingDuration,
31
+ presentationDelay: attribution.presentationDelay,
24
32
  loadState: attribution.loadState
25
33
  };
26
34
  interactionToNextPaint.update({
@@ -26,7 +26,9 @@ if (_runtime.isBrowserScope) {
26
26
  element: attribution.element,
27
27
  timeToFirstByte: attribution.timeToFirstByte,
28
28
  resourceLoadDelay: attribution.resourceLoadDelay,
29
- resourceLoadTime: attribution.resourceLoadTime,
29
+ resourceLoadDuration: attribution.resourceLoadDuration,
30
+ resourceLoadTime: attribution.resourceLoadDuration,
31
+ // kept for NR backwards compatibility, deprecated in v3->v4
30
32
  elementRenderDelay: attribution.elementRenderDelay
31
33
  };
32
34
  if (attribution.url) attrs.elUrl = (0, _cleanUrl.cleanURL)(attribution.url);
@@ -70,8 +70,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
70
70
  } = (0, _runtime.getRuntime)(this.agentIdentifier);
71
71
  const {
72
72
  proxy,
73
- privacy,
74
- page_view_timing
73
+ privacy
75
74
  } = (0, _init.getConfiguration)(this.agentIdentifier);
76
75
  if (loaderType) this.storeSupportabilityMetrics("Generic/LoaderType/".concat(loaderType, "/Detected"));
77
76
  if (distMethod) this.storeSupportabilityMetrics("Generic/DistMethod/".concat(distMethod, "/Detected"));
@@ -89,7 +88,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
89
88
  });
90
89
  });
91
90
  if (!privacy.cookies_enabled) this.storeSupportabilityMetrics('Config/SessionTracking/Disabled');
92
- if (page_view_timing.long_task) this.storeSupportabilityMetrics('Config/LongTask/Enabled');
93
91
  } else if (_runtime2.isWorkerScope) {
94
92
  this.storeSupportabilityMetrics('Generic/Runtime/Worker/Detected');
95
93
  } else {
@@ -142,20 +142,20 @@ class Aggregate extends _aggregateBase.AggregateBase {
142
142
  this.ee.abort();
143
143
  return;
144
144
  }
145
- try {
146
- this.timeKeeper.processRumRequest(xhr, rumStartTime, rumEndTime);
147
- if (!this.timeKeeper.ready) throw new Error('TimeKeeper not ready');
148
- agentRuntime.timeKeeper = this.timeKeeper;
149
- } catch (error) {
150
- this.ee.abort();
151
- (0, _console.warn)(17, error);
152
- return;
153
- }
154
145
  try {
155
146
  const {
156
147
  app,
157
148
  ...flags
158
149
  } = JSON.parse(responseText);
150
+ try {
151
+ this.timeKeeper.processRumRequest(xhr, rumStartTime, rumEndTime, app.nrServerTime);
152
+ if (!this.timeKeeper.ready) throw new Error('TimeKeeper not ready');
153
+ agentRuntime.timeKeeper = this.timeKeeper;
154
+ } catch (error) {
155
+ this.ee.abort();
156
+ (0, _console.warn)(17, error);
157
+ return;
158
+ }
159
159
  agentRuntime.appMetadata = app;
160
160
  (0, _featureFlags.activateFeatures)(flags, this.agentIdentifier);
161
161
  this.drain();
@@ -20,7 +20,6 @@ var _firstPaint = require("../../../common/vitals/first-paint");
20
20
  var _interactionToNextPaint = require("../../../common/vitals/interaction-to-next-paint");
21
21
  var _largestContentfulPaint = require("../../../common/vitals/largest-contentful-paint");
22
22
  var _timeToFirstByte = require("../../../common/vitals/time-to-first-byte");
23
- var _longTask = require("../../../common/vitals/long-task");
24
23
  var _pageVisibility = require("../../../common/window/page-visibility");
25
24
  var _constants2 = require("../../../common/vitals/constants");
26
25
  var _eventBuffer = require("../../utils/event-buffer");
@@ -42,7 +41,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
42
41
  super(agentIdentifier, aggregator, _constants.FEATURE_NAME);
43
42
  this.timings = new _eventBuffer.EventBuffer();
44
43
  this.curSessEndRecorded = false;
45
- if ((0, _init.getConfigurationValue)(this.agentIdentifier, 'page_view_timing.long_task') === true) _longTask.longTask.subscribe(this.#handleVitalMetric);
46
44
  (0, _registerHandler.registerHandler)('docHidden', msTimestamp => this.endCurrentSession(msTimestamp), this.featureName, this.ee);
47
45
  (0, _registerHandler.registerHandler)('winPagehide', msTimestamp => this.recordPageUnload(msTimestamp), this.featureName, this.ee);
48
46
  const harvestTimeSeconds = (0, _init.getConfigurationValue)(this.agentIdentifier, 'page_view_timing.harvestTimeSeconds') || 30;
@@ -108,7 +108,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
108
108
  block_selector,
109
109
  mask_text_selector,
110
110
  mask_all_inputs,
111
- inline_stylesheet,
112
111
  inline_images,
113
112
  collect_fonts
114
113
  } = (0, _init.getConfigurationValue)(this.agentIdentifier, 'session_replay');
@@ -135,7 +134,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
135
134
  /** 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 */
136
135
  if (!autoStart) (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/AutoStart/Modified'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
137
136
  if (collect_fonts === true) (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/CollectFonts/Modified'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
138
- if (inline_stylesheet !== true) (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineStylesheet/Modified'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
139
137
  if (inline_images === true) (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineImages/Modifed'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
140
138
  if (mask_all_inputs !== true) (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/MaskAllInputs/Modified'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
141
139
  if (block_selector !== '[data-nr-block]') (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/BlockSelector/Modified'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
@@ -253,7 +251,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
253
251
  prepareHarvest({
254
252
  opts
255
253
  } = {}) {
256
- if (!this.recorder || !this.timeKeeper?.ready) return;
254
+ if (!this.recorder || !this.timeKeeper?.ready || !this.recorder.hasSeenSnapshot) return;
257
255
  const recorderEvents = this.recorder.getEvents();
258
256
  // get the event type and use that to trigger another harvest if needed
259
257
  if (!recorderEvents.events.length || this.mode !== _constants3.MODE.FULL || this.blocked) return;
@@ -33,14 +33,14 @@ class Recorder {
33
33
  this.recording = false;
34
34
  /** The pointer to the current bucket holding rrweb events */
35
35
  this.currentBufferTarget = this.#events;
36
+ /** 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 */
37
+ this.hasSeenSnapshot = false;
36
38
  /** 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 */
37
39
  this.lastMeta = false;
38
40
  /** The parent class that instantiated the recorder */
39
41
  this.parent = parent;
40
- /** Config to inform to inline stylesheet contents (true default) */
41
- this.shouldInlineStylesheets = (0, _init.getConfigurationValue)(this.parent.agentIdentifier, 'session_replay.inline_stylesheet');
42
42
  /** A flag that can be set to false by failing conversions to stop the fetching process */
43
- this.shouldFix = this.shouldInlineStylesheets && (0, _init.getConfigurationValue)(this.parent.agentIdentifier, 'session_replay.fix_stylesheets');
43
+ this.shouldFix = (0, _init.getConfigurationValue)(this.parent.agentIdentifier, 'session_replay.fix_stylesheets');
44
44
  /** The method to stop recording. This defaults to a noop, but is overwritten once the recording library is imported and initialized */
45
45
  this.stopRecording = () => {/* no-op until set by rrweb initializer */};
46
46
  }
@@ -82,13 +82,16 @@ class Recorder {
82
82
  mask_input_options,
83
83
  mask_text_selector,
84
84
  mask_all_inputs,
85
- inline_stylesheet,
86
85
  inline_images,
87
86
  collect_fonts
88
87
  } = (0, _init.getConfigurationValue)(this.parent.agentIdentifier, 'session_replay');
89
88
  const customMasker = (text, element) => {
90
- if (element?.type?.toLowerCase() !== 'password' && (element?.dataset.nrUnmask !== undefined || element?.classList.contains('nr-unmask'))) return text;
91
- return '*'.repeat(text.length);
89
+ try {
90
+ if (typeof element?.type === 'string' && element.type.toLowerCase() !== 'password' && (element?.dataset?.nrUnmask !== undefined || element?.classList?.contains('nr-unmask'))) return text;
91
+ } catch (err) {
92
+ // likely an element was passed to this handler that was invalid and was missing attributes or methods
93
+ }
94
+ return '*'.repeat(text?.length || 0);
92
95
  };
93
96
  // set up rrweb configurations for maximum privacy --
94
97
  // https://newrelic.atlassian.net/wiki/spaces/O11Y/pages/2792293280/2023+02+28+Browser+-+Session+Replay#Configuration-options
@@ -103,7 +106,7 @@ class Recorder {
103
106
  maskTextFn: customMasker,
104
107
  maskAllInputs: mask_all_inputs,
105
108
  maskInputFn: customMasker,
106
- inlineStylesheet: inline_stylesheet,
109
+ inlineStylesheet: true,
107
110
  inlineImages: inline_images,
108
111
  collectFonts: collect_fonts,
109
112
  checkoutEveryNms: _constants.CHECKOUT_MS[this.parent.mode],
@@ -130,13 +133,17 @@ class Recorder {
130
133
  * @param {*} isCheckout - Flag indicating if the payload was triggered as a checkout
131
134
  */
132
135
  audit(event, isCheckout) {
133
- /** only run the audit if inline_stylesheets is configured as on (default behavior) */
134
- if (this.shouldInlineStylesheets === false || !this.shouldFix) {
135
- this.currentBufferTarget.inlinedAllStylesheets = false;
136
- return this.store(event, isCheckout);
137
- }
138
136
  /** An count of stylesheet objects that were blocked from accessing contents via JS */
139
137
  const incompletes = _stylesheetEvaluator.stylesheetEvaluator.evaluate();
138
+ const missingInlineSMTag = 'SessionReplay/Payload/Missing-Inline-Css/';
139
+ /** only run the full fixing behavior (more costly) if fix_stylesheets is configured as on (default behavior) */
140
+ if (!this.shouldFix) {
141
+ if (incompletes > 0) {
142
+ this.currentBufferTarget.inlinedAllStylesheets = false;
143
+ (0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Skipped', incompletes], undefined, _features.FEATURE_NAMES.metrics, this.parent.ee);
144
+ }
145
+ return this.store(event, isCheckout);
146
+ }
140
147
  /** 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) */
141
148
  if (!incompletes && this.#fixing && event.type === _constants.RRWEB_EVENT_TYPES.Meta) this.#fixing = false;
142
149
  if (incompletes > 0) {
@@ -146,8 +153,8 @@ class Recorder {
146
153
  this.currentBufferTarget.inlinedAllStylesheets = false;
147
154
  this.shouldFix = false;
148
155
  }
149
- (0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css/Failed', failedToFix], undefined, _features.FEATURE_NAMES.metrics, this.parent.ee);
150
- (0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css/Fixed', incompletes - failedToFix], undefined, _features.FEATURE_NAMES.metrics, this.parent.ee);
156
+ (0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Failed', failedToFix], undefined, _features.FEATURE_NAMES.metrics, this.parent.ee);
157
+ (0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Fixed', incompletes - failedToFix], undefined, _features.FEATURE_NAMES.metrics, this.parent.ee);
151
158
  this.takeFullSnapshot();
152
159
  });
153
160
  /** Only start ignoring data if got a faulty snapshot */
@@ -190,6 +197,7 @@ class Recorder {
190
197
  // snapshot event
191
198
  if (event.type === _constants.RRWEB_EVENT_TYPES.FullSnapshot) {
192
199
  this.currentBufferTarget.hasSnapshot = true;
200
+ this.hasSeenSnapshot = true;
193
201
  }
194
202
  this.currentBufferTarget.add(event);
195
203
 
@@ -8,7 +8,7 @@ var _nreum = require("../../../common/window/nreum");
8
8
  var _runtime = require("../../../common/constants/runtime");
9
9
  class StylesheetEvaluator {
10
10
  #evaluated = new WeakSet();
11
- #fetchProms = [];
11
+ #brokenSheets = [];
12
12
  /**
13
13
  * Flipped to true if stylesheets that cannot be natively inlined are detected by the stylesheetEvaluator class
14
14
  * Used at harvest time to denote that all subsequent payloads are subject to this and customers should be advised to handle crossorigin decoration
@@ -22,6 +22,7 @@ class StylesheetEvaluator {
22
22
  */
23
23
  evaluate() {
24
24
  let incompletes = 0;
25
+ this.#brokenSheets = [];
25
26
  if (_runtime.isBrowserScope) {
26
27
  for (let i = 0; i < Object.keys(document.styleSheets).length; i++) {
27
28
  if (!this.#evaluated.has(document.styleSheets[i])) {
@@ -32,7 +33,7 @@ class StylesheetEvaluator {
32
33
  } catch (err) {
33
34
  if (!document.styleSheets[i].href) return;
34
35
  incompletes++;
35
- this.#fetchProms.push(this.#fetchAndOverride(document.styleSheets[i]));
36
+ this.#brokenSheets.push(document.styleSheets[i]);
36
37
  }
37
38
  }
38
39
  }
@@ -46,8 +47,8 @@ class StylesheetEvaluator {
46
47
  * @returns {Promise}
47
48
  */
48
49
  async fix() {
49
- await Promise.all(this.#fetchProms);
50
- this.#fetchProms = [];
50
+ await Promise.all(this.#brokenSheets.map(sheet => this.#fetchAndOverride(sheet)));
51
+ this.#brokenSheets = [];
51
52
  const failedToFix = this.failedToFix;
52
53
  this.failedToFix = 0;
53
54
  return failedToFix;
@@ -50,7 +50,7 @@ class EventBuffer {
50
50
  * held is another event buffer
51
51
  */
52
52
  get held() {
53
- this.#held ??= new EventBuffer(this.maxPayloadSize);
53
+ if (!this.#held) this.#held = new EventBuffer(this.maxPayloadSize);
54
54
  return this.#held;
55
55
  }
56
56
 
@@ -63,7 +63,8 @@ class EventBuffer {
63
63
  }
64
64
 
65
65
  /**
66
- * Adds an event object to the buffer while tallying size
66
+ * Adds an event object to the buffer while tallying size. Only adds the event if it is valid
67
+ * and would not make the event buffer exceed the maxPayloadSize.
67
68
  * @param {Object} event the event object to add to the buffer
68
69
  * @returns {EventBuffer} returns the event buffer for chaining
69
70
  */
@@ -15,6 +15,7 @@ var _instrument7 = require("../features/spa/instrument");
15
15
  var _instrument8 = require("../features/session_replay/instrument");
16
16
  var _instrument9 = require("../features/generic_events/instrument");
17
17
  var _instrument10 = require("../features/logging/instrument");
18
+ var _instrument11 = require("../features/soft_navigations/instrument");
18
19
  /**
19
20
  * An agent class with all feature modules available. Features may be disabled and enabled via runtime configuration.
20
21
  * The BrowserAgent class is the most convenient and reliable option for most use cases.
@@ -23,7 +24,7 @@ class BrowserAgent extends _agent.Agent {
23
24
  constructor(args) {
24
25
  super({
25
26
  ...args,
26
- features: [_instrument5.Instrument, _instrument.Instrument, _instrument2.Instrument, _instrument6.Instrument, _instrument3.Instrument, _instrument4.Instrument, _instrument7.Instrument, _instrument8.Instrument, _instrument9.Instrument, _instrument10.Instrument],
27
+ features: [_instrument5.Instrument, _instrument.Instrument, _instrument2.Instrument, _instrument6.Instrument, _instrument3.Instrument, _instrument4.Instrument, _instrument7.Instrument, _instrument11.Instrument, _instrument8.Instrument, _instrument9.Instrument, _instrument10.Instrument],
27
28
  loaderType: 'browser-agent'
28
29
  });
29
30
  }
@@ -79,7 +79,6 @@ const model = () => {
79
79
  page_view_timing: {
80
80
  enabled: true,
81
81
  harvestTimeSeconds: 30,
82
- long_task: false,
83
82
  autoStart: true
84
83
  },
85
84
  privacy: {
@@ -110,10 +109,8 @@ const model = () => {
110
109
  // 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
111
110
  inline_images: false,
112
111
  // 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
113
- inline_stylesheet: true,
114
- // serialize css for collection without public asset url
115
112
  fix_stylesheets: true,
116
- // fetch missing stylesheet resources for inlining, only works if 'inline_stylesheet' is also true
113
+ // fetch missing stylesheet resources for inlining
117
114
  // recording config settings
118
115
  mask_all_inputs: true,
119
116
  // this has a getter/setter to facilitate validation of the selectors
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Exposes the version of the agent
8
8
  */
9
- export const VERSION = "1.265.1";
9
+ export const VERSION = "1.267.0";
10
10
 
11
11
  /**
12
12
  * Exposes the build type of the agent
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Exposes the version of the agent
8
8
  */
9
- export const VERSION = "1.265.1";
9
+ export const VERSION = "1.267.0";
10
10
 
11
11
  /**
12
12
  * Exposes the build type of the agent
@@ -1,6 +1,5 @@
1
1
  import { originTime } from '../constants/runtime';
2
2
  import { getRuntime } from '../config/runtime';
3
- 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$/;
4
3
 
5
4
  /**
6
5
  * Class used to adjust the timestamp of harvested data to New Relic server time. This
@@ -52,26 +51,21 @@ export class TimeKeeper {
52
51
  * @param rumRequest {XMLHttpRequest} The xhr for the rum request
53
52
  * @param startTime {number} The start time of the RUM request
54
53
  * @param endTime {number} The end time of the RUM request
54
+ * @param nrServerTime {number} the unix number value of the NR server time in MS, returned in the RUM request body
55
55
  */
56
- processRumRequest(rumRequest, startTime, endTime) {
56
+ processRumRequest(rumRequest, startTime, endTime, nrServerTime) {
57
57
  this.processStoredDiff(); // Check session entity for stored time diff
58
58
  if (this.#ready) return; // Server time calculated from session entity
59
59
 
60
- const responseDateHeader = rumRequest.getResponseHeader('Date');
61
- if (!responseDateHeader) {
62
- throw new Error('Missing date header on rum response.');
63
- }
64
- if (!rfc2616Regex.test(responseDateHeader)) {
65
- throw new Error('Date header invalid format.');
66
- }
60
+ if (!nrServerTime) throw new Error('nrServerTime not found');
67
61
  const medianRumOffset = (endTime - startTime) / 2;
68
62
  const serverOffset = startTime + medianRumOffset;
69
63
 
70
64
  // Corrected page origin time
71
- this.#correctedOriginTime = Math.floor(Date.parse(responseDateHeader) - serverOffset);
65
+ this.#correctedOriginTime = Math.floor(nrServerTime - serverOffset);
72
66
  this.#localTimeDiff = originTime - this.#correctedOriginTime;
73
67
  if (isNaN(this.#correctedOriginTime)) {
74
- throw new Error('Date header invalid format.');
68
+ throw new Error('Failed to correct browser time to server time');
75
69
  }
76
70
  this.#session?.write({
77
71
  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
  };
@@ -12,9 +12,17 @@ if (isBrowserScope) {
12
12
  }) => {
13
13
  const attrs = {
14
14
  metricId: id,
15
- eventTarget: attribution.eventTarget,
16
- eventType: attribution.eventType,
17
- eventTime: attribution.eventTime,
15
+ eventTarget: attribution.interactionTarget,
16
+ // event* attrs deprecated in v4, kept for NR backwards compatibility
17
+ eventTime: attribution.interactionTime,
18
+ // event* attrs deprecated in v4, kept for NR backwards compatibility
19
+ interactionTarget: attribution.interactionTarget,
20
+ interactionTime: attribution.interactionTime,
21
+ interactionType: attribution.interactionType,
22
+ inputDelay: attribution.inputDelay,
23
+ nextPaintTime: attribution.nextPaintTime,
24
+ processingDuration: attribution.processingDuration,
25
+ presentationDelay: attribution.presentationDelay,
18
26
  loadState: attribution.loadState
19
27
  };
20
28
  interactionToNextPaint.update({
@@ -20,7 +20,9 @@ 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,
25
+ // kept for NR backwards compatibility, deprecated in v3->v4
24
26
  elementRenderDelay: attribution.elementRenderDelay
25
27
  };
26
28
  if (attribution.url) attrs.elUrl = cleanURL(attribution.url);
@@ -64,8 +64,7 @@ export class Aggregate extends AggregateBase {
64
64
  } = getRuntime(this.agentIdentifier);
65
65
  const {
66
66
  proxy,
67
- privacy,
68
- page_view_timing
67
+ privacy
69
68
  } = getConfiguration(this.agentIdentifier);
70
69
  if (loaderType) this.storeSupportabilityMetrics("Generic/LoaderType/".concat(loaderType, "/Detected"));
71
70
  if (distMethod) this.storeSupportabilityMetrics("Generic/DistMethod/".concat(distMethod, "/Detected"));
@@ -83,7 +82,6 @@ export class Aggregate extends AggregateBase {
83
82
  });
84
83
  });
85
84
  if (!privacy.cookies_enabled) this.storeSupportabilityMetrics('Config/SessionTracking/Disabled');
86
- if (page_view_timing.long_task) this.storeSupportabilityMetrics('Config/LongTask/Enabled');
87
85
  } else if (isWorkerScope) {
88
86
  this.storeSupportabilityMetrics('Generic/Runtime/Worker/Detected');
89
87
  } else {
@@ -134,20 +134,20 @@ export class Aggregate extends AggregateBase {
134
134
  this.ee.abort();
135
135
  return;
136
136
  }
137
- try {
138
- this.timeKeeper.processRumRequest(xhr, rumStartTime, rumEndTime);
139
- if (!this.timeKeeper.ready) throw new Error('TimeKeeper not ready');
140
- agentRuntime.timeKeeper = this.timeKeeper;
141
- } catch (error) {
142
- this.ee.abort();
143
- warn(17, error);
144
- return;
145
- }
146
137
  try {
147
138
  const {
148
139
  app,
149
140
  ...flags
150
141
  } = JSON.parse(responseText);
142
+ try {
143
+ this.timeKeeper.processRumRequest(xhr, rumStartTime, rumEndTime, app.nrServerTime);
144
+ if (!this.timeKeeper.ready) throw new Error('TimeKeeper not ready');
145
+ agentRuntime.timeKeeper = this.timeKeeper;
146
+ } catch (error) {
147
+ this.ee.abort();
148
+ warn(17, error);
149
+ return;
150
+ }
151
151
  agentRuntime.appMetadata = app;
152
152
  activateFeatures(flags, this.agentIdentifier);
153
153
  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';
@@ -36,7 +35,6 @@ export class Aggregate extends AggregateBase {
36
35
  super(agentIdentifier, aggregator, FEATURE_NAME);
37
36
  this.timings = new EventBuffer();
38
37
  this.curSessEndRecorded = false;
39
- if (getConfigurationValue(this.agentIdentifier, 'page_view_timing.long_task') === true) longTask.subscribe(this.#handleVitalMetric);
40
38
  registerHandler('docHidden', msTimestamp => this.endCurrentSession(msTimestamp), this.featureName, this.ee);
41
39
  registerHandler('winPagehide', msTimestamp => this.recordPageUnload(msTimestamp), this.featureName, this.ee);
42
40
  const harvestTimeSeconds = getConfigurationValue(this.agentIdentifier, 'page_view_timing.harvestTimeSeconds') || 30;