@newrelic/browser-agent 1.232.0 → 1.233.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 (273) hide show
  1. package/dist/cjs/cdn/polyfills.js +5 -2
  2. package/dist/cjs/common/config/state/configurable.js +15 -26
  3. package/dist/cjs/common/config/state/info.js +1 -1
  4. package/dist/cjs/common/config/state/init.js +101 -56
  5. package/dist/cjs/common/config/state/loader-config.js +1 -1
  6. package/dist/cjs/common/config/state/runtime.js +1 -5
  7. package/dist/cjs/common/constants/env.cdn.js +1 -1
  8. package/dist/cjs/common/constants/env.npm.js +1 -1
  9. package/dist/cjs/common/drain/drain.js +1 -1
  10. package/dist/cjs/common/harvest/harvest-scheduler.js +30 -10
  11. package/dist/cjs/common/harvest/harvest.js +119 -55
  12. package/dist/cjs/common/session/session-entity.js +35 -22
  13. package/dist/cjs/common/session/session-entity.test.js +73 -49
  14. package/dist/cjs/common/timer/interaction-timer.js +9 -12
  15. package/dist/cjs/common/url/canonicalize-url.js +32 -0
  16. package/dist/cjs/common/url/canonicalize-url.test.js +42 -0
  17. package/dist/cjs/common/url/clean-url.js +10 -3
  18. package/dist/cjs/common/url/protocol.test.js +0 -1
  19. package/dist/cjs/common/util/feature-flags.js +2 -1
  20. package/dist/cjs/common/util/global-scope.js +4 -2
  21. package/dist/cjs/common/util/submit-data.js +57 -18
  22. package/dist/cjs/common/wrap/wrap-fetch.js +1 -3
  23. package/dist/cjs/common/wrap/wrap-function.js +1 -3
  24. package/dist/cjs/common/wrap/wrap-promise.js +1 -1
  25. package/dist/cjs/features/ajax/aggregate/index.js +2 -2
  26. package/dist/cjs/features/ajax/instrument/index.js +1 -1
  27. package/dist/cjs/features/jserrors/aggregate/canonical-function-name.js +12 -4
  28. package/dist/cjs/features/jserrors/aggregate/compute-stack-trace.js +93 -10
  29. package/dist/cjs/features/jserrors/aggregate/compute-stack-trace.test.js +164 -38
  30. package/dist/cjs/features/jserrors/aggregate/index.js +29 -48
  31. package/dist/cjs/features/jserrors/instrument/index.js +0 -2
  32. package/dist/cjs/features/metrics/aggregate/framework-detection.js +67 -0
  33. package/dist/cjs/features/metrics/aggregate/framework-detection.test.js +137 -0
  34. package/dist/cjs/features/metrics/aggregate/index.js +7 -3
  35. package/dist/cjs/features/metrics/aggregate/polyfill-detection.es5.js +14 -0
  36. package/dist/cjs/features/metrics/aggregate/polyfill-detection.es5.test.js +17 -0
  37. package/dist/cjs/features/metrics/aggregate/polyfill-detection.js +53 -0
  38. package/dist/cjs/features/metrics/aggregate/polyfill-detection.test.js +165 -0
  39. package/dist/cjs/features/page_action/aggregate/index.js +2 -2
  40. package/dist/cjs/features/page_view_event/aggregate/index.js +6 -3
  41. package/dist/cjs/features/page_view_timing/aggregate/index.js +2 -2
  42. package/dist/cjs/features/session_replay/aggregate/index.js +333 -0
  43. package/dist/cjs/features/session_replay/constants.js +9 -0
  44. package/dist/cjs/features/session_replay/index.js +12 -0
  45. package/dist/cjs/features/session_replay/instrument/index.js +29 -0
  46. package/dist/cjs/features/session_trace/aggregate/index.js +163 -164
  47. package/dist/cjs/features/session_trace/constants.js +2 -9
  48. package/dist/cjs/features/session_trace/instrument/index.js +24 -66
  49. package/dist/cjs/features/spa/aggregate/index.js +2 -2
  50. package/dist/cjs/features/utils/agent-session.js +1 -2
  51. package/dist/cjs/features/utils/aggregate-base.js +64 -0
  52. package/dist/cjs/features/utils/feature-base.js +0 -31
  53. package/dist/cjs/features/utils/handler-cache.js +3 -4
  54. package/dist/cjs/features/utils/instrument-base.js +42 -10
  55. package/dist/cjs/features/utils/{lazy-loader.js → lazy-feature-loader.js} +4 -2
  56. package/dist/cjs/loaders/agent.js +1 -1
  57. package/dist/cjs/loaders/api/apiAsync.js +3 -1
  58. package/dist/cjs/loaders/configure/configure.js +3 -3
  59. package/dist/cjs/loaders/features/featureDependencies.js +0 -12
  60. package/dist/cjs/loaders/features/features.js +3 -1
  61. package/dist/cjs/loaders/micro-agent.js +6 -6
  62. package/dist/esm/cdn/polyfills.js +5 -2
  63. package/dist/esm/common/config/state/configurable.js +14 -24
  64. package/dist/esm/common/config/state/info.js +2 -2
  65. package/dist/esm/common/config/state/init.js +102 -57
  66. package/dist/esm/common/config/state/loader-config.js +2 -2
  67. package/dist/esm/common/config/state/runtime.js +2 -4
  68. package/dist/esm/common/constants/env.cdn.js +1 -1
  69. package/dist/esm/common/constants/env.npm.js +1 -1
  70. package/dist/esm/common/drain/drain.js +1 -1
  71. package/dist/esm/common/harvest/harvest-scheduler.js +30 -10
  72. package/dist/esm/common/harvest/harvest.js +121 -56
  73. package/dist/esm/common/session/session-entity.js +35 -22
  74. package/dist/esm/common/session/session-entity.test.js +73 -49
  75. package/dist/esm/common/timer/interaction-timer.js +9 -12
  76. package/dist/esm/common/url/canonicalize-url.js +27 -0
  77. package/dist/esm/common/url/canonicalize-url.test.js +38 -0
  78. package/dist/esm/common/url/clean-url.js +10 -3
  79. package/dist/esm/common/url/protocol.test.js +0 -1
  80. package/dist/esm/common/util/feature-flags.js +2 -1
  81. package/dist/esm/common/util/global-scope.js +1 -0
  82. package/dist/esm/common/util/submit-data.js +57 -18
  83. package/dist/esm/common/wrap/wrap-fetch.js +1 -2
  84. package/dist/esm/common/wrap/wrap-function.js +1 -2
  85. package/dist/esm/common/wrap/wrap-promise.js +1 -1
  86. package/dist/esm/features/ajax/aggregate/index.js +2 -2
  87. package/dist/esm/features/ajax/instrument/index.js +1 -1
  88. package/dist/esm/features/jserrors/aggregate/canonical-function-name.js +12 -4
  89. package/dist/esm/features/jserrors/aggregate/compute-stack-trace.js +93 -10
  90. package/dist/esm/features/jserrors/aggregate/compute-stack-trace.test.js +149 -25
  91. package/dist/esm/features/jserrors/aggregate/index.js +30 -48
  92. package/dist/esm/features/jserrors/instrument/index.js +0 -1
  93. package/dist/esm/features/metrics/aggregate/framework-detection.js +61 -0
  94. package/dist/esm/features/metrics/aggregate/framework-detection.test.js +133 -0
  95. package/dist/esm/features/metrics/aggregate/index.js +7 -3
  96. package/dist/esm/features/metrics/aggregate/polyfill-detection.es5.js +8 -0
  97. package/dist/esm/features/metrics/aggregate/polyfill-detection.es5.test.js +15 -0
  98. package/dist/esm/features/metrics/aggregate/polyfill-detection.js +47 -0
  99. package/dist/esm/features/metrics/aggregate/polyfill-detection.test.js +163 -0
  100. package/dist/esm/features/page_action/aggregate/index.js +2 -2
  101. package/dist/esm/features/page_view_event/aggregate/index.js +6 -3
  102. package/dist/esm/features/page_view_timing/aggregate/index.js +2 -2
  103. package/dist/esm/features/session_replay/aggregate/index.js +327 -0
  104. package/dist/esm/features/session_replay/constants.js +2 -0
  105. package/dist/esm/features/session_replay/index.js +12 -0
  106. package/dist/esm/features/session_replay/instrument/index.js +21 -0
  107. package/dist/esm/features/session_trace/aggregate/index.js +163 -163
  108. package/dist/esm/features/session_trace/constants.js +1 -5
  109. package/dist/esm/features/session_trace/instrument/index.js +24 -66
  110. package/dist/esm/features/spa/aggregate/index.js +2 -2
  111. package/dist/esm/features/utils/agent-session.js +1 -2
  112. package/dist/esm/features/utils/aggregate-base.js +57 -0
  113. package/dist/esm/features/utils/feature-base.js +1 -32
  114. package/dist/esm/features/utils/handler-cache.js +3 -4
  115. package/dist/esm/features/utils/instrument-base.js +42 -10
  116. package/dist/esm/features/utils/{lazy-loader.js → lazy-feature-loader.js} +3 -1
  117. package/dist/esm/loaders/agent.js +1 -1
  118. package/dist/esm/loaders/api/apiAsync.js +3 -1
  119. package/dist/esm/loaders/configure/configure.js +3 -3
  120. package/dist/esm/loaders/features/featureDependencies.js +0 -11
  121. package/dist/esm/loaders/features/features.js +3 -1
  122. package/dist/esm/loaders/micro-agent.js +6 -6
  123. package/dist/types/common/config/state/configurable.d.ts +1 -3
  124. package/dist/types/common/config/state/configurable.d.ts.map +1 -1
  125. package/dist/types/common/config/state/init.d.ts.map +1 -1
  126. package/dist/types/common/config/state/runtime.d.ts.map +1 -1
  127. package/dist/types/common/harvest/harvest-scheduler.d.ts.map +1 -1
  128. package/dist/types/common/harvest/harvest.d.ts +37 -34
  129. package/dist/types/common/harvest/harvest.d.ts.map +1 -1
  130. package/dist/types/common/session/session-entity.d.ts +6 -3
  131. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  132. package/dist/types/common/timer/interaction-timer.d.ts +2 -1
  133. package/dist/types/common/timer/interaction-timer.d.ts.map +1 -1
  134. package/dist/types/common/url/canonicalize-url.d.ts +9 -0
  135. package/dist/types/common/url/canonicalize-url.d.ts.map +1 -0
  136. package/dist/types/common/url/clean-url.d.ts +7 -1
  137. package/dist/types/common/url/clean-url.d.ts.map +1 -1
  138. package/dist/types/common/util/feature-flags.d.ts.map +1 -1
  139. package/dist/types/common/util/global-scope.d.ts +1 -0
  140. package/dist/types/common/util/global-scope.d.ts.map +1 -1
  141. package/dist/types/common/util/submit-data.d.ts +40 -14
  142. package/dist/types/common/util/submit-data.d.ts.map +1 -1
  143. package/dist/types/common/wrap/wrap-fetch.d.ts.map +1 -1
  144. package/dist/types/common/wrap/wrap-function.d.ts.map +1 -1
  145. package/dist/types/features/ajax/aggregate/index.d.ts +2 -2
  146. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  147. package/dist/types/features/jserrors/aggregate/canonical-function-name.d.ts +8 -1
  148. package/dist/types/features/jserrors/aggregate/canonical-function-name.d.ts.map +1 -1
  149. package/dist/types/features/jserrors/aggregate/compute-stack-trace.d.ts +48 -19
  150. package/dist/types/features/jserrors/aggregate/compute-stack-trace.d.ts.map +1 -1
  151. package/dist/types/features/jserrors/aggregate/index.d.ts +14 -5
  152. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  153. package/dist/types/features/jserrors/instrument/index.d.ts.map +1 -1
  154. package/dist/types/features/metrics/aggregate/framework-detection.d.ts.map +1 -0
  155. package/dist/types/features/metrics/aggregate/index.d.ts +2 -2
  156. package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
  157. package/dist/types/features/metrics/aggregate/polyfill-detection.d.ts +6 -0
  158. package/dist/types/features/metrics/aggregate/polyfill-detection.d.ts.map +1 -0
  159. package/dist/types/features/metrics/aggregate/polyfill-detection.es5.d.ts +7 -0
  160. package/dist/types/features/metrics/aggregate/polyfill-detection.es5.d.ts.map +1 -0
  161. package/dist/types/features/page_action/aggregate/index.d.ts +3 -3
  162. package/dist/types/features/page_action/aggregate/index.d.ts.map +1 -1
  163. package/dist/types/features/page_view_event/aggregate/index.d.ts +2 -2
  164. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  165. package/dist/types/features/page_view_timing/aggregate/index.d.ts +2 -2
  166. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  167. package/dist/types/features/session_replay/aggregate/index.d.ts +96 -0
  168. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -0
  169. package/dist/types/features/session_replay/constants.d.ts +2 -0
  170. package/dist/types/features/session_replay/constants.d.ts.map +1 -0
  171. package/dist/types/features/session_replay/index.d.ts +2 -0
  172. package/dist/types/features/session_replay/index.d.ts.map +1 -0
  173. package/dist/types/features/session_replay/instrument/index.d.ts +6 -0
  174. package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -0
  175. package/dist/types/features/session_trace/aggregate/index.d.ts +8 -57
  176. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  177. package/dist/types/features/session_trace/constants.d.ts +0 -3
  178. package/dist/types/features/session_trace/constants.d.ts.map +1 -1
  179. package/dist/types/features/session_trace/instrument/index.d.ts +1 -3
  180. package/dist/types/features/session_trace/instrument/index.d.ts.map +1 -1
  181. package/dist/types/features/spa/aggregate/index.d.ts +2 -2
  182. package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
  183. package/dist/types/features/utils/agent-session.d.ts.map +1 -1
  184. package/dist/types/features/utils/aggregate-base.d.ts +11 -0
  185. package/dist/types/features/utils/aggregate-base.d.ts.map +1 -0
  186. package/dist/types/features/utils/feature-base.d.ts +0 -5
  187. package/dist/types/features/utils/feature-base.d.ts.map +1 -1
  188. package/dist/types/features/utils/handler-cache.d.ts.map +1 -1
  189. package/dist/types/features/utils/instrument-base.d.ts +3 -1
  190. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  191. package/dist/types/features/utils/{lazy-loader.d.ts → lazy-feature-loader.d.ts} +2 -2
  192. package/dist/types/features/utils/lazy-feature-loader.d.ts.map +1 -0
  193. package/dist/types/loaders/configure/configure.d.ts.map +1 -1
  194. package/dist/types/loaders/features/featureDependencies.d.ts +0 -1
  195. package/dist/types/loaders/features/featureDependencies.d.ts.map +1 -1
  196. package/dist/types/loaders/features/features.d.ts +1 -0
  197. package/dist/types/loaders/features/features.d.ts.map +1 -1
  198. package/package.json +31 -22
  199. package/src/cdn/polyfills.js +4 -1
  200. package/src/common/config/state/configurable.js +18 -24
  201. package/src/common/config/state/info.js +2 -2
  202. package/src/common/config/state/init.js +62 -28
  203. package/src/common/config/state/loader-config.js +2 -2
  204. package/src/common/config/state/runtime.js +2 -4
  205. package/src/common/drain/drain.js +1 -1
  206. package/src/common/harvest/harvest-scheduler.js +35 -10
  207. package/src/common/harvest/harvest.js +73 -50
  208. package/src/common/session/session-entity.js +34 -23
  209. package/src/common/session/session-entity.test.js +57 -51
  210. package/src/common/timer/interaction-timer.js +9 -12
  211. package/src/common/url/canonicalize-url.js +28 -0
  212. package/src/common/url/canonicalize-url.test.js +34 -0
  213. package/src/common/url/clean-url.js +10 -3
  214. package/src/common/url/protocol.test.js +0 -1
  215. package/src/common/util/feature-flags.js +2 -2
  216. package/src/common/util/global-scope.js +2 -0
  217. package/src/common/util/submit-data.js +28 -17
  218. package/src/common/wrap/wrap-fetch.js +1 -2
  219. package/src/common/wrap/wrap-function.js +1 -2
  220. package/src/common/wrap/wrap-promise.js +1 -1
  221. package/src/features/ajax/aggregate/index.js +2 -2
  222. package/src/features/ajax/instrument/index.js +1 -1
  223. package/src/features/jserrors/aggregate/canonical-function-name.js +12 -4
  224. package/src/features/jserrors/aggregate/compute-stack-trace.js +85 -11
  225. package/src/features/jserrors/aggregate/compute-stack-trace.test.js +141 -24
  226. package/src/features/jserrors/aggregate/index.js +28 -52
  227. package/src/features/jserrors/instrument/index.js +0 -1
  228. package/src/features/metrics/aggregate/framework-detection.js +73 -0
  229. package/src/features/metrics/aggregate/framework-detection.test.js +201 -0
  230. package/src/features/metrics/aggregate/index.js +8 -3
  231. package/src/features/metrics/aggregate/polyfill-detection.es5.js +9 -0
  232. package/src/features/metrics/aggregate/polyfill-detection.es5.test.js +16 -0
  233. package/src/features/metrics/aggregate/polyfill-detection.js +48 -0
  234. package/src/features/metrics/aggregate/polyfill-detection.test.js +163 -0
  235. package/src/features/page_action/aggregate/index.js +2 -2
  236. package/src/features/page_view_event/aggregate/index.js +5 -5
  237. package/src/features/page_view_timing/aggregate/index.js +2 -2
  238. package/src/features/session_replay/aggregate/index.js +314 -0
  239. package/src/features/session_replay/constants.js +3 -0
  240. package/src/features/session_replay/index.js +12 -0
  241. package/src/features/session_replay/instrument/index.js +22 -0
  242. package/src/features/session_trace/aggregate/index.js +148 -188
  243. package/src/features/session_trace/constants.js +0 -4
  244. package/src/features/session_trace/instrument/index.js +17 -69
  245. package/src/features/spa/aggregate/index.js +2 -2
  246. package/src/features/utils/agent-session.js +1 -2
  247. package/src/features/utils/aggregate-base.js +51 -0
  248. package/src/features/utils/feature-base.js +1 -31
  249. package/src/features/utils/handler-cache.js +3 -4
  250. package/src/features/utils/instrument-base.js +40 -8
  251. package/src/features/utils/{lazy-loader.js → lazy-feature-loader.js} +3 -1
  252. package/src/loaders/agent.js +1 -1
  253. package/src/loaders/api/apiAsync.js +1 -1
  254. package/src/loaders/configure/configure.js +4 -3
  255. package/src/loaders/features/featureDependencies.js +0 -12
  256. package/src/loaders/features/features.js +3 -1
  257. package/src/loaders/micro-agent.js +4 -4
  258. package/dist/cjs/common/metrics/framework-detection.js +0 -72
  259. package/dist/cjs/common/util/user-agent.js +0 -57
  260. package/dist/cjs/common/window/supports-performance-observer.js +0 -15
  261. package/dist/esm/common/metrics/framework-detection.js +0 -66
  262. package/dist/esm/common/util/user-agent.js +0 -48
  263. package/dist/esm/common/window/supports-performance-observer.js +0 -9
  264. package/dist/types/common/metrics/framework-detection.d.ts.map +0 -1
  265. package/dist/types/common/util/user-agent.d.ts +0 -5
  266. package/dist/types/common/util/user-agent.d.ts.map +0 -1
  267. package/dist/types/common/window/supports-performance-observer.d.ts +0 -2
  268. package/dist/types/common/window/supports-performance-observer.d.ts.map +0 -1
  269. package/dist/types/features/utils/lazy-loader.d.ts.map +0 -1
  270. package/src/common/metrics/framework-detection.js +0 -71
  271. package/src/common/util/user-agent.js +0 -56
  272. package/src/common/window/supports-performance-observer.js +0 -10
  273. /package/dist/types/{common/metrics → features/metrics/aggregate}/framework-detection.d.ts +0 -0
@@ -3,16 +3,17 @@ import { registerHandler } from '../../../common/event-emitter/register-handler'
3
3
  import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler';
4
4
  import { FEATURE_NAME, SUPPORTABILITY_METRIC, CUSTOM_METRIC, SUPPORTABILITY_METRIC_CHANNEL, CUSTOM_METRIC_CHANNEL } from '../constants';
5
5
  import { drain } from '../../../common/drain/drain';
6
- import { getFrameworks } from '../../../common/metrics/framework-detection';
6
+ import { getFrameworks } from './framework-detection';
7
+ import { getPolyfills } from './polyfill-detection';
7
8
  import { isFileProtocol } from '../../../common/url/protocol';
8
9
  import { getRules, validateRules } from '../../../common/util/obfuscate';
9
10
  import { VERSION } from "../../../common/constants/env.npm";
10
11
  import { onDOMContentLoaded } from '../../../common/window/load';
11
12
  import { windowAddEventListener } from '../../../common/event-listener/event-listener-opts';
12
13
  import { isBrowserScope } from '../../../common/util/global-scope';
13
- import { FeatureBase } from '../../utils/feature-base';
14
+ import { AggregateBase } from '../../utils/aggregate-base';
14
15
  import { stringify } from '../../../common/util/stringify';
15
- export class Aggregate extends FeatureBase {
16
+ export class Aggregate extends AggregateBase {
16
17
  static featureName = FEATURE_NAME;
17
18
  constructor(agentIdentifier, aggregator) {
18
19
  super(agentIdentifier, aggregator, FEATURE_NAME);
@@ -76,6 +77,9 @@ export class Aggregate extends FeatureBase {
76
77
  });
77
78
  });
78
79
  }
80
+ getPolyfills().forEach(polyfill => {
81
+ this.storeSupportabilityMetrics('Polyfill/' + polyfill + '/Detected');
82
+ });
79
83
 
80
84
  // file protocol detection
81
85
  if (isFileProtocol()) {
@@ -0,0 +1,8 @@
1
+ /**
2
+ * This file is used as a replacement for the polyfill-detection.es5.js
3
+ * file when running the polyfills webpack build.
4
+ * @returns {string[]} an empty array
5
+ */
6
+ export function getPolyfills() {
7
+ return [];
8
+ }
@@ -0,0 +1,15 @@
1
+ import { getPolyfills } from './polyfill-detection.es5';
2
+ afterEach(() => {
3
+ jest.resetAllMocks();
4
+ });
5
+ test('should always return an empty array', () => {
6
+ const originalFn = Array.from;
7
+ Array.from = jest.fn(function () {
8
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
9
+ args[_key] = arguments[_key];
10
+ }
11
+ return originalFn.apply(this, args);
12
+ });
13
+ expect(getPolyfills()).toEqual([]);
14
+ Array.from = originalFn;
15
+ });
@@ -0,0 +1,47 @@
1
+ const detectionRegex = /\{ \[native code\] \}/;
2
+ const POLYFILLS = {
3
+ FUNCTION_BIND: 'Function.bind',
4
+ FUNCTION_APPLY: 'Function.apply',
5
+ FUNCTION_CALL: 'Function.call',
6
+ ARRAY_INCLUDES: 'Array.includes',
7
+ ARRAY_FROM: 'Array.from',
8
+ ARRAY_FIND: 'Array.find',
9
+ ARRAY_FLAT: 'Array.flat',
10
+ ARRAY_FLATMAP: 'Array.flatMap',
11
+ OBJECT_ASSIGN: 'Object.assign',
12
+ OBJECT_ENTRIES: 'Object.entries',
13
+ OBJECT_VALUES: 'Object.values',
14
+ MAP: 'Map',
15
+ SET: 'Set',
16
+ WEAK_MAP: 'WeakMap',
17
+ WEAK_SET: 'WeakSet'
18
+ };
19
+
20
+ /**
21
+ * Checks for native globals to see if they have been polyfilled by customer code.
22
+ * @returns {string[]} Array of methods that were detected to have been polyfilled
23
+ */
24
+ export function getPolyfills() {
25
+ const polyfills = [];
26
+ const mockFunction = function () {/* noop */};
27
+ try {
28
+ if (typeof mockFunction.bind === 'function' && !detectionRegex.test(mockFunction.bind.toString())) polyfills.push(POLYFILLS.FUNCTION_BIND);
29
+ if (typeof mockFunction.apply === 'function' && !detectionRegex.test(mockFunction.apply.toString())) polyfills.push(POLYFILLS.FUNCTION_APPLY);
30
+ if (typeof mockFunction.call === 'function' && !detectionRegex.test(mockFunction.call.toString())) polyfills.push(POLYFILLS.FUNCTION_CALL);
31
+ if (typeof [].includes === 'function' && !detectionRegex.test([].includes.toString())) polyfills.push(POLYFILLS.ARRAY_INCLUDES);
32
+ if (typeof Array.from === 'function' && !detectionRegex.test(Array.from.toString())) polyfills.push(POLYFILLS.ARRAY_FROM);
33
+ if (typeof [].find === 'function' && !detectionRegex.test([].find.toString())) polyfills.push(POLYFILLS.ARRAY_FIND);
34
+ if (typeof [].flat === 'function' && !detectionRegex.test([].flat.toString())) polyfills.push(POLYFILLS.ARRAY_FLAT);
35
+ if (typeof [].flatMap === 'function' && !detectionRegex.test([].flatMap.toString())) polyfills.push(POLYFILLS.ARRAY_FLATMAP);
36
+ if (typeof Object.assign === 'function' && !detectionRegex.test(Object.assign.toString())) polyfills.push(POLYFILLS.OBJECT_ASSIGN);
37
+ if (typeof Object.entries === 'function' && !detectionRegex.test(Object.entries.toString())) polyfills.push(POLYFILLS.OBJECT_ENTRIES);
38
+ if (typeof Object.values === 'function' && !detectionRegex.test(Object.values.toString())) polyfills.push(POLYFILLS.OBJECT_VALUES);
39
+ if (typeof Map === 'function' && !detectionRegex.test(Map.toString())) polyfills.push(POLYFILLS.MAP);
40
+ if (typeof Set === 'function' && !detectionRegex.test(Set.toString())) polyfills.push(POLYFILLS.SET);
41
+ if (typeof WeakMap === 'function' && !detectionRegex.test(WeakMap.toString())) polyfills.push(POLYFILLS.WEAK_MAP);
42
+ if (typeof WeakSet === 'function' && !detectionRegex.test(WeakSet.toString())) polyfills.push(POLYFILLS.WEAK_SET);
43
+ } catch (err) {
44
+ // Possibly not supported
45
+ }
46
+ return polyfills;
47
+ }
@@ -0,0 +1,163 @@
1
+ /* eslint-disable no-global-assign */
2
+ /* eslint-disable no-extend-native */
3
+ import { getPolyfills } from './polyfill-detection';
4
+
5
+ // DO NOT test Function.prototype.apply, it causes recursion in Jest
6
+
7
+ test('should return an empty array when nothing is polyfilled', () => {
8
+ expect(getPolyfills()).toEqual([]);
9
+ });
10
+ test('should indicate Function.prototype.bind is polyfilled', () => {
11
+ const originalFn = Function.prototype.bind;
12
+ Function.prototype.bind = jest.fn(function () {
13
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
14
+ args[_key] = arguments[_key];
15
+ }
16
+ return originalFn.apply(this, args);
17
+ });
18
+ expect(getPolyfills()).toEqual(['Function.bind']);
19
+ Function.prototype.bind = originalFn;
20
+ });
21
+ test('should indicate Function.prototype.call is polyfilled', () => {
22
+ const originalFn = Function.prototype.call;
23
+ Function.prototype.call = jest.fn(function () {
24
+ for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
25
+ args[_key2] = arguments[_key2];
26
+ }
27
+ return originalFn.apply(this, args);
28
+ });
29
+ expect(getPolyfills()).toEqual(['Function.call']);
30
+ Function.prototype.call = originalFn;
31
+ });
32
+ test('should indicate Array.prototype.includes is polyfilled', () => {
33
+ const originalFn = Array.prototype.includes;
34
+ Array.prototype.includes = jest.fn(function () {
35
+ for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
36
+ args[_key3] = arguments[_key3];
37
+ }
38
+ return originalFn.apply(this, args);
39
+ });
40
+ expect(getPolyfills()).toEqual(['Array.includes']);
41
+ Array.prototype.includes = originalFn;
42
+ });
43
+ test('should indicate Array.from is polyfilled', () => {
44
+ const originalFn = Array.from;
45
+ Array.from = jest.fn(function () {
46
+ for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
47
+ args[_key4] = arguments[_key4];
48
+ }
49
+ return originalFn.apply(this, args);
50
+ });
51
+ expect(getPolyfills()).toEqual(['Array.from']);
52
+ Array.from = originalFn;
53
+ });
54
+ test('should indicate Array.prototype.find is polyfilled', () => {
55
+ const originalFn = Array.prototype.find;
56
+ Array.prototype.find = jest.fn(function () {
57
+ for (var _len5 = arguments.length, args = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
58
+ args[_key5] = arguments[_key5];
59
+ }
60
+ return originalFn.apply(this, args);
61
+ });
62
+ expect(getPolyfills()).toEqual(['Array.find']);
63
+ Array.prototype.find = originalFn;
64
+ });
65
+ test('should indicate Array.prototype.flat is polyfilled', () => {
66
+ const originalFn = Array.prototype.flat;
67
+ Array.prototype.flat = jest.fn(function () {
68
+ for (var _len6 = arguments.length, args = new Array(_len6), _key6 = 0; _key6 < _len6; _key6++) {
69
+ args[_key6] = arguments[_key6];
70
+ }
71
+ return originalFn.apply(this, args);
72
+ });
73
+ expect(getPolyfills()).toEqual(['Array.flat']);
74
+ Array.prototype.flat = originalFn;
75
+ });
76
+ test('should indicate Array.prototype.flatMap is polyfilled', () => {
77
+ const originalFn = Array.prototype.flatMap;
78
+ Array.prototype.flatMap = jest.fn(function () {
79
+ for (var _len7 = arguments.length, args = new Array(_len7), _key7 = 0; _key7 < _len7; _key7++) {
80
+ args[_key7] = arguments[_key7];
81
+ }
82
+ return originalFn.apply(this, args);
83
+ });
84
+ expect(getPolyfills()).toEqual(['Array.flatMap']);
85
+ Array.prototype.flatMap = originalFn;
86
+ });
87
+ test('should indicate Object.assign is polyfilled', () => {
88
+ const originalFn = Object.assign;
89
+ Object.assign = jest.fn(function () {
90
+ for (var _len8 = arguments.length, args = new Array(_len8), _key8 = 0; _key8 < _len8; _key8++) {
91
+ args[_key8] = arguments[_key8];
92
+ }
93
+ return originalFn.apply(this, args);
94
+ });
95
+ expect(getPolyfills()).toEqual(['Object.assign']);
96
+ Object.assign = originalFn;
97
+ });
98
+ test('should indicate Object.entries is polyfilled', () => {
99
+ const originalFn = Object.entries;
100
+ Object.entries = jest.fn(function () {
101
+ for (var _len9 = arguments.length, args = new Array(_len9), _key9 = 0; _key9 < _len9; _key9++) {
102
+ args[_key9] = arguments[_key9];
103
+ }
104
+ return originalFn.apply(this, args);
105
+ });
106
+ expect(getPolyfills()).toEqual(['Object.entries']);
107
+ Object.entries = originalFn;
108
+ });
109
+ test('should indicate Object.values is polyfilled', () => {
110
+ const originalFn = Object.values;
111
+ Object.values = jest.fn(function () {
112
+ for (var _len10 = arguments.length, args = new Array(_len10), _key10 = 0; _key10 < _len10; _key10++) {
113
+ args[_key10] = arguments[_key10];
114
+ }
115
+ return originalFn.apply(this, args);
116
+ });
117
+ expect(getPolyfills()).toEqual(['Object.values']);
118
+ Object.values = originalFn;
119
+ });
120
+ test('should indicate Map constructor is polyfilled', () => {
121
+ const originalFn = Map;
122
+ Map = jest.fn(function () {
123
+ for (var _len11 = arguments.length, args = new Array(_len11), _key11 = 0; _key11 < _len11; _key11++) {
124
+ args[_key11] = arguments[_key11];
125
+ }
126
+ return originalFn.apply(this, args);
127
+ });
128
+ expect(getPolyfills()).toEqual(['Map']);
129
+ Map = originalFn;
130
+ });
131
+ test('should indicate Set constructor is polyfilled', () => {
132
+ const originalFn = Set;
133
+ Set = jest.fn(function () {
134
+ for (var _len12 = arguments.length, args = new Array(_len12), _key12 = 0; _key12 < _len12; _key12++) {
135
+ args[_key12] = arguments[_key12];
136
+ }
137
+ return originalFn.apply(this, args);
138
+ });
139
+ expect(getPolyfills()).toEqual(['Set']);
140
+ Set = originalFn;
141
+ });
142
+ test('should indicate WeakMap constructor is polyfilled', () => {
143
+ const originalFn = WeakMap;
144
+ WeakMap = jest.fn(function () {
145
+ for (var _len13 = arguments.length, args = new Array(_len13), _key13 = 0; _key13 < _len13; _key13++) {
146
+ args[_key13] = arguments[_key13];
147
+ }
148
+ return originalFn.apply(this, args);
149
+ });
150
+ expect(getPolyfills()).toEqual(['WeakMap']);
151
+ WeakMap = originalFn;
152
+ });
153
+ test('should indicate WeakSet constructor is polyfilled', () => {
154
+ const originalFn = WeakSet;
155
+ WeakSet = jest.fn(function () {
156
+ for (var _len14 = arguments.length, args = new Array(_len14), _key14 = 0; _key14 < _len14; _key14++) {
157
+ args[_key14] = arguments[_key14];
158
+ }
159
+ return originalFn.apply(this, args);
160
+ });
161
+ expect(getPolyfills()).toEqual(['WeakSet']);
162
+ WeakSet = originalFn;
163
+ });
@@ -12,8 +12,8 @@ import { getConfigurationValue, getInfo, getRuntime } from '../../../common/conf
12
12
  import { FEATURE_NAME } from '../constants';
13
13
  import { drain } from '../../../common/drain/drain';
14
14
  import { isBrowserScope } from '../../../common/util/global-scope';
15
- import { FeatureBase } from '../../utils/feature-base';
16
- export class Aggregate extends FeatureBase {
15
+ import { AggregateBase } from '../../utils/aggregate-base';
16
+ export class Aggregate extends AggregateBase {
17
17
  static featureName = FEATURE_NAME;
18
18
  constructor(agentIdentifier, aggregator) {
19
19
  var _this;
@@ -14,9 +14,9 @@ import * as CONSTANTS from '../constants';
14
14
  import { getActivatedFeaturesFlags } from './initialized-features';
15
15
  import { globalScope, isBrowserScope } from '../../../common/util/global-scope';
16
16
  import { drain } from '../../../common/drain/drain';
17
- import { FeatureBase } from '../../utils/feature-base';
17
+ import { AggregateBase } from '../../utils/aggregate-base';
18
18
  const jsonp = 'NREUM.setToken';
19
- export class Aggregate extends FeatureBase {
19
+ export class Aggregate extends AggregateBase {
20
20
  static featureName = CONSTANTS.FEATURE_NAME;
21
21
  constructor(agentIdentifier, aggregator) {
22
22
  super(agentIdentifier, aggregator, CONSTANTS.FEATURE_NAME);
@@ -134,7 +134,10 @@ export class Aggregate extends FeatureBase {
134
134
 
135
135
  // Capture query bytes sent to RUM call endpoint (currently `1`) as a supportability metric. See metrics aggregator (on unload).
136
136
  agentRuntime.queryBytesSent[protocol] = (agentRuntime.queryBytesSent[protocol] || 0) + queryString?.length || 0;
137
- const isValidJsonp = submitData.jsonp(this.getScheme() + '://' + info.beacon + '/' + protocol + '/' + info.licenseKey + queryString, jsonp);
137
+ const isValidJsonp = submitData.jsonp({
138
+ url: this.getScheme() + '://' + info.beacon + '/' + protocol + '/' + info.licenseKey + '?' + queryString,
139
+ jsonp
140
+ });
138
141
  // Usually `drain` is invoked automatically after processing feature flags contained in the JSONP callback from
139
142
  // ingest (see `activateFeatures`), so when JSONP cannot execute (as with module workers), we drain manually.
140
143
  if (!isValidJsonp) drain(this.agentIdentifier, CONSTANTS.FEATURE_NAME);
@@ -17,8 +17,8 @@ import { getInfo, getConfigurationValue, getRuntime } from '../../../common/conf
17
17
  import { FEATURE_NAME } from '../constants';
18
18
  import { drain } from '../../../common/drain/drain';
19
19
  import { FEATURE_NAMES } from '../../../loaders/features/features';
20
- import { FeatureBase } from '../../utils/feature-base';
21
- export class Aggregate extends FeatureBase {
20
+ import { AggregateBase } from '../../utils/aggregate-base';
21
+ export class Aggregate extends AggregateBase {
22
22
  static featureName = FEATURE_NAME;
23
23
  constructor(agentIdentifier, aggregator) {
24
24
  var _this;
@@ -0,0 +1,327 @@
1
+ /*
2
+ * Copyright 2023 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ /**
6
+ * @file Records, aggregates, and harvests session replay data.
7
+ *
8
+ * NOTE: This code is under development and dormant. It will not download to instrumented pages or record any data.
9
+ * It is not production ready, and is not intended to be imported or implemented in any build of the browser agent until
10
+ * functionality is validated and a full user experience is curated.
11
+ */
12
+
13
+ import { drain } from '../../../common/drain/drain';
14
+ import { registerHandler } from '../../../common/event-emitter/register-handler';
15
+ import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler';
16
+ import { FEATURE_NAME } from '../constants';
17
+ import { stringify } from '../../../common/util/stringify';
18
+ import { getConfigurationValue, getInfo, getRuntime } from '../../../common/config/config';
19
+ import { SESSION_EVENTS } from '../../../common/session/session-entity';
20
+ import { AggregateBase } from '../../utils/aggregate-base';
21
+
22
+ // would be better to get this dynamically in some way
23
+ export const RRWEB_VERSION = '2.0.0-alpha.8';
24
+ let recorder, gzipper, u8;
25
+
26
+ /** The "mode" with which the session replay is recording */
27
+ const MODE = {
28
+ OFF: 0,
29
+ FULL: 1,
30
+ ERROR: 2
31
+ };
32
+ /** Vortex caps payload sizes at 1MB */
33
+ const MAX_PAYLOAD_SIZE = 1000000;
34
+ /** Unloading caps around 64kb */
35
+ const IDEAL_PAYLOAD_SIZE = 64000;
36
+ /** Interval between forcing new full snapshots in "error" mode */
37
+ const CHECKOUT_MS = 30000;
38
+ export class Aggregate extends AggregateBase {
39
+ static featureName = FEATURE_NAME;
40
+ constructor(agentIdentifier, aggregator) {
41
+ super(agentIdentifier, aggregator, FEATURE_NAME);
42
+
43
+ /** Each page mutation or event will be stored (raw) in this array. This array will be cleared on each harvest */
44
+ this.events = [];
45
+ /** The interval to harvest at. This gets overridden if the size of the payload exceeds certain thresholds */
46
+ this.harvestTimeSeconds = getConfigurationValue(this.agentIdentifier, 'session_replay.harvestTimeSeconds') || 60;
47
+ /** Set once the recorder has fully initialized after flag checks and sampling */
48
+ this.initialized = false;
49
+ /** Set once an error has been detected on the page. */
50
+ this.errorNoticed = false;
51
+ /** The "mode" to record in. Defaults to "OFF" until flags and sampling are checked. See "MODE" constant. */
52
+ this.mode = MODE.OFF;
53
+ /** Set once the feature has been "aborted" to prevent other side-effects from continuing */
54
+ this.blocked = false;
55
+
56
+ /** Payload metadata -- Should indicate that the payload being sent is the first of a session */
57
+ this.isFirstChunk = false;
58
+ /** Payload metadata -- Should indicate that the payload being sent has a full DOM snapshot. This can happen
59
+ * -- When the recording library begins recording, it starts by taking a DOM snapshot
60
+ * -- When visibility changes from "hidden" -> "visible", it must capture a full snapshot for the replay to work correctly across tabs
61
+ */
62
+ this.hasSnapshot = false;
63
+ /** Payload metadata -- Should indicate that the payload being sent contains an error. Used for query/filter purposes in UI */
64
+ this.hasError = false;
65
+
66
+ /** A value which increments with every new mutation node reported. Resets after a harvest is sent */
67
+ this.payloadBytesEstimation = 0;
68
+
69
+ /** The method to stop recording. This defaults to a noop, but is overwritten once the recording library is imported and initialized */
70
+ this.stopRecording = () => {/* no-op until set by rrweb initializer */};
71
+
72
+ // 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.
73
+ this.ee.on('session-reset', () => {
74
+ this.abort();
75
+ });
76
+
77
+ // The SessionEntity class can emit a message indicating the session was paused (visibility change). This feature must stop recording if that occurs.
78
+ this.ee.on(SESSION_EVENTS.PAUSE, () => {
79
+ this.stopRecording();
80
+ });
81
+ // 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.
82
+ this.ee.on(SESSION_EVENTS.RESUME, () => {
83
+ if (!this.initialized || this.mode === MODE.OFF) return;
84
+ this.startRecording();
85
+ this.takeFullSnapshot();
86
+ });
87
+
88
+ // Bespoke logic for new endpoint. This will change as downstream dependencies become solidified.
89
+ this.scheduler = new HarvestScheduler('blob', {
90
+ onFinished: this.onHarvestFinished.bind(this),
91
+ retryDelay: this.harvestTimeSeconds,
92
+ getPayload: this.prepareHarvest.bind(this),
93
+ // TODO -- this stuff needs a better way to be handled
94
+ includeBaseParams: false,
95
+ customUrl: 'https://vortex-alb.stg-single-tooth.cell.us.nr-data.net/blob',
96
+ raw: true,
97
+ gzip: true
98
+ }, this);
99
+
100
+ // Wait for an error to be reported. This currently is wrapped around the "Error" feature. This is a feature-feature dependency.
101
+ // This was to ensure that all errors, including those on the page before load and those handled with "noticeError" are accounted for. Needs evalulation
102
+ registerHandler('errorAgg', e => {
103
+ this.hasError = true;
104
+ // run once
105
+ if (this.mode === MODE.ERROR) {
106
+ this.mode = MODE.FULL;
107
+ // if the error was noticed AFTER the recorder was already imported....
108
+ if (recorder && this.initialized) {
109
+ this.stopRecording();
110
+ this.startRecording();
111
+ this.scheduler.startTimer(this.harvestTimeSeconds);
112
+ const {
113
+ session
114
+ } = getRuntime(this.agentIdentifier);
115
+ session.state.sessionReplay = this.mode;
116
+ }
117
+ }
118
+ }, this.featureName, this.ee);
119
+
120
+ // new handler for waiting for multiple flags. will be useful if/when backend designs multiple flags, or for evaluating multiple feature flags simultaneously (stn vs sr)
121
+ this.waitForFlags(['sr']).then(_ref => {
122
+ let [{
123
+ value
124
+ }] = _ref;
125
+ this.initializeRecording(value, Math.random() < getConfigurationValue(this.agentIdentifier, 'session_replay.errorSampleRate'), Math.random() < getConfigurationValue(this.agentIdentifier, 'session_replay.sampleRate'));
126
+ });
127
+ drain(this.agentIdentifier, this.featureName);
128
+ }
129
+
130
+ /**
131
+ * Evaluate entitlements and sampling before starting feature mechanics, importing and configuring recording library, and setting storage state
132
+ * @param {boolean} entitlements - the true/false state of the "sr" flag from RUM response
133
+ * @param {boolean} errorSample - the true/false state of the error sampling decision
134
+ * @param {boolean} fullSample - the true/false state of the full sampling decision
135
+ * @returns {void}
136
+ */
137
+ async initializeRecording(entitlements, errorSample, fullSample) {
138
+ this.initialized = true;
139
+ if (!entitlements) return;
140
+ const {
141
+ session
142
+ } = getRuntime(this.agentIdentifier);
143
+ // if theres an existing session replay in progress, there's no need to sample, just check the entitlements response
144
+ // if not, these sample flags need to be checked
145
+ // if this isnt the FIRST load of a session AND
146
+ // we are not actively recording SR... DO NOT import or run the recording library
147
+ // session replay samples can only be decided on the first load of a session
148
+ // session replays can continue if already in progress
149
+ if (!session.isNew) {
150
+ // inherit the mode of the existing session
151
+ this.mode = session.state.sessionReplay;
152
+ } else {
153
+ // The session is new... determine the mode the new session should start in
154
+ if (fullSample) this.mode = MODE.FULL; // full mode has precedence over error mode
155
+ else if (errorSample) this.mode = MODE.ERROR;
156
+ // If neither are selected, then don't record (early return)
157
+ return;
158
+ }
159
+
160
+ // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
161
+ // ERROR mode will do this until an error is thrown, and then switch into FULL mode.
162
+ // If an error happened in ERROR mode before we've gotten to this stage, it will have already set the mode to FULL
163
+ if (this.mode === MODE.FULL) {
164
+ // We only report (harvest) in FULL mode
165
+ this.scheduler.startTimer(this.harvestTimeSeconds);
166
+ }
167
+ // We record in FULL or ERROR mode
168
+
169
+ recorder = (await import( /* webpackChunkName: "recorder" */'rrweb')).record;
170
+ this.startRecording();
171
+ const {
172
+ gzipSync,
173
+ strToU8
174
+ } = await import( /* webpackChunkName: "compressor" */'fflate');
175
+ gzipper = gzipSync;
176
+ u8 = strToU8;
177
+ this.isFirstChunk = !!session.isNew;
178
+ session.state.sessionReplay = this.mode;
179
+ }
180
+ prepareHarvest(options) {
181
+ if (this.events.length === 0) return;
182
+ const payload = this.getHarvestContents();
183
+ try {
184
+ payload.body = gzipper(u8(stringify(payload.body)));
185
+ this.scheduler.opts.gzip = true;
186
+ } catch (err) {
187
+ // failed to gzip
188
+ this.scheduler.opts.gzip = false;
189
+ }
190
+ // TODO -- Gracefully handle the buffer for retries.
191
+ this.clearBuffer();
192
+ return [payload];
193
+ }
194
+ getHarvestContents() {
195
+ const agentRuntime = getRuntime(this.agentIdentifier);
196
+ const info = getInfo(this.agentIdentifier);
197
+ return {
198
+ qs: {
199
+ protocol_version: '0'
200
+ },
201
+ body: {
202
+ type: 'SessionReplay',
203
+ appId: Number(info.applicationID),
204
+ timestamp: Date.now(),
205
+ blob: JSON.stringify(this.events),
206
+ // this needs to be a stringified JSON array of rrweb nodes
207
+ attributes: {
208
+ session: agentRuntime.session.state.value,
209
+ hasSnapshot: this.hasSnapshot,
210
+ hasError: this.hasError,
211
+ agentVersion: agentRuntime.version,
212
+ isFirstChunk: this.isFirstChunk,
213
+ 'nr.rrweb.version': RRWEB_VERSION
214
+ }
215
+ }
216
+ };
217
+ }
218
+ onHarvestFinished(result) {
219
+ // The mutual decision for now is to stop recording and clear buffers if ingest is experiencing 429 rate limiting
220
+ if (result.status === 429) {
221
+ this.abort();
222
+ }
223
+ if (this.blocked) this.scheduler.stopTimer(true);
224
+ }
225
+
226
+ /** Clears the buffer (this.events), and resets all payload metadata properties */
227
+ clearBuffer() {
228
+ this.events = [];
229
+ this.isFirstChunk = false;
230
+ this.hasSnapshot = false;
231
+ this.hasError = false;
232
+ this.payloadBytesEstimation = 0;
233
+ }
234
+
235
+ /** Begin recording using configured recording lib */
236
+ startRecording() {
237
+ if (!recorder) {
238
+ warn('Recording library was never imported');
239
+ return this.abort();
240
+ }
241
+ const {
242
+ blockClass,
243
+ ignoreClass,
244
+ maskTextClass,
245
+ blockSelector,
246
+ maskInputOptions,
247
+ maskTextSelector,
248
+ maskAllInputs
249
+ } = getConfigurationValue(this.agentIdentifier, 'session_replay');
250
+ this.hasSnapshot = true;
251
+ // set up rrweb configurations for maximum privacy --
252
+ // https://newrelic.atlassian.net/wiki/spaces/O11Y/pages/2792293280/2023+02+28+Browser+-+Session+Replay#Configuration-options
253
+ this.stopRecording = recorder({
254
+ emit: this.store.bind(this),
255
+ blockClass,
256
+ ignoreClass,
257
+ maskTextClass,
258
+ blockSelector,
259
+ maskInputOptions,
260
+ maskTextSelector,
261
+ maskAllInputs,
262
+ ...(this.mode === MODE.ERROR && {
263
+ checkoutEveryNms: CHECKOUT_MS
264
+ })
265
+ });
266
+ }
267
+
268
+ /** Store a payload in the buffer (this.events). This should be the callback to the recording lib noticing a mutation */
269
+ store(event, isCheckout) {
270
+ if (this.blocked) return;
271
+ const eventBytes = stringify(event).length;
272
+ /** The estimated size of the payload after compression */
273
+ const payloadSize = this.getPayloadSize(eventBytes);
274
+ // Vortex will block payloads at a certain size, we might as well not send.
275
+ if (payloadSize > MAX_PAYLOAD_SIZE) {
276
+ return this.abort();
277
+ }
278
+ // Checkout events are flags by the recording lib that indicate a fullsnapshot was taken every n ms. These are important
279
+ // to help reconstruct the replay later and must be included. While waiting and buffering for errors to come through,
280
+ // each time we see a new checkout, we can drop the old data.
281
+ if (this.mode === MODE.ERROR && isCheckout) {
282
+ // we are still waiting for an error to throw, so keep wiping the buffer over time
283
+ this.clearBuffer();
284
+ }
285
+ this.events.push(event);
286
+ this.payloadBytesEstimation += eventBytes;
287
+
288
+ // We are making an effort to try to keep payloads manageable for unloading. If they reach the unload limit before their interval,
289
+ // it will send immediately. This often happens on the first snapshot, which can be significantly larger than the other payloads.
290
+ if (payloadSize > IDEAL_PAYLOAD_SIZE) {
291
+ // if we've made it to the ideal size of ~64kb before the interval timer, we should send early.
292
+ this.scheduler.runHarvest();
293
+ }
294
+ }
295
+
296
+ /** force the recording lib to take a full DOM snapshot. This needs to occur in certain cases, like visibility changes */
297
+ takeFullSnapshot() {
298
+ if (!recorder) return;
299
+ recorder.takeFullSnapshot();
300
+ this.hasSnapshot = true;
301
+ }
302
+
303
+ /** Estimate the payload size */
304
+ getPayloadSize() {
305
+ let newBytes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
306
+ // the 1KB gives us some padding for the other metadata
307
+ return this.estimateCompression(this.payloadBytesEstimation + newBytes) + 1000;
308
+ }
309
+
310
+ /** Abort the feature, once aborted it will not resume */
311
+ abort() {
312
+ this.blocked = true;
313
+ this.stopRecording();
314
+ const {
315
+ session
316
+ } = getRuntime(this.agentIdentifier);
317
+ session.state.sessionReplay = this.mode;
318
+ }
319
+
320
+ /** Extensive research has yielded about an 88% compression factor on these payloads.
321
+ * This is an estimation using that factor as to not cause performance issues while evaluating
322
+ * https://staging.onenr.io/037jbJWxbjy
323
+ * */
324
+ estimateCompression(data) {
325
+ return data * 0.12;
326
+ }
327
+ }
@@ -0,0 +1,2 @@
1
+ import { FEATURE_NAMES } from '../../loaders/features/features';
2
+ export const FEATURE_NAME = FEATURE_NAMES.sessionReplay;
@@ -0,0 +1,12 @@
1
+ /*
2
+ * Copyright 2023 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ /**
6
+ * @file The top-level entry point for the eventual Session Replay feature.
7
+ *
8
+ * NOTE: This code is under development and dormant. It will not download to instrumented pages or record any data.
9
+ * It is not production ready, and is not intended to be imported or implemented in any build of the browser agent until
10
+ * functionality is validated and a full user experience is curated.
11
+ */
12
+ export { Instrument as SessionReplay } from './instrument/index';