@newrelic/browser-agent 1.241.0 → 1.243.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 (132) hide show
  1. package/CHANGELOG.md +1465 -0
  2. package/dist/cjs/cdn/polyfills/lite.js +13 -1
  3. package/dist/cjs/cdn/polyfills/pro.js +17 -1
  4. package/dist/cjs/cdn/polyfills/spa.js +18 -1
  5. package/dist/cjs/common/config/state/init.js +32 -5
  6. package/dist/cjs/common/constants/env.cdn.js +1 -1
  7. package/dist/cjs/common/constants/env.npm.js +1 -1
  8. package/dist/cjs/common/dom/query-selector.js +16 -0
  9. package/dist/cjs/common/session/session-entity.js +20 -2
  10. package/dist/cjs/common/wrap/wrap-function.js +1 -1
  11. package/dist/cjs/features/ajax/aggregate/index.js +1 -1
  12. package/dist/cjs/features/session_replay/aggregate/index.js +84 -50
  13. package/dist/cjs/features/utils/feature-base.js +1 -2
  14. package/dist/cjs/features/utils/instrument-base.js +1 -0
  15. package/dist/cjs/loaders/api/api.js +2 -2
  16. package/dist/cjs/loaders/api/apiAsync.js +0 -37
  17. package/dist/cjs/loaders/configure/configure.js +1 -1
  18. package/dist/cjs/loaders/configure/public-path.js +6 -3
  19. package/dist/esm/cdn/polyfills/lite.js +8 -1
  20. package/dist/esm/cdn/polyfills/pro.js +13 -2
  21. package/dist/esm/cdn/polyfills/spa.js +13 -1
  22. package/dist/esm/common/config/state/init.js +32 -5
  23. package/dist/esm/common/constants/env.cdn.js +1 -1
  24. package/dist/esm/common/constants/env.npm.js +1 -1
  25. package/dist/esm/common/dom/query-selector.js +9 -0
  26. package/dist/esm/common/session/session-entity.js +18 -1
  27. package/dist/esm/common/wrap/wrap-function.js +1 -1
  28. package/dist/esm/features/ajax/aggregate/index.js +1 -1
  29. package/dist/esm/features/session_replay/aggregate/index.js +83 -50
  30. package/dist/esm/features/utils/feature-base.js +1 -2
  31. package/dist/esm/features/utils/instrument-base.js +1 -0
  32. package/dist/esm/loaders/api/api.js +2 -2
  33. package/dist/esm/loaders/api/apiAsync.js +1 -36
  34. package/dist/esm/loaders/configure/configure.js +1 -1
  35. package/dist/esm/loaders/configure/public-path.js +6 -3
  36. package/dist/types/common/config/state/init.d.ts.map +1 -1
  37. package/dist/types/common/dom/query-selector.d.ts +2 -0
  38. package/dist/types/common/dom/query-selector.d.ts.map +1 -0
  39. package/dist/types/common/session/session-entity.d.ts +5 -0
  40. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  41. package/dist/types/features/session_replay/aggregate/index.d.ts +11 -14
  42. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  43. package/dist/types/features/utils/feature-base.d.ts.map +1 -1
  44. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  45. package/dist/types/loaders/api/api.d.ts.map +1 -1
  46. package/dist/types/loaders/api/apiAsync.d.ts.map +1 -1
  47. package/dist/types/loaders/configure/public-path.d.ts +1 -1
  48. package/dist/types/loaders/configure/public-path.d.ts.map +1 -1
  49. package/package.json +2 -2
  50. package/src/cdn/polyfills/lite.js +14 -1
  51. package/src/cdn/polyfills/pro.js +23 -2
  52. package/src/cdn/polyfills/spa.js +24 -1
  53. package/src/common/config/state/init.js +33 -4
  54. package/src/common/dom/query-selector.js +9 -0
  55. package/src/common/session/session-entity.js +20 -1
  56. package/src/common/wrap/wrap-function.js +1 -1
  57. package/src/features/ajax/aggregate/index.js +2 -2
  58. package/src/features/session_replay/aggregate/index.js +82 -34
  59. package/src/features/utils/feature-base.js +1 -2
  60. package/src/features/utils/instrument-base.js +1 -0
  61. package/src/loaders/api/api.js +1 -2
  62. package/src/loaders/api/apiAsync.js +1 -39
  63. package/src/loaders/configure/configure.js +1 -1
  64. package/src/loaders/configure/public-path.js +6 -3
  65. package/src/common/aggregate/aggregator.test.js +0 -107
  66. package/src/common/config/state/configurable.test.js +0 -73
  67. package/src/common/config/state/info.test.js +0 -31
  68. package/src/common/config/state/init.test.js +0 -28
  69. package/src/common/config/state/loader-config.test.js +0 -21
  70. package/src/common/config/state/runtime.test.js +0 -21
  71. package/src/common/constants/env.cdn.test.js +0 -7
  72. package/src/common/constants/env.npm.test.js +0 -7
  73. package/src/common/constants/env.test.js +0 -7
  74. package/src/common/constants/runtime.test.js +0 -176
  75. package/src/common/deny-list/deny-list.test.js +0 -104
  76. package/src/common/drain/drain.test.js +0 -74
  77. package/src/common/event-emitter/contextual-ee.component-test.js +0 -293
  78. package/src/common/event-emitter/handle.test.js +0 -56
  79. package/src/common/event-emitter/register-handler.test.js +0 -61
  80. package/src/common/harvest/harvest-scheduler.test.js +0 -492
  81. package/src/common/harvest/harvest.test.js +0 -813
  82. package/src/common/ids/id.test.js +0 -92
  83. package/src/common/ids/unique-id.test.js +0 -58
  84. package/src/common/session/session-entity.component-test.js +0 -346
  85. package/src/common/storage/local-storage.test.js +0 -17
  86. package/src/common/timer/interaction-timer.component-test.js +0 -212
  87. package/src/common/timer/timer.test.js +0 -99
  88. package/src/common/timing/nav-timing.test.js +0 -161
  89. package/src/common/url/canonicalize-url.test.js +0 -45
  90. package/src/common/url/clean-url.test.js +0 -25
  91. package/src/common/url/encode.test.js +0 -81
  92. package/src/common/url/location.test.js +0 -15
  93. package/src/common/url/parse-url.test.js +0 -110
  94. package/src/common/url/protocol.test.js +0 -17
  95. package/src/common/util/console.test.js +0 -34
  96. package/src/common/util/data-size.test.js +0 -56
  97. package/src/common/util/feature-flags.test.js +0 -94
  98. package/src/common/util/get-or-set.test.js +0 -58
  99. package/src/common/util/invoke.test.js +0 -65
  100. package/src/common/util/map-own.test.js +0 -52
  101. package/src/common/util/obfuscate.component-test.js +0 -173
  102. package/src/common/util/stringify.test.js +0 -49
  103. package/src/common/util/submit-data.test.js +0 -183
  104. package/src/common/util/traverse.test.js +0 -50
  105. package/src/common/vitals/cumulative-layout-shift.test.js +0 -71
  106. package/src/common/vitals/first-contentful-paint.test.js +0 -124
  107. package/src/common/vitals/first-input-delay.test.js +0 -88
  108. package/src/common/vitals/first-paint.test.js +0 -127
  109. package/src/common/vitals/interaction-to-next-paint.test.js +0 -74
  110. package/src/common/vitals/largest-contentful-paint.test.js +0 -94
  111. package/src/common/vitals/long-task.test.js +0 -122
  112. package/src/common/vitals/time-to-first-byte.test.js +0 -147
  113. package/src/common/vitals/vital-metric.test.js +0 -171
  114. package/src/common/wrap/wrap-promise.component-test.js +0 -110
  115. package/src/features/ajax/instrument/distributed-tracing.test.js +0 -375
  116. package/src/features/jserrors/aggregate/canonical-function-name.test.js +0 -13
  117. package/src/features/jserrors/aggregate/compute-stack-trace.test.js +0 -414
  118. package/src/features/jserrors/aggregate/format-stack-trace.test.js +0 -39
  119. package/src/features/jserrors/aggregate/string-hash-code.test.js +0 -12
  120. package/src/features/metrics/aggregate/framework-detection.test.js +0 -332
  121. package/src/features/page_view_timing/aggregate/index.component-test.js +0 -86
  122. package/src/features/session_replay/aggregate/index.component-test.js +0 -317
  123. package/src/features/spa/aggregate/interaction-node.test.js +0 -17
  124. package/src/features/utils/agent-session.test.js +0 -194
  125. package/src/features/utils/aggregate-base.test.js +0 -123
  126. package/src/features/utils/feature-base.test.js +0 -45
  127. package/src/features/utils/handler-cache.test.js +0 -72
  128. package/src/features/utils/instrument-base.test.js +0 -216
  129. package/src/features/utils/lazy-feature-loader.test.js +0 -37
  130. package/src/loaders/api/api.component-test.js +0 -45
  131. package/src/loaders/api/api.test.js +0 -85
  132. package/src/loaders/api/apiAsync.test.js +0 -17
@@ -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.243.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.243.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;
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.SessionEntity = exports.SESSION_EVENTS = exports.MODE = void 0;
6
+ exports.SessionEntity = exports.SESSION_EVENT_TYPES = exports.SESSION_EVENTS = exports.MODE = void 0;
7
7
  var _uniqueId = require("../ids/unique-id");
8
8
  var _console = require("../util/console");
9
9
  var _stringify = require("../util/stringify");
@@ -17,6 +17,7 @@ var _configurable = require("../config/state/configurable");
17
17
  var _handle = require("../event-emitter/handle");
18
18
  var _constants2 = require("../../features/metrics/constants");
19
19
  var _features = require("../../loaders/features/features");
20
+ var _eventListenerOpts = require("../event-listener/event-listener-opts");
20
21
  const MODE = {
21
22
  OFF: 0,
22
23
  FULL: 1,
@@ -31,15 +32,22 @@ const model = {
31
32
  expiresAt: 0,
32
33
  updatedAt: Date.now(),
33
34
  sessionReplay: MODE.OFF,
35
+ sessionReplaySentFirstChunk: false,
34
36
  sessionTraceMode: MODE.OFF,
35
37
  custom: {}
36
38
  };
37
39
  const SESSION_EVENTS = {
38
40
  PAUSE: 'session-pause',
39
41
  RESET: 'session-reset',
40
- RESUME: 'session-resume'
42
+ RESUME: 'session-resume',
43
+ UPDATE: 'session-update'
41
44
  };
42
45
  exports.SESSION_EVENTS = SESSION_EVENTS;
46
+ const SESSION_EVENT_TYPES = {
47
+ SAME_TAB: 'same-tab',
48
+ CROSS_TAB: 'cross-tab'
49
+ };
50
+ exports.SESSION_EVENT_TYPES = SESSION_EVENT_TYPES;
43
51
  class SessionEntity {
44
52
  /**
45
53
  * Create a self-managing Session Entity. This entity is scoped to the agent identifier which triggered it, allowing for multiple simultaneous session objects to exist.
@@ -65,6 +73,15 @@ class SessionEntity {
65
73
  this.ee = _contextualEe.ee.get(agentIdentifier);
66
74
  (0, _wrap.wrapEvents)(this.ee);
67
75
  this.setup(opts);
76
+ if (_runtime.isBrowserScope) {
77
+ (0, _eventListenerOpts.windowAddEventListener)('storage', event => {
78
+ if (event.key === this.lookupKey) {
79
+ const obj = typeof event.newValue === 'string' ? JSON.parse(event.newValue) : event.newValue;
80
+ this.sync(obj);
81
+ this.ee.emit(SESSION_EVENTS.UPDATE, [SESSION_EVENT_TYPES.CROSS_TAB, this.state]);
82
+ }
83
+ });
84
+ }
68
85
  }
69
86
  setup(_ref) {
70
87
  let {
@@ -198,6 +215,7 @@ class SessionEntity {
198
215
  //
199
216
  // TODO - compression would need happen here if we decide to do it
200
217
  this.storage.set(this.lookupKey, (0, _stringify.stringify)(this.state));
218
+ this.ee.emit(SESSION_EVENTS.UPDATE, [SESSION_EVENT_TYPES.SAME_TAB, this.state]);
201
219
  return data;
202
220
  } catch (e) {
203
221
  // storage is inaccessible
@@ -218,5 +218,5 @@ function copy(from, to, emitter) {
218
218
  * @returns {boolean} Whether the passed function is ineligible to be wrapped.
219
219
  */
220
220
  function notWrappable(fn) {
221
- return !(fn && fn instanceof Function && fn.apply && !fn[flag]);
221
+ return !(fn && typeof fn === 'function' && fn.apply && !fn[flag]);
222
222
  }
@@ -83,7 +83,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
83
83
  } else {
84
84
  hash = (0, _stringify.stringify)([params.status, params.host, params.pathname]);
85
85
  }
86
- (0, _handle.handle)('bstXhrAgg', ['xhr', hash, params, metrics], undefined, _features.FEATURE_NAMES.sessionTrace, ee);
87
86
 
88
87
  // store as metric
89
88
  aggregator.store('xhr', hash, params, metrics);
@@ -97,6 +96,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
97
96
  }
98
97
  return;
99
98
  }
99
+ (0, _handle.handle)('bstXhrAgg', ['xhr', hash, params, metrics], undefined, _features.FEATURE_NAMES.sessionTrace, ee);
100
100
  var xhrContext = this;
101
101
  var event = {
102
102
  method: params.method,
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.RRWEB_VERSION = exports.MAX_PAYLOAD_SIZE = exports.IDEAL_PAYLOAD_SIZE = exports.Aggregate = exports.AVG_COMPRESSION = void 0;
6
+ exports.RRWEB_VERSION = exports.RRWEB_EVENT_TYPES = exports.MAX_PAYLOAD_SIZE = exports.IDEAL_PAYLOAD_SIZE = exports.Aggregate = exports.AVG_COMPRESSION = void 0;
7
7
  var _registerHandler = require("../../../common/event-emitter/register-handler");
8
8
  var _harvestScheduler = require("../../../common/harvest/harvest-scheduler");
9
9
  var _constants = require("../constants");
@@ -15,6 +15,7 @@ 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");
18
19
  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
20
  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
21
  * Copyright 2023 New Relic Corporation. All rights reserved.
@@ -31,6 +32,33 @@ const RRWEB_VERSION = '2.0.0-alpha.8';
31
32
  exports.RRWEB_VERSION = RRWEB_VERSION;
32
33
  const AVG_COMPRESSION = 0.12;
33
34
  exports.AVG_COMPRESSION = AVG_COMPRESSION;
35
+ const RRWEB_EVENT_TYPES = {
36
+ DomContentLoaded: 0,
37
+ Load: 1,
38
+ FullSnapshot: 2,
39
+ IncrementalSnapshot: 3,
40
+ Meta: 4,
41
+ Custom: 5
42
+ };
43
+ exports.RRWEB_EVENT_TYPES = RRWEB_EVENT_TYPES;
44
+ const ABORT_REASONS = {
45
+ RESET: {
46
+ message: 'Session was reset',
47
+ sm: 'Reset'
48
+ },
49
+ IMPORT: {
50
+ message: 'Recorder failed to import',
51
+ sm: 'Import'
52
+ },
53
+ TOO_MANY: {
54
+ message: '429: Too Many Requests',
55
+ sm: 'Too-Many'
56
+ },
57
+ TOO_BIG: {
58
+ message: 'Payload was too large',
59
+ sm: 'Too-Big'
60
+ }
61
+ };
34
62
  let recorder, gzipper, u8;
35
63
 
36
64
  /** Vortex caps payload sizes at 1MB */
@@ -66,8 +94,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
66
94
  /** can shut off efforts to compress the data */
67
95
  this.shouldCompress = true;
68
96
 
69
- /** Payload metadata -- Should indicate that the payload being sent is the first of a session */
70
- this.isFirstChunk = false;
71
97
  /** Payload metadata -- Should indicate that the payload being sent has a full DOM snapshot. This can happen
72
98
  * -- When the recording library begins recording, it starts by taking a DOM snapshot
73
99
  * -- When visibility changes from "hidden" -> "visible", it must capture a full snapshot for the replay to work correctly across tabs
@@ -81,16 +107,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
81
107
  /** Payload metadata -- Should indicate when a replay blob started recording. Resets each time a harvest occurs.
82
108
  * cycle timestamps are used as fallbacks if event timestamps cannot be used
83
109
  */
84
- this.timestamp = {
85
- event: {
86
- first: undefined,
87
- last: undefined
88
- },
89
- cycle: {
90
- first: undefined,
91
- last: undefined
92
- }
93
- };
110
+ this.cycleTimestamp = undefined;
94
111
 
95
112
  /** A value which increments with every new mutation node reported. Resets after a harvest is sent */
96
113
  this.payloadBytesEstimation = 0;
@@ -104,7 +121,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
104
121
  if (shouldSetup) {
105
122
  // 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
123
  this.ee.on(_sessionEntity.SESSION_EVENTS.RESET, () => {
107
- this.abort();
124
+ this.abort(ABORT_REASONS.RESET);
108
125
  });
109
126
 
110
127
  // The SessionEntity class can emit a message indicating the session was paused (visibility change). This feature must stop recording if that occurs.
@@ -113,9 +130,19 @@ class Aggregate extends _aggregateBase.AggregateBase {
113
130
  });
114
131
  // The SessionEntity class can emit a message indicating the session was resumed (visibility change). This feature must start running again (if already running) if that occurs.
115
132
  this.ee.on(_sessionEntity.SESSION_EVENTS.RESUME, () => {
133
+ // if the mode changed on a different tab, it needs to update this instance to match
134
+ const {
135
+ session
136
+ } = (0, _config.getRuntime)(this.agentIdentifier);
137
+ this.mode = session.state.sessionReplay;
116
138
  if (!this.initialized || this.mode === _sessionEntity.MODE.OFF) return;
117
139
  this.startRecording();
118
140
  });
141
+ this.ee.on(_sessionEntity.SESSION_EVENTS.UPDATE, (type, data) => {
142
+ if (!this.initialized || this.blocked || type !== _sessionEntity.SESSION_EVENT_TYPES.CROSS_TAB) return;
143
+ if (this.mode !== _sessionEntity.MODE.OFF && data.sessionReplay === _sessionEntity.MODE.OFF) this.abort('Session Entity was set to OFF on another tab');
144
+ this.mode = data.sessionReplay;
145
+ });
119
146
 
120
147
  // Bespoke logic for new endpoint. This will change as downstream dependencies become solidified.
121
148
  this.scheduler = new _harvestScheduler.HarvestScheduler('browser/blobs', {
@@ -131,7 +158,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
131
158
  this.hasError = true;
132
159
  this.errorNoticed = true;
133
160
  // run once
134
- if (this.mode === _sessionEntity.MODE.ERROR) {
161
+ if (this.mode === _sessionEntity.MODE.ERROR && _runtime.globalScope?.document.visibilityState === 'visible') {
135
162
  this.mode = _sessionEntity.MODE.FULL;
136
163
  // if the error was noticed AFTER the recorder was already imported....
137
164
  if (recorder && this.initialized) {
@@ -191,7 +218,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
191
218
  // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
192
219
  recorder = (await Promise.resolve().then(() => _interopRequireWildcard(require( /* webpackChunkName: "recorder" */'rrweb')))).record;
193
220
  } catch (err) {
194
- return this.abort();
221
+ return this.abort(ABORT_REASONS.IMPORT);
195
222
  }
196
223
 
197
224
  // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
@@ -214,7 +241,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
214
241
  this.shouldCompress = false;
215
242
  }
216
243
  this.startRecording();
217
- this.isFirstChunk = !!session.isNew;
218
244
  this.syncWithSessionManager({
219
245
  sessionReplay: this.mode
220
246
  });
@@ -229,14 +255,39 @@ class Aggregate extends _aggregateBase.AggregateBase {
229
255
  this.scheduler.opts.gzip = false;
230
256
  }
231
257
  // TODO -- Gracefully handle the buffer for retries.
258
+ const {
259
+ session
260
+ } = (0, _config.getRuntime)(this.agentIdentifier);
261
+ if (!session.state.sessionReplaySentFirstChunk) this.syncWithSessionManager({
262
+ sessionReplaySentFirstChunk: true
263
+ });
232
264
  this.clearBuffer();
233
265
  return [payload];
234
266
  }
235
267
  getHarvestContents() {
236
268
  const agentRuntime = (0, _config.getRuntime)(this.agentIdentifier);
237
269
  const info = (0, _config.getInfo)(this.agentIdentifier);
238
- const firstTimestamp = this.timestamp.event.first || this.timestamp.cycle.first;
239
- const lastTimestamp = this.timestamp.event.last || this.timestamp.cycle.last;
270
+
271
+ // do not let the last node be a meta node, since this NEEDS to precede a snapshot
272
+ // we will manually inject it later if we find a payload that is missing a meta node
273
+ const payloadEndsWithMeta = this.events[this.events.length - 1]?.type === RRWEB_EVENT_TYPES.Meta;
274
+ if (payloadEndsWithMeta) {
275
+ this.lastMeta = this.events[this.events.length - 1];
276
+ this.events = this.events.slice(0, this.events.length - 1);
277
+ this.hasMeta = !!this.events.find(x => x.type === RRWEB_EVENT_TYPES.Meta);
278
+ }
279
+
280
+ // do not let the first node be a full snapshot node, since this NEEDS to be preceded by a meta node
281
+ // we will manually inject it if this happens
282
+ const payloadStartsWithFullSnapshot = this.events[0]?.type === RRWEB_EVENT_TYPES.FullSnapshot;
283
+ if (payloadStartsWithFullSnapshot) {
284
+ this.hasMeta = true;
285
+ this.events.unshift(this.lastMeta);
286
+ }
287
+ const firstEventTimestamp = this.events[0]?.timestamp;
288
+ const lastEventTimestamp = this.events[this.events.length - 1]?.timestamp;
289
+ const firstTimestamp = firstEventTimestamp || this.cycleTimestamp;
290
+ const lastTimestamp = lastEventTimestamp || (0, _config.getRuntime)(this.agentIdentifier).offset + _runtime.globalScope.performance.now();
240
291
  return {
241
292
  qs: {
242
293
  browser_monitoring_key: info.licenseKey,
@@ -250,12 +301,13 @@ class Aggregate extends _aggregateBase.AggregateBase {
250
301
  'replay.firstTimestamp': firstTimestamp,
251
302
  'replay.lastTimestamp': lastTimestamp,
252
303
  'replay.durationMs': lastTimestamp - firstTimestamp,
304
+ 'replay.nodes': this.events.length,
253
305
  agentVersion: agentRuntime.version,
254
306
  session: agentRuntime.session.state.value,
255
307
  hasMeta: this.hasMeta,
256
308
  hasSnapshot: this.hasSnapshot,
257
309
  hasError: this.hasError,
258
- isFirstChunk: this.isFirstChunk,
310
+ isFirstChunk: agentRuntime.session.state.sessionReplaySentFirstChunk === false,
259
311
  decompressedBytes: this.payloadBytesEstimation,
260
312
  'nr.rrweb.version': RRWEB_VERSION
261
313
  }, MAX_PAYLOAD_SIZE - this.payloadBytesEstimation).substring(1) // remove the leading '&'
@@ -267,7 +319,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
267
319
  onHarvestFinished(result) {
268
320
  // The mutual decision for now is to stop recording and clear buffers if ingest is experiencing 429 rate limiting
269
321
  if (result.status === 429) {
270
- this.abort();
322
+ this.abort(ABORT_REASONS.TOO_MANY);
271
323
  }
272
324
  if (this.blocked) this.scheduler.stopTimer(true);
273
325
  }
@@ -275,7 +327,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
275
327
  /** Clears the buffer (this.events), and resets all payload metadata properties */
276
328
  clearBuffer() {
277
329
  this.events = [];
278
- this.isFirstChunk = false;
279
330
  this.hasSnapshot = false;
280
331
  this.hasMeta = false;
281
332
  this.hasError = false;
@@ -287,7 +338,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
287
338
  startRecording() {
288
339
  if (!recorder) {
289
340
  (0, _console.warn)('Recording library was never imported');
290
- return this.abort();
341
+ return this.abort(ABORT_REASONS.IMPORT);
291
342
  }
292
343
  this.clearTimestamps();
293
344
  // set the fallbacks as early as possible
@@ -323,7 +374,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
323
374
 
324
375
  /** Store a payload in the buffer (this.events). This should be the callback to the recording lib noticing a mutation */
325
376
  store(event, isCheckout) {
326
- this.setTimestamps(event);
377
+ this.setTimestamps();
327
378
  if (this.blocked) return;
328
379
  const eventBytes = (0, _stringify.stringify)(event).length;
329
380
  /** The estimated size of the payload after compression */
@@ -331,7 +382,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
331
382
  // Vortex will block payloads at a certain size, we might as well not send.
332
383
  if (payloadSize > MAX_PAYLOAD_SIZE) {
333
384
  this.clearBuffer();
334
- return this.abort();
385
+ return this.abort(ABORT_REASONS.TOO_BIG);
335
386
  }
336
387
  // Checkout events are flags by the recording lib that indicate a fullsnapshot was taken every n ms. These are important
337
388
  // to help reconstruct the replay later and must be included. While waiting and buffering for errors to come through,
@@ -342,19 +393,13 @@ class Aggregate extends _aggregateBase.AggregateBase {
342
393
  }
343
394
 
344
395
  // meta event
345
- if (event.type === 4) {
396
+ if (event.type === RRWEB_EVENT_TYPES.Meta) {
346
397
  this.hasMeta = true;
347
398
  this.lastMeta = event;
348
399
  }
349
400
  // snapshot event
350
- if (event.type === 2) {
401
+ if (event.type === RRWEB_EVENT_TYPES.FullSnapshot) {
351
402
  this.hasSnapshot = true;
352
- // small chance that the meta event got separated from its matching snapshot across payload harvests
353
- // it needs to precede the snapshot, so shove it in first.
354
- if (!this.hasMeta) {
355
- this.events.push(this.lastMeta);
356
- this.hasMeta = true;
357
- }
358
403
  }
359
404
  this.events.push(event);
360
405
  this.payloadBytesEstimation += eventBytes;
@@ -372,26 +417,12 @@ class Aggregate extends _aggregateBase.AggregateBase {
372
417
  if (!recorder) return;
373
418
  recorder.takeFullSnapshot();
374
419
  }
375
- setTimestamps(event) {
420
+ setTimestamps() {
376
421
  // fallbacks if timestamps cannot be derived from rrweb events
377
- this.timestamp.cycle.last = (0, _config.getRuntime)(this.agentIdentifier).offset + _runtime.globalScope.performance.now();
378
- if (!this.timestamp.cycle.first) this.timestamp.cycle.first = this.timestamp.cycle.last;
379
- // timestamps based on rrweb events
380
- if (!event || !event.timestamp) return;
381
- if (!this.timestamp.event.first) this.timestamp.event.first = event.timestamp;
382
- this.timestamp.event.last = event.timestamp;
422
+ if (!this.cycleTimestamp) this.cycleTimestamp = (0, _config.getRuntime)(this.agentIdentifier).offset + _runtime.globalScope.performance.now();
383
423
  }
384
424
  clearTimestamps() {
385
- this.timestamp = {
386
- event: {
387
- first: undefined,
388
- last: undefined
389
- },
390
- cycle: {
391
- first: undefined,
392
- last: undefined
393
- }
394
- };
425
+ this.cycleTimestamp = undefined;
395
426
  }
396
427
 
397
428
  /** Estimate the payload size */
@@ -403,6 +434,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
403
434
 
404
435
  /** Abort the feature, once aborted it will not resume */
405
436
  abort() {
437
+ let reason = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
438
+ (0, _console.warn)("SR aborted -- ".concat(reason.message));
439
+ this.ee.emit(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ["SessionReplay/Abort/".concat(ABORT_REASONS[reason.sm])]);
406
440
  this.blocked = true;
407
441
  this.mode = _sessionEntity.MODE.OFF;
408
442
  this.stopRecording();
@@ -4,7 +4,6 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.FeatureBase = void 0;
7
- var _config = require("../../common/config/config");
8
7
  var _contextualEe = require("../../common/event-emitter/contextual-ee");
9
8
  class FeatureBase {
10
9
  constructor(agentIdentifier, aggregator, featureName) {
@@ -13,7 +12,7 @@ class FeatureBase {
13
12
  /** @type {Aggregator} */
14
13
  this.aggregator = aggregator;
15
14
  /** @type {ContextualEE} */
16
- this.ee = _contextualEe.ee.get(agentIdentifier, (0, _config.getRuntime)(this.agentIdentifier).isolatedBacklog);
15
+ this.ee = _contextualEe.ee.get(agentIdentifier);
17
16
  /** @type {string} */
18
17
  this.featureName = featureName;
19
18
  /**
@@ -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
  };
@@ -26,7 +26,7 @@ const CUSTOM_ATTR_GROUP = 'CUSTOM/'; // the subgroup items should be stored unde
26
26
  exports.CUSTOM_ATTR_GROUP = CUSTOM_ATTR_GROUP;
27
27
  function setTopLevelCallers() {
28
28
  const nr = (0, _nreum.gosCDN)();
29
- const funcs = ['setErrorHandler', 'finished', 'addToTrace', 'inlineHit', 'addRelease', 'addPageAction', 'setCurrentRouteName', 'setPageViewName', 'setCustomAttribute', 'interaction', 'noticeError', 'setUserId', 'setApplicationVersion', 'start'];
29
+ const funcs = ['setErrorHandler', 'finished', 'addToTrace', 'addRelease', 'addPageAction', 'setCurrentRouteName', 'setPageViewName', 'setCustomAttribute', 'interaction', 'noticeError', 'setUserId', 'setApplicationVersion', 'start'];
30
30
  funcs.forEach(f => {
31
31
  nr[f] = function () {
32
32
  for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
@@ -53,7 +53,7 @@ function setAPI(agentIdentifier, forceDrain) {
53
53
  const apiInterface = {};
54
54
  var instanceEE = _contextualEe.ee.get(agentIdentifier);
55
55
  var tracerEE = instanceEE.get('tracer');
56
- var asyncApiFns = ['setErrorHandler', 'finished', 'addToTrace', 'inlineHit', 'addRelease'];
56
+ var asyncApiFns = ['setErrorHandler', 'finished', 'addToTrace', 'addRelease'];
57
57
  var prefix = 'api-';
58
58
  var spaPrefix = prefix + 'ixn-';
59
59