@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
@@ -10,19 +10,13 @@ var _contextualEe = require("../../common/event-emitter/contextual-ee");
10
10
  var _handle = require("../../common/event-emitter/handle");
11
11
  var _registerHandler = require("../../common/event-emitter/register-handler");
12
12
  var _invoke = require("../../common/util/invoke");
13
- var submitData = _interopRequireWildcard(require("../../common/util/submit-data"));
14
- var _runtime = require("../../common/constants/runtime");
15
13
  var _constants = require("../../features/metrics/constants");
16
- 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); }
17
- 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; }
18
14
  function setAPI(agentIdentifier) {
19
15
  var instanceEE = _contextualEe.ee.get(agentIdentifier);
20
- var cycle = 0;
21
16
  var api = {
22
17
  finished: (0, _invoke.single)(finished),
23
18
  setErrorHandler,
24
19
  addToTrace,
25
- inlineHit,
26
20
  addRelease
27
21
  };
28
22
 
@@ -58,37 +52,6 @@ function setAPI(agentIdentifier) {
58
52
  };
59
53
  (0, _handle.handle)('bstApi', [report], undefined, _features.FEATURE_NAMES.sessionTrace, instanceEE);
60
54
  }
61
-
62
- // NREUM.inlineHit(requestName, queueTime, appTime, totalBackEndTime, domTime, frontEndTime)
63
- //
64
- // requestName - the 'web page' name or service name
65
- // queueTime - the amount of time spent in the app tier queue
66
- // appTime - the amount of time spent in the application code
67
- // totalBackEndTime - the total roundtrip time of the remote service call
68
- // domTime - the time spent processing the result of the service call (or user defined)
69
- // frontEndTime - the time spent rendering the result of the service call (or user defined)
70
- function inlineHit(t, requestName, queueTime, appTime, totalBackEndTime, domTime, frontEndTime) {
71
- if (!_runtime.isBrowserScope) return;
72
- requestName = window.encodeURIComponent(requestName);
73
- cycle += 1;
74
- const agentInfo = (0, _config.getInfo)(agentIdentifier);
75
- if (!agentInfo.beacon) return;
76
- const agentInit = (0, _config.getConfiguration)(agentIdentifier);
77
- const scheme = agentInit.ssl === false ? 'http' : 'https';
78
- const beacon = agentInit.proxy.beacon || agentInfo.beacon;
79
- let url = "".concat(scheme, "://").concat(beacon, "/1/").concat(agentInfo.licenseKey);
80
- url += '?a=' + agentInfo.applicationID + '&';
81
- url += 't=' + requestName + '&';
82
- url += 'qt=' + ~~queueTime + '&';
83
- url += 'ap=' + ~~appTime + '&';
84
- url += 'be=' + ~~totalBackEndTime + '&';
85
- url += 'dc=' + ~~domTime + '&';
86
- url += 'fe=' + ~~frontEndTime + '&';
87
- url += 'c=' + cycle;
88
- submitData.xhr({
89
- url
90
- });
91
- }
92
55
  function setErrorHandler(t, handler) {
93
56
  (0, _config.getRuntime)(agentIdentifier).onerror = handler;
94
57
  }
@@ -47,7 +47,7 @@ function configure(agentIdentifier) {
47
47
  if (!alreadySetOnce) {
48
48
  alreadySetOnce = true;
49
49
  if (updatedInit.proxy.assets) {
50
- (0, _publicPath.redefinePublicPath)(updatedInit.proxy.assets + '/'); // much like the info.beacon & init.proxy.beacon, this input should not end in a slash, but one is needed for webpack concat
50
+ (0, _publicPath.redefinePublicPath)(updatedInit.proxy.assets);
51
51
  internalTrafficList.push(updatedInit.proxy.assets);
52
52
  }
53
53
  if (updatedInit.proxy.beacon) internalTrafficList.push(updatedInit.proxy.beacon);
@@ -6,8 +6,11 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.redefinePublicPath = void 0;
7
7
  // Set the default CDN or remote for fetching the assets; NPM shouldn't change this var.
8
8
 
9
- const redefinePublicPath = url => {
10
- // There's no URL validation here, so caller should check arg if need be.
11
- __webpack_public_path__ = url; // eslint-disable-line
9
+ const redefinePublicPath = urlString => {
10
+ const isOrigin = urlString.startsWith('http');
11
+ // Input is not expected to end in a slash, but webpack concats as-is, so one is inserted.
12
+ urlString += '/';
13
+ // If there's no existing HTTP scheme, the secure protocol is prepended by default.
14
+ __webpack_public_path__ = isOrigin ? urlString : 'https://' + urlString; // eslint-disable-line
12
15
  };
13
16
  exports.redefinePublicPath = redefinePublicPath;
@@ -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.243.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.243.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
+ };
@@ -11,6 +11,7 @@ import { getModeledObject } from '../config/state/configurable';
11
11
  import { handle } from '../event-emitter/handle';
12
12
  import { SUPPORTABILITY_METRIC_CHANNEL } from '../../features/metrics/constants';
13
13
  import { FEATURE_NAMES } from '../../loaders/features/features';
14
+ import { windowAddEventListener } from '../event-listener/event-listener-opts';
14
15
  export const MODE = {
15
16
  OFF: 0,
16
17
  FULL: 1,
@@ -24,13 +25,19 @@ const model = {
24
25
  expiresAt: 0,
25
26
  updatedAt: Date.now(),
26
27
  sessionReplay: MODE.OFF,
28
+ sessionReplaySentFirstChunk: false,
27
29
  sessionTraceMode: MODE.OFF,
28
30
  custom: {}
29
31
  };
30
32
  export const SESSION_EVENTS = {
31
33
  PAUSE: 'session-pause',
32
34
  RESET: 'session-reset',
33
- RESUME: 'session-resume'
35
+ RESUME: 'session-resume',
36
+ UPDATE: 'session-update'
37
+ };
38
+ export const SESSION_EVENT_TYPES = {
39
+ SAME_TAB: 'same-tab',
40
+ CROSS_TAB: 'cross-tab'
34
41
  };
35
42
  export class SessionEntity {
36
43
  /**
@@ -57,6 +64,15 @@ export class SessionEntity {
57
64
  this.ee = ee.get(agentIdentifier);
58
65
  wrapEvents(this.ee);
59
66
  this.setup(opts);
67
+ if (isBrowserScope) {
68
+ windowAddEventListener('storage', event => {
69
+ if (event.key === this.lookupKey) {
70
+ const obj = typeof event.newValue === 'string' ? JSON.parse(event.newValue) : event.newValue;
71
+ this.sync(obj);
72
+ this.ee.emit(SESSION_EVENTS.UPDATE, [SESSION_EVENT_TYPES.CROSS_TAB, this.state]);
73
+ }
74
+ });
75
+ }
60
76
  }
61
77
  setup(_ref) {
62
78
  let {
@@ -190,6 +206,7 @@ export class SessionEntity {
190
206
  //
191
207
  // TODO - compression would need happen here if we decide to do it
192
208
  this.storage.set(this.lookupKey, stringify(this.state));
209
+ this.ee.emit(SESSION_EVENTS.UPDATE, [SESSION_EVENT_TYPES.SAME_TAB, this.state]);
193
210
  return data;
194
211
  } catch (e) {
195
212
  // storage is inaccessible
@@ -210,5 +210,5 @@ function copy(from, to, emitter) {
210
210
  * @returns {boolean} Whether the passed function is ineligible to be wrapped.
211
211
  */
212
212
  function notWrappable(fn) {
213
- return !(fn && fn instanceof Function && fn.apply && !fn[flag]);
213
+ return !(fn && typeof fn === 'function' && fn.apply && !fn[flag]);
214
214
  }
@@ -76,7 +76,6 @@ export class Aggregate extends AggregateBase {
76
76
  } else {
77
77
  hash = stringify([params.status, params.host, params.pathname]);
78
78
  }
79
- handle('bstXhrAgg', ['xhr', hash, params, metrics], undefined, FEATURE_NAMES.sessionTrace, ee);
80
79
 
81
80
  // store as metric
82
81
  aggregator.store('xhr', hash, params, metrics);
@@ -90,6 +89,7 @@ export class Aggregate extends AggregateBase {
90
89
  }
91
90
  return;
92
91
  }
92
+ handle('bstXhrAgg', ['xhr', hash, params, metrics], undefined, FEATURE_NAMES.sessionTrace, ee);
93
93
  var xhrContext = this;
94
94
  var event = {
95
95
  method: params.method,
@@ -15,16 +15,43 @@ import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler';
15
15
  import { FEATURE_NAME } from '../constants';
16
16
  import { stringify } from '../../../common/util/stringify';
17
17
  import { getConfigurationValue, getInfo, getRuntime } from '../../../common/config/config';
18
- import { SESSION_EVENTS, MODE } from '../../../common/session/session-entity';
18
+ import { SESSION_EVENTS, MODE, SESSION_EVENT_TYPES } from '../../../common/session/session-entity';
19
19
  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
23
  import { globalScope } from '../../../common/constants/runtime';
24
+ import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants';
24
25
 
25
26
  // would be better to get this dynamically in some way
26
27
  export const RRWEB_VERSION = '2.0.0-alpha.8';
27
28
  export const AVG_COMPRESSION = 0.12;
29
+ export const RRWEB_EVENT_TYPES = {
30
+ DomContentLoaded: 0,
31
+ Load: 1,
32
+ FullSnapshot: 2,
33
+ IncrementalSnapshot: 3,
34
+ Meta: 4,
35
+ Custom: 5
36
+ };
37
+ const ABORT_REASONS = {
38
+ RESET: {
39
+ message: 'Session was reset',
40
+ sm: 'Reset'
41
+ },
42
+ IMPORT: {
43
+ message: 'Recorder failed to import',
44
+ sm: 'Import'
45
+ },
46
+ TOO_MANY: {
47
+ message: '429: Too Many Requests',
48
+ sm: 'Too-Many'
49
+ },
50
+ TOO_BIG: {
51
+ message: 'Payload was too large',
52
+ sm: 'Too-Big'
53
+ }
54
+ };
28
55
  let recorder, gzipper, u8;
29
56
 
30
57
  /** Vortex caps payload sizes at 1MB */
@@ -58,8 +85,6 @@ export class Aggregate extends AggregateBase {
58
85
  /** can shut off efforts to compress the data */
59
86
  this.shouldCompress = true;
60
87
 
61
- /** Payload metadata -- Should indicate that the payload being sent is the first of a session */
62
- this.isFirstChunk = false;
63
88
  /** Payload metadata -- Should indicate that the payload being sent has a full DOM snapshot. This can happen
64
89
  * -- When the recording library begins recording, it starts by taking a DOM snapshot
65
90
  * -- When visibility changes from "hidden" -> "visible", it must capture a full snapshot for the replay to work correctly across tabs
@@ -73,16 +98,7 @@ export class Aggregate extends AggregateBase {
73
98
  /** Payload metadata -- Should indicate when a replay blob started recording. Resets each time a harvest occurs.
74
99
  * cycle timestamps are used as fallbacks if event timestamps cannot be used
75
100
  */
76
- this.timestamp = {
77
- event: {
78
- first: undefined,
79
- last: undefined
80
- },
81
- cycle: {
82
- first: undefined,
83
- last: undefined
84
- }
85
- };
101
+ this.cycleTimestamp = undefined;
86
102
 
87
103
  /** A value which increments with every new mutation node reported. Resets after a harvest is sent */
88
104
  this.payloadBytesEstimation = 0;
@@ -96,7 +112,7 @@ export class Aggregate extends AggregateBase {
96
112
  if (shouldSetup) {
97
113
  // 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
114
  this.ee.on(SESSION_EVENTS.RESET, () => {
99
- this.abort();
115
+ this.abort(ABORT_REASONS.RESET);
100
116
  });
101
117
 
102
118
  // The SessionEntity class can emit a message indicating the session was paused (visibility change). This feature must stop recording if that occurs.
@@ -105,9 +121,19 @@ export class Aggregate extends AggregateBase {
105
121
  });
106
122
  // 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.
107
123
  this.ee.on(SESSION_EVENTS.RESUME, () => {
124
+ // if the mode changed on a different tab, it needs to update this instance to match
125
+ const {
126
+ session
127
+ } = getRuntime(this.agentIdentifier);
128
+ this.mode = session.state.sessionReplay;
108
129
  if (!this.initialized || this.mode === MODE.OFF) return;
109
130
  this.startRecording();
110
131
  });
132
+ this.ee.on(SESSION_EVENTS.UPDATE, (type, data) => {
133
+ if (!this.initialized || this.blocked || type !== SESSION_EVENT_TYPES.CROSS_TAB) return;
134
+ if (this.mode !== MODE.OFF && data.sessionReplay === MODE.OFF) this.abort('Session Entity was set to OFF on another tab');
135
+ this.mode = data.sessionReplay;
136
+ });
111
137
 
112
138
  // Bespoke logic for new endpoint. This will change as downstream dependencies become solidified.
113
139
  this.scheduler = new HarvestScheduler('browser/blobs', {
@@ -123,7 +149,7 @@ export class Aggregate extends AggregateBase {
123
149
  this.hasError = true;
124
150
  this.errorNoticed = true;
125
151
  // run once
126
- if (this.mode === MODE.ERROR) {
152
+ if (this.mode === MODE.ERROR && globalScope?.document.visibilityState === 'visible') {
127
153
  this.mode = MODE.FULL;
128
154
  // if the error was noticed AFTER the recorder was already imported....
129
155
  if (recorder && this.initialized) {
@@ -183,7 +209,7 @@ export class Aggregate extends AggregateBase {
183
209
  // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
184
210
  recorder = (await import( /* webpackChunkName: "recorder" */'rrweb')).record;
185
211
  } catch (err) {
186
- return this.abort();
212
+ return this.abort(ABORT_REASONS.IMPORT);
187
213
  }
188
214
 
189
215
  // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
@@ -206,7 +232,6 @@ export class Aggregate extends AggregateBase {
206
232
  this.shouldCompress = false;
207
233
  }
208
234
  this.startRecording();
209
- this.isFirstChunk = !!session.isNew;
210
235
  this.syncWithSessionManager({
211
236
  sessionReplay: this.mode
212
237
  });
@@ -221,14 +246,39 @@ export class Aggregate extends AggregateBase {
221
246
  this.scheduler.opts.gzip = false;
222
247
  }
223
248
  // TODO -- Gracefully handle the buffer for retries.
249
+ const {
250
+ session
251
+ } = getRuntime(this.agentIdentifier);
252
+ if (!session.state.sessionReplaySentFirstChunk) this.syncWithSessionManager({
253
+ sessionReplaySentFirstChunk: true
254
+ });
224
255
  this.clearBuffer();
225
256
  return [payload];
226
257
  }
227
258
  getHarvestContents() {
228
259
  const agentRuntime = getRuntime(this.agentIdentifier);
229
260
  const info = getInfo(this.agentIdentifier);
230
- const firstTimestamp = this.timestamp.event.first || this.timestamp.cycle.first;
231
- const lastTimestamp = this.timestamp.event.last || this.timestamp.cycle.last;
261
+
262
+ // do not let the last node be a meta node, since this NEEDS to precede a snapshot
263
+ // we will manually inject it later if we find a payload that is missing a meta node
264
+ const payloadEndsWithMeta = this.events[this.events.length - 1]?.type === RRWEB_EVENT_TYPES.Meta;
265
+ if (payloadEndsWithMeta) {
266
+ this.lastMeta = this.events[this.events.length - 1];
267
+ this.events = this.events.slice(0, this.events.length - 1);
268
+ this.hasMeta = !!this.events.find(x => x.type === RRWEB_EVENT_TYPES.Meta);
269
+ }
270
+
271
+ // do not let the first node be a full snapshot node, since this NEEDS to be preceded by a meta node
272
+ // we will manually inject it if this happens
273
+ const payloadStartsWithFullSnapshot = this.events[0]?.type === RRWEB_EVENT_TYPES.FullSnapshot;
274
+ if (payloadStartsWithFullSnapshot) {
275
+ this.hasMeta = true;
276
+ this.events.unshift(this.lastMeta);
277
+ }
278
+ const firstEventTimestamp = this.events[0]?.timestamp;
279
+ const lastEventTimestamp = this.events[this.events.length - 1]?.timestamp;
280
+ const firstTimestamp = firstEventTimestamp || this.cycleTimestamp;
281
+ const lastTimestamp = lastEventTimestamp || getRuntime(this.agentIdentifier).offset + globalScope.performance.now();
232
282
  return {
233
283
  qs: {
234
284
  browser_monitoring_key: info.licenseKey,
@@ -242,12 +292,13 @@ export class Aggregate extends AggregateBase {
242
292
  'replay.firstTimestamp': firstTimestamp,
243
293
  'replay.lastTimestamp': lastTimestamp,
244
294
  'replay.durationMs': lastTimestamp - firstTimestamp,
295
+ 'replay.nodes': this.events.length,
245
296
  agentVersion: agentRuntime.version,
246
297
  session: agentRuntime.session.state.value,
247
298
  hasMeta: this.hasMeta,
248
299
  hasSnapshot: this.hasSnapshot,
249
300
  hasError: this.hasError,
250
- isFirstChunk: this.isFirstChunk,
301
+ isFirstChunk: agentRuntime.session.state.sessionReplaySentFirstChunk === false,
251
302
  decompressedBytes: this.payloadBytesEstimation,
252
303
  'nr.rrweb.version': RRWEB_VERSION
253
304
  }, MAX_PAYLOAD_SIZE - this.payloadBytesEstimation).substring(1) // remove the leading '&'
@@ -259,7 +310,7 @@ export class Aggregate extends AggregateBase {
259
310
  onHarvestFinished(result) {
260
311
  // The mutual decision for now is to stop recording and clear buffers if ingest is experiencing 429 rate limiting
261
312
  if (result.status === 429) {
262
- this.abort();
313
+ this.abort(ABORT_REASONS.TOO_MANY);
263
314
  }
264
315
  if (this.blocked) this.scheduler.stopTimer(true);
265
316
  }
@@ -267,7 +318,6 @@ export class Aggregate extends AggregateBase {
267
318
  /** Clears the buffer (this.events), and resets all payload metadata properties */
268
319
  clearBuffer() {
269
320
  this.events = [];
270
- this.isFirstChunk = false;
271
321
  this.hasSnapshot = false;
272
322
  this.hasMeta = false;
273
323
  this.hasError = false;
@@ -279,7 +329,7 @@ export class Aggregate extends AggregateBase {
279
329
  startRecording() {
280
330
  if (!recorder) {
281
331
  warn('Recording library was never imported');
282
- return this.abort();
332
+ return this.abort(ABORT_REASONS.IMPORT);
283
333
  }
284
334
  this.clearTimestamps();
285
335
  // set the fallbacks as early as possible
@@ -315,7 +365,7 @@ export class Aggregate extends AggregateBase {
315
365
 
316
366
  /** Store a payload in the buffer (this.events). This should be the callback to the recording lib noticing a mutation */
317
367
  store(event, isCheckout) {
318
- this.setTimestamps(event);
368
+ this.setTimestamps();
319
369
  if (this.blocked) return;
320
370
  const eventBytes = stringify(event).length;
321
371
  /** The estimated size of the payload after compression */
@@ -323,7 +373,7 @@ export class Aggregate extends AggregateBase {
323
373
  // Vortex will block payloads at a certain size, we might as well not send.
324
374
  if (payloadSize > MAX_PAYLOAD_SIZE) {
325
375
  this.clearBuffer();
326
- return this.abort();
376
+ return this.abort(ABORT_REASONS.TOO_BIG);
327
377
  }
328
378
  // Checkout events are flags by the recording lib that indicate a fullsnapshot was taken every n ms. These are important
329
379
  // to help reconstruct the replay later and must be included. While waiting and buffering for errors to come through,
@@ -334,19 +384,13 @@ export class Aggregate extends AggregateBase {
334
384
  }
335
385
 
336
386
  // meta event
337
- if (event.type === 4) {
387
+ if (event.type === RRWEB_EVENT_TYPES.Meta) {
338
388
  this.hasMeta = true;
339
389
  this.lastMeta = event;
340
390
  }
341
391
  // snapshot event
342
- if (event.type === 2) {
392
+ if (event.type === RRWEB_EVENT_TYPES.FullSnapshot) {
343
393
  this.hasSnapshot = true;
344
- // small chance that the meta event got separated from its matching snapshot across payload harvests
345
- // it needs to precede the snapshot, so shove it in first.
346
- if (!this.hasMeta) {
347
- this.events.push(this.lastMeta);
348
- this.hasMeta = true;
349
- }
350
394
  }
351
395
  this.events.push(event);
352
396
  this.payloadBytesEstimation += eventBytes;
@@ -364,26 +408,12 @@ export class Aggregate extends AggregateBase {
364
408
  if (!recorder) return;
365
409
  recorder.takeFullSnapshot();
366
410
  }
367
- setTimestamps(event) {
411
+ setTimestamps() {
368
412
  // fallbacks if timestamps cannot be derived from rrweb events
369
- this.timestamp.cycle.last = getRuntime(this.agentIdentifier).offset + globalScope.performance.now();
370
- if (!this.timestamp.cycle.first) this.timestamp.cycle.first = this.timestamp.cycle.last;
371
- // timestamps based on rrweb events
372
- if (!event || !event.timestamp) return;
373
- if (!this.timestamp.event.first) this.timestamp.event.first = event.timestamp;
374
- this.timestamp.event.last = event.timestamp;
413
+ if (!this.cycleTimestamp) this.cycleTimestamp = getRuntime(this.agentIdentifier).offset + globalScope.performance.now();
375
414
  }
376
415
  clearTimestamps() {
377
- this.timestamp = {
378
- event: {
379
- first: undefined,
380
- last: undefined
381
- },
382
- cycle: {
383
- first: undefined,
384
- last: undefined
385
- }
386
- };
416
+ this.cycleTimestamp = undefined;
387
417
  }
388
418
 
389
419
  /** Estimate the payload size */
@@ -395,6 +425,9 @@ export class Aggregate extends AggregateBase {
395
425
 
396
426
  /** Abort the feature, once aborted it will not resume */
397
427
  abort() {
428
+ let reason = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
429
+ warn("SR aborted -- ".concat(reason.message));
430
+ this.ee.emit(SUPPORTABILITY_METRIC_CHANNEL, ["SessionReplay/Abort/".concat(ABORT_REASONS[reason.sm])]);
398
431
  this.blocked = true;
399
432
  this.mode = MODE.OFF;
400
433
  this.stopRecording();
@@ -1,4 +1,3 @@
1
- import { getRuntime } from '../../common/config/config';
2
1
  import { ee } from '../../common/event-emitter/contextual-ee';
3
2
  export class FeatureBase {
4
3
  constructor(agentIdentifier, aggregator, featureName) {
@@ -7,7 +6,7 @@ export class FeatureBase {
7
6
  /** @type {Aggregator} */
8
7
  this.aggregator = aggregator;
9
8
  /** @type {ContextualEE} */
10
- this.ee = ee.get(agentIdentifier, getRuntime(this.agentIdentifier).isolatedBacklog);
9
+ this.ee = ee.get(agentIdentifier);
11
10
  /** @type {string} */
12
11
  this.featureName = featureName;
13
12
  /**
@@ -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
  };
@@ -17,7 +17,7 @@ export const CUSTOM_ATTR_GROUP = 'CUSTOM/'; // the subgroup items should be stor
17
17
 
18
18
  export function setTopLevelCallers() {
19
19
  const nr = gosCDN();
20
- const funcs = ['setErrorHandler', 'finished', 'addToTrace', 'inlineHit', 'addRelease', 'addPageAction', 'setCurrentRouteName', 'setPageViewName', 'setCustomAttribute', 'interaction', 'noticeError', 'setUserId', 'setApplicationVersion', 'start'];
20
+ const funcs = ['setErrorHandler', 'finished', 'addToTrace', 'addRelease', 'addPageAction', 'setCurrentRouteName', 'setPageViewName', 'setCustomAttribute', 'interaction', 'noticeError', 'setUserId', 'setApplicationVersion', 'start'];
21
21
  funcs.forEach(f => {
22
22
  nr[f] = function () {
23
23
  for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
@@ -44,7 +44,7 @@ export function setAPI(agentIdentifier, forceDrain) {
44
44
  const apiInterface = {};
45
45
  var instanceEE = ee.get(agentIdentifier);
46
46
  var tracerEE = instanceEE.get('tracer');
47
- var asyncApiFns = ['setErrorHandler', 'finished', 'addToTrace', 'inlineHit', 'addRelease'];
47
+ var asyncApiFns = ['setErrorHandler', 'finished', 'addToTrace', 'addRelease'];
48
48
  var prefix = 'api-';
49
49
  var spaPrefix = prefix + 'ixn-';
50
50