@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
package/README.md CHANGED
@@ -245,6 +245,9 @@ browserAgent.recordCustomEvent(...)
245
245
 
246
246
  ## Deploying one or more "micro" agents per page
247
247
 
248
+ > **ℹ️ Note:**
249
+ > 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.
250
+
248
251
  The examples above use the `Agent` class at their core, which is ideal for most cases as it will automatically detect page-level events across your web application.
249
252
 
250
253
  Using the `MicroAgent` class, it is possible to skip the "auto" instrumentation phases of the other loader types, and provide a *very small* agent designed for capturing data in a controlled manner via the API interfaces. The `MicroAgent` captures a distinct `PageView` event when instantiated, and additional `PageAction` and `JavaScriptError` events may be captured by calling the `noticeError` and `addPageAction` methods.
@@ -3,11 +3,12 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.MAX_PAYLOAD_SIZE = exports.IDEAL_PAYLOAD_SIZE = exports.DEFAULT_KEY = void 0;
6
+ exports.SESSION_ERROR = exports.MAX_PAYLOAD_SIZE = exports.IDEAL_PAYLOAD_SIZE = exports.DEFAULT_KEY = void 0;
7
7
  /**
8
8
  * Copyright 2020-2025 New Relic, Inc. All rights reserved.
9
9
  * SPDX-License-Identifier: Apache-2.0
10
10
  */
11
11
  const IDEAL_PAYLOAD_SIZE = exports.IDEAL_PAYLOAD_SIZE = 16000;
12
12
  const MAX_PAYLOAD_SIZE = exports.MAX_PAYLOAD_SIZE = 1000000;
13
- const DEFAULT_KEY = exports.DEFAULT_KEY = 'NR_CONTAINER_AGENT';
13
+ const DEFAULT_KEY = exports.DEFAULT_KEY = 'NR_CONTAINER_AGENT';
14
+ const SESSION_ERROR = exports.SESSION_ERROR = 'SESSION_ERROR';
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
17
17
  /**
18
18
  * Exposes the version of the agent
19
19
  */
20
- const VERSION = exports.VERSION = "1.297.0-rc.1";
20
+ const VERSION = exports.VERSION = "1.297.0-rc.3";
21
21
 
22
22
  /**
23
23
  * Exposes the build type of the agent
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
17
17
  /**
18
18
  * Exposes the version of the agent
19
19
  */
20
- const VERSION = exports.VERSION = "1.297.0-rc.1";
20
+ const VERSION = exports.VERSION = "1.297.0-rc.3";
21
21
 
22
22
  /**
23
23
  * Exposes the build type of the agent
@@ -46,8 +46,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
46
46
  this.entitled = false;
47
47
  /** set at BCS response, stored in runtime */
48
48
  this.timeKeeper = undefined;
49
- this.recorder = args?.recorder;
50
- this.errorNoticed = args?.errorNoticed || false;
49
+ this.instrumentClass = args;
50
+ // point this var here just in case it already exists and can be used by APIs (session pause, resume, etc.) before handling rum flags
51
+ this.recorder = this.instrumentClass?.recorder;
51
52
  this.harvestOpts.raw = true;
52
53
  this.isSessionTrackingEnabled = (0, _featureGates.canEnableSessionTracking)(agentRef.init) && !!agentRef.runtime.session;
53
54
  this.reportSupportabilityMetric('Config/SessionReplay/Enabled');
@@ -67,7 +68,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
67
68
  // if the mode changed on a different tab, it needs to update this instance to match
68
69
  this.mode = agentRef.runtime.session.state.sessionReplayMode;
69
70
  if (!this.initialized || this.mode === _constants2.MODE.OFF) return;
70
- this.recorder?.startRecording();
71
+ this.recorder?.startRecording(_constants.TRIGGERS.RESUME, this.mode);
71
72
  });
72
73
  this.ee.on(_constants2.SESSION_EVENTS.UPDATE, (type, data) => {
73
74
  if (!this.recorder || !this.initialized || this.blocked || type !== _constants2.SESSION_EVENT_TYPES.CROSS_TAB) return;
@@ -136,7 +137,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
136
137
  this.mode = _constants2.MODE.FULL;
137
138
  // if the error was noticed AFTER the recorder was already imported....
138
139
  if (this.recorder && this.initialized) {
139
- if (!this.agentRef.runtime.isRecording) this.recorder.startRecording();
140
+ if (!this.agentRef.runtime.isRecording) this.recorder.startRecording(_constants.TRIGGERS.SWITCH_TO_FULL, this.mode); // off --> full
140
141
  this.syncWithSessionManager({
141
142
  sessionReplayMode: this.mode
142
143
  });
@@ -149,9 +150,10 @@ class Aggregate extends _aggregateBase.AggregateBase {
149
150
  * Evaluate entitlements and sampling before starting feature mechanics, importing and configuring recording library, and setting storage state
150
151
  * @param {boolean} srMode - the true/false state of the "sr" flag (aka. entitlements) from RUM response
151
152
  * @param {boolean} ignoreSession - whether to force the method to ignore the session state and use just the sample flags
153
+ * @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
152
154
  * @returns {void}
153
155
  */
154
- async initializeRecording(srMode, ignoreSession) {
156
+ async initializeRecording(srMode, ignoreSession, trigger = _constants.TRIGGERS.INITIALIZE) {
155
157
  this.initialized = true;
156
158
  if (!this.entitled) return;
157
159
 
@@ -166,7 +168,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
166
168
  timeKeeper
167
169
  } = this.agentRef.runtime;
168
170
  this.timeKeeper = timeKeeper;
169
- if (this.recorder?.parent.trigger === _constants.TRIGGERS.API && this.agentRef.runtime.isRecording) {
171
+ if (this.recorder?.trigger === _constants.TRIGGERS.API && this.agentRef.runtime.isRecording) {
170
172
  this.mode = _constants2.MODE.FULL;
171
173
  } else if (!session.isNew && !ignoreSession) {
172
174
  // inherit the mode of the existing session
@@ -177,30 +179,25 @@ class Aggregate extends _aggregateBase.AggregateBase {
177
179
  }
178
180
  // If off, then don't record (early return)
179
181
  if (this.mode === _constants2.MODE.OFF) return;
180
- if (!this.recorder) {
181
- try {
182
- // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
183
- const {
184
- Recorder
185
- } = await Promise.resolve().then(() => _interopRequireWildcard(require(/* webpackChunkName: "recorder" */'../shared/recorder')));
186
- this.recorder = new Recorder(this);
187
- this.recorder.events.hasError = this.errorNoticed;
188
- } catch (err) {
189
- return this.abort(_constants.ABORT_REASONS.IMPORT);
190
- }
191
- } else {
192
- this.recorder.parent = this;
182
+ try {
183
+ /** will return a recorder instance if already imported, otherwise, will fetch the recorder and initialize it */
184
+ this.recorder ??= await this.instrumentClass.importRecorder();
185
+ } catch (err) {
186
+ /** if the recorder fails to import, abort the feature */
187
+ return this.abort(_constants.ABORT_REASONS.IMPORT, err);
193
188
  }
194
189
 
195
190
  // If an error was noticed before the mode could be set (like in the early lifecycle of the page), immediately set to FULL mode
196
- if (this.mode === _constants2.MODE.ERROR && this.errorNoticed) this.mode = _constants2.MODE.FULL;
191
+ if (this.mode === _constants2.MODE.ERROR && this.instrumentClass.errorNoticed) {
192
+ this.mode = _constants2.MODE.FULL;
193
+ }
197
194
 
198
195
  // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
199
196
  // ERROR mode will do this until an error is thrown, and then switch into FULL mode.
200
197
  // The makeHarvestPayload should ensure that no payload is returned if we're not in FULL mode...
201
198
 
202
199
  await this.prepUtils();
203
- if (!this.agentRef.runtime.isRecording) this.recorder.startRecording();
200
+ if (!this.agentRef.runtime.isRecording) this.recorder.startRecording(trigger, this.mode);
204
201
  this.syncWithSessionManager({
205
202
  sessionReplayMode: this.mode
206
203
  });
@@ -61,5 +61,9 @@ const ABORT_REASONS = exports.ABORT_REASONS = {
61
61
  /** Reserved room for query param attrs */
62
62
  const QUERY_PARAM_PADDING = exports.QUERY_PARAM_PADDING = 5000;
63
63
  const TRIGGERS = exports.TRIGGERS = {
64
- API: 'api'
64
+ API: 'api',
65
+ RESUME: 'resume',
66
+ SWITCH_TO_FULL: 'switchToFull',
67
+ INITIALIZE: 'initialize',
68
+ PRELOAD: 'preload'
65
69
  };
@@ -20,8 +20,10 @@ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e;
20
20
  */
21
21
  class Instrument extends _instrumentBase.InstrumentBase {
22
22
  static featureName = _constants2.FEATURE_NAME;
23
- #mode;
24
- #agentRef;
23
+ /** @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`. */
24
+ #stagedImport;
25
+ /** The RRWEB recorder instance, if imported */
26
+ recorder;
25
27
  constructor(agentRef) {
26
28
  super(agentRef, _constants2.FEATURE_NAME);
27
29
 
@@ -29,7 +31,6 @@ class Instrument extends _instrumentBase.InstrumentBase {
29
31
  (0, _recordReplay.setupRecordReplayAPI)(agentRef);
30
32
  (0, _pauseReplay.setupPauseReplayAPI)(agentRef);
31
33
  let session;
32
- this.#agentRef = agentRef;
33
34
  try {
34
35
  session = JSON.parse(localStorage.getItem("".concat(_constants.PREFIX, "_").concat(_constants.DEFAULT_KEY)));
35
36
  } catch (err) {}
@@ -37,15 +38,16 @@ class Instrument extends _instrumentBase.InstrumentBase {
37
38
  this.ee.on(_constants2.SR_EVENT_EMITTER_TYPES.RECORD, () => this.#apiStartOrRestartReplay());
38
39
  }
39
40
  if (this.#canPreloadRecorder(session)) {
40
- this.#mode = session?.sessionReplayMode;
41
- this.#preloadStartRecording();
42
- } else {
43
- this.importAggregator(this.#agentRef, () => Promise.resolve().then(() => _interopRequireWildcard(require(/* webpackChunkName: "session_replay-aggregate" */'../aggregate'))));
41
+ this.importRecorder().then(recorder => {
42
+ recorder.startRecording(_constants2.TRIGGERS.PRELOAD, session?.sessionReplayMode);
43
+ }); // could handle specific fail-state behaviors with a .catch block here
44
44
  }
45
+ this.importAggregator(this.agentRef, () => Promise.resolve().then(() => _interopRequireWildcard(require(/* webpackChunkName: "session_replay-aggregate" */'../aggregate'))), this);
45
46
 
46
47
  /** If the recorder is running, we can pass error events on to the agg to help it switch to full mode later */
47
48
  this.ee.on('err', e => {
48
- if (this.#agentRef.runtime.isRecording) {
49
+ if (this.blocked) return;
50
+ if (this.agentRef.runtime.isRecording) {
49
51
  this.errorNoticed = true;
50
52
  (0, _handle.handle)(_constants2.SR_EVENT_EMITTER_TYPES.ERROR_DURING_REPLAY, [e], undefined, this.featureName, this.ee);
51
53
  }
@@ -58,66 +60,50 @@ class Instrument extends _instrumentBase.InstrumentBase {
58
60
  // this might be a new session if entity initializes: conservatively start recording if first-time config allows
59
61
  // 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
60
62
  // entitlement or sampling decision, or otherwise intentionally opted-in for the feature.
61
- return (0, _utils.isPreloadAllowed)(this.#agentRef.init);
63
+ return (0, _utils.isPreloadAllowed)(this.agentRef.init);
62
64
  } else if (session.sessionReplayMode === _constants.MODE.FULL || session.sessionReplayMode === _constants.MODE.ERROR) {
63
65
  return true; // existing sessions get to continue recording, regardless of this page's configs or if it has expired (conservatively)
64
66
  } else {
65
67
  // SR mode was OFF but may potentially be turned on if session resets and configs allows the new session to have replay...
66
- return (0, _utils.isPreloadAllowed)(this.#agentRef.init);
68
+ return (0, _utils.isPreloadAllowed)(this.agentRef.init);
67
69
  }
68
70
  }
69
- #alreadyStarted = false;
71
+
70
72
  /**
71
- * 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.
73
+ * Returns a promise that imports the recorder module. Only lets the recorder module be imported and instantiated once. Rejects if failed to import/instantiate.
74
+ * @returns {Promise}
72
75
  */
73
- async #preloadStartRecording(trigger) {
74
- if (this.#alreadyStarted) return;
75
- this.#alreadyStarted = true;
76
- try {
77
- const {
78
- Recorder
79
- } = await Promise.resolve().then(() => _interopRequireWildcard(require(/* webpackChunkName: "recorder" */'../shared/recorder')));
80
-
81
- // If startReplay() has been used by this point, we must record in full mode regardless of session preload:
82
- // Note: recorder starts here with w/e the mode is at this time, but this may be changed later (see #apiStartOrRestartReplay else-case)
83
- this.recorder ??= new Recorder({
84
- ...this,
85
- mode: this.#mode,
86
- agentRef: this.#agentRef,
87
- trigger,
88
- timeKeeper: this.#agentRef.runtime.timeKeeper
89
- }); // if TK exists due to deferred state, pass it
90
- this.recorder.startRecording();
91
- this.abortHandler = this.recorder.stopRecording;
92
- } catch (err) {
93
- this.parent.ee.emit('internal-error', [err]);
94
- }
95
- this.importAggregator(this.#agentRef, () => Promise.resolve().then(() => _interopRequireWildcard(require(/* webpackChunkName: "session_replay-aggregate" */'../aggregate'))), {
96
- recorder: this.recorder,
97
- errorNoticed: this.errorNoticed
76
+ importRecorder() {
77
+ /** if we already have a recorder fully set up, just return it */
78
+ if (this.recorder) return Promise.resolve(this.recorder);
79
+ /** conditional -- if we have never started importing, stage the import and store it in state */
80
+ this.#stagedImport ??= Promise.resolve().then(() => _interopRequireWildcard(require(/* webpackChunkName: "recorder" */'../shared/recorder'))).then(({
81
+ Recorder
82
+ }) => {
83
+ this.recorder = new Recorder(this);
84
+ /** return the recorder for promise chaining */
85
+ return this.recorder;
86
+ }).catch(err => {
87
+ this.ee.emit('internal-error', [err]);
88
+ this.blocked = true;
89
+ /** return the err for promise chaining */
90
+ throw err;
98
91
  });
92
+ return this.#stagedImport;
99
93
  }
100
94
 
101
95
  /**
102
96
  * Called whenever startReplay API is used. That could occur any time, pre or post load.
103
97
  */
104
98
  #apiStartOrRestartReplay() {
99
+ if (this.blocked) return;
105
100
  if (this.featAggregate) {
106
101
  // post-load; there's possibly already an ongoing recording
107
- if (this.featAggregate.mode !== _constants.MODE.FULL) this.featAggregate.initializeRecording(_constants.MODE.FULL, true);
102
+ if (this.featAggregate.mode !== _constants.MODE.FULL) this.featAggregate.initializeRecording(_constants.MODE.FULL, true, _constants2.TRIGGERS.API);
108
103
  } 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
- }
104
+ this.importRecorder().then(() => {
105
+ this.recorder.startRecording(_constants2.TRIGGERS.API, _constants.MODE.FULL);
106
+ }); // could handle specific fail-state behaviors with a .catch block here
121
107
  }
122
108
  }
123
109
  }
@@ -29,11 +29,19 @@ class Recorder {
29
29
  #fixing = false;
30
30
  #warnCSSOnce = (0, _invoke.single)(() => (0, _console.warn)(47)); // notifies user of potential replayer issue if fix_stylesheets is off
31
31
 
32
- constructor(parent) {
33
- /** The parent class that instantiated the recorder */
34
- this.parent = parent;
32
+ #canRecord = true;
33
+ triggerHistory = []; // useful for debugging
34
+
35
+ constructor(srInstrument) {
36
+ /** The parent classes that share the recorder */
37
+ this.srInstrument = srInstrument;
38
+ // --- shortcuts
39
+ this.ee = srInstrument.ee;
40
+ this.srFeatureName = srInstrument.featureName;
41
+ this.agentRef = srInstrument.agentRef;
42
+ this.isErrorMode = false;
35
43
  /** A flag that can be set to false by failing conversions to stop the fetching process */
36
- this.shouldFix = this.parent.agentRef.init.session_replay.fix_stylesheets;
44
+ this.shouldFix = this.agentRef.init.session_replay.fix_stylesheets;
37
45
 
38
46
  /** Each page mutation or event will be stored (raw) in this array. This array will be cleared on each harvest */
39
47
  this.events = new _recorderEvents.RecorderEvents(this.shouldFix);
@@ -45,11 +53,18 @@ class Recorder {
45
53
  this.lastMeta = false;
46
54
  /** The method to stop recording. This defaults to a noop, but is overwritten once the recording library is imported and initialized */
47
55
  this.stopRecording = () => {
48
- this.parent.agentRef.runtime.isRecording = false;
56
+ this.agentRef.runtime.isRecording = false;
49
57
  };
58
+ (0, _registerHandler.registerHandler)(_agentConstants.SESSION_ERROR, () => {
59
+ this.#canRecord = false;
60
+ this.stopRecording();
61
+ }, this.srFeatureName, this.ee);
50
62
  (0, _registerHandler.registerHandler)(RRWEB_DATA_CHANNEL, (event, isCheckout) => {
51
63
  this.audit(event, isCheckout);
52
- }, this.parent.featureName, this.parent.ee);
64
+ }, this.srFeatureName, this.ee);
65
+ }
66
+ get trigger() {
67
+ return this.triggerHistory[this.triggerHistory.length - 1];
53
68
  }
54
69
  getEvents() {
55
70
  return {
@@ -66,13 +81,20 @@ class Recorder {
66
81
 
67
82
  /** Clears the buffer (this.events), and resets all payload metadata properties */
68
83
  clearBuffer() {
69
- this.backloggedEvents = this.parent.mode === _constants2.MODE.ERROR ? this.events : new _recorderEvents.RecorderEvents(this.shouldFix);
84
+ this.backloggedEvents = this.isErrorMode ? this.events : new _recorderEvents.RecorderEvents(this.shouldFix);
70
85
  this.events = new _recorderEvents.RecorderEvents(this.shouldFix);
71
86
  }
72
87
 
73
88
  /** Begin recording using configured recording lib */
74
- startRecording() {
75
- this.parent.agentRef.runtime.isRecording = true;
89
+ startRecording(trigger, mode) {
90
+ if (!this.#canRecord) return;
91
+ this.triggerHistory.push(trigger); // keep track of all triggers, useful for lifecycle debugging. "this.trigger" returns the latest entry
92
+
93
+ this.isErrorMode = mode === _constants2.MODE.ERROR;
94
+
95
+ /** if the recorder is already recording... lets stop it before starting a new one */
96
+ this.stopRecording();
97
+ this.agentRef.runtime.isRecording = true;
76
98
  const {
77
99
  block_class,
78
100
  ignore_class,
@@ -83,7 +105,7 @@ class Recorder {
83
105
  mask_all_inputs,
84
106
  inline_images,
85
107
  collect_fonts
86
- } = this.parent.agentRef.init.session_replay;
108
+ } = this.agentRef.init.session_replay;
87
109
 
88
110
  // set up rrweb configurations for maximum privacy --
89
111
  // https://newrelic.atlassian.net/wiki/spaces/O11Y/pages/2792293280/2023+02+28+Browser+-+Session+Replay#Configuration-options
@@ -91,7 +113,7 @@ class Recorder {
91
113
  try {
92
114
  stop = (0, _rrweb.record)({
93
115
  emit: (event, isCheckout) => {
94
- (0, _handle.handle)(RRWEB_DATA_CHANNEL, [event, isCheckout], undefined, this.parent.featureName, this.parent.ee);
116
+ (0, _handle.handle)(RRWEB_DATA_CHANNEL, [event, isCheckout], undefined, this.srFeatureName, this.ee);
95
117
  },
96
118
  blockClass: block_class,
97
119
  ignoreClass: ignore_class,
@@ -105,14 +127,14 @@ class Recorder {
105
127
  inlineStylesheet: true,
106
128
  inlineImages: inline_images,
107
129
  collectFonts: collect_fonts,
108
- checkoutEveryNms: _constants.CHECKOUT_MS[this.parent.mode],
130
+ checkoutEveryNms: _constants.CHECKOUT_MS[mode],
109
131
  recordAfter: 'DOMContentLoaded'
110
132
  });
111
133
  } catch (err) {
112
- this.parent.ee.emit('internal-error', [err]);
134
+ this.ee.emit('internal-error', [err]);
113
135
  }
114
136
  this.stopRecording = () => {
115
- this.parent.agentRef.runtime.isRecording = false;
137
+ this.agentRef.runtime.isRecording = false;
116
138
  stop?.();
117
139
  };
118
140
  }
@@ -125,14 +147,14 @@ class Recorder {
125
147
  */
126
148
  audit(event, isCheckout) {
127
149
  /** An count of stylesheet objects that were blocked from accessing contents via JS */
128
- const incompletes = this.parent.agentRef.init.session_replay.fix_stylesheets ? _stylesheetEvaluator.stylesheetEvaluator.evaluate() : 0;
150
+ const incompletes = this.agentRef.init.session_replay.fix_stylesheets ? _stylesheetEvaluator.stylesheetEvaluator.evaluate() : 0;
129
151
  const missingInlineSMTag = 'SessionReplay/Payload/Missing-Inline-Css/';
130
152
  /** only run the full fixing behavior (more costly) if fix_stylesheets is configured as on (default behavior) */
131
153
  if (!this.shouldFix) {
132
154
  if (incompletes > 0) {
133
155
  this.events.inlinedAllStylesheets = false;
134
156
  this.#warnCSSOnce();
135
- (0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Skipped', incompletes], undefined, _features.FEATURE_NAMES.metrics, this.parent.ee);
157
+ (0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Skipped', incompletes], undefined, _features.FEATURE_NAMES.metrics, this.ee);
136
158
  }
137
159
  return this.store(event, isCheckout);
138
160
  }
@@ -145,8 +167,8 @@ class Recorder {
145
167
  this.events.inlinedAllStylesheets = false;
146
168
  this.shouldFix = false;
147
169
  }
148
- (0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Failed', failedToFix], undefined, _features.FEATURE_NAMES.metrics, this.parent.ee);
149
- (0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Fixed', incompletes - failedToFix], undefined, _features.FEATURE_NAMES.metrics, this.parent.ee);
170
+ (0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Failed', failedToFix], undefined, _features.FEATURE_NAMES.metrics, this.ee);
171
+ (0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Fixed', incompletes - failedToFix], undefined, _features.FEATURE_NAMES.metrics, this.ee);
150
172
  this.takeFullSnapshot();
151
173
  });
152
174
  /** Only start ignoring data if got a faulty snapshot */
@@ -158,10 +180,10 @@ class Recorder {
158
180
 
159
181
  /** Store a payload in the buffer (this.events). This should be the callback to the recording lib noticing a mutation */
160
182
  store(event, isCheckout) {
161
- if (!event || this.parent.blocked) return;
183
+ if (!event || this.srInstrument.featAggregate?.blocked) return;
162
184
 
163
185
  /** because we've waited until draining to process the buffered rrweb events, we can guarantee the timekeeper exists */
164
- event.timestamp = this.parent.timeKeeper.correctAbsoluteTimestamp(event.timestamp);
186
+ event.timestamp = this.agentRef.runtime.timeKeeper.correctAbsoluteTimestamp(event.timestamp);
165
187
  event.__serialized = (0, _stringify.stringify)(event);
166
188
  const eventBytes = event.__serialized.length;
167
189
  /** The estimated size of the payload after compression */
@@ -170,7 +192,7 @@ class Recorder {
170
192
  // to help reconstruct the replay later and must be included. While waiting and buffering for errors to come through,
171
193
  // each time we see a new checkout, we can drop the old data.
172
194
  // we need to check for meta because rrweb will flag it as checkout twice, once for meta, then once for snapshot
173
- if (this.parent.mode === _constants2.MODE.ERROR && isCheckout && event.type === _constants.RRWEB_EVENT_TYPES.Meta) {
195
+ if (this.isErrorMode && isCheckout && event.type === _constants.RRWEB_EVENT_TYPES.Meta) {
174
196
  // we are still waiting for an error to throw, so keep wiping the buffer over time
175
197
  this.clearBuffer();
176
198
  }
@@ -185,16 +207,16 @@ class Recorder {
185
207
 
186
208
  // We are making an effort to try to keep payloads manageable for unloading. If they reach the unload limit before their interval,
187
209
  // it will send immediately. This often happens on the first snapshot, which can be significantly larger than the other payloads.
188
- if ((this.events.hasSnapshot && this.events.hasMeta || payloadSize > _agentConstants.IDEAL_PAYLOAD_SIZE) && this.parent.mode === _constants2.MODE.FULL) {
210
+ if ((this.events.hasSnapshot && this.events.hasMeta || payloadSize > _agentConstants.IDEAL_PAYLOAD_SIZE) && !this.isErrorMode) {
189
211
  // if we've made it to the ideal size of ~16kb before the interval timer, we should send early.
190
- this.parent.agentRef.runtime.harvester.triggerHarvestFor(this.parent);
212
+ this.agentRef.runtime.harvester.triggerHarvestFor(this.srInstrument.featAggregate);
191
213
  }
192
214
  }
193
215
 
194
216
  /** force the recording lib to take a full DOM snapshot. This needs to occur in certain cases, like visibility changes */
195
217
  takeFullSnapshot() {
196
218
  try {
197
- if (!this.parent.agentRef.runtime.isRecording) return;
219
+ if (!this.agentRef.runtime.isRecording) return;
198
220
  _rrweb.record.takeFullSnapshot();
199
221
  } catch (err) {
200
222
  // 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
@@ -215,7 +237,7 @@ class Recorder {
215
237
  * https://staging.onenr.io/037jbJWxbjy
216
238
  * */
217
239
  estimateCompression(data) {
218
- if (!!this.parent.gzipper && !!this.parent.u8) return data * _constants.AVG_COMPRESSION;
240
+ if (!!this.srInstrument.featAggregate?.gzipper && !!this.srInstrument.featAggregate?.u8) return data * _constants.AVG_COMPRESSION;
219
241
  return data;
220
242
  }
221
243
  }
@@ -13,6 +13,8 @@ var _features = require("../../loaders/features/features");
13
13
  var _utils = require("../session_replay/shared/utils");
14
14
  var _featureGates = require("./feature-gates");
15
15
  var _invoke = require("../../common/util/invoke");
16
+ var _agentConstants = require("../../common/constants/agent-constants");
17
+ var _handle = require("../../common/event-emitter/handle");
16
18
  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); }
17
19
  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 && {}.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; } /**
18
20
  * Copyright 2020-2025 New Relic, Inc. All rights reserved.
@@ -34,6 +36,7 @@ class InstrumentBase extends _featureBase.FeatureBase {
34
36
  */
35
37
  constructor(agentRef, featureName) {
36
38
  super(agentRef.agentIdentifier, featureName);
39
+ this.agentRef = agentRef;
37
40
 
38
41
  /** @type {Function | undefined} This should be set by any derived Instrument class if it has things to do when feature fails or is killed. */
39
42
  this.abortHandler = undefined;
@@ -76,7 +79,7 @@ class InstrumentBase extends _featureBase.FeatureBase {
76
79
  * @param {Object} agentRef - reference to the base agent ancestor that this feature belongs to
77
80
  * @param {Function} fetchAggregator - a function that returns a promise that resolves to the aggregate module
78
81
  * @param {Object} [argsObjFromInstrument] - any values or references to pass down to aggregate
79
- * @returns void
82
+ * @returns
80
83
  */
81
84
  importAggregator(agentRef, fetchAggregator, argsObjFromInstrument = {}) {
82
85
  if (this.featAggregate) return;
@@ -101,7 +104,7 @@ class InstrumentBase extends _featureBase.FeatureBase {
101
104
  } catch (e) {
102
105
  (0, _console.warn)(20, e);
103
106
  this.ee.emit('internal-error', [e]);
104
- if (this.featureName === _features.FEATURE_NAMES.sessionReplay) this.abortHandler?.(); // SR should stop recording if session DNE
107
+ (0, _handle.handle)(_agentConstants.SESSION_ERROR, [e], undefined, this.featureName, this.ee);
105
108
  }
106
109
 
107
110
  /**
@@ -142,6 +145,7 @@ class InstrumentBase extends _featureBase.FeatureBase {
142
145
  * @returns
143
146
  */
144
147
  #shouldImportAgg(featureName, session, agentInit) {
148
+ if (this.blocked) return false;
145
149
  switch (featureName) {
146
150
  case _features.FEATURE_NAMES.sessionReplay:
147
151
  // the session manager must be initialized successfully for Replay & Trace features
@@ -33,10 +33,13 @@ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e;
33
33
  const nonAutoFeatures = [_features.FEATURE_NAMES.jserrors, _features.FEATURE_NAMES.genericEvents, _features.FEATURE_NAMES.metrics, _features.FEATURE_NAMES.logging];
34
34
 
35
35
  /**
36
- * @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.
37
- * --- A minimal agent class designed to only respond to manual user input. As such, this class does not
36
+ * A minimal agent class designed to only respond to manual user input. As such, this class does not
38
37
  * automatically instrument. Instead, each MicroAgent instance will lazy load the required features and can support loading multiple instances on one page.
39
38
  * Out of the box, it can manually handle and report Page View, Page Action, and Error events.
39
+ *
40
+ * @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.
41
+ *
42
+ * @see {@link https://www.npmjs.com/package/@newrelic/browser-agent#deploying-one-or-more-micro-agents-per-page} for more information in the documentation.
40
43
  */
41
44
  class MicroAgent extends _microAgentBase.MicroAgentBase {
42
45
  /**
@@ -4,4 +4,5 @@
4
4
  */
5
5
  export const IDEAL_PAYLOAD_SIZE = 16000;
6
6
  export const MAX_PAYLOAD_SIZE = 1000000;
7
- export const DEFAULT_KEY = 'NR_CONTAINER_AGENT';
7
+ export const DEFAULT_KEY = 'NR_CONTAINER_AGENT';
8
+ export const SESSION_ERROR = 'SESSION_ERROR';
@@ -11,7 +11,7 @@
11
11
  /**
12
12
  * Exposes the version of the agent
13
13
  */
14
- export const VERSION = "1.297.0-rc.1";
14
+ export const VERSION = "1.297.0-rc.3";
15
15
 
16
16
  /**
17
17
  * Exposes the build type of the agent
@@ -11,7 +11,7 @@
11
11
  /**
12
12
  * Exposes the version of the agent
13
13
  */
14
- export const VERSION = "1.297.0-rc.1";
14
+ export const VERSION = "1.297.0-rc.3";
15
15
 
16
16
  /**
17
17
  * Exposes the build type of the agent