@newrelic/browser-agent 1.258.2 → 1.260.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 (132) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/cjs/cdn/polyfills.js +3 -1
  3. package/dist/cjs/common/constants/env.cdn.js +1 -1
  4. package/dist/cjs/common/constants/env.npm.js +1 -1
  5. package/dist/cjs/common/drain/drain.js +1 -1
  6. package/dist/cjs/common/harvest/harvest-scheduler.js +4 -2
  7. package/dist/cjs/common/session/session-entity.js +8 -1
  8. package/dist/cjs/common/timing/time-keeper.js +9 -30
  9. package/dist/cjs/features/ajax/constants.js +3 -2
  10. package/dist/cjs/features/jserrors/instrument/index.js +3 -4
  11. package/dist/cjs/features/metrics/aggregate/index.js +0 -8
  12. package/dist/cjs/features/page_view_event/aggregate/index.js +8 -6
  13. package/dist/cjs/features/session_replay/aggregate/index.js +31 -36
  14. package/dist/cjs/features/session_replay/constants.js +5 -2
  15. package/dist/cjs/features/session_replay/instrument/index.js +53 -13
  16. package/dist/cjs/features/session_replay/shared/recorder.js +8 -1
  17. package/dist/cjs/features/session_replay/shared/utils.js +3 -5
  18. package/dist/cjs/features/session_trace/aggregate/index.js +181 -527
  19. package/dist/cjs/features/session_trace/aggregate/trace/node.js +19 -0
  20. package/dist/cjs/features/session_trace/aggregate/trace/storage.js +289 -0
  21. package/dist/cjs/features/session_trace/constants.js +3 -2
  22. package/dist/cjs/features/session_trace/instrument/index.js +7 -3
  23. package/dist/cjs/features/utils/aggregate-base.js +1 -0
  24. package/dist/cjs/features/utils/feature-gates.js +17 -0
  25. package/dist/cjs/features/utils/instrument-base.js +2 -1
  26. package/dist/cjs/loaders/agent-base.js +4 -0
  27. package/dist/cjs/loaders/api/api-methods.js +1 -1
  28. package/dist/cjs/loaders/api/api.js +2 -2
  29. package/dist/cjs/loaders/configure/configure.js +1 -0
  30. package/dist/esm/cdn/polyfills.js +3 -1
  31. package/dist/esm/common/constants/env.cdn.js +1 -1
  32. package/dist/esm/common/constants/env.npm.js +1 -1
  33. package/dist/esm/common/drain/drain.js +1 -1
  34. package/dist/esm/common/harvest/harvest-scheduler.js +4 -2
  35. package/dist/esm/common/session/session-entity.js +8 -1
  36. package/dist/esm/common/timing/time-keeper.js +9 -30
  37. package/dist/esm/features/ajax/constants.js +2 -1
  38. package/dist/esm/features/jserrors/instrument/index.js +3 -4
  39. package/dist/esm/features/metrics/aggregate/index.js +0 -8
  40. package/dist/esm/features/page_view_event/aggregate/index.js +8 -6
  41. package/dist/esm/features/session_replay/aggregate/index.js +32 -37
  42. package/dist/esm/features/session_replay/constants.js +4 -1
  43. package/dist/esm/features/session_replay/instrument/index.js +54 -14
  44. package/dist/esm/features/session_replay/shared/recorder.js +8 -1
  45. package/dist/esm/features/session_replay/shared/utils.js +4 -6
  46. package/dist/esm/features/session_trace/aggregate/index.js +182 -527
  47. package/dist/esm/features/session_trace/aggregate/trace/node.js +12 -0
  48. package/dist/esm/features/session_trace/aggregate/trace/storage.js +282 -0
  49. package/dist/esm/features/session_trace/constants.js +2 -1
  50. package/dist/esm/features/session_trace/instrument/index.js +7 -3
  51. package/dist/esm/features/utils/aggregate-base.js +1 -0
  52. package/dist/esm/features/utils/feature-gates.js +11 -0
  53. package/dist/esm/features/utils/instrument-base.js +3 -2
  54. package/dist/esm/loaders/agent-base.js +4 -0
  55. package/dist/esm/loaders/api/api-methods.js +1 -1
  56. package/dist/esm/loaders/api/api.js +2 -2
  57. package/dist/esm/loaders/configure/configure.js +1 -0
  58. package/dist/types/common/harvest/harvest-scheduler.d.ts +1 -0
  59. package/dist/types/common/harvest/harvest-scheduler.d.ts.map +1 -1
  60. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  61. package/dist/types/common/timing/time-keeper.d.ts +2 -0
  62. package/dist/types/common/timing/time-keeper.d.ts.map +1 -1
  63. package/dist/types/features/ajax/constants.d.ts +1 -0
  64. package/dist/types/features/ajax/constants.d.ts.map +1 -1
  65. package/dist/types/features/jserrors/instrument/index.d.ts.map +1 -1
  66. package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
  67. package/dist/types/features/page_view_event/aggregate/index.d.ts +2 -0
  68. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  69. package/dist/types/features/session_replay/aggregate/index.d.ts +1 -1
  70. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  71. package/dist/types/features/session_replay/constants.d.ts +3 -0
  72. package/dist/types/features/session_replay/instrument/index.d.ts +0 -1
  73. package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -1
  74. package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
  75. package/dist/types/features/session_replay/shared/utils.d.ts +1 -1
  76. package/dist/types/features/session_replay/shared/utils.d.ts.map +1 -1
  77. package/dist/types/features/session_trace/aggregate/index.d.ts +39 -52
  78. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  79. package/dist/types/features/session_trace/aggregate/trace/node.d.ts +12 -0
  80. package/dist/types/features/session_trace/aggregate/trace/node.d.ts.map +1 -0
  81. package/dist/types/features/session_trace/aggregate/trace/storage.d.ts +43 -0
  82. package/dist/types/features/session_trace/aggregate/trace/storage.d.ts.map +1 -0
  83. package/dist/types/features/session_trace/constants.d.ts +1 -0
  84. package/dist/types/features/session_trace/constants.d.ts.map +1 -1
  85. package/dist/types/features/session_trace/instrument/index.d.ts.map +1 -1
  86. package/dist/types/features/utils/aggregate-base.d.ts +1 -0
  87. package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
  88. package/dist/types/features/utils/feature-gates.d.ts +2 -0
  89. package/dist/types/features/utils/feature-gates.d.ts.map +1 -0
  90. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  91. package/dist/types/loaders/agent-base.d.ts +1 -0
  92. package/dist/types/loaders/agent-base.d.ts.map +1 -1
  93. package/dist/types/loaders/configure/configure.d.ts.map +1 -1
  94. package/package.json +1 -1
  95. package/src/cdn/polyfills.js +2 -0
  96. package/src/common/drain/drain.js +1 -1
  97. package/src/common/harvest/harvest-scheduler.js +4 -2
  98. package/src/common/session/session-entity.js +3 -1
  99. package/src/common/timing/time-keeper.js +9 -30
  100. package/src/features/ajax/constants.js +2 -0
  101. package/src/features/jserrors/instrument/index.js +3 -4
  102. package/src/features/metrics/aggregate/index.js +0 -8
  103. package/src/features/page_view_event/aggregate/index.js +9 -5
  104. package/src/features/session_replay/aggregate/index.js +30 -39
  105. package/src/features/session_replay/constants.js +4 -0
  106. package/src/features/session_replay/instrument/index.js +48 -8
  107. package/src/features/session_replay/shared/__mocks__/utils.js +0 -1
  108. package/src/features/session_replay/shared/recorder.js +8 -1
  109. package/src/features/session_replay/shared/utils.js +4 -7
  110. package/src/features/session_trace/aggregate/index.js +157 -493
  111. package/src/features/session_trace/aggregate/trace/node.js +12 -0
  112. package/src/features/session_trace/aggregate/trace/storage.js +287 -0
  113. package/src/features/session_trace/constants.js +1 -0
  114. package/src/features/session_trace/instrument/index.js +7 -2
  115. package/src/features/utils/__mocks__/feature-gates.js +1 -0
  116. package/src/features/utils/aggregate-base.js +1 -0
  117. package/src/features/utils/feature-gates.js +11 -0
  118. package/src/features/utils/instrument-base.js +3 -2
  119. package/src/loaders/agent-base.js +4 -0
  120. package/src/loaders/api/api-methods.js +1 -1
  121. package/src/loaders/api/api.js +2 -2
  122. package/src/loaders/configure/configure.js +1 -0
  123. package/dist/cjs/features/session_replay/shared/replay-mode.js +0 -28
  124. package/dist/cjs/features/utils/handler-cache.js +0 -70
  125. package/dist/esm/features/session_replay/shared/replay-mode.js +0 -23
  126. package/dist/esm/features/utils/handler-cache.js +0 -63
  127. package/dist/types/features/session_replay/shared/replay-mode.d.ts +0 -9
  128. package/dist/types/features/session_replay/shared/replay-mode.d.ts.map +0 -1
  129. package/dist/types/features/utils/handler-cache.d.ts +0 -23
  130. package/dist/types/features/utils/handler-cache.d.ts.map +0 -1
  131. package/src/features/session_replay/shared/replay-mode.js +0 -23
  132. package/src/features/utils/handler-cache.js +0 -65
package/CHANGELOG.md CHANGED
@@ -3,6 +3,26 @@
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.260.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.259.0...v1.260.0) (2024-05-13)
7
+
8
+
9
+ ### Features
10
+
11
+ * Improve time stamping of page view events ([#1026](https://github.com/newrelic/newrelic-browser-agent/issues/1026)) ([67a658d](https://github.com/newrelic/newrelic-browser-agent/commit/67a658d2645b680a479175dff06a4fd95bd6086a))
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * Add internal error handler to session replay recorder ([#1029](https://github.com/newrelic/newrelic-browser-agent/issues/1029)) ([84c101c](https://github.com/newrelic/newrelic-browser-agent/commit/84c101ccbff8da207bdf215714f49b7d49941388))
17
+ * Adjust session entity to not race between tabs ([#1032](https://github.com/newrelic/newrelic-browser-agent/issues/1032)) ([d86becf](https://github.com/newrelic/newrelic-browser-agent/commit/d86becf2fc02aa133430332aa4e4c2bc26297750))
18
+
19
+ ## [1.259.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.258.2...v1.259.0) (2024-05-08)
20
+
21
+
22
+ ### Features
23
+
24
+ * Migrate Session Traces to Use Blob Consumer with Feature Flags ([#821](https://github.com/newrelic/newrelic-browser-agent/issues/821)) ([55b0e00](https://github.com/newrelic/newrelic-browser-agent/commit/55b0e00e9d8dce6d0cdbed978a98302d40123f3d))
25
+
6
26
  ## [1.258.2](https://github.com/newrelic/newrelic-browser-agent/compare/v1.258.1...v1.258.2) (2024-05-07)
7
27
 
8
28
 
@@ -7,6 +7,7 @@ require("core-js/stable/array/flat");
7
7
  require("core-js/stable/array/flat-map");
8
8
  require("core-js/stable/array/from");
9
9
  require("core-js/stable/array/some");
10
+ require("core-js/stable/array/find-index");
10
11
  require("core-js/stable/object/assign");
11
12
  require("core-js/stable/object/entries");
12
13
  require("core-js/stable/object/values");
@@ -19,4 +20,5 @@ require("core-js/stable/object/get-own-property-descriptors");
19
20
  require("core-js/stable/url");
20
21
  require("core-js/stable/url-search-params");
21
22
  require("core-js/stable/string/starts-with");
22
- require("core-js/stable/number/is-nan");
23
+ require("core-js/stable/number/is-nan");
24
+ require("core-js/stable/string/includes");
@@ -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.258.2";
15
+ const VERSION = exports.VERSION = "1.260.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.258.2";
15
+ const VERSION = exports.VERSION = "1.260.0";
16
16
 
17
17
  /**
18
18
  * Exposes the build type of the agent
@@ -83,7 +83,7 @@ function drain() {
83
83
  function checkCanDrainAll(agentIdentifier) {
84
84
  // Only when the event-groups for all features are ready to drain (staged) do we execute the drain. This has the effect
85
85
  // that the last feature to call drain triggers drain for all features.
86
- const items = [...registry[agentIdentifier]];
86
+ const items = Array.from(registry[agentIdentifier]);
87
87
  if (items.every(_ref => {
88
88
  let [key, values] = _ref;
89
89
  return values.staged;
@@ -38,7 +38,7 @@ class HarvestScheduler extends _sharedContext.SharedContext {
38
38
  this.started = false;
39
39
  this.timeoutHandle = null;
40
40
  this.aborted = false; // this controls the per-interval and final harvests for the scheduler (currently per feature specific!)
41
-
41
+ this.harvesting = false;
42
42
  this.harvest = new _harvest.Harvest(this.sharedContext);
43
43
 
44
44
  // unload if EOL mechanism fires
@@ -89,12 +89,14 @@ class HarvestScheduler extends _sharedContext.SharedContext {
89
89
  }
90
90
  runHarvest(opts) {
91
91
  if (this.aborted) return;
92
+ this.harvesting = true;
92
93
 
93
94
  /**
94
95
  * This is executed immediately after harvest sends the data via XHR, or if there's nothing to send. Note that this excludes on unloading / sendBeacon.
95
96
  * @param {Object} result
96
97
  */
97
98
  const cbRanAfterSend = result => {
99
+ this.harvesting = false;
98
100
  if (opts?.forceNoRetry) result.retry = false; // discard unsent data rather than re-queuing for next harvest attempt
99
101
  this.onHarvestFinished(opts, result);
100
102
  };
@@ -110,7 +112,7 @@ class HarvestScheduler extends _sharedContext.SharedContext {
110
112
  const retry = !opts?.unload && submitMethod === submitData.xhr;
111
113
  payload = this.opts.getPayload({
112
114
  retry,
113
- opts
115
+ ...opts
114
116
  });
115
117
  if (!payload) {
116
118
  if (this.started) {
@@ -74,8 +74,15 @@ class SessionEntity {
74
74
  expiresMs = _constants.DEFAULT_EXPIRES_MS,
75
75
  inactiveMs = _constants.DEFAULT_INACTIVE_MS
76
76
  } = _ref;
77
+ /** Ensure that certain properties are preserved across a reset if already set */
78
+ const persistentAttributes = {
79
+ serverTimeDiff: this.state.serverTimeDiff || model.serverTimeDiff
80
+ };
77
81
  this.state = {};
78
- this.sync(model);
82
+ this.sync({
83
+ ...model,
84
+ ...persistentAttributes
85
+ });
79
86
 
80
87
  // value is intended to act as the primary value of the k=v pair
81
88
  this.state.value = value;
@@ -5,9 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.TimeKeeper = void 0;
7
7
  var _runtime = require("../constants/runtime");
8
- var _contextualEe = require("../event-emitter/contextual-ee");
9
8
  var _config = require("../config/config");
10
- var _constants = require("../session/constants");
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
@@ -41,18 +39,7 @@ class TimeKeeper {
41
39
  #ready = false;
42
40
  constructor(agentIdentifier) {
43
41
  this.#session = (0, _config.getRuntime)(agentIdentifier)?.session;
44
- if (this.#session) {
45
- const ee = _contextualEe.ee.get(agentIdentifier);
46
- ee.on(_constants.SESSION_EVENTS.UPDATE, this.#processSessionUpdate.bind(this));
47
- ee.on(_constants.SESSION_EVENTS.STARTED, () => {
48
- if (this.#ready) {
49
- this.#session.write({
50
- serverTimeDiff: this.#localTimeDiff
51
- });
52
- }
53
- });
54
- this.#processSessionUpdate(null, this.#session.read());
55
- }
42
+ this.processStoredDiff();
56
43
  }
57
44
  get ready() {
58
45
  return this.#ready;
@@ -68,8 +55,8 @@ class TimeKeeper {
68
55
  * @param endTime {number} The end time of the RUM request
69
56
  */
70
57
  processRumRequest(rumRequest, startTime, endTime) {
58
+ this.processStoredDiff();
71
59
  if (this.#ready) return; // Server time calculated from session entity
72
-
73
60
  const responseDateHeader = rumRequest.getResponseHeader('Date');
74
61
  if (!responseDateHeader) {
75
62
  throw new Error('Missing date header on rum response.');
@@ -80,10 +67,10 @@ class TimeKeeper {
80
67
  // Corrected page origin time
81
68
  this.#correctedOriginTime = Math.floor(Date.parse(responseDateHeader) - serverOffset);
82
69
  this.#localTimeDiff = _runtime.originTime - this.#correctedOriginTime;
83
- if (Number.isNaN(this.#correctedOriginTime)) {
70
+ if (isNaN(this.#correctedOriginTime)) {
84
71
  throw new Error('Date header invalid format.');
85
72
  }
86
- if (this.#session) this.#session.write({
73
+ this.#session?.write({
87
74
  serverTimeDiff: this.#localTimeDiff
88
75
  });
89
76
  this.#ready = true;
@@ -108,19 +95,11 @@ class TimeKeeper {
108
95
  return Math.floor(timestamp - this.#localTimeDiff);
109
96
  }
110
97
 
111
- /**
112
- * Processes a session entity update payload to extract the server time calculated.
113
- * @param {import('../session/constants').SESSION_EVENT_TYPES | null} type
114
- * @param {Object} data
115
- */
116
- #processSessionUpdate(type, data) {
117
- if (typeof data?.serverTimeDiff !== 'number') return;
118
- if (!type && !this.#ready ||
119
- // This captures the initial read from the session entity when the timekeeper first initializes
120
- type === _constants.SESSION_EVENT_TYPES.CROSS_TAB // This captures any cross-tab write of the session entity
121
- ) {
122
- // This captures the initial read from the session entity when the timekeeper first initializes
123
- this.#localTimeDiff = data.serverTimeDiff;
98
+ /** Process the session entity and use the info to set the main time calculations if present */
99
+ processStoredDiff() {
100
+ const storedServerTimeDiff = this.#session?.read()?.serverTimeDiff;
101
+ if (typeof storedServerTimeDiff === 'number' && !isNaN(storedServerTimeDiff)) {
102
+ this.#localTimeDiff = storedServerTimeDiff;
124
103
  this.#correctedOriginTime = _runtime.originTime - this.#localTimeDiff;
125
104
  this.#ready = true;
126
105
  }
@@ -3,6 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.FEATURE_NAME = void 0;
6
+ exports.MAX_PAYLOAD_SIZE = exports.FEATURE_NAME = void 0;
7
7
  var _features = require("../../loaders/features/features");
8
- const FEATURE_NAME = exports.FEATURE_NAME = _features.FEATURE_NAMES.ajax;
8
+ const FEATURE_NAME = exports.FEATURE_NAME = _features.FEATURE_NAMES.ajax;
9
+ const MAX_PAYLOAD_SIZE = exports.MAX_PAYLOAD_SIZE = 1000000;
@@ -7,7 +7,6 @@ exports.Instrument = void 0;
7
7
  var _handle = require("../../../common/event-emitter/handle");
8
8
  var _instrumentBase = require("../../utils/instrument-base");
9
9
  var _constants = require("../constants");
10
- var _features = require("../../../loaders/features/features");
11
10
  var _runtime = require("../../../common/constants/runtime");
12
11
  var _eventListenerOpts = require("../../../common/event-listener/event-listener-opts");
13
12
  var _now = require("../../../common/timing/now");
@@ -30,7 +29,7 @@ class Instrument extends _instrumentBase.InstrumentBase {
30
29
  } catch (e) {}
31
30
  this.ee.on('internal-error', error => {
32
31
  if (!this.abortHandler) return;
33
- (0, _handle.handle)('ierr', [(0, _castError.castError)(error), (0, _now.now)(), true, {}, this.#replayRunning], undefined, _features.FEATURE_NAMES.jserrors, this.ee);
32
+ (0, _handle.handle)('ierr', [(0, _castError.castError)(error), (0, _now.now)(), true, {}, this.#replayRunning], undefined, this.featureName, this.ee);
34
33
  });
35
34
  this.ee.on(_constants2.SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, isRunning => {
36
35
  this.#replayRunning = isRunning;
@@ -39,11 +38,11 @@ class Instrument extends _instrumentBase.InstrumentBase {
39
38
  if (!this.abortHandler) return;
40
39
  (0, _handle.handle)('err', [(0, _castError.castPromiseRejectionEvent)(promiseRejectionEvent), (0, _now.now)(), false, {
41
40
  unhandledPromiseRejection: 1
42
- }, this.#replayRunning], undefined, _features.FEATURE_NAMES.jserrors, this.ee);
41
+ }, this.#replayRunning], undefined, this.featureName, this.ee);
43
42
  }, (0, _eventListenerOpts.eventListenerOpts)(false, this.removeOnAbort?.signal));
44
43
  _runtime.globalScope.addEventListener('error', errorEvent => {
45
44
  if (!this.abortHandler) return;
46
- (0, _handle.handle)('err', [(0, _castError.castErrorEvent)(errorEvent), (0, _now.now)(), false, {}, this.#replayRunning], undefined, _features.FEATURE_NAMES.jserrors, this.ee);
45
+ (0, _handle.handle)('err', [(0, _castError.castErrorEvent)(errorEvent), (0, _now.now)(), false, {}, this.#replayRunning], undefined, this.featureName, this.ee);
47
46
  }, (0, _eventListenerOpts.eventListenerOpts)(false, this.removeOnAbort?.signal));
48
47
  this.abortHandler = this.#abort; // we also use this as a flag to denote that the feature is active or on and handling errors
49
48
  this.importAggregator();
@@ -124,8 +124,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
124
124
  if (this.resourcesSent) return;
125
125
  this.resourcesSent = true; // make sure this only gets sent once
126
126
 
127
- const agentRuntime = (0, _config.getRuntime)(this.agentIdentifier);
128
-
129
127
  // Capture SMs around network resources using the performance API to assess
130
128
  // work to split this out from the ST nodes
131
129
  // differentiate between internal+external and ajax+non-ajax
@@ -146,12 +144,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
146
144
  }
147
145
  });
148
146
 
149
- // Capture SMs for session trace if active (`ptid` is set when returned by replay ingest).
150
- // Retain these SMs while we are working through the session_replay feature
151
- if (agentRuntime.ptid) {
152
- this.storeSupportabilityMetrics('PageSession/Feature/SessionTrace/DurationMs', Math.round(performance.now()));
153
- }
154
-
155
147
  // Capture SMs for performance markers and measures to assess the usage and possible inclusion of this
156
148
  // data in the agent for use in NR
157
149
  if (typeof performance !== 'undefined') {
@@ -32,7 +32,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
32
32
  this.timeToFirstByte = 0;
33
33
  this.firstByteToWindowLoad = 0; // our "frontend" duration
34
34
  this.firstByteToDomContent = 0; // our "dom processing" duration
35
-
35
+ this.timeKeeper = new _timeKeeper.TimeKeeper(this.agentIdentifier);
36
36
  if (_runtime.isBrowserScope) {
37
37
  _timeToFirstByte.timeToFirstByte.subscribe(_ref => {
38
38
  let {
@@ -118,6 +118,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
118
118
  }
119
119
  queryParameters.fp = _firstPaint.firstPaint.current.value;
120
120
  queryParameters.fcp = _firstContentfulPaint.firstContentfulPaint.current.value;
121
+ if (this.timeKeeper?.ready) {
122
+ queryParameters.timestamp = this.timeKeeper.convertRelativeTimestamp((0, _now.now)());
123
+ }
121
124
  const rumStartTime = (0, _now.now)();
122
125
  harvester.send({
123
126
  endpoint: 'rum',
@@ -142,15 +145,14 @@ class Aggregate extends _aggregateBase.AggregateBase {
142
145
  return;
143
146
  }
144
147
  try {
145
- const timeKeeper = new _timeKeeper.TimeKeeper(this.agentIdentifier);
146
- timeKeeper.processRumRequest(xhr, rumStartTime, rumEndTime);
147
- if (!timeKeeper.ready) throw new Error('TimeKeeper not ready');
148
- agentRuntime.timeKeeper = timeKeeper;
148
+ this.timeKeeper.processRumRequest(xhr, rumStartTime, rumEndTime);
149
+ if (!this.timeKeeper.ready) throw new Error('TimeKeeper not ready');
150
+ agentRuntime.timeKeeper = this.timeKeeper;
149
151
  } catch (error) {
150
152
  (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['PVE/NRTime/Calculation/Failed'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
151
153
  (0, _drain.drain)(this.agentIdentifier, _features.FEATURE_NAMES.metrics, true);
152
154
  this.ee.abort();
153
- (0, _console.warn)('Could not calculate New Relic server time. Agent shutting down.');
155
+ (0, _console.warn)('Could not calculate New Relic server time. Agent shutting down.', error);
154
156
  return;
155
157
  }
156
158
  try {
@@ -96,15 +96,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
96
96
  getPayload: this.prepareHarvest.bind(this),
97
97
  raw: true
98
98
  }, this);
99
- (0, _registerHandler.registerHandler)(_constants.SR_EVENT_EMITTER_TYPES.RECORD, () => {
100
- // if it has aborted or BCS returned bad entitlements, do not allow
101
- if (this.blocked || !this.entitled) return;
102
- // if it isnt already (fully) initialized... initialize it
103
- if (!this.recorder) this.initializeRecording(false, true, true);
104
- // its been initialized and imported the recorder but its not recording (mode === off || error)
105
- else if (this.mode !== _constants3.MODE.FULL) this.switchToFull();
106
- // if it gets all the way to here, that means a full session is already recording... do nothing
107
- }, this.featureName, this.ee);
108
99
  (0, _registerHandler.registerHandler)(_constants.SR_EVENT_EMITTER_TYPES.PAUSE, () => {
109
100
  this.forceStop(this.mode !== _constants3.MODE.ERROR);
110
101
  }, this.featureName, this.ee);
@@ -122,9 +113,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
122
113
  inline_images,
123
114
  collect_fonts
124
115
  } = (0, _config.getConfigurationValue)(this.agentIdentifier, 'session_replay');
125
- this.waitForFlags(['sr']).then(_ref => {
126
- let [flagOn] = _ref;
127
- this.entitled = flagOn;
116
+ this.waitForFlags(['srs', 'sr']).then(_ref => {
117
+ let [srMode, entitled] = _ref;
118
+ this.entitled = !!entitled;
128
119
  if (!this.entitled) {
129
120
  (0, _drain.deregisterDrain)(this.agentIdentifier, this.featureName);
130
121
  if (this.recorder?.recording) {
@@ -134,11 +125,14 @@ class Aggregate extends _aggregateBase.AggregateBase {
134
125
  return;
135
126
  }
136
127
  this.drain();
137
- this.initializeRecording(Math.random() * 100 < error_sampling_rate, Math.random() * 100 < sampling_rate);
128
+ this.initializeRecording(srMode);
138
129
  }).then(() => {
139
- if (this.mode === _constants3.MODE.OFF) args?.recorder?.stopRecording(); // stop any conservative preload recording launched by instrument
140
- _sharedChannel.sharedChannel.onReplayReady(this.mode); // notify watchers that replay started with the mode
141
- });
130
+ if (this.mode === _constants3.MODE.OFF) {
131
+ this.recorder?.stopRecording(); // stop any conservative preload recording launched by instrument
132
+ while (this.recorder?.getEvents().events.length) this.recorder?.clearBuffer?.();
133
+ }
134
+ _sharedChannel.sharedChannel.onReplayReady(this.mode);
135
+ }); // notify watchers that replay started with the mode
142
136
 
143
137
  /** 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 */
144
138
  if (!autoStart) (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/AutoStart/Modified'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
@@ -181,7 +175,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
181
175
  * @param {boolean} ignoreSession - whether to force the method to ignore the session state and use just the sample flags
182
176
  * @returns {void}
183
177
  */
184
- async initializeRecording(errorSample, fullSample, ignoreSession) {
178
+ async initializeRecording(srMode, ignoreSession) {
185
179
  this.initialized = true;
186
180
  if (!this.entitled) return;
187
181
 
@@ -196,23 +190,17 @@ class Aggregate extends _aggregateBase.AggregateBase {
196
190
  timeKeeper
197
191
  } = (0, _config.getRuntime)(this.agentIdentifier);
198
192
  this.timeKeeper = timeKeeper;
199
- if (!session.isNew && !ignoreSession) {
193
+ if (this.recorder?.parent.trigger === _constants.TRIGGERS.API && this.recorder?.recording) {
194
+ this.mode = _constants3.MODE.FULL;
195
+ } else if (!session.isNew && !ignoreSession) {
200
196
  // inherit the mode of the existing session
201
197
  this.mode = session.state.sessionReplayMode;
202
198
  } else {
203
199
  // The session is new... determine the mode the new session should start in
204
- if (fullSample) this.mode = _constants3.MODE.FULL; // full mode has precedence over error mode
205
- else if (errorSample) this.mode = _constants3.MODE.ERROR;
206
- // If neither are selected, then don't record (early return)
207
- else {
208
- return;
209
- }
210
- }
211
- if (this.recorder?.getEvents().type === 'preloaded') {
212
- this.prepUtils().then(() => {
213
- this.scheduler.runHarvest();
214
- });
200
+ this.mode = srMode;
215
201
  }
202
+ // If off, then don't record (early return)
203
+ if (this.mode === _constants3.MODE.OFF) return;
216
204
  if (!this.recorder) {
217
205
  try {
218
206
  // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
@@ -230,13 +218,20 @@ class Aggregate extends _aggregateBase.AggregateBase {
230
218
 
231
219
  // If an error was noticed before the mode could be set (like in the early lifecycle of the page), immediately set to FULL mode
232
220
  if (this.mode === _constants3.MODE.ERROR && this.errorNoticed) this.mode = _constants3.MODE.FULL;
233
-
234
- // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
235
- // ERROR mode will do this until an error is thrown, and then switch into FULL mode.
236
- // If an error happened in ERROR mode before we've gotten to this stage, it will have already set the mode to FULL
237
- if (this.mode === _constants3.MODE.FULL && !this.scheduler.started) {
238
- // We only report (harvest) in FULL mode
239
- this.scheduler.startTimer(this.harvestTimeSeconds);
221
+ if (this.mode === _constants3.MODE.FULL) {
222
+ // If theres preloaded events and we are in full mode, just harvest immediately to clear up space and for consistency
223
+ if (this.recorder?.getEvents().type === 'preloaded') {
224
+ this.prepUtils().then(() => {
225
+ this.scheduler.runHarvest();
226
+ });
227
+ }
228
+ // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
229
+ // ERROR mode will do this until an error is thrown, and then switch into FULL mode.
230
+ // If an error happened in ERROR mode before we've gotten to this stage, it will have already set the mode to FULL
231
+ if (!this.scheduler.started) {
232
+ // We only report (harvest) in FULL mode
233
+ this.scheduler.startTimer(this.harvestTimeSeconds);
234
+ }
240
235
  }
241
236
  await this.prepUtils();
242
237
  if (!this.recorder.recording) this.recorder.startRecording();
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.SR_EVENT_EMITTER_TYPES = exports.RRWEB_EVENT_TYPES = exports.QUERY_PARAM_PADDING = exports.MAX_PAYLOAD_SIZE = exports.IDEAL_PAYLOAD_SIZE = exports.FEATURE_NAME = exports.CHECKOUT_MS = exports.AVG_COMPRESSION = exports.ABORT_REASONS = void 0;
6
+ exports.TRIGGERS = exports.SR_EVENT_EMITTER_TYPES = exports.RRWEB_EVENT_TYPES = exports.QUERY_PARAM_PADDING = exports.MAX_PAYLOAD_SIZE = exports.IDEAL_PAYLOAD_SIZE = exports.FEATURE_NAME = exports.CHECKOUT_MS = exports.AVG_COMPRESSION = exports.ABORT_REASONS = void 0;
7
7
  var _constants = require("../../common/session/constants");
8
8
  var _features = require("../../loaders/features/features");
9
9
  const FEATURE_NAME = exports.FEATURE_NAME = _features.FEATURE_NAMES.sessionReplay;
@@ -59,4 +59,7 @@ const ABORT_REASONS = exports.ABORT_REASONS = {
59
59
  }
60
60
  };
61
61
  /** Reserved room for query param attrs */
62
- const QUERY_PARAM_PADDING = exports.QUERY_PARAM_PADDING = 5000;
62
+ const QUERY_PARAM_PADDING = exports.QUERY_PARAM_PADDING = 5000;
63
+ const TRIGGERS = exports.TRIGGERS = {
64
+ API: 'api'
65
+ };
@@ -7,8 +7,8 @@ exports.Instrument = void 0;
7
7
  var _handle = require("../../../common/event-emitter/handle");
8
8
  var _constants = require("../../../common/session/constants");
9
9
  var _instrumentBase = require("../../utils/instrument-base");
10
- var _constants2 = require("../constants");
11
10
  var _utils = require("../shared/utils");
11
+ var _constants2 = require("../constants");
12
12
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
13
13
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } /*
14
14
  * Copyright 2023 New Relic Corporation. All rights reserved.
@@ -22,6 +22,7 @@ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e;
22
22
  */
23
23
  class Instrument extends _instrumentBase.InstrumentBase {
24
24
  static featureName = _constants2.FEATURE_NAME;
25
+ #mode;
25
26
  constructor(agentIdentifier, aggregator) {
26
27
  let auto = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
27
28
  super(agentIdentifier, aggregator, _constants2.FEATURE_NAME, auto);
@@ -30,8 +31,12 @@ class Instrument extends _instrumentBase.InstrumentBase {
30
31
  try {
31
32
  session = JSON.parse(localStorage.getItem("".concat(_constants.PREFIX, "_").concat(_constants.DEFAULT_KEY)));
32
33
  } catch (err) {}
34
+ if ((0, _utils.hasReplayPrerequisite)(agentIdentifier)) {
35
+ this.ee.on('recordReplay', () => this.#apiStartOrRestartReplay());
36
+ }
33
37
  if (this.#canPreloadRecorder(session)) {
34
- this.#startRecording(session?.sessionReplayMode);
38
+ this.#mode = session?.sessionReplayMode;
39
+ this.#preloadStartRecording();
35
40
  } else {
36
41
  this.importAggregator();
37
42
  }
@@ -64,21 +69,56 @@ class Instrument extends _instrumentBase.InstrumentBase {
64
69
  return (0, _utils.isPreloadAllowed)(this.agentIdentifier);
65
70
  }
66
71
  }
67
- async #startRecording(mode) {
68
- const {
69
- Recorder
70
- } = await Promise.resolve().then(() => _interopRequireWildcard(require( /* webpackChunkName: "recorder" */'../shared/recorder')));
71
- this.recorder = new Recorder({
72
- mode,
73
- agentIdentifier: this.agentIdentifier,
74
- ee: this.ee
75
- });
76
- this.recorder.startRecording();
77
- this.abortHandler = this.recorder.stopRecording;
72
+ #alreadyStarted = false;
73
+ /**
74
+ * This func is use for early pre-load recording prior to replay feature (agg) being loaded onto the page. It should only setup once, including if already called and in-progress.
75
+ */
76
+ async #preloadStartRecording(trigger) {
77
+ if (this.#alreadyStarted) return;
78
+ this.#alreadyStarted = true;
79
+ try {
80
+ const {
81
+ Recorder
82
+ } = await Promise.resolve().then(() => _interopRequireWildcard(require( /* webpackChunkName: "recorder" */'../shared/recorder')));
83
+
84
+ // If startReplay() has been used by this point, we must record in full mode regardless of session preload:
85
+ // Note: recorder starts here with w/e the mode is at this time, but this may be changed later (see #apiStartOrRestartReplay else-case)
86
+ this.recorder ??= new Recorder({
87
+ mode: this.#mode,
88
+ agentIdentifier: this.agentIdentifier,
89
+ trigger,
90
+ ee: this.ee
91
+ });
92
+ this.recorder.startRecording();
93
+ this.abortHandler = this.recorder.stopRecording;
94
+ } catch (e) {}
78
95
  this.importAggregator({
79
96
  recorder: this.recorder,
80
97
  errorNoticed: this.errorNoticed
81
98
  });
82
99
  }
100
+
101
+ /**
102
+ * Called whenever startReplay API is used. That could occur any time, pre or post load.
103
+ */
104
+ #apiStartOrRestartReplay() {
105
+ if (this.featAggregate) {
106
+ // post-load; there's possibly already an ongoing recording
107
+ if (this.featAggregate.mode !== _constants.MODE.FULL) this.featAggregate.initializeRecording(_constants.MODE.FULL, true);
108
+ } else {
109
+ // pre-load
110
+ this.#mode = _constants.MODE.FULL;
111
+ this.#preloadStartRecording(_constants2.TRIGGERS.API);
112
+ // There's a race here wherein either:
113
+ // a. Recorder has not been initialized, and we've set the enforced mode, so we're good, or;
114
+ // b. Record has been initialized, possibly with the "wrong" mode, so we have to correct that + restart.
115
+ if (this.recorder && this.recorder.parent.mode !== _constants.MODE.FULL) {
116
+ this.recorder.parent.mode = _constants.MODE.FULL;
117
+ this.recorder.stopRecording();
118
+ this.recorder.startRecording();
119
+ this.abortHandler = this.recorder.stopRecording;
120
+ }
121
+ }
122
+ }
83
123
  }
84
124
  exports.Instrument = Instrument;
@@ -101,7 +101,14 @@ class Recorder {
101
101
  inlineStylesheet: inline_stylesheet,
102
102
  inlineImages: inline_images,
103
103
  collectFonts: collect_fonts,
104
- checkoutEveryNms: _constants.CHECKOUT_MS[this.parent.mode]
104
+ checkoutEveryNms: _constants.CHECKOUT_MS[this.parent.mode],
105
+ /** Emits errors thrown by rrweb directly before bubbling them up to the window */
106
+ errorHandler: err => {
107
+ /** capture rrweb errors as "internal" errors only */
108
+ this.parent.ee.emit('internal-error', [err]);
109
+ /** returning true informs rrweb to swallow the error instead of throwing it to the window */
110
+ return true;
111
+ }
105
112
  });
106
113
  this.stopRecording = () => {
107
114
  this.recording = false;
@@ -5,17 +5,15 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.buildNRMetaNode = buildNRMetaNode;
7
7
  exports.canImportReplayAgg = canImportReplayAgg;
8
- exports.enableSessionTracking = enableSessionTracking;
8
+ exports.hasReplayPrerequisite = hasReplayPrerequisite;
9
9
  exports.isPreloadAllowed = isPreloadAllowed;
10
10
  var _config = require("../../../common/config/config");
11
+ var _featureGates = require("../../utils/feature-gates");
11
12
  var _runtime = require("../../../common/constants/runtime");
12
- function enableSessionTracking(agentId) {
13
- return _runtime.isBrowserScope && (0, _config.getConfigurationValue)(agentId, 'privacy.cookies_enabled') === true;
14
- }
15
13
  function hasReplayPrerequisite(agentId) {
16
14
  return !!_config.originals.MO &&
17
15
  // Session Replay cannot work without Mutation Observer
18
- enableSessionTracking(agentId) &&
16
+ (0, _featureGates.canEnableSessionTracking)(agentId) &&
19
17
  // requires session tracking to be running (hence "session" replay...)
20
18
  (0, _config.getConfigurationValue)(agentId, 'session_trace.enabled') === true; // Session Replay as of now is tightly coupled with Session Trace in the UI
21
19
  }