@newrelic/browser-agent 1.297.0-rc.1 → 1.297.0-rc.3

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 (40) hide show
  1. package/README.md +3 -0
  2. package/dist/cjs/common/constants/agent-constants.js +3 -2
  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/features/session_replay/aggregate/index.js +18 -21
  6. package/dist/cjs/features/session_replay/constants.js +5 -1
  7. package/dist/cjs/features/session_replay/instrument/index.js +36 -50
  8. package/dist/cjs/features/session_replay/shared/recorder.js +47 -25
  9. package/dist/cjs/features/utils/instrument-base.js +6 -2
  10. package/dist/cjs/loaders/micro-agent.js +5 -2
  11. package/dist/esm/common/constants/agent-constants.js +2 -1
  12. package/dist/esm/common/constants/env.cdn.js +1 -1
  13. package/dist/esm/common/constants/env.npm.js +1 -1
  14. package/dist/esm/features/session_replay/aggregate/index.js +18 -21
  15. package/dist/esm/features/session_replay/constants.js +5 -1
  16. package/dist/esm/features/session_replay/instrument/index.js +36 -50
  17. package/dist/esm/features/session_replay/shared/recorder.js +48 -26
  18. package/dist/esm/features/utils/instrument-base.js +6 -2
  19. package/dist/esm/loaders/micro-agent.js +5 -2
  20. package/dist/types/common/constants/agent-constants.d.ts +1 -0
  21. package/dist/types/common/constants/agent-constants.d.ts.map +1 -1
  22. package/dist/types/features/session_replay/aggregate/index.d.ts +9 -2
  23. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  24. package/dist/types/features/session_replay/constants.d.ts +4 -0
  25. package/dist/types/features/session_replay/instrument/index.d.ts +7 -0
  26. package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -1
  27. package/dist/types/features/session_replay/shared/recorder.d.ts +10 -4
  28. package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
  29. package/dist/types/features/utils/instrument-base.d.ts +2 -1
  30. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  31. package/dist/types/loaders/micro-agent.d.ts +5 -2
  32. package/dist/types/loaders/micro-agent.d.ts.map +1 -1
  33. package/package.json +1 -1
  34. package/src/common/constants/agent-constants.js +1 -0
  35. package/src/features/session_replay/aggregate/index.js +18 -19
  36. package/src/features/session_replay/constants.js +5 -1
  37. package/src/features/session_replay/instrument/index.js +39 -40
  38. package/src/features/session_replay/shared/recorder.js +53 -26
  39. package/src/features/utils/instrument-base.js +7 -2
  40. package/src/loaders/micro-agent.js +5 -2
@@ -41,8 +41,9 @@ export class Aggregate extends AggregateBase {
41
41
  this.entitled = false;
42
42
  /** set at BCS response, stored in runtime */
43
43
  this.timeKeeper = undefined;
44
- this.recorder = args?.recorder;
45
- this.errorNoticed = args?.errorNoticed || false;
44
+ this.instrumentClass = args;
45
+ // point this var here just in case it already exists and can be used by APIs (session pause, resume, etc.) before handling rum flags
46
+ this.recorder = this.instrumentClass?.recorder;
46
47
  this.harvestOpts.raw = true;
47
48
  this.isSessionTrackingEnabled = canEnableSessionTracking(agentRef.init) && !!agentRef.runtime.session;
48
49
  this.reportSupportabilityMetric('Config/SessionReplay/Enabled');
@@ -62,7 +63,7 @@ export class Aggregate extends AggregateBase {
62
63
  // if the mode changed on a different tab, it needs to update this instance to match
63
64
  this.mode = agentRef.runtime.session.state.sessionReplayMode;
64
65
  if (!this.initialized || this.mode === MODE.OFF) return;
65
- this.recorder?.startRecording();
66
+ this.recorder?.startRecording(TRIGGERS.RESUME, this.mode);
66
67
  });
67
68
  this.ee.on(SESSION_EVENTS.UPDATE, (type, data) => {
68
69
  if (!this.recorder || !this.initialized || this.blocked || type !== SESSION_EVENT_TYPES.CROSS_TAB) return;
@@ -131,7 +132,7 @@ export class Aggregate extends AggregateBase {
131
132
  this.mode = MODE.FULL;
132
133
  // if the error was noticed AFTER the recorder was already imported....
133
134
  if (this.recorder && this.initialized) {
134
- if (!this.agentRef.runtime.isRecording) this.recorder.startRecording();
135
+ if (!this.agentRef.runtime.isRecording) this.recorder.startRecording(TRIGGERS.SWITCH_TO_FULL, this.mode); // off --> full
135
136
  this.syncWithSessionManager({
136
137
  sessionReplayMode: this.mode
137
138
  });
@@ -144,9 +145,10 @@ export class Aggregate extends AggregateBase {
144
145
  * Evaluate entitlements and sampling before starting feature mechanics, importing and configuring recording library, and setting storage state
145
146
  * @param {boolean} srMode - the true/false state of the "sr" flag (aka. entitlements) from RUM response
146
147
  * @param {boolean} ignoreSession - whether to force the method to ignore the session state and use just the sample flags
148
+ * @param {TRIGGERS} [trigger=TRIGGERS.INITIALIZE] - the trigger that initiated the recording. Usually TRIGGERS.INITIALIZE, but could be TRIGGERS.API since in certain cases that trigger calls this method
147
149
  * @returns {void}
148
150
  */
149
- async initializeRecording(srMode, ignoreSession) {
151
+ async initializeRecording(srMode, ignoreSession, trigger = TRIGGERS.INITIALIZE) {
150
152
  this.initialized = true;
151
153
  if (!this.entitled) return;
152
154
 
@@ -161,7 +163,7 @@ export class Aggregate extends AggregateBase {
161
163
  timeKeeper
162
164
  } = this.agentRef.runtime;
163
165
  this.timeKeeper = timeKeeper;
164
- if (this.recorder?.parent.trigger === TRIGGERS.API && this.agentRef.runtime.isRecording) {
166
+ if (this.recorder?.trigger === TRIGGERS.API && this.agentRef.runtime.isRecording) {
165
167
  this.mode = MODE.FULL;
166
168
  } else if (!session.isNew && !ignoreSession) {
167
169
  // inherit the mode of the existing session
@@ -172,30 +174,25 @@ export class Aggregate extends AggregateBase {
172
174
  }
173
175
  // If off, then don't record (early return)
174
176
  if (this.mode === MODE.OFF) return;
175
- if (!this.recorder) {
176
- try {
177
- // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
178
- const {
179
- Recorder
180
- } = await import(/* webpackChunkName: "recorder" */'../shared/recorder');
181
- this.recorder = new Recorder(this);
182
- this.recorder.events.hasError = this.errorNoticed;
183
- } catch (err) {
184
- return this.abort(ABORT_REASONS.IMPORT);
185
- }
186
- } else {
187
- this.recorder.parent = this;
177
+ try {
178
+ /** will return a recorder instance if already imported, otherwise, will fetch the recorder and initialize it */
179
+ this.recorder ??= await this.instrumentClass.importRecorder();
180
+ } catch (err) {
181
+ /** if the recorder fails to import, abort the feature */
182
+ return this.abort(ABORT_REASONS.IMPORT, err);
188
183
  }
189
184
 
190
185
  // If an error was noticed before the mode could be set (like in the early lifecycle of the page), immediately set to FULL mode
191
- if (this.mode === MODE.ERROR && this.errorNoticed) this.mode = MODE.FULL;
186
+ if (this.mode === MODE.ERROR && this.instrumentClass.errorNoticed) {
187
+ this.mode = MODE.FULL;
188
+ }
192
189
 
193
190
  // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
194
191
  // ERROR mode will do this until an error is thrown, and then switch into FULL mode.
195
192
  // The makeHarvestPayload should ensure that no payload is returned if we're not in FULL mode...
196
193
 
197
194
  await this.prepUtils();
198
- if (!this.agentRef.runtime.isRecording) this.recorder.startRecording();
195
+ if (!this.agentRef.runtime.isRecording) this.recorder.startRecording(trigger, this.mode);
199
196
  this.syncWithSessionManager({
200
197
  sessionReplayMode: this.mode
201
198
  });
@@ -54,5 +54,9 @@ export const ABORT_REASONS = {
54
54
  /** Reserved room for query param attrs */
55
55
  export const QUERY_PARAM_PADDING = 5000;
56
56
  export const TRIGGERS = {
57
- API: 'api'
57
+ API: 'api',
58
+ RESUME: 'resume',
59
+ SWITCH_TO_FULL: 'switchToFull',
60
+ INITIALIZE: 'initialize',
61
+ PRELOAD: 'preload'
58
62
  };
@@ -15,8 +15,10 @@ import { setupRecordReplayAPI } from '../../../loaders/api/recordReplay';
15
15
  import { setupPauseReplayAPI } from '../../../loaders/api/pauseReplay';
16
16
  export class Instrument extends InstrumentBase {
17
17
  static featureName = FEATURE_NAME;
18
- #mode;
19
- #agentRef;
18
+ /** @type {Promise|undefined} A promise that resolves when the recorder module is imported and added to the class. Undefined if the recorder has never been staged to import with `importRecorder`. */
19
+ #stagedImport;
20
+ /** The RRWEB recorder instance, if imported */
21
+ recorder;
20
22
  constructor(agentRef) {
21
23
  super(agentRef, FEATURE_NAME);
22
24
 
@@ -24,7 +26,6 @@ export class Instrument extends InstrumentBase {
24
26
  setupRecordReplayAPI(agentRef);
25
27
  setupPauseReplayAPI(agentRef);
26
28
  let session;
27
- this.#agentRef = agentRef;
28
29
  try {
29
30
  session = JSON.parse(localStorage.getItem("".concat(PREFIX, "_").concat(DEFAULT_KEY)));
30
31
  } catch (err) {}
@@ -32,15 +33,16 @@ export class Instrument extends InstrumentBase {
32
33
  this.ee.on(SR_EVENT_EMITTER_TYPES.RECORD, () => this.#apiStartOrRestartReplay());
33
34
  }
34
35
  if (this.#canPreloadRecorder(session)) {
35
- this.#mode = session?.sessionReplayMode;
36
- this.#preloadStartRecording();
37
- } else {
38
- this.importAggregator(this.#agentRef, () => import(/* webpackChunkName: "session_replay-aggregate" */'../aggregate'));
36
+ this.importRecorder().then(recorder => {
37
+ recorder.startRecording(TRIGGERS.PRELOAD, session?.sessionReplayMode);
38
+ }); // could handle specific fail-state behaviors with a .catch block here
39
39
  }
40
+ this.importAggregator(this.agentRef, () => import(/* webpackChunkName: "session_replay-aggregate" */'../aggregate'), this);
40
41
 
41
42
  /** If the recorder is running, we can pass error events on to the agg to help it switch to full mode later */
42
43
  this.ee.on('err', e => {
43
- if (this.#agentRef.runtime.isRecording) {
44
+ if (this.blocked) return;
45
+ if (this.agentRef.runtime.isRecording) {
44
46
  this.errorNoticed = true;
45
47
  handle(SR_EVENT_EMITTER_TYPES.ERROR_DURING_REPLAY, [e], undefined, this.featureName, this.ee);
46
48
  }
@@ -53,66 +55,50 @@ export class Instrument extends InstrumentBase {
53
55
  // this might be a new session if entity initializes: conservatively start recording if first-time config allows
54
56
  // Note: users with SR enabled, as well as these other configs enabled by-default, will be penalized by the recorder overhead EVEN IF they don't actually have or get
55
57
  // entitlement or sampling decision, or otherwise intentionally opted-in for the feature.
56
- return isPreloadAllowed(this.#agentRef.init);
58
+ return isPreloadAllowed(this.agentRef.init);
57
59
  } else if (session.sessionReplayMode === MODE.FULL || session.sessionReplayMode === MODE.ERROR) {
58
60
  return true; // existing sessions get to continue recording, regardless of this page's configs or if it has expired (conservatively)
59
61
  } else {
60
62
  // SR mode was OFF but may potentially be turned on if session resets and configs allows the new session to have replay...
61
- return isPreloadAllowed(this.#agentRef.init);
63
+ return isPreloadAllowed(this.agentRef.init);
62
64
  }
63
65
  }
64
- #alreadyStarted = false;
66
+
65
67
  /**
66
- * 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.
68
+ * Returns a promise that imports the recorder module. Only lets the recorder module be imported and instantiated once. Rejects if failed to import/instantiate.
69
+ * @returns {Promise}
67
70
  */
68
- async #preloadStartRecording(trigger) {
69
- if (this.#alreadyStarted) return;
70
- this.#alreadyStarted = true;
71
- try {
72
- const {
73
- Recorder
74
- } = await import(/* webpackChunkName: "recorder" */'../shared/recorder');
75
-
76
- // If startReplay() has been used by this point, we must record in full mode regardless of session preload:
77
- // Note: recorder starts here with w/e the mode is at this time, but this may be changed later (see #apiStartOrRestartReplay else-case)
78
- this.recorder ??= new Recorder({
79
- ...this,
80
- mode: this.#mode,
81
- agentRef: this.#agentRef,
82
- trigger,
83
- timeKeeper: this.#agentRef.runtime.timeKeeper
84
- }); // if TK exists due to deferred state, pass it
85
- this.recorder.startRecording();
86
- this.abortHandler = this.recorder.stopRecording;
87
- } catch (err) {
88
- this.parent.ee.emit('internal-error', [err]);
89
- }
90
- this.importAggregator(this.#agentRef, () => import(/* webpackChunkName: "session_replay-aggregate" */'../aggregate'), {
91
- recorder: this.recorder,
92
- errorNoticed: this.errorNoticed
71
+ importRecorder() {
72
+ /** if we already have a recorder fully set up, just return it */
73
+ if (this.recorder) return Promise.resolve(this.recorder);
74
+ /** conditional -- if we have never started importing, stage the import and store it in state */
75
+ this.#stagedImport ??= import(/* webpackChunkName: "recorder" */'../shared/recorder').then(({
76
+ Recorder
77
+ }) => {
78
+ this.recorder = new Recorder(this);
79
+ /** return the recorder for promise chaining */
80
+ return this.recorder;
81
+ }).catch(err => {
82
+ this.ee.emit('internal-error', [err]);
83
+ this.blocked = true;
84
+ /** return the err for promise chaining */
85
+ throw err;
93
86
  });
87
+ return this.#stagedImport;
94
88
  }
95
89
 
96
90
  /**
97
91
  * Called whenever startReplay API is used. That could occur any time, pre or post load.
98
92
  */
99
93
  #apiStartOrRestartReplay() {
94
+ if (this.blocked) return;
100
95
  if (this.featAggregate) {
101
96
  // post-load; there's possibly already an ongoing recording
102
- if (this.featAggregate.mode !== MODE.FULL) this.featAggregate.initializeRecording(MODE.FULL, true);
97
+ if (this.featAggregate.mode !== MODE.FULL) this.featAggregate.initializeRecording(MODE.FULL, true, TRIGGERS.API);
103
98
  } else {
104
- // pre-load
105
- this.#mode = MODE.FULL;
106
- this.#preloadStartRecording(TRIGGERS.API);
107
- // There's a race here wherein either:
108
- // a. Recorder has not been initialized, and we've set the enforced mode, so we're good, or;
109
- // b. Record has been initialized, possibly with the "wrong" mode, so we have to correct that + restart.
110
- if (this.recorder && this.recorder.parent.mode !== MODE.FULL) {
111
- this.recorder.parent.mode = MODE.FULL;
112
- this.recorder.stopRecording();
113
- this.recorder.startRecording();
114
- this.abortHandler = this.recorder.stopRecording;
115
- }
99
+ this.importRecorder().then(() => {
100
+ this.recorder.startRecording(TRIGGERS.API, MODE.FULL);
101
+ }); // could handle specific fail-state behaviors with a .catch block here
116
102
  }
117
103
  }
118
104
  }
@@ -12,7 +12,7 @@ import { handle } from '../../../common/event-emitter/handle';
12
12
  import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants';
13
13
  import { FEATURE_NAMES } from '../../../loaders/features/features';
14
14
  import { customMasker } from './utils';
15
- import { IDEAL_PAYLOAD_SIZE } from '../../../common/constants/agent-constants';
15
+ import { IDEAL_PAYLOAD_SIZE, SESSION_ERROR } from '../../../common/constants/agent-constants';
16
16
  import { warn } from '../../../common/util/console';
17
17
  import { single } from '../../../common/util/invoke';
18
18
  import { registerHandler } from '../../../common/event-emitter/register-handler';
@@ -22,11 +22,19 @@ export class Recorder {
22
22
  #fixing = false;
23
23
  #warnCSSOnce = single(() => warn(47)); // notifies user of potential replayer issue if fix_stylesheets is off
24
24
 
25
- constructor(parent) {
26
- /** The parent class that instantiated the recorder */
27
- this.parent = parent;
25
+ #canRecord = true;
26
+ triggerHistory = []; // useful for debugging
27
+
28
+ constructor(srInstrument) {
29
+ /** The parent classes that share the recorder */
30
+ this.srInstrument = srInstrument;
31
+ // --- shortcuts
32
+ this.ee = srInstrument.ee;
33
+ this.srFeatureName = srInstrument.featureName;
34
+ this.agentRef = srInstrument.agentRef;
35
+ this.isErrorMode = false;
28
36
  /** A flag that can be set to false by failing conversions to stop the fetching process */
29
- this.shouldFix = this.parent.agentRef.init.session_replay.fix_stylesheets;
37
+ this.shouldFix = this.agentRef.init.session_replay.fix_stylesheets;
30
38
 
31
39
  /** Each page mutation or event will be stored (raw) in this array. This array will be cleared on each harvest */
32
40
  this.events = new RecorderEvents(this.shouldFix);
@@ -38,11 +46,18 @@ export class Recorder {
38
46
  this.lastMeta = false;
39
47
  /** The method to stop recording. This defaults to a noop, but is overwritten once the recording library is imported and initialized */
40
48
  this.stopRecording = () => {
41
- this.parent.agentRef.runtime.isRecording = false;
49
+ this.agentRef.runtime.isRecording = false;
42
50
  };
51
+ registerHandler(SESSION_ERROR, () => {
52
+ this.#canRecord = false;
53
+ this.stopRecording();
54
+ }, this.srFeatureName, this.ee);
43
55
  registerHandler(RRWEB_DATA_CHANNEL, (event, isCheckout) => {
44
56
  this.audit(event, isCheckout);
45
- }, this.parent.featureName, this.parent.ee);
57
+ }, this.srFeatureName, this.ee);
58
+ }
59
+ get trigger() {
60
+ return this.triggerHistory[this.triggerHistory.length - 1];
46
61
  }
47
62
  getEvents() {
48
63
  return {
@@ -59,13 +74,20 @@ export class Recorder {
59
74
 
60
75
  /** Clears the buffer (this.events), and resets all payload metadata properties */
61
76
  clearBuffer() {
62
- this.backloggedEvents = this.parent.mode === MODE.ERROR ? this.events : new RecorderEvents(this.shouldFix);
77
+ this.backloggedEvents = this.isErrorMode ? this.events : new RecorderEvents(this.shouldFix);
63
78
  this.events = new RecorderEvents(this.shouldFix);
64
79
  }
65
80
 
66
81
  /** Begin recording using configured recording lib */
67
- startRecording() {
68
- this.parent.agentRef.runtime.isRecording = true;
82
+ startRecording(trigger, mode) {
83
+ if (!this.#canRecord) return;
84
+ this.triggerHistory.push(trigger); // keep track of all triggers, useful for lifecycle debugging. "this.trigger" returns the latest entry
85
+
86
+ this.isErrorMode = mode === MODE.ERROR;
87
+
88
+ /** if the recorder is already recording... lets stop it before starting a new one */
89
+ this.stopRecording();
90
+ this.agentRef.runtime.isRecording = true;
69
91
  const {
70
92
  block_class,
71
93
  ignore_class,
@@ -76,7 +98,7 @@ export class Recorder {
76
98
  mask_all_inputs,
77
99
  inline_images,
78
100
  collect_fonts
79
- } = this.parent.agentRef.init.session_replay;
101
+ } = this.agentRef.init.session_replay;
80
102
 
81
103
  // set up rrweb configurations for maximum privacy --
82
104
  // https://newrelic.atlassian.net/wiki/spaces/O11Y/pages/2792293280/2023+02+28+Browser+-+Session+Replay#Configuration-options
@@ -84,7 +106,7 @@ export class Recorder {
84
106
  try {
85
107
  stop = recorder({
86
108
  emit: (event, isCheckout) => {
87
- handle(RRWEB_DATA_CHANNEL, [event, isCheckout], undefined, this.parent.featureName, this.parent.ee);
109
+ handle(RRWEB_DATA_CHANNEL, [event, isCheckout], undefined, this.srFeatureName, this.ee);
88
110
  },
89
111
  blockClass: block_class,
90
112
  ignoreClass: ignore_class,
@@ -98,14 +120,14 @@ export class Recorder {
98
120
  inlineStylesheet: true,
99
121
  inlineImages: inline_images,
100
122
  collectFonts: collect_fonts,
101
- checkoutEveryNms: CHECKOUT_MS[this.parent.mode],
123
+ checkoutEveryNms: CHECKOUT_MS[mode],
102
124
  recordAfter: 'DOMContentLoaded'
103
125
  });
104
126
  } catch (err) {
105
- this.parent.ee.emit('internal-error', [err]);
127
+ this.ee.emit('internal-error', [err]);
106
128
  }
107
129
  this.stopRecording = () => {
108
- this.parent.agentRef.runtime.isRecording = false;
130
+ this.agentRef.runtime.isRecording = false;
109
131
  stop?.();
110
132
  };
111
133
  }
@@ -118,14 +140,14 @@ export class Recorder {
118
140
  */
119
141
  audit(event, isCheckout) {
120
142
  /** An count of stylesheet objects that were blocked from accessing contents via JS */
121
- const incompletes = this.parent.agentRef.init.session_replay.fix_stylesheets ? stylesheetEvaluator.evaluate() : 0;
143
+ const incompletes = this.agentRef.init.session_replay.fix_stylesheets ? stylesheetEvaluator.evaluate() : 0;
122
144
  const missingInlineSMTag = 'SessionReplay/Payload/Missing-Inline-Css/';
123
145
  /** only run the full fixing behavior (more costly) if fix_stylesheets is configured as on (default behavior) */
124
146
  if (!this.shouldFix) {
125
147
  if (incompletes > 0) {
126
148
  this.events.inlinedAllStylesheets = false;
127
149
  this.#warnCSSOnce();
128
- handle(SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Skipped', incompletes], undefined, FEATURE_NAMES.metrics, this.parent.ee);
150
+ handle(SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Skipped', incompletes], undefined, FEATURE_NAMES.metrics, this.ee);
129
151
  }
130
152
  return this.store(event, isCheckout);
131
153
  }
@@ -138,8 +160,8 @@ export class Recorder {
138
160
  this.events.inlinedAllStylesheets = false;
139
161
  this.shouldFix = false;
140
162
  }
141
- handle(SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Failed', failedToFix], undefined, FEATURE_NAMES.metrics, this.parent.ee);
142
- handle(SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Fixed', incompletes - failedToFix], undefined, FEATURE_NAMES.metrics, this.parent.ee);
163
+ handle(SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Failed', failedToFix], undefined, FEATURE_NAMES.metrics, this.ee);
164
+ handle(SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Fixed', incompletes - failedToFix], undefined, FEATURE_NAMES.metrics, this.ee);
143
165
  this.takeFullSnapshot();
144
166
  });
145
167
  /** Only start ignoring data if got a faulty snapshot */
@@ -151,10 +173,10 @@ export class Recorder {
151
173
 
152
174
  /** Store a payload in the buffer (this.events). This should be the callback to the recording lib noticing a mutation */
153
175
  store(event, isCheckout) {
154
- if (!event || this.parent.blocked) return;
176
+ if (!event || this.srInstrument.featAggregate?.blocked) return;
155
177
 
156
178
  /** because we've waited until draining to process the buffered rrweb events, we can guarantee the timekeeper exists */
157
- event.timestamp = this.parent.timeKeeper.correctAbsoluteTimestamp(event.timestamp);
179
+ event.timestamp = this.agentRef.runtime.timeKeeper.correctAbsoluteTimestamp(event.timestamp);
158
180
  event.__serialized = stringify(event);
159
181
  const eventBytes = event.__serialized.length;
160
182
  /** The estimated size of the payload after compression */
@@ -163,7 +185,7 @@ export class Recorder {
163
185
  // to help reconstruct the replay later and must be included. While waiting and buffering for errors to come through,
164
186
  // each time we see a new checkout, we can drop the old data.
165
187
  // we need to check for meta because rrweb will flag it as checkout twice, once for meta, then once for snapshot
166
- if (this.parent.mode === MODE.ERROR && isCheckout && event.type === RRWEB_EVENT_TYPES.Meta) {
188
+ if (this.isErrorMode && isCheckout && event.type === RRWEB_EVENT_TYPES.Meta) {
167
189
  // we are still waiting for an error to throw, so keep wiping the buffer over time
168
190
  this.clearBuffer();
169
191
  }
@@ -178,16 +200,16 @@ export class Recorder {
178
200
 
179
201
  // We are making an effort to try to keep payloads manageable for unloading. If they reach the unload limit before their interval,
180
202
  // it will send immediately. This often happens on the first snapshot, which can be significantly larger than the other payloads.
181
- if ((this.events.hasSnapshot && this.events.hasMeta || payloadSize > IDEAL_PAYLOAD_SIZE) && this.parent.mode === MODE.FULL) {
203
+ if ((this.events.hasSnapshot && this.events.hasMeta || payloadSize > IDEAL_PAYLOAD_SIZE) && !this.isErrorMode) {
182
204
  // if we've made it to the ideal size of ~16kb before the interval timer, we should send early.
183
- this.parent.agentRef.runtime.harvester.triggerHarvestFor(this.parent);
205
+ this.agentRef.runtime.harvester.triggerHarvestFor(this.srInstrument.featAggregate);
184
206
  }
185
207
  }
186
208
 
187
209
  /** force the recording lib to take a full DOM snapshot. This needs to occur in certain cases, like visibility changes */
188
210
  takeFullSnapshot() {
189
211
  try {
190
- if (!this.parent.agentRef.runtime.isRecording) return;
212
+ if (!this.agentRef.runtime.isRecording) return;
191
213
  recorder.takeFullSnapshot();
192
214
  } catch (err) {
193
215
  // in the off chance we think we are recording, but rrweb does not, rrweb's lib will throw an error. This catch is just a precaution
@@ -208,7 +230,7 @@ export class Recorder {
208
230
  * https://staging.onenr.io/037jbJWxbjy
209
231
  * */
210
232
  estimateCompression(data) {
211
- if (!!this.parent.gzipper && !!this.parent.u8) return data * AVG_COMPRESSION;
233
+ if (!!this.srInstrument.featAggregate?.gzipper && !!this.srInstrument.featAggregate?.u8) return data * AVG_COMPRESSION;
212
234
  return data;
213
235
  }
214
236
  }
@@ -18,6 +18,8 @@ import { FEATURE_NAMES } from '../../loaders/features/features';
18
18
  import { hasReplayPrerequisite } from '../session_replay/shared/utils';
19
19
  import { canEnableSessionTracking } from './feature-gates';
20
20
  import { single } from '../../common/util/invoke';
21
+ import { SESSION_ERROR } from '../../common/constants/agent-constants';
22
+ import { handle } from '../../common/event-emitter/handle';
21
23
 
22
24
  /**
23
25
  * Base class for instrumenting a feature.
@@ -31,6 +33,7 @@ export class InstrumentBase extends FeatureBase {
31
33
  */
32
34
  constructor(agentRef, featureName) {
33
35
  super(agentRef.agentIdentifier, featureName);
36
+ this.agentRef = agentRef;
34
37
 
35
38
  /** @type {Function | undefined} This should be set by any derived Instrument class if it has things to do when feature fails or is killed. */
36
39
  this.abortHandler = undefined;
@@ -73,7 +76,7 @@ export class InstrumentBase extends FeatureBase {
73
76
  * @param {Object} agentRef - reference to the base agent ancestor that this feature belongs to
74
77
  * @param {Function} fetchAggregator - a function that returns a promise that resolves to the aggregate module
75
78
  * @param {Object} [argsObjFromInstrument] - any values or references to pass down to aggregate
76
- * @returns void
79
+ * @returns
77
80
  */
78
81
  importAggregator(agentRef, fetchAggregator, argsObjFromInstrument = {}) {
79
82
  if (this.featAggregate) return;
@@ -98,7 +101,7 @@ export class InstrumentBase extends FeatureBase {
98
101
  } catch (e) {
99
102
  warn(20, e);
100
103
  this.ee.emit('internal-error', [e]);
101
- if (this.featureName === FEATURE_NAMES.sessionReplay) this.abortHandler?.(); // SR should stop recording if session DNE
104
+ handle(SESSION_ERROR, [e], undefined, this.featureName, this.ee);
102
105
  }
103
106
 
104
107
  /**
@@ -139,6 +142,7 @@ export class InstrumentBase extends FeatureBase {
139
142
  * @returns
140
143
  */
141
144
  #shouldImportAgg(featureName, session, agentInit) {
145
+ if (this.blocked) return false;
142
146
  switch (featureName) {
143
147
  case FEATURE_NAMES.sessionReplay:
144
148
  // the session manager must be initialized successfully for Replay & Trace features
@@ -28,10 +28,13 @@ import { setupWrapLoggerAPI } from './api/wrapLogger';
28
28
  const nonAutoFeatures = [FEATURE_NAMES.jserrors, FEATURE_NAMES.genericEvents, FEATURE_NAMES.metrics, FEATURE_NAMES.logging];
29
29
 
30
30
  /**
31
- * @deprecated This feature has been deprecated and will be removed in a future release. A future product centralizing around a single agent instance will be released as a replacement, at which time this loader will be removed.
32
- * --- A minimal agent class designed to only respond to manual user input. As such, this class does not
31
+ * A minimal agent class designed to only respond to manual user input. As such, this class does not
33
32
  * automatically instrument. Instead, each MicroAgent instance will lazy load the required features and can support loading multiple instances on one page.
34
33
  * Out of the box, it can manually handle and report Page View, Page Action, and Error events.
34
+ *
35
+ * @note This loader strategy is slated to be deprecated and eventually removed in a future product release. For better memory usage, build size impacts, entity management and relationships -- a new strategy focused around using a single centralized browser agent instance is actively being worked on. Reach out by email to browser-agent@newrelic.com for more information or if you would like to participate in a limited preview when the feature is ready for early adoption.
36
+ *
37
+ * @see {@link https://www.npmjs.com/package/@newrelic/browser-agent#deploying-one-or-more-micro-agents-per-page} for more information in the documentation.
35
38
  */
36
39
  export class MicroAgent extends MicroAgentBase {
37
40
  /**
@@ -5,4 +5,5 @@
5
5
  export const IDEAL_PAYLOAD_SIZE: 16000;
6
6
  export const MAX_PAYLOAD_SIZE: 1000000;
7
7
  export const DEFAULT_KEY: "NR_CONTAINER_AGENT";
8
+ export const SESSION_ERROR: "SESSION_ERROR";
8
9
  //# sourceMappingURL=agent-constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"agent-constants.d.ts","sourceRoot":"","sources":["../../../../src/common/constants/agent-constants.js"],"names":[],"mappings":"AAAA;;;GAGG;AACH,iCAAkC,KAAK,CAAA;AACvC,+BAAgC,OAAO,CAAA;AACvC,0BAA2B,oBAAoB,CAAA"}
1
+ {"version":3,"file":"agent-constants.d.ts","sourceRoot":"","sources":["../../../../src/common/constants/agent-constants.js"],"names":[],"mappings":"AAAA;;;GAGG;AACH,iCAAkC,KAAK,CAAA;AACvC,+BAAgC,OAAO,CAAA;AACvC,0BAA2B,oBAAoB,CAAA;AAC/C,4BAA6B,eAAe,CAAA"}
@@ -12,8 +12,8 @@ export class Aggregate extends AggregateBase {
12
12
  entitled: boolean;
13
13
  /** set at BCS response, stored in runtime */
14
14
  timeKeeper: any;
15
+ instrumentClass: any;
15
16
  recorder: any;
16
- errorNoticed: any;
17
17
  isSessionTrackingEnabled: boolean;
18
18
  replayIsActive(): boolean;
19
19
  handleError(e: any): void;
@@ -22,9 +22,16 @@ export class Aggregate extends AggregateBase {
22
22
  * Evaluate entitlements and sampling before starting feature mechanics, importing and configuring recording library, and setting storage state
23
23
  * @param {boolean} srMode - the true/false state of the "sr" flag (aka. entitlements) from RUM response
24
24
  * @param {boolean} ignoreSession - whether to force the method to ignore the session state and use just the sample flags
25
+ * @param {TRIGGERS} [trigger=TRIGGERS.INITIALIZE] - the trigger that initiated the recording. Usually TRIGGERS.INITIALIZE, but could be TRIGGERS.API since in certain cases that trigger calls this method
25
26
  * @returns {void}
26
27
  */
27
- initializeRecording(srMode: boolean, ignoreSession: boolean): void;
28
+ initializeRecording(srMode: boolean, ignoreSession: boolean, trigger?: {
29
+ API: string;
30
+ RESUME: string;
31
+ SWITCH_TO_FULL: string;
32
+ INITIALIZE: string;
33
+ PRELOAD: string;
34
+ }): void;
28
35
  prepUtils(): Promise<void>;
29
36
  makeHarvestPayload(shouldRetryOnFail: any): {
30
37
  targetApp: undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"AAwBA;IACE,2BAAiC;IAIjC,sCAqFC;IAxFD,aAAe;IAKb,iFAAiF;IACjF,qBAAwB;IAGxB,2CAA2C;IAC3C,sDAAwB;IACxB,6CAA6C;IAC7C,gDAAmB;IAEnB,0BAA0B;IAC1B,kBAAqB;IACrB,6CAA6C;IAC7C,gBAA2B;IAE3B,cAA8B;IAC9B,kBAA+C;IAG/C,kCAAqG;IAmEvG,0BAEC;IAED,0BAMC;IAED,qBAUC;IAED;;;;;OAKG;IACH,4BAJW,OAAO,iBACP,OAAO,GACL,IAAI,CAkDhB;IAED,2BASC;IAED;;;oBAyCC;IAED;;;;OAIG;IACH,6BAHW,MAAM,EAAE,GACN;QAAE,UAAU,EAAE,MAAM,GAAC,SAAS,CAAC;QAAC,SAAS,EAAE,MAAM,GAAC,SAAS,CAAA;KAAE,CAUzE;IAED;;;;;;;;;;MAsEC;IAED,sCAKC;IAED;;;;OAIG;IACH,mCAKC;IAED,yDAAyD;IACzD,+CASC;IAED,yCAIC;CACF;8BA3W6B,4BAA4B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"AAwBA;IACE,2BAAiC;IAIjC,sCAuFC;IA1FD,aAAe;IAKb,iFAAiF;IACjF,qBAAwB;IAGxB,2CAA2C;IAC3C,sDAAwB;IACxB,6CAA6C;IAC7C,gDAAmB;IAEnB,0BAA0B;IAC1B,kBAAqB;IACrB,6CAA6C;IAC7C,gBAA2B;IAE3B,qBAA2B;IAE3B,cAA8C;IAI9C,kCAAqG;IAmEvG,0BAEC;IAED,0BAMC;IAED,qBAUC;IAED;;;;;;OAMG;IACH,4BALW,OAAO,iBACP,OAAO;;;;;;QAEL,IAAI,CA8ChB;IAED,2BASC;IAED;;;oBAyCC;IAED;;;;OAIG;IACH,6BAHW,MAAM,EAAE,GACN;QAAE,UAAU,EAAE,MAAM,GAAC,SAAS,CAAC;QAAC,SAAS,EAAE,MAAM,GAAC,SAAS,CAAA;KAAE,CAUzE;IAED;;;;;;;;;;MAsEC;IAED,sCAKC;IAED;;;;OAIG;IACH,mCAKC;IAED,yDAAyD;IACzD,+CASC;IAED,yCAIC;CACF;8BA1W6B,4BAA4B"}
@@ -57,5 +57,9 @@ export namespace ABORT_REASONS {
57
57
  export const QUERY_PARAM_PADDING: 5000;
58
58
  export namespace TRIGGERS {
59
59
  let API: string;
60
+ let RESUME: string;
61
+ let SWITCH_TO_FULL: string;
62
+ let INITIALIZE: string;
63
+ let PRELOAD: string;
60
64
  }
61
65
  //# sourceMappingURL=constants.d.ts.map
@@ -1,7 +1,14 @@
1
1
  export class Instrument extends InstrumentBase {
2
2
  static featureName: string;
3
3
  constructor(agentRef: any);
4
+ /** The RRWEB recorder instance, if imported */
5
+ recorder: any;
4
6
  errorNoticed: boolean;
7
+ /**
8
+ * Returns a promise that imports the recorder module. Only lets the recorder module be imported and instantiated once. Rejects if failed to import/instantiate.
9
+ * @returns {Promise}
10
+ */
11
+ importRecorder(): Promise<any>;
5
12
  #private;
6
13
  }
7
14
  export const SessionReplay: typeof Instrument;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/instrument/index.js"],"names":[],"mappings":"AAgBA;IACE,2BAAiC;IAIjC,2BA+BC;IAJK,sBAAwB;;CA6D/B;AAED,8CAAuC;+BArGR,6BAA6B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/instrument/index.js"],"names":[],"mappings":"AAgBA;IACE,2BAAiC;IAMjC,2BAgCC;IAnCD,+CAA+C;IAC/C,cAAQ;IA8BF,sBAAwB;IAmB9B;;;OAGG;IACH,+BAkBC;;CAgBF;AAED,8CAAuC;+BApGR,6BAA6B"}
@@ -1,7 +1,12 @@
1
1
  export class Recorder {
2
- constructor(parent: any);
3
- /** The parent class that instantiated the recorder */
4
- parent: any;
2
+ constructor(srInstrument: any);
3
+ triggerHistory: any[];
4
+ /** The parent classes that share the recorder */
5
+ srInstrument: any;
6
+ ee: any;
7
+ srFeatureName: any;
8
+ agentRef: any;
9
+ isErrorMode: boolean;
5
10
  /** A flag that can be set to false by failing conversions to stop the fetching process */
6
11
  shouldFix: any;
7
12
  /** Each page mutation or event will be stored (raw) in this array. This array will be cleared on each harvest */
@@ -14,6 +19,7 @@ export class Recorder {
14
19
  lastMeta: boolean;
15
20
  /** The method to stop recording. This defaults to a noop, but is overwritten once the recording library is imported and initialized */
16
21
  stopRecording: () => void;
22
+ get trigger(): any;
17
23
  getEvents(): {
18
24
  events: any[];
19
25
  type: string;
@@ -27,7 +33,7 @@ export class Recorder {
27
33
  /** Clears the buffer (this.events), and resets all payload metadata properties */
28
34
  clearBuffer(): void;
29
35
  /** Begin recording using configured recording lib */
30
- startRecording(): void;
36
+ startRecording(trigger: any, mode: any): void;
31
37
  /**
32
38
  * audit - Checks if the event node payload is missing certain attributes
33
39
  * will forward on to the "store" method if nothing needs async fixing
@@ -1 +1 @@
1
- {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/recorder.js"],"names":[],"mappings":"AAqBA;IAME,yBAkBC;IAjBC,sDAAsD;IACtD,YAAoB;IACpB,0FAA0F;IAC1F,eAAyE;IAEzE,iHAAiH;IACjH,uBAAgD;IAChD,mFAAmF;IACnF,iCAA0D;IAC1D,uIAAuI;IACvI,yBAA4B;IAC5B,kIAAkI;IAClI,kBAAqB;IACrB,uIAAuI;IACvI,0BAA+E;IAKjF;;;;;;;;;MAWC;IAED,kFAAkF;IAClF,oBAGC;IAED,qDAAqD;IACrD,uBAiCC;IAED;;;;;OAKG;IACH,aAHW,GAAC,cACD,GAAC,QAiCX;IAED,yHAAyH;IACzH,yCAgCC;IAED,0HAA0H;IAC1H,yBAOC;IAED,wBAEC;IAED,gCAAgC;IAChC,uCAGC;IAED;;;SAGK;IACL,oCAGC;;CACF;+BArM8B,mBAAmB"}
1
+ {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/recorder.js"],"names":[],"mappings":"AAqBA;IAUE,+BA6BC;IA/BD,sBAAmB;IAGjB,iDAAiD;IACjD,kBAAgC;IAEhC,QAAyB;IACzB,mBAA6C;IAC7C,cAAqC;IAErC,qBAAwB;IACxB,0FAA0F;IAC1F,eAAkE;IAElE,iHAAiH;IACjH,uBAAgD;IAChD,mFAAmF;IACnF,iCAA0D;IAC1D,uIAAuI;IACvI,yBAA4B;IAC5B,kIAAkI;IAClI,kBAAqB;IACrB,uIAAuI;IACvI,0BAAwE;IAU1E,mBAEC;IAED;;;;;;;;;MAWC;IAED,kFAAkF;IAClF,oBAGC;IAED,qDAAqD;IACrD,8CAyCC;IAED;;;;;OAKG;IACH,aAHW,GAAC,cACD,GAAC,QAiCX;IAED,yHAAyH;IACzH,yCAgCC;IAED,0HAA0H;IAC1H,yBAOC;IAED,wBAEC;IAED,gCAAgC;IAChC,uCAGC;IAED;;;SAGK;IACL,oCAGC;;CACF;+BAhO8B,mBAAmB"}
@@ -9,6 +9,7 @@ export class InstrumentBase extends FeatureBase {
9
9
  * @param {string} featureName - The name of the feature module (used to construct file path).
10
10
  */
11
11
  constructor(agentRef: any, featureName: string);
12
+ agentRef: any;
12
13
  /** @type {Function | undefined} This should be set by any derived Instrument class if it has things to do when feature fails or is killed. */
13
14
  abortHandler: Function | undefined;
14
15
  /**
@@ -32,7 +33,7 @@ export class InstrumentBase extends FeatureBase {
32
33
  * @param {Object} agentRef - reference to the base agent ancestor that this feature belongs to
33
34
  * @param {Function} fetchAggregator - a function that returns a promise that resolves to the aggregate module
34
35
  * @param {Object} [argsObjFromInstrument] - any values or references to pass down to aggregate
35
- * @returns void
36
+ * @returns
36
37
  */
37
38
  importAggregator(agentRef: Object, fetchAggregator: Function, argsObjFromInstrument?: Object): void;
38
39
  #private;
@@ -1 +1 @@
1
- {"version":3,"file":"instrument-base.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/instrument-base.js"],"names":[],"mappings":"AAqBA;;;GAGG;AACH;IACE;;;;OAIG;IACH,wCAFW,MAAM,EAuChB;IAlCC,8IAA8I;IAC9I,cADW,WAAW,SAAS,CACF;IAE7B;;;MAGE;IACF,eAHU,OAAO,kBAAkB,EAAE,aAAa,CAGpB;IAE9B;;;MAGE;IACF,kCAAoC;IAEpC;;;MAGE;IACF,uBAAiC;IAiBnC;;;;;;;OAOG;IACH,2BALW,MAAM,qDAEN,MAAM,QA0DhB;;CAkBF;4BA3I2B,gBAAgB"}
1
+ {"version":3,"file":"instrument-base.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/instrument-base.js"],"names":[],"mappings":"AAuBA;;;GAGG;AACH;IACE;;;;OAIG;IACH,wCAFW,MAAM,EAyChB;IApCC,cAAwB;IAExB,8IAA8I;IAC9I,cADW,WAAW,SAAS,CACF;IAE7B;;;MAGE;IACF,eAHU,OAAO,kBAAkB,EAAE,aAAa,CAGpB;IAE9B;;;MAGE;IACF,kCAAoC;IAEpC;;;MAGE;IACF,uBAAiC;IAiBnC;;;;;;;OAOG;IACH,2BALW,MAAM,qDAEN,MAAM,QA0DhB;;CAmBF;4BAhJ2B,gBAAgB"}