@newrelic/browser-agent 1.258.2 → 1.259.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 (117) hide show
  1. package/CHANGELOG.md +7 -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/features/ajax/constants.js +3 -2
  8. package/dist/cjs/features/jserrors/aggregate/index.js +2 -1
  9. package/dist/cjs/features/metrics/aggregate/index.js +0 -8
  10. package/dist/cjs/features/page_view_event/aggregate/index.js +1 -1
  11. package/dist/cjs/features/session_replay/aggregate/index.js +31 -36
  12. package/dist/cjs/features/session_replay/constants.js +5 -2
  13. package/dist/cjs/features/session_replay/instrument/index.js +53 -13
  14. package/dist/cjs/features/session_replay/shared/utils.js +3 -5
  15. package/dist/cjs/features/session_trace/aggregate/index.js +181 -527
  16. package/dist/cjs/features/session_trace/aggregate/trace/node.js +19 -0
  17. package/dist/cjs/features/session_trace/aggregate/trace/storage.js +289 -0
  18. package/dist/cjs/features/session_trace/constants.js +3 -2
  19. package/dist/cjs/features/session_trace/instrument/index.js +7 -3
  20. package/dist/cjs/features/utils/aggregate-base.js +1 -0
  21. package/dist/cjs/features/utils/feature-gates.js +17 -0
  22. package/dist/cjs/features/utils/instrument-base.js +2 -1
  23. package/dist/cjs/loaders/agent-base.js +4 -0
  24. package/dist/cjs/loaders/api/api-methods.js +1 -1
  25. package/dist/cjs/loaders/api/api.js +2 -2
  26. package/dist/cjs/loaders/configure/configure.js +1 -0
  27. package/dist/esm/cdn/polyfills.js +3 -1
  28. package/dist/esm/common/constants/env.cdn.js +1 -1
  29. package/dist/esm/common/constants/env.npm.js +1 -1
  30. package/dist/esm/common/drain/drain.js +1 -1
  31. package/dist/esm/common/harvest/harvest-scheduler.js +4 -2
  32. package/dist/esm/features/ajax/constants.js +2 -1
  33. package/dist/esm/features/jserrors/aggregate/index.js +2 -1
  34. package/dist/esm/features/metrics/aggregate/index.js +0 -8
  35. package/dist/esm/features/page_view_event/aggregate/index.js +1 -1
  36. package/dist/esm/features/session_replay/aggregate/index.js +32 -37
  37. package/dist/esm/features/session_replay/constants.js +4 -1
  38. package/dist/esm/features/session_replay/instrument/index.js +54 -14
  39. package/dist/esm/features/session_replay/shared/utils.js +4 -6
  40. package/dist/esm/features/session_trace/aggregate/index.js +182 -527
  41. package/dist/esm/features/session_trace/aggregate/trace/node.js +12 -0
  42. package/dist/esm/features/session_trace/aggregate/trace/storage.js +282 -0
  43. package/dist/esm/features/session_trace/constants.js +2 -1
  44. package/dist/esm/features/session_trace/instrument/index.js +7 -3
  45. package/dist/esm/features/utils/aggregate-base.js +1 -0
  46. package/dist/esm/features/utils/feature-gates.js +11 -0
  47. package/dist/esm/features/utils/instrument-base.js +3 -2
  48. package/dist/esm/loaders/agent-base.js +4 -0
  49. package/dist/esm/loaders/api/api-methods.js +1 -1
  50. package/dist/esm/loaders/api/api.js +2 -2
  51. package/dist/esm/loaders/configure/configure.js +1 -0
  52. package/dist/types/common/harvest/harvest-scheduler.d.ts +1 -0
  53. package/dist/types/common/harvest/harvest-scheduler.d.ts.map +1 -1
  54. package/dist/types/features/ajax/constants.d.ts +1 -0
  55. package/dist/types/features/ajax/constants.d.ts.map +1 -1
  56. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  57. package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
  58. package/dist/types/features/session_replay/aggregate/index.d.ts +1 -1
  59. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  60. package/dist/types/features/session_replay/constants.d.ts +3 -0
  61. package/dist/types/features/session_replay/instrument/index.d.ts +0 -1
  62. package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -1
  63. package/dist/types/features/session_replay/shared/utils.d.ts +1 -1
  64. package/dist/types/features/session_replay/shared/utils.d.ts.map +1 -1
  65. package/dist/types/features/session_trace/aggregate/index.d.ts +39 -52
  66. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  67. package/dist/types/features/session_trace/aggregate/trace/node.d.ts +12 -0
  68. package/dist/types/features/session_trace/aggregate/trace/node.d.ts.map +1 -0
  69. package/dist/types/features/session_trace/aggregate/trace/storage.d.ts +43 -0
  70. package/dist/types/features/session_trace/aggregate/trace/storage.d.ts.map +1 -0
  71. package/dist/types/features/session_trace/constants.d.ts +1 -0
  72. package/dist/types/features/session_trace/constants.d.ts.map +1 -1
  73. package/dist/types/features/session_trace/instrument/index.d.ts.map +1 -1
  74. package/dist/types/features/utils/aggregate-base.d.ts +1 -0
  75. package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
  76. package/dist/types/features/utils/feature-gates.d.ts +2 -0
  77. package/dist/types/features/utils/feature-gates.d.ts.map +1 -0
  78. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  79. package/dist/types/loaders/agent-base.d.ts +1 -0
  80. package/dist/types/loaders/agent-base.d.ts.map +1 -1
  81. package/dist/types/loaders/configure/configure.d.ts.map +1 -1
  82. package/package.json +1 -1
  83. package/src/cdn/polyfills.js +2 -0
  84. package/src/common/drain/drain.js +1 -1
  85. package/src/common/harvest/harvest-scheduler.js +4 -2
  86. package/src/features/ajax/constants.js +2 -0
  87. package/src/features/jserrors/aggregate/index.js +2 -1
  88. package/src/features/metrics/aggregate/index.js +0 -8
  89. package/src/features/page_view_event/aggregate/index.js +1 -1
  90. package/src/features/session_replay/aggregate/index.js +30 -39
  91. package/src/features/session_replay/constants.js +4 -0
  92. package/src/features/session_replay/instrument/index.js +48 -8
  93. package/src/features/session_replay/shared/__mocks__/utils.js +0 -1
  94. package/src/features/session_replay/shared/utils.js +4 -7
  95. package/src/features/session_trace/aggregate/index.js +157 -493
  96. package/src/features/session_trace/aggregate/trace/node.js +12 -0
  97. package/src/features/session_trace/aggregate/trace/storage.js +287 -0
  98. package/src/features/session_trace/constants.js +1 -0
  99. package/src/features/session_trace/instrument/index.js +7 -2
  100. package/src/features/utils/__mocks__/feature-gates.js +1 -0
  101. package/src/features/utils/aggregate-base.js +1 -0
  102. package/src/features/utils/feature-gates.js +11 -0
  103. package/src/features/utils/instrument-base.js +3 -2
  104. package/src/loaders/agent-base.js +4 -0
  105. package/src/loaders/api/api-methods.js +1 -1
  106. package/src/loaders/api/api.js +2 -2
  107. package/src/loaders/configure/configure.js +1 -0
  108. package/dist/cjs/features/session_replay/shared/replay-mode.js +0 -28
  109. package/dist/cjs/features/utils/handler-cache.js +0 -70
  110. package/dist/esm/features/session_replay/shared/replay-mode.js +0 -23
  111. package/dist/esm/features/utils/handler-cache.js +0 -63
  112. package/dist/types/features/session_replay/shared/replay-mode.d.ts +0 -9
  113. package/dist/types/features/session_replay/shared/replay-mode.d.ts.map +0 -1
  114. package/dist/types/features/utils/handler-cache.d.ts +0 -23
  115. package/dist/types/features/utils/handler-cache.d.ts.map +0 -1
  116. package/src/features/session_replay/shared/replay-mode.js +0 -23
  117. package/src/features/utils/handler-cache.js +0 -65
@@ -12,7 +12,7 @@
12
12
 
13
13
  import { registerHandler } from '../../../common/event-emitter/register-handler';
14
14
  import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler';
15
- import { ABORT_REASONS, FEATURE_NAME, MAX_PAYLOAD_SIZE, QUERY_PARAM_PADDING, RRWEB_EVENT_TYPES, SR_EVENT_EMITTER_TYPES } from '../constants';
15
+ import { ABORT_REASONS, FEATURE_NAME, MAX_PAYLOAD_SIZE, QUERY_PARAM_PADDING, RRWEB_EVENT_TYPES, SR_EVENT_EMITTER_TYPES, TRIGGERS } from '../constants';
16
16
  import { getConfigurationValue, getInfo, getRuntime } from '../../../common/config/config';
17
17
  import { AggregateBase } from '../../utils/aggregate-base';
18
18
  import { sharedChannel } from '../../../common/constants/shared-channel';
@@ -91,15 +91,6 @@ export class Aggregate extends AggregateBase {
91
91
  getPayload: this.prepareHarvest.bind(this),
92
92
  raw: true
93
93
  }, this);
94
- registerHandler(SR_EVENT_EMITTER_TYPES.RECORD, () => {
95
- // if it has aborted or BCS returned bad entitlements, do not allow
96
- if (this.blocked || !this.entitled) return;
97
- // if it isnt already (fully) initialized... initialize it
98
- if (!this.recorder) this.initializeRecording(false, true, true);
99
- // its been initialized and imported the recorder but its not recording (mode === off || error)
100
- else if (this.mode !== MODE.FULL) this.switchToFull();
101
- // if it gets all the way to here, that means a full session is already recording... do nothing
102
- }, this.featureName, this.ee);
103
94
  registerHandler(SR_EVENT_EMITTER_TYPES.PAUSE, () => {
104
95
  this.forceStop(this.mode !== MODE.ERROR);
105
96
  }, this.featureName, this.ee);
@@ -117,9 +108,9 @@ export class Aggregate extends AggregateBase {
117
108
  inline_images,
118
109
  collect_fonts
119
110
  } = getConfigurationValue(this.agentIdentifier, 'session_replay');
120
- this.waitForFlags(['sr']).then(_ref => {
121
- let [flagOn] = _ref;
122
- this.entitled = flagOn;
111
+ this.waitForFlags(['srs', 'sr']).then(_ref => {
112
+ let [srMode, entitled] = _ref;
113
+ this.entitled = !!entitled;
123
114
  if (!this.entitled) {
124
115
  deregisterDrain(this.agentIdentifier, this.featureName);
125
116
  if (this.recorder?.recording) {
@@ -129,11 +120,14 @@ export class Aggregate extends AggregateBase {
129
120
  return;
130
121
  }
131
122
  this.drain();
132
- this.initializeRecording(Math.random() * 100 < error_sampling_rate, Math.random() * 100 < sampling_rate);
123
+ this.initializeRecording(srMode);
133
124
  }).then(() => {
134
- if (this.mode === MODE.OFF) args?.recorder?.stopRecording(); // stop any conservative preload recording launched by instrument
135
- sharedChannel.onReplayReady(this.mode); // notify watchers that replay started with the mode
136
- });
125
+ if (this.mode === MODE.OFF) {
126
+ this.recorder?.stopRecording(); // stop any conservative preload recording launched by instrument
127
+ while (this.recorder?.getEvents().events.length) this.recorder?.clearBuffer?.();
128
+ }
129
+ sharedChannel.onReplayReady(this.mode);
130
+ }); // notify watchers that replay started with the mode
137
131
 
138
132
  /** 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 */
139
133
  if (!autoStart) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/AutoStart/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
@@ -176,7 +170,7 @@ export class Aggregate extends AggregateBase {
176
170
  * @param {boolean} ignoreSession - whether to force the method to ignore the session state and use just the sample flags
177
171
  * @returns {void}
178
172
  */
179
- async initializeRecording(errorSample, fullSample, ignoreSession) {
173
+ async initializeRecording(srMode, ignoreSession) {
180
174
  this.initialized = true;
181
175
  if (!this.entitled) return;
182
176
 
@@ -191,23 +185,17 @@ export class Aggregate extends AggregateBase {
191
185
  timeKeeper
192
186
  } = getRuntime(this.agentIdentifier);
193
187
  this.timeKeeper = timeKeeper;
194
- if (!session.isNew && !ignoreSession) {
188
+ if (this.recorder?.parent.trigger === TRIGGERS.API && this.recorder?.recording) {
189
+ this.mode = MODE.FULL;
190
+ } else if (!session.isNew && !ignoreSession) {
195
191
  // inherit the mode of the existing session
196
192
  this.mode = session.state.sessionReplayMode;
197
193
  } else {
198
194
  // The session is new... determine the mode the new session should start in
199
- if (fullSample) this.mode = MODE.FULL; // full mode has precedence over error mode
200
- else if (errorSample) this.mode = MODE.ERROR;
201
- // If neither are selected, then don't record (early return)
202
- else {
203
- return;
204
- }
205
- }
206
- if (this.recorder?.getEvents().type === 'preloaded') {
207
- this.prepUtils().then(() => {
208
- this.scheduler.runHarvest();
209
- });
195
+ this.mode = srMode;
210
196
  }
197
+ // If off, then don't record (early return)
198
+ if (this.mode === MODE.OFF) return;
211
199
  if (!this.recorder) {
212
200
  try {
213
201
  // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
@@ -225,13 +213,20 @@ export class Aggregate extends AggregateBase {
225
213
 
226
214
  // If an error was noticed before the mode could be set (like in the early lifecycle of the page), immediately set to FULL mode
227
215
  if (this.mode === MODE.ERROR && this.errorNoticed) this.mode = MODE.FULL;
228
-
229
- // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
230
- // ERROR mode will do this until an error is thrown, and then switch into FULL mode.
231
- // If an error happened in ERROR mode before we've gotten to this stage, it will have already set the mode to FULL
232
- if (this.mode === MODE.FULL && !this.scheduler.started) {
233
- // We only report (harvest) in FULL mode
234
- this.scheduler.startTimer(this.harvestTimeSeconds);
216
+ if (this.mode === MODE.FULL) {
217
+ // If theres preloaded events and we are in full mode, just harvest immediately to clear up space and for consistency
218
+ if (this.recorder?.getEvents().type === 'preloaded') {
219
+ this.prepUtils().then(() => {
220
+ this.scheduler.runHarvest();
221
+ });
222
+ }
223
+ // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
224
+ // ERROR mode will do this until an error is thrown, and then switch into FULL mode.
225
+ // If an error happened in ERROR mode before we've gotten to this stage, it will have already set the mode to FULL
226
+ if (!this.scheduler.started) {
227
+ // We only report (harvest) in FULL mode
228
+ this.scheduler.startTimer(this.harvestTimeSeconds);
229
+ }
235
230
  }
236
231
  await this.prepUtils();
237
232
  if (!this.recorder.recording) this.recorder.startRecording();
@@ -53,4 +53,7 @@ export const ABORT_REASONS = {
53
53
  }
54
54
  };
55
55
  /** Reserved room for query param attrs */
56
- export const QUERY_PARAM_PADDING = 5000;
56
+ export const QUERY_PARAM_PADDING = 5000;
57
+ export const TRIGGERS = {
58
+ API: 'api'
59
+ };
@@ -12,10 +12,11 @@
12
12
  import { handle } from '../../../common/event-emitter/handle';
13
13
  import { DEFAULT_KEY, MODE, PREFIX } from '../../../common/session/constants';
14
14
  import { InstrumentBase } from '../../utils/instrument-base';
15
- import { FEATURE_NAME, SR_EVENT_EMITTER_TYPES } from '../constants';
16
- import { isPreloadAllowed } from '../shared/utils';
15
+ import { hasReplayPrerequisite, isPreloadAllowed } from '../shared/utils';
16
+ import { FEATURE_NAME, SR_EVENT_EMITTER_TYPES, TRIGGERS } from '../constants';
17
17
  export class Instrument extends InstrumentBase {
18
18
  static featureName = FEATURE_NAME;
19
+ #mode;
19
20
  constructor(agentIdentifier, aggregator) {
20
21
  let auto = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
21
22
  super(agentIdentifier, aggregator, FEATURE_NAME, auto);
@@ -24,8 +25,12 @@ export class Instrument extends InstrumentBase {
24
25
  try {
25
26
  session = JSON.parse(localStorage.getItem("".concat(PREFIX, "_").concat(DEFAULT_KEY)));
26
27
  } catch (err) {}
28
+ if (hasReplayPrerequisite(agentIdentifier)) {
29
+ this.ee.on('recordReplay', () => this.#apiStartOrRestartReplay());
30
+ }
27
31
  if (this.#canPreloadRecorder(session)) {
28
- this.#startRecording(session?.sessionReplayMode);
32
+ this.#mode = session?.sessionReplayMode;
33
+ this.#preloadStartRecording();
29
34
  } else {
30
35
  this.importAggregator();
31
36
  }
@@ -58,20 +63,55 @@ export class Instrument extends InstrumentBase {
58
63
  return isPreloadAllowed(this.agentIdentifier);
59
64
  }
60
65
  }
61
- async #startRecording(mode) {
62
- const {
63
- Recorder
64
- } = await import( /* webpackChunkName: "recorder" */'../shared/recorder');
65
- this.recorder = new Recorder({
66
- mode,
67
- agentIdentifier: this.agentIdentifier,
68
- ee: this.ee
69
- });
70
- this.recorder.startRecording();
71
- this.abortHandler = this.recorder.stopRecording;
66
+ #alreadyStarted = false;
67
+ /**
68
+ * 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.
69
+ */
70
+ async #preloadStartRecording(trigger) {
71
+ if (this.#alreadyStarted) return;
72
+ this.#alreadyStarted = true;
73
+ try {
74
+ const {
75
+ Recorder
76
+ } = await import( /* webpackChunkName: "recorder" */'../shared/recorder');
77
+
78
+ // If startReplay() has been used by this point, we must record in full mode regardless of session preload:
79
+ // Note: recorder starts here with w/e the mode is at this time, but this may be changed later (see #apiStartOrRestartReplay else-case)
80
+ this.recorder ??= new Recorder({
81
+ mode: this.#mode,
82
+ agentIdentifier: this.agentIdentifier,
83
+ trigger,
84
+ ee: this.ee
85
+ });
86
+ this.recorder.startRecording();
87
+ this.abortHandler = this.recorder.stopRecording;
88
+ } catch (e) {}
72
89
  this.importAggregator({
73
90
  recorder: this.recorder,
74
91
  errorNoticed: this.errorNoticed
75
92
  });
76
93
  }
94
+
95
+ /**
96
+ * Called whenever startReplay API is used. That could occur any time, pre or post load.
97
+ */
98
+ #apiStartOrRestartReplay() {
99
+ if (this.featAggregate) {
100
+ // post-load; there's possibly already an ongoing recording
101
+ if (this.featAggregate.mode !== MODE.FULL) this.featAggregate.initializeRecording(MODE.FULL, true);
102
+ } else {
103
+ // pre-load
104
+ this.#mode = MODE.FULL;
105
+ this.#preloadStartRecording(TRIGGERS.API);
106
+ // There's a race here wherein either:
107
+ // a. Recorder has not been initialized, and we've set the enforced mode, so we're good, or;
108
+ // b. Record has been initialized, possibly with the "wrong" mode, so we have to correct that + restart.
109
+ if (this.recorder && this.recorder.parent.mode !== MODE.FULL) {
110
+ this.recorder.parent.mode = MODE.FULL;
111
+ this.recorder.stopRecording();
112
+ this.recorder.startRecording();
113
+ this.abortHandler = this.recorder.stopRecording;
114
+ }
115
+ }
116
+ }
77
117
  }
@@ -1,12 +1,10 @@
1
1
  import { getConfigurationValue, originals } from '../../../common/config/config';
2
- import { isBrowserScope, originTime } from '../../../common/constants/runtime';
3
- export function enableSessionTracking(agentId) {
4
- return isBrowserScope && getConfigurationValue(agentId, 'privacy.cookies_enabled') === true;
5
- }
6
- function hasReplayPrerequisite(agentId) {
2
+ import { canEnableSessionTracking } from '../../utils/feature-gates';
3
+ import { originTime } from '../../../common/constants/runtime';
4
+ export function hasReplayPrerequisite(agentId) {
7
5
  return !!originals.MO &&
8
6
  // Session Replay cannot work without Mutation Observer
9
- enableSessionTracking(agentId) &&
7
+ canEnableSessionTracking(agentId) &&
10
8
  // requires session tracking to be running (hence "session" replay...)
11
9
  getConfigurationValue(agentId, 'session_trace.enabled') === true; // Session Replay as of now is tightly coupled with Session Trace in the UI
12
10
  }