@newrelic/browser-agent 1.241.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 (34) 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 +32 -5
  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/session_replay/aggregate/index.js +10 -6
  9. package/dist/cjs/features/utils/instrument-base.js +1 -0
  10. package/dist/esm/cdn/polyfills/lite.js +8 -1
  11. package/dist/esm/cdn/polyfills/pro.js +13 -2
  12. package/dist/esm/cdn/polyfills/spa.js +13 -1
  13. package/dist/esm/common/config/state/init.js +32 -5
  14. package/dist/esm/common/constants/env.cdn.js +1 -1
  15. package/dist/esm/common/constants/env.npm.js +1 -1
  16. package/dist/esm/common/dom/query-selector.js +9 -0
  17. package/dist/esm/features/session_replay/aggregate/index.js +10 -6
  18. package/dist/esm/features/utils/instrument-base.js +1 -0
  19. package/dist/types/common/config/state/init.d.ts.map +1 -1
  20. package/dist/types/common/dom/query-selector.d.ts +2 -0
  21. package/dist/types/common/dom/query-selector.d.ts.map +1 -0
  22. package/dist/types/features/session_replay/aggregate/index.d.ts +1 -1
  23. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  24. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  25. package/package.json +1 -1
  26. package/src/cdn/polyfills/lite.js +14 -1
  27. package/src/cdn/polyfills/pro.js +23 -2
  28. package/src/cdn/polyfills/spa.js +24 -1
  29. package/src/common/config/state/init.js +33 -4
  30. package/src/common/config/state/init.test.js +40 -0
  31. package/src/common/dom/query-selector.js +9 -0
  32. package/src/common/dom/query-selector.test.js +24 -0
  33. package/src/features/session_replay/aggregate/index.js +10 -6
  34. package/src/features/utils/instrument-base.js +1 -0
@@ -1,4 +1,16 @@
1
1
  "use strict";
2
2
 
3
3
  require("../polyfills.js");
4
- require("../lite");
4
+ var _agent = require("../../loaders/agent");
5
+ var _instrument = require("../../features/page_view_event/instrument");
6
+ var _instrument2 = require("../../features/page_view_timing/instrument");
7
+ var _instrument3 = require("../../features/metrics/instrument");
8
+ /**
9
+ * @file Creates a version of the "Lite" agent loader with [core-js]{@link https://github.com/zloirock/core-js}
10
+ * polyfills for pre-ES6 browsers and IE 11.
11
+ */
12
+
13
+ new _agent.Agent({
14
+ features: [_instrument.Instrument, _instrument2.Instrument, _instrument3.Instrument],
15
+ loaderType: 'lite-polyfills'
16
+ });
@@ -1,4 +1,20 @@
1
1
  "use strict";
2
2
 
3
3
  require("../polyfills.js");
4
- require("../pro");
4
+ var _agent = require("../../loaders/agent");
5
+ var _instrument = require("../../features/page_view_event/instrument");
6
+ var _instrument2 = require("../../features/page_view_timing/instrument");
7
+ var _instrument3 = require("../../features/metrics/instrument");
8
+ var _instrument4 = require("../../features/jserrors/instrument");
9
+ var _instrument5 = require("../../features/ajax/instrument");
10
+ var _instrument6 = require("../../features/session_trace/instrument");
11
+ var _instrument7 = require("../../features/page_action/instrument");
12
+ /**
13
+ * @file Creates a version of the "PRO" agent loader with [core-js]{@link https://github.com/zloirock/core-js}
14
+ * polyfills for pre-ES6 browsers and IE 11.
15
+ */
16
+
17
+ new _agent.Agent({
18
+ features: [_instrument.Instrument, _instrument2.Instrument, _instrument6.Instrument, _instrument5.Instrument, _instrument3.Instrument, _instrument7.Instrument, _instrument4.Instrument],
19
+ loaderType: 'pro-polyfills'
20
+ });
@@ -1,4 +1,21 @@
1
1
  "use strict";
2
2
 
3
3
  require("../polyfills.js");
4
- require("../spa");
4
+ var _agent = require("../../loaders/agent");
5
+ var _instrument = require("../../features/page_view_event/instrument");
6
+ var _instrument2 = require("../../features/page_view_timing/instrument");
7
+ var _instrument3 = require("../../features/metrics/instrument");
8
+ var _instrument4 = require("../../features/jserrors/instrument");
9
+ var _instrument5 = require("../../features/ajax/instrument");
10
+ var _instrument6 = require("../../features/session_trace/instrument");
11
+ var _instrument7 = require("../../features/spa/instrument");
12
+ var _instrument8 = require("../../features/page_action/instrument");
13
+ /**
14
+ * @file Creates a version of the "SPA" agent loader with [core-js]{@link https://github.com/zloirock/core-js}
15
+ * polyfills for pre-ES6 browsers and IE 11.
16
+ */
17
+
18
+ new _agent.Agent({
19
+ features: [_instrument5.Instrument, _instrument.Instrument, _instrument2.Instrument, _instrument6.Instrument, _instrument3.Instrument, _instrument8.Instrument, _instrument4.Instrument, _instrument7.Instrument],
20
+ loaderType: 'spa-polyfills'
21
+ });
@@ -6,16 +6,36 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.getConfiguration = getConfiguration;
7
7
  exports.getConfigurationValue = getConfigurationValue;
8
8
  exports.setConfiguration = setConfiguration;
9
+ var _querySelector = require("../../dom/query-selector");
9
10
  var _constants = require("../../session/constants");
11
+ var _console = require("../../util/console");
10
12
  var _nreum = require("../../window/nreum");
11
13
  var _configurable = require("./configurable");
12
14
  const model = () => {
13
15
  const hiddenState = {
16
+ mask_selector: '*',
14
17
  block_selector: '[data-nr-block]',
15
18
  mask_input_options: {
16
- password: true
19
+ color: false,
20
+ date: false,
21
+ 'datetime-local': false,
22
+ email: false,
23
+ month: false,
24
+ number: false,
25
+ range: false,
26
+ search: false,
27
+ tel: false,
28
+ text: false,
29
+ time: false,
30
+ url: false,
31
+ week: false,
32
+ // unify textarea and select element with text input
33
+ textarea: false,
34
+ select: false,
35
+ password: true // This will be enforced to always be true in the setter
17
36
  }
18
37
  };
38
+
19
39
  return {
20
40
  proxy: {
21
41
  assets: undefined,
@@ -91,8 +111,15 @@ const model = () => {
91
111
  error_sampling_rate: 50,
92
112
  // float from 0 - 100
93
113
  // recording config settings
94
- mask_text_selector: '*',
95
114
  mask_all_inputs: true,
115
+ // this has a getter/setter to facilitate validation of the selectors
116
+ get mask_text_selector() {
117
+ return hiddenState.mask_selector;
118
+ },
119
+ set mask_text_selector(val) {
120
+ if ((0, _querySelector.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
121
+ else (0, _console.warn)('An invalid session_replay.mask_selector was provided and will not be used', val);
122
+ },
96
123
  // these properties only have getters because they are enforcable constants and should error if someone tries to override them
97
124
  get block_class() {
98
125
  return 'nr-block';
@@ -109,17 +136,17 @@ const model = () => {
109
136
  return hiddenState.block_selector;
110
137
  },
111
138
  set block_selector(val) {
112
- hiddenState.block_selector += ",".concat(val);
139
+ if ((0, _querySelector.isValidSelector)(val)) hiddenState.block_selector += ",".concat(val);else if (val !== '') (0, _console.warn)('An invalid session_replay.block_selector was provided and will not be used', val);
113
140
  },
114
141
  // password: must always be present and true no matter what customer sets
115
142
  get mask_input_options() {
116
143
  return hiddenState.mask_input_options;
117
144
  },
118
145
  set mask_input_options(val) {
119
- hiddenState.mask_input_options = {
146
+ if (val && typeof val === 'object') hiddenState.mask_input_options = {
120
147
  ...val,
121
148
  password: true
122
- };
149
+ };else (0, _console.warn)('An invalid session_replay.mask_input_option was provided and will not be used', val);
123
150
  }
124
151
  },
125
152
  spa: {
@@ -12,7 +12,7 @@ exports.VERSION = exports.DIST_METHOD = exports.BUILD_ENV = void 0;
12
12
  /**
13
13
  * Exposes the version of the agent
14
14
  */
15
- const VERSION = "1.241.0";
15
+ const VERSION = "1.242.0";
16
16
 
17
17
  /**
18
18
  * Exposes the build type of the agent
@@ -12,7 +12,7 @@ exports.VERSION = exports.DIST_METHOD = exports.BUILD_ENV = void 0;
12
12
  /**
13
13
  * Exposes the version of the agent
14
14
  */
15
- const VERSION = "1.241.0";
15
+ const VERSION = "1.242.0";
16
16
 
17
17
  /**
18
18
  * Exposes the build type of the agent
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.isValidSelector = void 0;
7
+ const isValidSelector = selector => {
8
+ if (!selector || typeof selector !== 'string') return false;
9
+ try {
10
+ document.createDocumentFragment().querySelector(selector);
11
+ } catch {
12
+ return false;
13
+ }
14
+ return true;
15
+ };
16
+ exports.isValidSelector = isValidSelector;
@@ -15,6 +15,8 @@ var _sharedChannel = require("../../../common/constants/shared-channel");
15
15
  var _encode = require("../../../common/url/encode");
16
16
  var _console = require("../../../common/util/console");
17
17
  var _runtime = require("../../../common/constants/runtime");
18
+ var _constants2 = require("../../metrics/constants");
19
+ var _features = require("../../../loaders/features/features");
18
20
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
19
21
  function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } /*
20
22
  * Copyright 2023 New Relic Corporation. All rights reserved.
@@ -104,7 +106,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
104
106
  if (shouldSetup) {
105
107
  // 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.
106
108
  this.ee.on(_sessionEntity.SESSION_EVENTS.RESET, () => {
107
- this.abort();
109
+ this.abort('Session Reset');
108
110
  });
109
111
 
110
112
  // The SessionEntity class can emit a message indicating the session was paused (visibility change). This feature must stop recording if that occurs.
@@ -191,7 +193,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
191
193
  // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
192
194
  recorder = (await Promise.resolve().then(() => _interopRequireWildcard(require( /* webpackChunkName: "recorder" */'rrweb')))).record;
193
195
  } catch (err) {
194
- return this.abort();
196
+ return this.abort('Recorder failed to import');
195
197
  }
196
198
 
197
199
  // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
@@ -267,7 +269,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
267
269
  onHarvestFinished(result) {
268
270
  // The mutual decision for now is to stop recording and clear buffers if ingest is experiencing 429 rate limiting
269
271
  if (result.status === 429) {
270
- this.abort();
272
+ this.abort('429: Too many requests');
271
273
  }
272
274
  if (this.blocked) this.scheduler.stopTimer(true);
273
275
  }
@@ -287,7 +289,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
287
289
  startRecording() {
288
290
  if (!recorder) {
289
291
  (0, _console.warn)('Recording library was never imported');
290
- return this.abort();
292
+ return this.abort('Recorder was never imported');
291
293
  }
292
294
  this.clearTimestamps();
293
295
  // set the fallbacks as early as possible
@@ -331,7 +333,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
331
333
  // Vortex will block payloads at a certain size, we might as well not send.
332
334
  if (payloadSize > MAX_PAYLOAD_SIZE) {
333
335
  this.clearBuffer();
334
- return this.abort();
336
+ this.ee.emit(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Too-Big/Seen'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
337
+ return this.abort('Payload too big');
335
338
  }
336
339
  // Checkout events are flags by the recording lib that indicate a fullsnapshot was taken every n ms. These are important
337
340
  // to help reconstruct the replay later and must be included. While waiting and buffering for errors to come through,
@@ -402,7 +405,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
402
405
  }
403
406
 
404
407
  /** Abort the feature, once aborted it will not resume */
405
- abort() {
408
+ abort(reason) {
409
+ (0, _console.warn)("SR aborted -- ".concat(reason));
406
410
  this.blocked = true;
407
411
  this.mode = _sessionEntity.MODE.OFF;
408
412
  this.stopRecording();
@@ -119,6 +119,7 @@ class InstrumentBase extends _featureBase.FeatureBase {
119
119
  (0, _console.warn)("Downloading and initializing ".concat(this.featureName, " failed..."), e);
120
120
  this.abortHandler?.(); // undo any important alterations made to the page
121
121
  // not supported yet but nice to do: "abort" this agent's EE for this feature specifically
122
+ (0, _drain.drain)(this.agentIdentifier, this.featureName);
122
123
  loadedSuccessfully(false);
123
124
  }
124
125
  };
@@ -4,4 +4,11 @@
4
4
  */
5
5
 
6
6
  import '../polyfills.js';
7
- import '../lite';
7
+ import { Agent } from '../../loaders/agent';
8
+ import { Instrument as InstrumentPageViewEvent } from '../../features/page_view_event/instrument';
9
+ import { Instrument as InstrumentPageViewTiming } from '../../features/page_view_timing/instrument';
10
+ import { Instrument as InstrumentMetrics } from '../../features/metrics/instrument';
11
+ new Agent({
12
+ features: [InstrumentPageViewEvent, InstrumentPageViewTiming, InstrumentMetrics],
13
+ loaderType: 'lite-polyfills'
14
+ });
@@ -1,7 +1,18 @@
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
+ import { Instrument as InstrumentPageViewEvent } from '../../features/page_view_event/instrument';
9
+ import { Instrument as InstrumentPageViewTiming } from '../../features/page_view_timing/instrument';
10
+ import { Instrument as InstrumentMetrics } from '../../features/metrics/instrument';
11
+ import { Instrument as InstrumentErrors } from '../../features/jserrors/instrument';
12
+ import { Instrument as InstrumentXhr } from '../../features/ajax/instrument';
13
+ import { Instrument as InstrumentSessionTrace } from '../../features/session_trace/instrument';
14
+ import { Instrument as InstrumentPageAction } from '../../features/page_action/instrument';
15
+ new Agent({
16
+ features: [InstrumentPageViewEvent, InstrumentPageViewTiming, InstrumentSessionTrace, InstrumentXhr, InstrumentMetrics, InstrumentPageAction, InstrumentErrors],
17
+ loaderType: 'pro-polyfills'
18
+ });
@@ -4,4 +4,16 @@
4
4
  */
5
5
 
6
6
  import '../polyfills.js';
7
- import '../spa';
7
+ import { Agent } from '../../loaders/agent';
8
+ import { Instrument as InstrumentPageViewEvent } from '../../features/page_view_event/instrument';
9
+ import { Instrument as InstrumentPageViewTiming } from '../../features/page_view_timing/instrument';
10
+ import { Instrument as InstrumentMetrics } from '../../features/metrics/instrument';
11
+ import { Instrument as InstrumentErrors } from '../../features/jserrors/instrument';
12
+ import { Instrument as InstrumentXhr } from '../../features/ajax/instrument';
13
+ import { Instrument as InstrumentSessionTrace } from '../../features/session_trace/instrument';
14
+ import { Instrument as InstrumentSpa } from '../../features/spa/instrument';
15
+ import { Instrument as InstrumentPageAction } from '../../features/page_action/instrument';
16
+ new Agent({
17
+ features: [InstrumentXhr, InstrumentPageViewEvent, InstrumentPageViewTiming, InstrumentSessionTrace, InstrumentMetrics, InstrumentPageAction, InstrumentErrors, InstrumentSpa],
18
+ loaderType: 'spa-polyfills'
19
+ });
@@ -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 = {
8
+ mask_selector: '*',
6
9
  block_selector: '[data-nr-block]',
7
10
  mask_input_options: {
8
- password: true
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,
@@ -83,8 +103,15 @@ const model = () => {
83
103
  error_sampling_rate: 50,
84
104
  // float from 0 - 100
85
105
  // recording config settings
86
- mask_text_selector: '*',
87
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
+ },
88
115
  // these properties only have getters because they are enforcable constants and should error if someone tries to override them
89
116
  get block_class() {
90
117
  return 'nr-block';
@@ -101,17 +128,17 @@ const model = () => {
101
128
  return hiddenState.block_selector;
102
129
  },
103
130
  set block_selector(val) {
104
- hiddenState.block_selector += ",".concat(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);
105
132
  },
106
133
  // password: must always be present and true no matter what customer sets
107
134
  get mask_input_options() {
108
135
  return hiddenState.mask_input_options;
109
136
  },
110
137
  set mask_input_options(val) {
111
- hiddenState.mask_input_options = {
138
+ if (val && typeof val === 'object') hiddenState.mask_input_options = {
112
139
  ...val,
113
140
  password: true
114
- };
141
+ };else warn('An invalid session_replay.mask_input_option was provided and will not be used', val);
115
142
  }
116
143
  },
117
144
  spa: {
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Exposes the version of the agent
8
8
  */
9
- export const VERSION = "1.241.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.241.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
+ };
@@ -21,6 +21,8 @@ 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
23
  import { globalScope } from '../../../common/constants/runtime';
24
+ import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants';
25
+ import { FEATURE_NAMES } from '../../../loaders/features/features';
24
26
 
25
27
  // would be better to get this dynamically in some way
26
28
  export const RRWEB_VERSION = '2.0.0-alpha.8';
@@ -96,7 +98,7 @@ export class Aggregate extends AggregateBase {
96
98
  if (shouldSetup) {
97
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.
98
100
  this.ee.on(SESSION_EVENTS.RESET, () => {
99
- this.abort();
101
+ this.abort('Session Reset');
100
102
  });
101
103
 
102
104
  // The SessionEntity class can emit a message indicating the session was paused (visibility change). This feature must stop recording if that occurs.
@@ -183,7 +185,7 @@ export class Aggregate extends AggregateBase {
183
185
  // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
184
186
  recorder = (await import( /* webpackChunkName: "recorder" */'rrweb')).record;
185
187
  } catch (err) {
186
- return this.abort();
188
+ return this.abort('Recorder failed to import');
187
189
  }
188
190
 
189
191
  // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
@@ -259,7 +261,7 @@ export class Aggregate extends AggregateBase {
259
261
  onHarvestFinished(result) {
260
262
  // The mutual decision for now is to stop recording and clear buffers if ingest is experiencing 429 rate limiting
261
263
  if (result.status === 429) {
262
- this.abort();
264
+ this.abort('429: Too many requests');
263
265
  }
264
266
  if (this.blocked) this.scheduler.stopTimer(true);
265
267
  }
@@ -279,7 +281,7 @@ export class Aggregate extends AggregateBase {
279
281
  startRecording() {
280
282
  if (!recorder) {
281
283
  warn('Recording library was never imported');
282
- return this.abort();
284
+ return this.abort('Recorder was never imported');
283
285
  }
284
286
  this.clearTimestamps();
285
287
  // set the fallbacks as early as possible
@@ -323,7 +325,8 @@ export class Aggregate extends AggregateBase {
323
325
  // Vortex will block payloads at a certain size, we might as well not send.
324
326
  if (payloadSize > MAX_PAYLOAD_SIZE) {
325
327
  this.clearBuffer();
326
- 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');
327
330
  }
328
331
  // Checkout events are flags by the recording lib that indicate a fullsnapshot was taken every n ms. These are important
329
332
  // to help reconstruct the replay later and must be included. While waiting and buffering for errors to come through,
@@ -394,7 +397,8 @@ export class Aggregate extends AggregateBase {
394
397
  }
395
398
 
396
399
  /** Abort the feature, once aborted it will not resume */
397
- abort() {
400
+ abort(reason) {
401
+ warn("SR aborted -- ".concat(reason));
398
402
  this.blocked = true;
399
403
  this.mode = MODE.OFF;
400
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
  };
@@ -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"}
@@ -94,7 +94,7 @@ export class Aggregate extends AggregateBase {
94
94
  /** Estimate the payload size */
95
95
  getPayloadSize(newBytes?: number): any;
96
96
  /** Abort the feature, once aborted it will not resume */
97
- abort(): void;
97
+ abort(reason: any): void;
98
98
  /** Extensive research has yielded about an 88% compression factor on these payloads.
99
99
  * This is an estimation using that factor as to not cause performance issues while evaluating
100
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":"AAyBA,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,yCA4CC;IAED,0HAA0H;IAC1H,yBAGC;IAED,gCAQC;IAED,wBAEC;IAED,gCAAgC;IAChC,uCAGC;IAED,yDAAyD;IACzD,cAOC;IAED;;;SAGK;IACL,oCAGC;IAED,yCAGC;CACF;8BA3X6B,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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/browser-agent",
3
- "version": "1.241.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",
@@ -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
+ })
@@ -4,4 +4,27 @@
4
4
  */
5
5
 
6
6
  import '../polyfills.js'
7
- import '../spa'
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 InstrumentSpa } from '../../features/spa/instrument'
16
+ import { Instrument as InstrumentPageAction } from '../../features/page_action/instrument'
17
+
18
+ new Agent({
19
+ features: [
20
+ InstrumentXhr,
21
+ InstrumentPageViewEvent,
22
+ InstrumentPageViewTiming,
23
+ InstrumentSessionTrace,
24
+ InstrumentMetrics,
25
+ InstrumentPageAction,
26
+ InstrumentErrors,
27
+ InstrumentSpa
28
+ ],
29
+ loaderType: 'spa-polyfills'
30
+ })
@@ -1,11 +1,32 @@
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
 
5
7
  const model = () => {
6
8
  const hiddenState = {
9
+ mask_selector: '*',
7
10
  block_selector: '[data-nr-block]',
8
- mask_input_options: { password: true }
11
+ mask_input_options: {
12
+ color: false,
13
+ date: false,
14
+ 'datetime-local': false,
15
+ email: false,
16
+ month: false,
17
+ number: false,
18
+ range: false,
19
+ search: false,
20
+ tel: false,
21
+ text: false,
22
+ time: false,
23
+ url: false,
24
+ week: false,
25
+ // unify textarea and select element with text input
26
+ textarea: false,
27
+ select: false,
28
+ password: true // This will be enforced to always be true in the setter
29
+ }
9
30
  }
10
31
  return {
11
32
  proxy: {
@@ -43,8 +64,14 @@ const model = () => {
43
64
  sampling_rate: 50, // float from 0 - 100
44
65
  error_sampling_rate: 50, // float from 0 - 100
45
66
  // recording config settings
46
- mask_text_selector: '*',
47
67
  mask_all_inputs: true,
68
+ // this has a getter/setter to facilitate validation of the selectors
69
+ get mask_text_selector () { return hiddenState.mask_selector },
70
+ set mask_text_selector (val) {
71
+ if (isValidSelector(val)) hiddenState.mask_selector = val + ',[data-nr-mask]'
72
+ else if (val === null) hiddenState.mask_selector = val // null is acceptable, which completely disables the behavior
73
+ else warn('An invalid session_replay.mask_selector was provided and will not be used', val)
74
+ },
48
75
  // these properties only have getters because they are enforcable constants and should error if someone tries to override them
49
76
  get block_class () { return 'nr-block' },
50
77
  get ignore_class () { return 'nr-ignore' },
@@ -55,14 +82,16 @@ const model = () => {
55
82
  return hiddenState.block_selector
56
83
  },
57
84
  set block_selector (val) {
58
- hiddenState.block_selector += `,${val}`
85
+ if (isValidSelector(val)) hiddenState.block_selector += `,${val}`
86
+ else if (val !== '') warn('An invalid session_replay.block_selector was provided and will not be used', val)
59
87
  },
60
88
  // password: must always be present and true no matter what customer sets
61
89
  get mask_input_options () {
62
90
  return hiddenState.mask_input_options
63
91
  },
64
92
  set mask_input_options (val) {
65
- hiddenState.mask_input_options = { ...val, password: true }
93
+ if (val && typeof val === 'object') hiddenState.mask_input_options = { ...val, password: true }
94
+ else warn('An invalid session_replay.mask_input_option was provided and will not be used', val)
66
95
  }
67
96
  },
68
97
  spa: { enabled: true, harvestTimeSeconds: 10, autoStart: true }
@@ -26,3 +26,43 @@ test('getConfigurationValue parses path correctly', () => {
26
26
  expect(getConfigurationValue('ab', 'page_action')).toEqual({ enabled: true, harvestTimeSeconds: 1000, autoStart: true })
27
27
  expect(getConfigurationValue('ab', 'page_action.harvestTimeSeconds')).toEqual(1000)
28
28
  })
29
+
30
+ describe('property getters/setters used for validation', () => {
31
+ test('invalid values do not pass through', () => {
32
+ setConfiguration('12345', {
33
+ session_replay: {
34
+ block_selector: '[invalid selector]',
35
+ mask_text_selector: '[invalid selector]',
36
+ mask_input_options: 'select:true'
37
+ }
38
+ })
39
+
40
+ expect(getConfigurationValue('12345', 'session_replay.block_selector')).toEqual('[data-nr-block]')
41
+ expect(getConfigurationValue('12345', 'session_replay.mask_text_selector')).toEqual('*')
42
+ expect(getConfigurationValue('12345', 'session_replay.mask_input_options')).toMatchObject({ password: true, select: false })
43
+ })
44
+
45
+ test('valid values do pass through', () => {
46
+ setConfiguration('23456', {
47
+ session_replay: {
48
+ block_selector: '[block-text-test]',
49
+ mask_text_selector: '[mask-text-test]',
50
+ mask_input_options: { select: true }
51
+ }
52
+ })
53
+
54
+ expect(getConfigurationValue('23456', 'session_replay.block_selector')).toEqual('[data-nr-block],[block-text-test]')
55
+ expect(getConfigurationValue('23456', 'session_replay.mask_text_selector')).toEqual('[mask-text-test],[data-nr-mask]')
56
+ expect(getConfigurationValue('23456', 'session_replay.mask_input_options')).toMatchObject({ password: true, select: true })
57
+ })
58
+
59
+ test('null accepted for mask_text', () => {
60
+ setConfiguration('34567', {
61
+ session_replay: {
62
+ mask_text_selector: null
63
+ }
64
+ })
65
+
66
+ expect(getConfigurationValue('34567', 'session_replay.mask_text_selector')).toEqual(null)
67
+ })
68
+ })
@@ -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
+ }
@@ -0,0 +1,24 @@
1
+ import { isValidSelector } from './query-selector'
2
+ describe('query selector tests', () => {
3
+ test('handles nullish values', () => {
4
+ expect(isValidSelector(null)).toEqual(false)
5
+ expect(isValidSelector('')).toEqual(false)
6
+ expect(isValidSelector(0)).toEqual(false)
7
+ expect(isValidSelector(false)).toEqual(false)
8
+ })
9
+
10
+ test('handles truthy but invalid values', () => {
11
+ expect(isValidSelector({ test: 1 })).toEqual(false)
12
+ expect(isValidSelector([1, 2, 3])).toEqual(false)
13
+ expect(isValidSelector(1)).toEqual(false)
14
+ expect(isValidSelector(',')).toEqual(false)
15
+ expect(isValidSelector('[invalid space]')).toEqual(false)
16
+ })
17
+
18
+ test('handles valid selectors', () => {
19
+ expect(isValidSelector('#id')).toEqual(true)
20
+ expect(isValidSelector('.class')).toEqual(true)
21
+ expect(isValidSelector('.multiple,#selectors')).toEqual(true)
22
+ expect(isValidSelector('[attr-selector]')).toEqual(true)
23
+ })
24
+ })
@@ -21,6 +21,8 @@ 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
23
  import { globalScope } from '../../../common/constants/runtime'
24
+ import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants'
25
+ import { FEATURE_NAMES } from '../../../loaders/features/features'
24
26
 
25
27
  // would be better to get this dynamically in some way
26
28
  export const RRWEB_VERSION = '2.0.0-alpha.8'
@@ -91,7 +93,7 @@ export class Aggregate extends AggregateBase {
91
93
  if (shouldSetup) {
92
94
  // 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.
93
95
  this.ee.on(SESSION_EVENTS.RESET, () => {
94
- this.abort()
96
+ this.abort('Session Reset')
95
97
  })
96
98
 
97
99
  // The SessionEntity class can emit a message indicating the session was paused (visibility change). This feature must stop recording if that occurs.
@@ -176,7 +178,7 @@ export class Aggregate extends AggregateBase {
176
178
  // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
177
179
  recorder = (await import(/* webpackChunkName: "recorder" */'rrweb')).record
178
180
  } catch (err) {
179
- return this.abort()
181
+ return this.abort('Recorder failed to import')
180
182
  }
181
183
 
182
184
  // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
@@ -251,7 +253,7 @@ export class Aggregate extends AggregateBase {
251
253
  onHarvestFinished (result) {
252
254
  // The mutual decision for now is to stop recording and clear buffers if ingest is experiencing 429 rate limiting
253
255
  if (result.status === 429) {
254
- this.abort()
256
+ this.abort('429: Too many requests')
255
257
  }
256
258
 
257
259
  if (this.blocked) this.scheduler.stopTimer(true)
@@ -272,7 +274,7 @@ export class Aggregate extends AggregateBase {
272
274
  startRecording () {
273
275
  if (!recorder) {
274
276
  warn('Recording library was never imported')
275
- return this.abort()
277
+ return this.abort('Recorder was never imported')
276
278
  }
277
279
  this.clearTimestamps()
278
280
  // set the fallbacks as early as possible
@@ -309,7 +311,8 @@ export class Aggregate extends AggregateBase {
309
311
  // Vortex will block payloads at a certain size, we might as well not send.
310
312
  if (payloadSize > MAX_PAYLOAD_SIZE) {
311
313
  this.clearBuffer()
312
- return this.abort()
314
+ this.ee.emit(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Too-Big/Seen'], undefined, FEATURE_NAMES.metrics, this.ee)
315
+ return this.abort('Payload too big')
313
316
  }
314
317
  // Checkout events are flags by the recording lib that indicate a fullsnapshot was taken every n ms. These are important
315
318
  // to help reconstruct the replay later and must be included. While waiting and buffering for errors to come through,
@@ -373,7 +376,8 @@ export class Aggregate extends AggregateBase {
373
376
  }
374
377
 
375
378
  /** Abort the feature, once aborted it will not resume */
376
- abort () {
379
+ abort (reason) {
380
+ warn(`SR aborted -- ${reason}`)
377
381
  this.blocked = true
378
382
  this.mode = MODE.OFF
379
383
  this.stopRecording()
@@ -108,6 +108,7 @@ export class InstrumentBase extends FeatureBase {
108
108
  warn(`Downloading and initializing ${this.featureName} failed...`, e)
109
109
  this.abortHandler?.() // undo any important alterations made to the page
110
110
  // not supported yet but nice to do: "abort" this agent's EE for this feature specifically
111
+ drain(this.agentIdentifier, this.featureName)
111
112
  loadedSuccessfully(false)
112
113
  }
113
114
  }