@newrelic/browser-agent 1.240.0 → 1.242.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 (48) hide show
  1. package/dist/cjs/cdn/polyfills/lite.js +13 -1
  2. package/dist/cjs/cdn/polyfills/pro.js +17 -1
  3. package/dist/cjs/cdn/polyfills/spa.js +18 -1
  4. package/dist/cjs/common/config/state/init.js +48 -19
  5. package/dist/cjs/common/constants/env.cdn.js +1 -1
  6. package/dist/cjs/common/constants/env.npm.js +1 -1
  7. package/dist/cjs/common/dom/query-selector.js +16 -0
  8. package/dist/cjs/features/metrics/aggregate/index.js +10 -7
  9. package/dist/cjs/features/page_view_timing/aggregate/index.js +9 -9
  10. package/dist/cjs/features/session_replay/aggregate/index.js +90 -40
  11. package/dist/cjs/features/utils/instrument-base.js +1 -0
  12. package/dist/cjs/loaders/browser-agent.js +2 -1
  13. package/dist/esm/cdn/polyfills/lite.js +8 -1
  14. package/dist/esm/cdn/polyfills/pro.js +13 -2
  15. package/dist/esm/cdn/polyfills/spa.js +13 -1
  16. package/dist/esm/common/config/state/init.js +48 -19
  17. package/dist/esm/common/constants/env.cdn.js +1 -1
  18. package/dist/esm/common/constants/env.npm.js +1 -1
  19. package/dist/esm/common/dom/query-selector.js +9 -0
  20. package/dist/esm/features/metrics/aggregate/index.js +10 -7
  21. package/dist/esm/features/page_view_timing/aggregate/index.js +9 -9
  22. package/dist/esm/features/session_replay/aggregate/index.js +90 -40
  23. package/dist/esm/features/utils/instrument-base.js +1 -0
  24. package/dist/esm/loaders/browser-agent.js +2 -1
  25. package/dist/types/common/config/state/init.d.ts.map +1 -1
  26. package/dist/types/common/dom/query-selector.d.ts +2 -0
  27. package/dist/types/common/dom/query-selector.d.ts.map +1 -0
  28. package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
  29. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  30. package/dist/types/features/session_replay/aggregate/index.d.ts +17 -5
  31. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  32. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  33. package/dist/types/loaders/browser-agent.d.ts.map +1 -1
  34. package/package.json +2 -3
  35. package/src/cdn/polyfills/lite.js +14 -1
  36. package/src/cdn/polyfills/pro.js +23 -2
  37. package/src/cdn/polyfills/spa.js +24 -1
  38. package/src/common/config/state/init.js +46 -17
  39. package/src/common/config/state/init.test.js +40 -0
  40. package/src/common/dom/query-selector.js +9 -0
  41. package/src/common/dom/query-selector.test.js +24 -0
  42. package/src/common/harvest/harvest-scheduler.test.js +2 -2
  43. package/src/features/metrics/aggregate/index.js +5 -3
  44. package/src/features/page_view_timing/aggregate/index.js +7 -6
  45. package/src/features/session_replay/aggregate/index.component-test.js +10 -10
  46. package/src/features/session_replay/aggregate/index.js +71 -34
  47. package/src/features/utils/instrument-base.js +1 -0
  48. package/src/loaders/browser-agent.js +3 -1
@@ -1,13 +1,33 @@
1
+ import { isValidSelector } from '../../dom/query-selector';
1
2
  import { DEFAULT_EXPIRES_MS, DEFAULT_INACTIVE_MS } from '../../session/constants';
3
+ import { warn } from '../../util/console';
2
4
  import { gosNREUMInitializedAgents } from '../../window/nreum';
3
5
  import { getModeledObject } from './configurable';
4
6
  const model = () => {
5
7
  const hiddenState = {
6
- blockSelector: '[data-nr-block]',
7
- maskInputOptions: {
8
- password: true
8
+ mask_selector: '*',
9
+ block_selector: '[data-nr-block]',
10
+ mask_input_options: {
11
+ color: false,
12
+ date: false,
13
+ 'datetime-local': false,
14
+ email: false,
15
+ month: false,
16
+ number: false,
17
+ range: false,
18
+ search: false,
19
+ tel: false,
20
+ text: false,
21
+ time: false,
22
+ url: false,
23
+ week: false,
24
+ // unify textarea and select element with text input
25
+ textarea: false,
26
+ select: false,
27
+ password: true // This will be enforced to always be true in the setter
9
28
  }
10
29
  };
30
+
11
31
  return {
12
32
  proxy: {
13
33
  assets: undefined,
@@ -78,38 +98,47 @@ const model = () => {
78
98
  autoStart: true,
79
99
  enabled: false,
80
100
  harvestTimeSeconds: 60,
81
- sampleRate: 0.1,
82
- errorSampleRate: 0.1,
101
+ sampling_rate: 50,
102
+ // float from 0 - 100
103
+ error_sampling_rate: 50,
104
+ // float from 0 - 100
83
105
  // recording config settings
84
- maskTextSelector: '*',
85
- maskAllInputs: true,
106
+ mask_all_inputs: true,
107
+ // this has a getter/setter to facilitate validation of the selectors
108
+ get mask_text_selector() {
109
+ return hiddenState.mask_selector;
110
+ },
111
+ set mask_text_selector(val) {
112
+ if (isValidSelector(val)) hiddenState.mask_selector = val + ',[data-nr-mask]';else if (val === null) hiddenState.mask_selector = val; // null is acceptable, which completely disables the behavior
113
+ else warn('An invalid session_replay.mask_selector was provided and will not be used', val);
114
+ },
86
115
  // these properties only have getters because they are enforcable constants and should error if someone tries to override them
87
- get blockClass() {
116
+ get block_class() {
88
117
  return 'nr-block';
89
118
  },
90
- get ignoreClass() {
119
+ get ignore_class() {
91
120
  return 'nr-ignore';
92
121
  },
93
- get maskTextClass() {
122
+ get mask_text_class() {
94
123
  return 'nr-mask';
95
124
  },
96
125
  // props with a getter and setter are used to extend enforcable constants with customer input
97
126
  // we must preserve data-nr-block no matter what else the customer sets
98
- get blockSelector() {
99
- return hiddenState.blockSelector;
127
+ get block_selector() {
128
+ return hiddenState.block_selector;
100
129
  },
101
- set blockSelector(val) {
102
- hiddenState.blockSelector += ",".concat(val);
130
+ set block_selector(val) {
131
+ if (isValidSelector(val)) hiddenState.block_selector += ",".concat(val);else if (val !== '') warn('An invalid session_replay.block_selector was provided and will not be used', val);
103
132
  },
104
133
  // password: must always be present and true no matter what customer sets
105
- get maskInputOptions() {
106
- return hiddenState.maskInputOptions;
134
+ get mask_input_options() {
135
+ return hiddenState.mask_input_options;
107
136
  },
108
- set maskInputOptions(val) {
109
- hiddenState.maskInputOptions = {
137
+ set mask_input_options(val) {
138
+ if (val && typeof val === 'object') hiddenState.mask_input_options = {
110
139
  ...val,
111
140
  password: true
112
- };
141
+ };else warn('An invalid session_replay.mask_input_option was provided and will not be used', val);
113
142
  }
114
143
  },
115
144
  spa: {
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Exposes the version of the agent
8
8
  */
9
- export const VERSION = "1.240.0";
9
+ export const VERSION = "1.242.0";
10
10
 
11
11
  /**
12
12
  * Exposes the build type of the agent
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Exposes the version of the agent
8
8
  */
9
- export const VERSION = "1.240.0";
9
+ export const VERSION = "1.242.0";
10
10
 
11
11
  /**
12
12
  * Exposes the build type of the agent
@@ -0,0 +1,9 @@
1
+ export const isValidSelector = selector => {
2
+ if (!selector || typeof selector !== 'string') return false;
3
+ try {
4
+ document.createDocumentFragment().querySelector(selector);
5
+ } catch {
6
+ return false;
7
+ }
8
+ return true;
9
+ };
@@ -27,13 +27,16 @@ export class Aggregate extends AggregateBase {
27
27
  this.singleChecks(); // checks that are run only one time, at script load
28
28
  this.eachSessionChecks(); // the start of every time user engages with page
29
29
 
30
- // *cli, Mar 23 - Per NR-94597, this feature should only harvest ONCE at the (potential) EoL time of the page.
31
- scheduler = new HarvestScheduler('jserrors', {
32
- onUnload: () => this.unload()
33
- }, this);
34
- scheduler.harvest.on('jserrors', () => ({
35
- body: this.aggregator.take(['cm', 'sm'])
36
- }));
30
+ this.ee.on("drain-".concat(this.featureName), () => {
31
+ // *cli, Mar 23 - Per NR-94597, this feature should only harvest ONCE at the (potential) EoL time of the page.
32
+ scheduler = new HarvestScheduler('jserrors', {
33
+ onUnload: () => this.unload()
34
+ }, this);
35
+ scheduler.harvest.on('jserrors', () => ({
36
+ body: this.aggregator.take(['cm', 'sm'])
37
+ }));
38
+ }); // this is needed to ensure EoL is "on" and sent
39
+
37
40
  this.drain();
38
41
  }
39
42
  storeSupportabilityMetrics(name, value) {
@@ -52,21 +52,21 @@ export class Aggregate extends AggregateBase {
52
52
 
53
53
  /* It's important that CWV api, like "onLCP", is called before this scheduler is initialized. The reason is because they listen to the same
54
54
  on vis change or pagehide events, and we'd want ex. onLCP to record the timing (win the race) before we try to send "final harvest". */
55
- this.scheduler = new HarvestScheduler('events', {
56
- onFinished: function () {
57
- return _this.onHarvestFinished(...arguments);
58
- },
59
- getPayload: function () {
60
- return _this.prepareHarvest(...arguments);
61
- }
62
- }, this);
63
- registerHandler('timing', (name, value, attrs) => this.addTiming(name, value, attrs), this.featureName, this.ee); // notice CLS is added to all timings via 4th param
55
+
64
56
  registerHandler('docHidden', msTimestamp => this.endCurrentSession(msTimestamp), this.featureName, this.ee);
65
57
  registerHandler('winPagehide', msTimestamp => this.recordPageUnload(msTimestamp), this.featureName, this.ee);
66
58
  const initialHarvestSeconds = getConfigurationValue(this.agentIdentifier, 'page_view_timing.initialHarvestSeconds') || 10;
67
59
  const harvestTimeSeconds = getConfigurationValue(this.agentIdentifier, 'page_view_timing.harvestTimeSeconds') || 30;
68
60
  // send initial data sooner, then start regular
69
61
  this.ee.on("drain-".concat(this.featureName), () => {
62
+ this.scheduler = new HarvestScheduler('events', {
63
+ onFinished: function () {
64
+ return _this.onHarvestFinished(...arguments);
65
+ },
66
+ getPayload: function () {
67
+ return _this.prepareHarvest(...arguments);
68
+ }
69
+ }, this);
70
70
  this.scheduler.startTimer(harvestTimeSeconds, initialHarvestSeconds);
71
71
  });
72
72
  this.drain();
@@ -20,6 +20,9 @@ import { AggregateBase } from '../../utils/aggregate-base';
20
20
  import { sharedChannel } from '../../../common/constants/shared-channel';
21
21
  import { obj as encodeObj } from '../../../common/url/encode';
22
22
  import { warn } from '../../../common/util/console';
23
+ import { globalScope } from '../../../common/constants/runtime';
24
+ import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants';
25
+ import { FEATURE_NAMES } from '../../../loaders/features/features';
23
26
 
24
27
  // would be better to get this dynamically in some way
25
28
  export const RRWEB_VERSION = '2.0.0-alpha.8';
@@ -64,17 +67,30 @@ export class Aggregate extends AggregateBase {
64
67
  * -- When visibility changes from "hidden" -> "visible", it must capture a full snapshot for the replay to work correctly across tabs
65
68
  */
66
69
  this.hasSnapshot = false;
70
+ /** Payload metadata -- Should indicate that the payload being sent has a meta node. The meta node should always precede a snapshot node. */
71
+ this.hasMeta = false;
67
72
  /** Payload metadata -- Should indicate that the payload being sent contains an error. Used for query/filter purposes in UI */
68
73
  this.hasError = false;
69
74
 
70
- /** Payload metadata -- Should indicate when a replay blob started recording. Resets each time a harvest occurs. */
75
+ /** Payload metadata -- Should indicate when a replay blob started recording. Resets each time a harvest occurs.
76
+ * cycle timestamps are used as fallbacks if event timestamps cannot be used
77
+ */
71
78
  this.timestamp = {
72
- first: undefined,
73
- last: undefined
79
+ event: {
80
+ first: undefined,
81
+ last: undefined
82
+ },
83
+ cycle: {
84
+ first: undefined,
85
+ last: undefined
86
+ }
74
87
  };
75
88
 
76
89
  /** A value which increments with every new mutation node reported. Resets after a harvest is sent */
77
90
  this.payloadBytesEstimation = 0;
91
+
92
+ /** Hold on to the last meta node, so that it can be re-inserted if the meta and snapshot nodes are broken up due to harvesting */
93
+ this.lastMeta = undefined;
78
94
  const shouldSetup = getConfigurationValue(agentIdentifier, 'privacy.cookies_enabled') === true && getConfigurationValue(agentIdentifier, 'session_trace.enabled') === true;
79
95
 
80
96
  /** The method to stop recording. This defaults to a noop, but is overwritten once the recording library is imported and initialized */
@@ -82,7 +98,7 @@ export class Aggregate extends AggregateBase {
82
98
  if (shouldSetup) {
83
99
  // The SessionEntity class can emit a message indicating the session was cleared and reset (expiry, inactivity). This feature must abort and never resume if that occurs.
84
100
  this.ee.on(SESSION_EVENTS.RESET, () => {
85
- this.abort();
101
+ this.abort('Session Reset');
86
102
  });
87
103
 
88
104
  // The SessionEntity class can emit a message indicating the session was paused (visibility change). This feature must stop recording if that occurs.
@@ -124,7 +140,7 @@ export class Aggregate extends AggregateBase {
124
140
  }, this.featureName, this.ee);
125
141
  this.waitForFlags(['sr']).then(_ref => {
126
142
  let [flagOn] = _ref;
127
- return this.initializeRecording(flagOn, Math.random() < getConfigurationValue(this.agentIdentifier, 'session_replay.errorSampleRate'), Math.random() < getConfigurationValue(this.agentIdentifier, 'session_replay.sampleRate'));
143
+ return this.initializeRecording(flagOn, Math.random() * 100 < getConfigurationValue(this.agentIdentifier, 'session_replay.error_sampling_rate'), Math.random() * 100 < getConfigurationValue(this.agentIdentifier, 'session_replay.sampling_rate'));
128
144
  }).then(() => sharedChannel.onReplayReady(this.mode)); // notify watchers that replay started with the mode
129
145
 
130
146
  this.drain();
@@ -165,6 +181,12 @@ export class Aggregate extends AggregateBase {
165
181
  if (this.mode === MODE.ERROR && this.errorNoticed) {
166
182
  this.mode = MODE.FULL;
167
183
  }
184
+ try {
185
+ // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
186
+ recorder = (await import( /* webpackChunkName: "recorder" */'rrweb')).record;
187
+ } catch (err) {
188
+ return this.abort('Recorder failed to import');
189
+ }
168
190
 
169
191
  // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
170
192
  // ERROR mode will do this until an error is thrown, and then switch into FULL mode.
@@ -173,12 +195,6 @@ export class Aggregate extends AggregateBase {
173
195
  // We only report (harvest) in FULL mode
174
196
  this.scheduler.startTimer(this.harvestTimeSeconds);
175
197
  }
176
- try {
177
- // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
178
- recorder = (await import( /* webpackChunkName: "recorder" */'rrweb')).record;
179
- } catch (err) {
180
- return this.abort();
181
- }
182
198
  try {
183
199
  // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
184
200
  const {
@@ -213,6 +229,8 @@ export class Aggregate extends AggregateBase {
213
229
  getHarvestContents() {
214
230
  const agentRuntime = getRuntime(this.agentIdentifier);
215
231
  const info = getInfo(this.agentIdentifier);
232
+ const firstTimestamp = this.timestamp.event.first || this.timestamp.cycle.first;
233
+ const lastTimestamp = this.timestamp.event.last || this.timestamp.cycle.last;
216
234
  return {
217
235
  qs: {
218
236
  browser_monitoring_key: info.licenseKey,
@@ -223,11 +241,12 @@ export class Aggregate extends AggregateBase {
223
241
  ...(this.shouldCompress && {
224
242
  content_encoding: 'gzip'
225
243
  }),
226
- 'replay.firstTimestamp': this.timestamp.first,
227
- 'replay.lastTimestamp': this.timestamp.last,
228
- 'replay.durationMs': this.timestamp.last - this.timestamp.first,
244
+ 'replay.firstTimestamp': firstTimestamp,
245
+ 'replay.lastTimestamp': lastTimestamp,
246
+ 'replay.durationMs': lastTimestamp - firstTimestamp,
229
247
  agentVersion: agentRuntime.version,
230
248
  session: agentRuntime.session.state.value,
249
+ hasMeta: this.hasMeta,
231
250
  hasSnapshot: this.hasSnapshot,
232
251
  hasError: this.hasError,
233
252
  isFirstChunk: this.isFirstChunk,
@@ -242,7 +261,7 @@ export class Aggregate extends AggregateBase {
242
261
  onHarvestFinished(result) {
243
262
  // The mutual decision for now is to stop recording and clear buffers if ingest is experiencing 429 rate limiting
244
263
  if (result.status === 429) {
245
- this.abort();
264
+ this.abort('429: Too many requests');
246
265
  }
247
266
  if (this.blocked) this.scheduler.stopTimer(true);
248
267
  }
@@ -252,6 +271,7 @@ export class Aggregate extends AggregateBase {
252
271
  this.events = [];
253
272
  this.isFirstChunk = false;
254
273
  this.hasSnapshot = false;
274
+ this.hasMeta = false;
255
275
  this.hasError = false;
256
276
  this.payloadBytesEstimation = 0;
257
277
  this.clearTimestamps();
@@ -261,29 +281,32 @@ export class Aggregate extends AggregateBase {
261
281
  startRecording() {
262
282
  if (!recorder) {
263
283
  warn('Recording library was never imported');
264
- return this.abort();
284
+ return this.abort('Recorder was never imported');
265
285
  }
286
+ this.clearTimestamps();
287
+ // set the fallbacks as early as possible
288
+ this.setTimestamps();
266
289
  this.recording = true;
267
290
  const {
268
- blockClass,
269
- ignoreClass,
270
- maskTextClass,
271
- blockSelector,
272
- maskInputOptions,
273
- maskTextSelector,
274
- maskAllInputs
291
+ block_class,
292
+ ignore_class,
293
+ mask_text_class,
294
+ block_selector,
295
+ mask_input_options,
296
+ mask_text_selector,
297
+ mask_all_inputs
275
298
  } = getConfigurationValue(this.agentIdentifier, 'session_replay');
276
299
  // set up rrweb configurations for maximum privacy --
277
300
  // https://newrelic.atlassian.net/wiki/spaces/O11Y/pages/2792293280/2023+02+28+Browser+-+Session+Replay#Configuration-options
278
301
  const stop = recorder({
279
302
  emit: this.store.bind(this),
280
- blockClass,
281
- ignoreClass,
282
- maskTextClass,
283
- blockSelector,
284
- maskInputOptions,
285
- maskTextSelector,
286
- maskAllInputs,
303
+ blockClass: block_class,
304
+ ignoreClass: ignore_class,
305
+ maskTextClass: mask_text_class,
306
+ blockSelector: block_selector,
307
+ maskInputOptions: mask_input_options,
308
+ maskTextSelector: mask_text_selector,
309
+ maskAllInputs: mask_all_inputs,
287
310
  checkoutEveryNms: CHECKOUT_MS[this.mode]
288
311
  });
289
312
  this.stopRecording = () => {
@@ -294,6 +317,7 @@ export class Aggregate extends AggregateBase {
294
317
 
295
318
  /** Store a payload in the buffer (this.events). This should be the callback to the recording lib noticing a mutation */
296
319
  store(event, isCheckout) {
320
+ this.setTimestamps(event);
297
321
  if (this.blocked) return;
298
322
  const eventBytes = stringify(event).length;
299
323
  /** The estimated size of the payload after compression */
@@ -301,7 +325,8 @@ export class Aggregate extends AggregateBase {
301
325
  // Vortex will block payloads at a certain size, we might as well not send.
302
326
  if (payloadSize > MAX_PAYLOAD_SIZE) {
303
327
  this.clearBuffer();
304
- return this.abort();
328
+ this.ee.emit(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Too-Big/Seen'], undefined, FEATURE_NAMES.metrics, this.ee);
329
+ return this.abort('Payload too big');
305
330
  }
306
331
  // Checkout events are flags by the recording lib that indicate a fullsnapshot was taken every n ms. These are important
307
332
  // to help reconstruct the replay later and must be included. While waiting and buffering for errors to come through,
@@ -310,8 +335,22 @@ export class Aggregate extends AggregateBase {
310
335
  // we are still waiting for an error to throw, so keep wiping the buffer over time
311
336
  this.clearBuffer();
312
337
  }
313
- this.setTimestamps(event);
314
- if (event.type === 2) this.hasSnapshot = true;
338
+
339
+ // meta event
340
+ if (event.type === 4) {
341
+ this.hasMeta = true;
342
+ this.lastMeta = event;
343
+ }
344
+ // snapshot event
345
+ if (event.type === 2) {
346
+ this.hasSnapshot = true;
347
+ // small chance that the meta event got separated from its matching snapshot across payload harvests
348
+ // it needs to precede the snapshot, so shove it in first.
349
+ if (!this.hasMeta) {
350
+ this.events.push(this.lastMeta);
351
+ this.hasMeta = true;
352
+ }
353
+ }
315
354
  this.events.push(event);
316
355
  this.payloadBytesEstimation += eventBytes;
317
356
 
@@ -328,15 +367,25 @@ export class Aggregate extends AggregateBase {
328
367
  if (!recorder) return;
329
368
  recorder.takeFullSnapshot();
330
369
  }
331
- setTimestamps(rrwebEvent) {
332
- if (!rrwebEvent) return;
333
- if (!this.timestamp.first) this.timestamp.first = rrwebEvent.timestamp;
334
- this.timestamp.last = rrwebEvent.timestamp;
370
+ setTimestamps(event) {
371
+ // fallbacks if timestamps cannot be derived from rrweb events
372
+ this.timestamp.cycle.last = getRuntime(this.agentIdentifier).offset + globalScope.performance.now();
373
+ if (!this.timestamp.cycle.first) this.timestamp.cycle.first = this.timestamp.cycle.last;
374
+ // timestamps based on rrweb events
375
+ if (!event || !event.timestamp) return;
376
+ if (!this.timestamp.event.first) this.timestamp.event.first = event.timestamp;
377
+ this.timestamp.event.last = event.timestamp;
335
378
  }
336
379
  clearTimestamps() {
337
380
  this.timestamp = {
338
- first: undefined,
339
- last: undefined
381
+ event: {
382
+ first: undefined,
383
+ last: undefined
384
+ },
385
+ cycle: {
386
+ first: undefined,
387
+ last: undefined
388
+ }
340
389
  };
341
390
  }
342
391
 
@@ -348,7 +397,8 @@ export class Aggregate extends AggregateBase {
348
397
  }
349
398
 
350
399
  /** Abort the feature, once aborted it will not resume */
351
- abort() {
400
+ abort(reason) {
401
+ warn("SR aborted -- ".concat(reason));
352
402
  this.blocked = true;
353
403
  this.mode = MODE.OFF;
354
404
  this.stopRecording();
@@ -114,6 +114,7 @@ export class InstrumentBase extends FeatureBase {
114
114
  warn("Downloading and initializing ".concat(this.featureName, " failed..."), e);
115
115
  this.abortHandler?.(); // undo any important alterations made to the page
116
116
  // not supported yet but nice to do: "abort" this agent's EE for this feature specifically
117
+ drain(this.agentIdentifier, this.featureName);
117
118
  loadedSuccessfully(false);
118
119
  }
119
120
  };
@@ -7,6 +7,7 @@ import { Instrument as InstrumentXhr } from '../features/ajax/instrument';
7
7
  import { Instrument as InstrumentSessionTrace } from '../features/session_trace/instrument';
8
8
  import { Instrument as InstrumentSpa } from '../features/spa/instrument';
9
9
  import { Instrument as InstrumentPageAction } from '../features/page_action/instrument';
10
+ import { Instrument as InstrumentSessionReplay } from '../features/session_replay/instrument';
10
11
 
11
12
  /**
12
13
  * An agent class with all feature modules available. Features may be disabled and enabled via runtime configuration.
@@ -16,7 +17,7 @@ export class BrowserAgent extends Agent {
16
17
  constructor(args) {
17
18
  super({
18
19
  ...args,
19
- features: [InstrumentXhr, InstrumentPageViewEvent, InstrumentPageViewTiming, InstrumentSessionTrace, InstrumentMetrics, InstrumentPageAction, InstrumentErrors, InstrumentSpa],
20
+ features: [InstrumentXhr, InstrumentPageViewEvent, InstrumentPageViewTiming, InstrumentSessionTrace, InstrumentMetrics, InstrumentPageAction, InstrumentErrors, InstrumentSpa, InstrumentSessionReplay],
20
21
  loaderType: 'browser-agent'
21
22
  });
22
23
  }
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../../src/common/config/state/init.js"],"names":[],"mappings":"AA0EA,+CAIC;AAED,0DAIC;AAED,+DAYC"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../../src/common/config/state/init.js"],"names":[],"mappings":"AAuGA,+CAIC;AAED,0DAIC;AAED,+DAYC"}
@@ -0,0 +1,2 @@
1
+ export function isValidSelector(selector: any): boolean;
2
+ //# sourceMappingURL=query-selector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-selector.d.ts","sourceRoot":"","sources":["../../../../src/common/dom/query-selector.js"],"names":[],"mappings":"AAAO,wDAQN"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/metrics/aggregate/index.js"],"names":[],"mappings":"AAYA;IACE,2BAAiC;IACjC,mDAsBC;IAED,wDAKC;IAED,iDAKC;IAED,qBAqCC;IAED,0BAOC;IAED,eA0CC;IAvCG,mCAAyB;CAwC9B;8BArI6B,4BAA4B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/metrics/aggregate/index.js"],"names":[],"mappings":"AAYA;IACE,2BAAiC;IACjC,mDAwBC;IAED,wDAKC;IAED,iDAKC;IAED,qBAqCC;IAED,0BAOC;IAED,eA0CC;IAvCG,mCAAyB;CAwC9B;8BAvI6B,4BAA4B"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_timing/aggregate/index.js"],"names":[],"mappings":"AAuBA;IACE,2BAAiC;IAMjC,mDAmCC;IAhCC,eAAiB;IACjB,mBAAqB;IACrB,4BAA+B;IAe/B,4BAGQ;IAcV;;;OAGG;IACH,6BAFW,MAAM,QAOhB;IAED;;OAEG;IACH,uCAUC;IAED,mDAqBC;IAED,qCAKC;IAED,gDAWC;IAGD;;;;kBAWC;IAGD,8BAuBC;;CACF;8BAlK6B,4BAA4B;iCANzB,2CAA2C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_timing/aggregate/index.js"],"names":[],"mappings":"AAuBA;IACE,2BAAiC;IAMjC,mDAoCC;IAjCC,eAAiB;IACjB,mBAAqB;IACrB,4BAA+B;IAuB7B,4BAGQ;IAOZ;;;OAGG;IACH,6BAFW,MAAM,QAOhB;IAED;;OAEG;IACH,uCAUC;IAED,mDAqBC;IAED,qCAKC;IAED,gDAWC;IAGD;;;;kBAWC;IAGD,8BAuBC;;CACF;8BAnK6B,4BAA4B;iCANzB,2CAA2C"}
@@ -28,15 +28,27 @@ export class Aggregate extends AggregateBase {
28
28
  * -- When visibility changes from "hidden" -> "visible", it must capture a full snapshot for the replay to work correctly across tabs
29
29
  */
30
30
  hasSnapshot: boolean;
31
+ /** Payload metadata -- Should indicate that the payload being sent has a meta node. The meta node should always precede a snapshot node. */
32
+ hasMeta: boolean;
31
33
  /** Payload metadata -- Should indicate that the payload being sent contains an error. Used for query/filter purposes in UI */
32
34
  hasError: boolean;
33
- /** Payload metadata -- Should indicate when a replay blob started recording. Resets each time a harvest occurs. */
35
+ /** Payload metadata -- Should indicate when a replay blob started recording. Resets each time a harvest occurs.
36
+ * cycle timestamps are used as fallbacks if event timestamps cannot be used
37
+ */
34
38
  timestamp: {
35
- first: undefined;
36
- last: undefined;
39
+ event: {
40
+ first: undefined;
41
+ last: undefined;
42
+ };
43
+ cycle: {
44
+ first: undefined;
45
+ last: undefined;
46
+ };
37
47
  };
38
48
  /** A value which increments with every new mutation node reported. Resets after a harvest is sent */
39
49
  payloadBytesEstimation: number;
50
+ /** Hold on to the last meta node, so that it can be re-inserted if the meta and snapshot nodes are broken up due to harvesting */
51
+ lastMeta: any;
40
52
  /** The method to stop recording. This defaults to a noop, but is overwritten once the recording library is imported and initialized */
41
53
  stopRecording: () => void;
42
54
  scheduler: HarvestScheduler | undefined;
@@ -77,12 +89,12 @@ export class Aggregate extends AggregateBase {
77
89
  store(event: any, isCheckout: any): void;
78
90
  /** force the recording lib to take a full DOM snapshot. This needs to occur in certain cases, like visibility changes */
79
91
  takeFullSnapshot(): void;
80
- setTimestamps(rrwebEvent: any): void;
92
+ setTimestamps(event: any): void;
81
93
  clearTimestamps(): void;
82
94
  /** Estimate the payload size */
83
95
  getPayloadSize(newBytes?: number): any;
84
96
  /** Abort the feature, once aborted it will not resume */
85
- abort(): void;
97
+ abort(reason: any): void;
86
98
  /** Extensive research has yielded about an 88% compression factor on these payloads.
87
99
  * This is an estimation using that factor as to not cause performance issues while evaluating
88
100
  * https://staging.onenr.io/037jbJWxbjy
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"AAwBA,4CAA4C;AAE5C,mCAAmC;AAInC,uCAAuC;AACvC,uCAAuC;AACvC,iCAAiC;AACjC,uCAAuC;AAIvC;IACE,2BAAiC;IACjC,mDA4FC;IA1FC,iHAAiH;IACjH,cAAgB;IAChB,8GAA8G;IAC9G,wBAAgH;IAChH,iFAAiF;IACjF,qBAAwB;IACxB,mEAAmE;IACnE,sBAAyB;IACzB,6GAA6G;IAC7G,aAAoB;IAGpB,iEAAiE;IACjE,mBAAsB;IACtB,gDAAgD;IAChD,wBAA0B;IAE1B,gGAAgG;IAChG,sBAAyB;IACzB;;;MAGE;IACF,qBAAwB;IACxB,+HAA+H;IAC/H,kBAAqB;IAErB,oHAAoH;IACpH;;;MAAsD;IAEtD,qGAAqG;IACrG,+BAA+B;IAO/B,uIAAuI;IACvI,0BAAyE;IAiBvE,wCAKQ;IA+BZ;;;;;;OAMG;IACH,kCALW,OAAO,eACP,OAAO,cACP,OAAO,GACL,IAAI,CAyDhB;IAED;;;;;;;;;oBAaC;IAED;;;;;;;;;MAyBC;IAED,qCAOC;IAED,kFAAkF;IAClF,oBAOC;IAED,qDAAqD;IACrD,uBAyBC;IAED,yHAAyH;IACzH,yCA8BC;IAED,0HAA0H;IAC1H,yBAGC;IAED,qCAIC;IAED,wBAEC;IAED,gCAAgC;IAChC,uCAGC;IAED,yDAAyD;IACzD,cAOC;IAED;;;SAGK;IACL,oCAGC;IAED,yCAGC;CACF;8BA1V6B,4BAA4B;iCALzB,2CAA2C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"AA2BA,4CAA4C;AAE5C,mCAAmC;AAInC,uCAAuC;AACvC,uCAAuC;AACvC,iCAAiC;AACjC,uCAAuC;AAIvC;IACE,2BAAiC;IACjC,mDAmGC;IAjGC,iHAAiH;IACjH,cAAgB;IAChB,8GAA8G;IAC9G,wBAAgH;IAChH,iFAAiF;IACjF,qBAAwB;IACxB,mEAAmE;IACnE,sBAAyB;IACzB,6GAA6G;IAC7G,aAAoB;IAGpB,iEAAiE;IACjE,mBAAsB;IACtB,gDAAgD;IAChD,wBAA0B;IAE1B,gGAAgG;IAChG,sBAAyB;IACzB;;;MAGE;IACF,qBAAwB;IACxB,4IAA4I;IAC5I,iBAAoB;IACpB,+HAA+H;IAC/H,kBAAqB;IAErB;;OAEG;IACH;;;;;;;;;MAA+G;IAE/G,qGAAqG;IACrG,+BAA+B;IAE/B,kIAAkI;IAClI,cAAyB;IAOzB,uIAAuI;IACvI,0BAAyE;IAiBvE,wCAKQ;IA+BZ;;;;;;OAMG;IACH,kCALW,OAAO,eACP,OAAO,cACP,OAAO,GACL,IAAI,CAyDhB;IAED;;;;;;;;;oBAaC;IAED;;;;;;;;;MA4BC;IAED,qCAOC;IAED,kFAAkF;IAClF,oBAQC;IAED,qDAAqD;IACrD,uBA4BC;IAED,yHAAyH;IACzH,yCA6CC;IAED,0HAA0H;IAC1H,yBAGC;IAED,gCAQC;IAED,wBAEC;IAED,gCAAgC;IAChC,uCAGC;IAED,yDAAyD;IACzD,yBAQC;IAED;;;SAGK;IACL,oCAGC;IAED,yCAGC;CACF;8BA/X6B,4BAA4B;iCALzB,2CAA2C"}
@@ -1 +1 @@
1
- {"version":3,"file":"instrument-base.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/instrument-base.js"],"names":[],"mappings":"AAcA;;;GAGG;AACH;IACE;;;;;;;;OAQG;IACH,6BAPW,MAAM,uCAEN,MAAM,8BA4BhB;IArBC,cAAgB;IAEhB,8IAA8I;IAC9I,cADW,WAAW,SAAS,CACF;IAE7B;;;MAGE;IACF,qBAA8B;IAE9B;;;MAGE;IACF,kCAAoC;IAQtC;;;;;OAKG;IACH,mEA2DC;IAED;;;;;KAKC;IACD,6BAJS,MAAM,mCAWd;CACF;4BA/H2B,gBAAgB"}
1
+ {"version":3,"file":"instrument-base.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/instrument-base.js"],"names":[],"mappings":"AAcA;;;GAGG;AACH;IACE;;;;;;;;OAQG;IACH,6BAPW,MAAM,uCAEN,MAAM,8BA4BhB;IArBC,cAAgB;IAEhB,8IAA8I;IAC9I,cADW,WAAW,SAAS,CACF;IAE7B;;;MAGE;IACF,qBAA8B;IAE9B;;;MAGE;IACF,kCAAoC;IAQtC;;;;;OAKG;IACH,mEA4DC;IAED;;;;;KAKC;IACD,6BAJS,MAAM,mCAWd;CACF;4BAhI2B,gBAAgB"}
@@ -1 +1 @@
1
- {"version":3,"file":"browser-agent.d.ts","sourceRoot":"","sources":["../../../src/loaders/browser-agent.js"],"names":[],"mappings":"AAWA;;;GAGG;AACH;IACE,uBAeC;CACF;sBAhCqB,SAAS"}
1
+ {"version":3,"file":"browser-agent.d.ts","sourceRoot":"","sources":["../../../src/loaders/browser-agent.js"],"names":[],"mappings":"AAYA;;;GAGG;AACH;IACE,uBAgBC;CACF;sBAlCqB,SAAS"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/browser-agent",
3
- "version": "1.240.0",
3
+ "version": "1.242.0",
4
4
  "private": false,
5
5
  "author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
6
6
  "description": "New Relic Browser Agent",
@@ -176,7 +176,7 @@
176
176
  "dependencies": {
177
177
  "core-js": "^3.26.0",
178
178
  "fflate": "^0.7.4",
179
- "rrweb": "^2.0.0-alpha.8",
179
+ "rrweb": "2.0.0-alpha.8",
180
180
  "web-vitals": "^3.1.0"
181
181
  },
182
182
  "devDependencies": {
@@ -236,7 +236,6 @@
236
236
  "jest-extended": "^3.2.4",
237
237
  "jung": "^2.1.0",
238
238
  "just-debounce": "^1.0.0",
239
- "newrelic": "^9.7.5",
240
239
  "node-fetch": "^3.3.0",
241
240
  "npm-run-all": "^4.1.5",
242
241
  "object-inspect": "^1.5.0",
@@ -4,4 +4,17 @@
4
4
  */
5
5
 
6
6
  import '../polyfills.js'
7
- import '../lite'
7
+ import { Agent } from '../../loaders/agent'
8
+
9
+ import { Instrument as InstrumentPageViewEvent } from '../../features/page_view_event/instrument'
10
+ import { Instrument as InstrumentPageViewTiming } from '../../features/page_view_timing/instrument'
11
+ import { Instrument as InstrumentMetrics } from '../../features/metrics/instrument'
12
+
13
+ new Agent({
14
+ features: [
15
+ InstrumentPageViewEvent,
16
+ InstrumentPageViewTiming,
17
+ InstrumentMetrics
18
+ ],
19
+ loaderType: 'lite-polyfills'
20
+ })
@@ -1,7 +1,28 @@
1
1
  /**
2
- * @file Creates a version of the "Pro" agent loader with [core-js]{@link https://github.com/zloirock/core-js}
2
+ * @file Creates a version of the "PRO" agent loader with [core-js]{@link https://github.com/zloirock/core-js}
3
3
  * polyfills for pre-ES6 browsers and IE 11.
4
4
  */
5
5
 
6
6
  import '../polyfills.js'
7
- import '../pro'
7
+ import { Agent } from '../../loaders/agent'
8
+
9
+ import { Instrument as InstrumentPageViewEvent } from '../../features/page_view_event/instrument'
10
+ import { Instrument as InstrumentPageViewTiming } from '../../features/page_view_timing/instrument'
11
+ import { Instrument as InstrumentMetrics } from '../../features/metrics/instrument'
12
+ import { Instrument as InstrumentErrors } from '../../features/jserrors/instrument'
13
+ import { Instrument as InstrumentXhr } from '../../features/ajax/instrument'
14
+ import { Instrument as InstrumentSessionTrace } from '../../features/session_trace/instrument'
15
+ import { Instrument as InstrumentPageAction } from '../../features/page_action/instrument'
16
+
17
+ new Agent({
18
+ features: [
19
+ InstrumentPageViewEvent,
20
+ InstrumentPageViewTiming,
21
+ InstrumentSessionTrace,
22
+ InstrumentXhr,
23
+ InstrumentMetrics,
24
+ InstrumentPageAction,
25
+ InstrumentErrors
26
+ ],
27
+ loaderType: 'pro-polyfills'
28
+ })