@newrelic/browser-agent 1.233.1 → 1.235.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/README.md +1 -1
  2. package/dist/cjs/common/constants/env.cdn.js +1 -1
  3. package/dist/cjs/common/constants/env.npm.js +1 -1
  4. package/dist/cjs/common/constants/shared-channel.js +19 -0
  5. package/dist/cjs/common/event-emitter/contextual-ee.test.js +10 -10
  6. package/dist/cjs/common/harvest/{harvest-scheduler.test.js → harvest-scheduler.component-test.js} +2 -2
  7. package/dist/cjs/common/harvest/harvest-scheduler.js +21 -5
  8. package/dist/cjs/common/harvest/harvest.component-test.js +224 -0
  9. package/dist/cjs/common/harvest/harvest.js +4 -11
  10. package/dist/cjs/common/session/{session-entity.test.js → session-entity.component-test.js} +79 -42
  11. package/dist/cjs/common/session/session-entity.js +19 -11
  12. package/dist/cjs/common/timer/interaction-timer.js +1 -1
  13. package/dist/cjs/common/url/canonicalize-url.test.js +26 -30
  14. package/dist/cjs/common/url/encode.js +2 -2
  15. package/dist/cjs/common/util/console.test.js +30 -0
  16. package/dist/cjs/common/util/data-size.test.js +37 -20
  17. package/dist/cjs/common/util/feature-flags.js +23 -12
  18. package/dist/cjs/common/util/feature-flags.test.js +84 -0
  19. package/dist/cjs/common/util/get-or-set.js +8 -1
  20. package/dist/cjs/common/util/get-or-set.test.js +47 -0
  21. package/dist/cjs/common/util/global-scope.js +1 -32
  22. package/dist/cjs/common/util/global-scope.test.js +72 -0
  23. package/dist/cjs/common/util/obfuscate.component-test.js +129 -0
  24. package/dist/cjs/common/util/obfuscate.js +2 -2
  25. package/dist/cjs/common/util/stringify.test.js +48 -0
  26. package/dist/cjs/common/util/submit-data.js +18 -18
  27. package/dist/cjs/common/util/submit-data.test.js +245 -0
  28. package/dist/cjs/common/util/traverse.js +19 -27
  29. package/dist/cjs/common/util/traverse.test.js +44 -0
  30. package/dist/cjs/common/wrap/wrap-raf.js +1 -1
  31. package/dist/cjs/common/wrap/wrap-timer.js +1 -1
  32. package/dist/cjs/features/jserrors/aggregate/index.js +4 -0
  33. package/dist/cjs/features/jserrors/instrument/index.js +2 -15
  34. package/dist/cjs/features/metrics/aggregate/endpoint-map.js +14 -0
  35. package/dist/cjs/features/metrics/aggregate/index.js +3 -2
  36. package/dist/cjs/features/metrics/instrument/index.js +0 -2
  37. package/dist/cjs/features/page_view_event/aggregate/index.js +58 -44
  38. package/dist/cjs/features/session_replay/aggregate/index.component-test.js +457 -0
  39. package/dist/cjs/features/session_replay/aggregate/index.js +99 -82
  40. package/dist/cjs/features/session_replay/replay-mode.js +28 -0
  41. package/dist/cjs/features/session_trace/aggregate/index.js +222 -99
  42. package/dist/cjs/features/session_trace/constants.js +1 -3
  43. package/dist/cjs/features/session_trace/instrument/index.js +0 -16
  44. package/dist/cjs/features/spa/constants.js +0 -1
  45. package/dist/cjs/features/utils/agent-session.js +20 -36
  46. package/dist/cjs/features/utils/agent-session.test.js +211 -0
  47. package/dist/cjs/features/utils/aggregate-base.js +7 -12
  48. package/dist/cjs/features/utils/aggregate-base.test.js +110 -0
  49. package/dist/cjs/features/utils/feature-base.test.js +42 -0
  50. package/dist/cjs/features/utils/handler-cache.js +28 -23
  51. package/dist/cjs/features/utils/handler-cache.test.js +53 -0
  52. package/dist/cjs/features/utils/instrument-base.js +58 -39
  53. package/dist/cjs/features/utils/instrument-base.test.js +179 -0
  54. package/dist/cjs/features/utils/lazy-feature-loader.test.js +30 -0
  55. package/dist/cjs/loaders/agent.js +0 -1
  56. package/dist/cjs/loaders/api/api.js +1 -1
  57. package/dist/cjs/loaders/configure/configure.js +0 -1
  58. package/dist/cjs/loaders/features/featureDependencies.js +2 -0
  59. package/dist/esm/common/constants/env.cdn.js +1 -1
  60. package/dist/esm/common/constants/env.npm.js +1 -1
  61. package/dist/esm/common/constants/shared-channel.js +12 -0
  62. package/dist/esm/common/event-emitter/contextual-ee.test.js +10 -10
  63. package/dist/esm/common/harvest/{harvest-scheduler.test.js → harvest-scheduler.component-test.js} +2 -2
  64. package/dist/esm/common/harvest/harvest-scheduler.js +21 -5
  65. package/dist/esm/common/harvest/harvest.component-test.js +222 -0
  66. package/dist/esm/common/harvest/harvest.js +4 -11
  67. package/dist/esm/common/session/{session-entity.test.js → session-entity.component-test.js} +77 -40
  68. package/dist/esm/common/session/session-entity.js +17 -11
  69. package/dist/esm/common/timer/interaction-timer.js +1 -1
  70. package/dist/esm/common/url/canonicalize-url.test.js +25 -29
  71. package/dist/esm/common/url/encode.js +2 -2
  72. package/dist/esm/common/util/console.test.js +28 -0
  73. package/dist/esm/common/util/data-size.test.js +35 -20
  74. package/dist/esm/common/util/feature-flags.js +23 -12
  75. package/dist/esm/common/util/feature-flags.test.js +80 -0
  76. package/dist/esm/common/util/get-or-set.js +8 -1
  77. package/dist/esm/common/util/get-or-set.test.js +45 -0
  78. package/dist/esm/common/util/global-scope.js +1 -29
  79. package/dist/esm/common/util/global-scope.test.js +70 -0
  80. package/dist/esm/common/util/obfuscate.component-test.js +125 -0
  81. package/dist/esm/common/util/obfuscate.js +2 -2
  82. package/dist/esm/common/util/stringify.test.js +46 -0
  83. package/dist/esm/common/util/submit-data.js +18 -18
  84. package/dist/esm/common/util/submit-data.test.js +241 -0
  85. package/dist/esm/common/util/traverse.js +19 -27
  86. package/dist/esm/common/util/traverse.test.js +42 -0
  87. package/dist/esm/common/wrap/wrap-raf.js +1 -1
  88. package/dist/esm/common/wrap/wrap-timer.js +1 -1
  89. package/dist/esm/features/jserrors/aggregate/index.js +4 -0
  90. package/dist/esm/features/jserrors/instrument/index.js +2 -15
  91. package/dist/esm/features/metrics/aggregate/endpoint-map.js +7 -0
  92. package/dist/esm/features/metrics/aggregate/index.js +3 -2
  93. package/dist/esm/features/metrics/instrument/index.js +0 -2
  94. package/dist/esm/features/page_view_event/aggregate/index.js +58 -44
  95. package/dist/esm/features/session_replay/aggregate/index.component-test.js +453 -0
  96. package/dist/esm/features/session_replay/aggregate/index.js +92 -78
  97. package/dist/esm/features/session_replay/replay-mode.js +23 -0
  98. package/dist/esm/features/session_trace/aggregate/index.js +223 -100
  99. package/dist/esm/features/session_trace/constants.js +0 -1
  100. package/dist/esm/features/session_trace/instrument/index.js +1 -17
  101. package/dist/esm/features/spa/constants.js +0 -1
  102. package/dist/esm/features/utils/agent-session.js +21 -37
  103. package/dist/esm/features/utils/agent-session.test.js +207 -0
  104. package/dist/esm/features/utils/aggregate-base.js +7 -12
  105. package/dist/esm/features/utils/aggregate-base.test.js +108 -0
  106. package/dist/esm/features/utils/feature-base.test.js +40 -0
  107. package/dist/esm/features/utils/handler-cache.js +28 -23
  108. package/dist/esm/features/utils/handler-cache.test.js +51 -0
  109. package/dist/esm/features/utils/instrument-base.js +58 -39
  110. package/dist/esm/features/utils/instrument-base.test.js +175 -0
  111. package/dist/esm/features/utils/lazy-feature-loader.test.js +29 -0
  112. package/dist/esm/loaders/agent.js +0 -1
  113. package/dist/esm/loaders/api/api.js +2 -2
  114. package/dist/esm/loaders/configure/configure.js +0 -1
  115. package/dist/esm/loaders/features/featureDependencies.js +2 -0
  116. package/dist/types/common/config/state/init.d.ts.map +1 -1
  117. package/dist/types/common/constants/shared-channel.d.ts +5 -0
  118. package/dist/types/common/constants/shared-channel.d.ts.map +1 -0
  119. package/dist/types/common/context/shared-context.d.ts.map +1 -1
  120. package/dist/types/common/harvest/harvest-scheduler.component-test.d.ts +2 -0
  121. package/dist/types/common/harvest/harvest-scheduler.component-test.d.ts.map +1 -0
  122. package/dist/types/common/harvest/harvest-scheduler.d.ts +4 -0
  123. package/dist/types/common/harvest/harvest-scheduler.d.ts.map +1 -1
  124. package/dist/types/common/harvest/harvest.component-test.d.ts +2 -0
  125. package/dist/types/common/harvest/harvest.component-test.d.ts.map +1 -0
  126. package/dist/types/common/harvest/harvest.d.ts.map +1 -1
  127. package/dist/types/common/session/session-entity.component-test.d.ts +2 -0
  128. package/dist/types/common/session/session-entity.component-test.d.ts.map +1 -0
  129. package/dist/types/common/session/session-entity.d.ts +9 -5
  130. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  131. package/dist/types/common/timer/interaction-timer.component-test.d.ts +2 -0
  132. package/dist/types/common/timer/interaction-timer.component-test.d.ts.map +1 -0
  133. package/dist/types/common/timer/timer.d.ts.map +1 -1
  134. package/dist/types/common/url/encode.component-test.d.ts +2 -0
  135. package/dist/types/common/url/encode.component-test.d.ts.map +1 -0
  136. package/dist/types/common/url/protocol.component-test.d.ts +2 -0
  137. package/dist/types/common/url/protocol.component-test.d.ts.map +1 -0
  138. package/dist/types/common/util/feature-flags.d.ts +1 -0
  139. package/dist/types/common/util/feature-flags.d.ts.map +1 -1
  140. package/dist/types/common/util/get-or-set.d.ts +9 -1
  141. package/dist/types/common/util/get-or-set.d.ts.map +1 -1
  142. package/dist/types/common/util/global-scope.d.ts +0 -9
  143. package/dist/types/common/util/global-scope.d.ts.map +1 -1
  144. package/dist/types/common/util/obfuscate.component-test.d.ts +2 -0
  145. package/dist/types/common/util/obfuscate.component-test.d.ts.map +1 -0
  146. package/dist/types/common/util/submit-data.d.ts +14 -10
  147. package/dist/types/common/util/submit-data.d.ts.map +1 -1
  148. package/dist/types/common/util/traverse.d.ts +10 -1
  149. package/dist/types/common/util/traverse.d.ts.map +1 -1
  150. package/dist/types/common/window/nreum.d.ts.map +1 -1
  151. package/dist/types/features/jserrors/aggregate/index.d.ts +1 -0
  152. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  153. package/dist/types/features/metrics/aggregate/endpoint-map.d.ts +8 -0
  154. package/dist/types/features/metrics/aggregate/endpoint-map.d.ts.map +1 -0
  155. package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
  156. package/dist/types/features/metrics/aggregate/polyfill-detection.es5.d.ts.map +1 -1
  157. package/dist/types/features/metrics/instrument/index.d.ts.map +1 -1
  158. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  159. package/dist/types/features/page_view_event/instrument/index.d.ts.map +1 -1
  160. package/dist/types/features/session_replay/aggregate/index.component-test.d.ts +2 -0
  161. package/dist/types/features/session_replay/aggregate/index.component-test.d.ts.map +1 -0
  162. package/dist/types/features/session_replay/aggregate/index.d.ts +14 -5
  163. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  164. package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -1
  165. package/dist/types/features/session_replay/replay-mode.d.ts +9 -0
  166. package/dist/types/features/session_replay/replay-mode.d.ts.map +1 -0
  167. package/dist/types/features/session_trace/aggregate/index.d.ts +21 -3
  168. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  169. package/dist/types/features/session_trace/constants.d.ts +0 -1
  170. package/dist/types/features/session_trace/constants.d.ts.map +1 -1
  171. package/dist/types/features/session_trace/instrument/index.d.ts +0 -2
  172. package/dist/types/features/session_trace/instrument/index.d.ts.map +1 -1
  173. package/dist/types/features/spa/constants.d.ts.map +1 -1
  174. package/dist/types/features/utils/agent-session.d.ts.map +1 -1
  175. package/dist/types/features/utils/aggregate-base.d.ts +6 -1
  176. package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
  177. package/dist/types/features/utils/handler-cache.d.ts +12 -11
  178. package/dist/types/features/utils/handler-cache.d.ts.map +1 -1
  179. package/dist/types/features/utils/instrument-base.d.ts +17 -1
  180. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  181. package/dist/types/loaders/agent.d.ts.map +1 -1
  182. package/dist/types/loaders/configure/configure.d.ts.map +1 -1
  183. package/dist/types/loaders/features/featureDependencies.d.ts.map +1 -1
  184. package/package.json +14 -8
  185. package/src/common/config/state/init.js +0 -1
  186. package/src/common/constants/shared-channel.js +13 -0
  187. package/src/common/context/shared-context.js +0 -1
  188. package/src/common/event-emitter/contextual-ee.test.js +10 -10
  189. package/src/common/harvest/{harvest-scheduler.test.js → harvest-scheduler.component-test.js} +2 -2
  190. package/src/common/harvest/harvest-scheduler.js +17 -6
  191. package/src/common/harvest/harvest.component-test.js +169 -0
  192. package/src/common/harvest/harvest.js +5 -9
  193. package/src/common/session/{session-entity.test.js → session-entity.component-test.js} +70 -48
  194. package/src/common/session/session-entity.js +15 -12
  195. package/src/common/timer/interaction-timer.js +1 -1
  196. package/src/common/timer/timer.js +0 -1
  197. package/src/common/url/canonicalize-url.test.js +32 -21
  198. package/src/common/url/encode.js +2 -2
  199. package/src/common/util/console.test.js +34 -0
  200. package/src/common/util/data-size.test.js +27 -20
  201. package/src/common/util/feature-flags.js +24 -12
  202. package/src/common/util/feature-flags.test.js +98 -0
  203. package/src/common/util/get-or-set.js +8 -1
  204. package/src/common/util/get-or-set.test.js +58 -0
  205. package/src/common/util/global-scope.js +0 -26
  206. package/src/common/util/global-scope.test.js +87 -0
  207. package/src/common/util/obfuscate.component-test.js +173 -0
  208. package/src/common/util/obfuscate.js +2 -2
  209. package/src/common/util/stringify.test.js +49 -0
  210. package/src/common/util/submit-data.js +18 -19
  211. package/src/common/util/submit-data.test.js +226 -0
  212. package/src/common/util/traverse.js +18 -27
  213. package/src/common/util/traverse.test.js +50 -0
  214. package/src/common/window/nreum.js +0 -1
  215. package/src/common/wrap/wrap-raf.js +1 -1
  216. package/src/common/wrap/wrap-timer.js +1 -1
  217. package/src/features/jserrors/aggregate/index.js +5 -0
  218. package/src/features/jserrors/instrument/index.js +2 -15
  219. package/src/features/metrics/aggregate/endpoint-map.js +7 -0
  220. package/src/features/metrics/aggregate/index.js +3 -2
  221. package/src/features/metrics/aggregate/polyfill-detection.es5.js +0 -1
  222. package/src/features/metrics/instrument/index.js +0 -2
  223. package/src/features/page_view_event/aggregate/index.js +48 -51
  224. package/src/features/page_view_event/instrument/index.js +0 -1
  225. package/src/features/session_replay/aggregate/index.component-test.js +368 -0
  226. package/src/features/session_replay/aggregate/index.js +96 -71
  227. package/src/features/session_replay/instrument/index.js +0 -1
  228. package/src/features/session_replay/replay-mode.js +23 -0
  229. package/src/features/session_trace/aggregate/index.js +198 -79
  230. package/src/features/session_trace/constants.js +0 -1
  231. package/src/features/session_trace/instrument/index.js +2 -19
  232. package/src/features/spa/constants.js +0 -1
  233. package/src/features/utils/agent-session.js +22 -34
  234. package/src/features/utils/agent-session.test.js +194 -0
  235. package/src/features/utils/aggregate-base.js +12 -9
  236. package/src/features/utils/aggregate-base.test.js +122 -0
  237. package/src/features/utils/feature-base.test.js +45 -0
  238. package/src/features/utils/handler-cache.js +29 -24
  239. package/src/features/utils/handler-cache.test.js +72 -0
  240. package/src/features/utils/instrument-base.js +45 -29
  241. package/src/features/utils/instrument-base.test.js +190 -0
  242. package/src/features/utils/lazy-feature-loader.test.js +37 -0
  243. package/src/loaders/agent.js +0 -1
  244. package/src/loaders/api/api.js +2 -2
  245. package/src/loaders/configure/configure.js +0 -1
  246. package/src/loaders/features/featureDependencies.js +2 -0
  247. package/dist/cjs/common/storage/local-memory.js +0 -35
  248. package/dist/cjs/common/storage/local-memory.test.js +0 -20
  249. package/dist/cjs/common/util/s-hash.js +0 -19
  250. package/dist/cjs/features/metrics/instrument/workers-helper.js +0 -124
  251. package/dist/esm/common/storage/local-memory.js +0 -28
  252. package/dist/esm/common/storage/local-memory.test.js +0 -18
  253. package/dist/esm/common/util/s-hash.js +0 -13
  254. package/dist/esm/features/metrics/instrument/workers-helper.js +0 -119
  255. package/dist/types/common/storage/local-memory.d.ts +0 -8
  256. package/dist/types/common/storage/local-memory.d.ts.map +0 -1
  257. package/dist/types/common/util/s-hash.d.ts +0 -2
  258. package/dist/types/common/util/s-hash.d.ts.map +0 -1
  259. package/dist/types/features/metrics/instrument/workers-helper.d.ts +0 -7
  260. package/dist/types/features/metrics/instrument/workers-helper.d.ts.map +0 -1
  261. package/src/common/storage/local-memory.js +0 -30
  262. package/src/common/storage/local-memory.test.js +0 -19
  263. package/src/common/util/s-hash.js +0 -14
  264. package/src/features/metrics/instrument/workers-helper.js +0 -113
  265. /package/dist/cjs/common/timer/{interaction-timer.test.js → interaction-timer.component-test.js} +0 -0
  266. /package/dist/cjs/common/url/{encode.test.js → encode.component-test.js} +0 -0
  267. /package/dist/cjs/common/url/{protocol.test.js → protocol.component-test.js} +0 -0
  268. /package/dist/esm/common/timer/{interaction-timer.test.js → interaction-timer.component-test.js} +0 -0
  269. /package/dist/esm/common/url/{encode.test.js → encode.component-test.js} +0 -0
  270. /package/dist/esm/common/url/{protocol.test.js → protocol.component-test.js} +0 -0
  271. /package/src/common/timer/{interaction-timer.test.js → interaction-timer.component-test.js} +0 -0
  272. /package/src/common/url/{encode.test.js → encode.component-test.js} +0 -0
  273. /package/src/common/url/{protocol.test.js → protocol.component-test.js} +0 -0
@@ -0,0 +1,179 @@
1
+ "use strict";
2
+
3
+ var _faker = require("@faker-js/faker");
4
+ var _instrumentBase = require("./instrument-base");
5
+ var _featureBase = require("./feature-base");
6
+ var _drain = require("../../common/drain/drain");
7
+ var _load = require("../../common/window/load");
8
+ var _lazyFeatureLoader = require("./lazy-feature-loader");
9
+ var _config = require("../../common/config/config");
10
+ var _agentSession = require("./agent-session");
11
+ var _console = require("../../common/util/console");
12
+ var globalScopeModule = _interopRequireWildcard(require("../../common/util/global-scope"));
13
+ var _features = require("../../loaders/features/features");
14
+ 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); }
15
+ 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; }
16
+ jest.enableAutomock();
17
+ jest.unmock('./instrument-base');
18
+ jest.unmock('../../loaders/features/features');
19
+ jest.mock('../../common/drain/drain', () => ({
20
+ __esModule: true,
21
+ drain: jest.fn(),
22
+ registerDrain: jest.fn()
23
+ }));
24
+ jest.mock('../../common/window/load', () => ({
25
+ __esModule: true,
26
+ onWindowLoad: jest.fn()
27
+ }));
28
+ jest.mock('../../common/util/global-scope', () => ({
29
+ __esModule: true,
30
+ isBrowserScope: undefined,
31
+ isWorkerScope: undefined
32
+ }));
33
+ jest.mock('../../common/config/config', () => ({
34
+ __esModule: true,
35
+ getConfigurationValue: jest.fn()
36
+ }));
37
+ jest.mock('./feature-base', () => ({
38
+ __esModule: true,
39
+ FeatureBase: jest.fn(function () {
40
+ this.agentIdentifier = arguments.length <= 0 ? undefined : arguments[0];
41
+ this.aggregator = arguments.length <= 1 ? undefined : arguments[1];
42
+ this.featureName = arguments.length <= 2 ? undefined : arguments[2];
43
+ })
44
+ }));
45
+ jest.mock('./agent-session', () => ({
46
+ __esModule: true,
47
+ setupAgentSession: jest.fn()
48
+ }));
49
+ let agentIdentifier;
50
+ let aggregator;
51
+ let featureName;
52
+ let mockAggregate;
53
+ beforeEach(() => {
54
+ jest.replaceProperty(globalScopeModule, 'isBrowserScope', true);
55
+ jest.replaceProperty(globalScopeModule, 'isWorkerScope', false);
56
+ agentIdentifier = _faker.faker.datatype.uuid();
57
+ aggregator = {};
58
+ featureName = _faker.faker.datatype.uuid();
59
+ mockAggregate = jest.fn(() => {/* noop */});
60
+ jest.mocked(_lazyFeatureLoader.lazyFeatureLoader).mockResolvedValue({
61
+ Aggregate: mockAggregate
62
+ });
63
+ });
64
+ test('should construct a new instrument', () => {
65
+ const instrument = new _instrumentBase.InstrumentBase(agentIdentifier, aggregator, featureName);
66
+ expect(_featureBase.FeatureBase).toHaveBeenCalledWith(agentIdentifier, aggregator, featureName);
67
+ expect(instrument.featAggregate).toBeUndefined();
68
+ expect(instrument.auto).toEqual(true);
69
+ expect(instrument.abortHandler).toBeUndefined();
70
+ expect(_drain.registerDrain).toHaveBeenCalledWith(agentIdentifier, featureName);
71
+ });
72
+ test('should not immediately drain', () => {
73
+ const instrument = new _instrumentBase.InstrumentBase(agentIdentifier, aggregator, featureName, false);
74
+ expect(_drain.registerDrain).not.toHaveBeenCalled();
75
+ });
76
+ test('should import aggregator on window load', async () => {
77
+ const instrument = new _instrumentBase.InstrumentBase(agentIdentifier, aggregator, featureName);
78
+ const aggregateArgs = {
79
+ [_faker.faker.datatype.uuid()]: _faker.faker.lorem.sentence()
80
+ };
81
+ instrument.importAggregator(aggregateArgs);
82
+ const windowLoadCallback = jest.mocked(_load.onWindowLoad).mock.calls[0][0];
83
+ await windowLoadCallback();
84
+ expect(_load.onWindowLoad).toHaveBeenCalledWith(expect.any(Function), true);
85
+ expect(_lazyFeatureLoader.lazyFeatureLoader).toHaveBeenCalledWith(featureName, 'aggregate');
86
+ expect(mockAggregate).toHaveBeenCalledWith(agentIdentifier, aggregator, aggregateArgs);
87
+ });
88
+ test('should immediately import aggregator in worker scope', async () => {
89
+ jest.replaceProperty(globalScopeModule, 'isBrowserScope', false);
90
+ jest.replaceProperty(globalScopeModule, 'isWorkerScope', true);
91
+ const instrument = new _instrumentBase.InstrumentBase(agentIdentifier, aggregator, featureName);
92
+ const aggregateArgs = {
93
+ [_faker.faker.datatype.uuid()]: _faker.faker.lorem.sentence()
94
+ };
95
+ instrument.importAggregator(aggregateArgs);
96
+
97
+ // In worker scope, we cannot wait on importLater method
98
+ await new Promise(process.nextTick);
99
+ expect(_load.onWindowLoad).not.toHaveBeenCalled();
100
+ expect(_lazyFeatureLoader.lazyFeatureLoader).toHaveBeenCalledWith(featureName, 'aggregate');
101
+ expect(mockAggregate).toHaveBeenCalledWith(agentIdentifier, aggregator, aggregateArgs);
102
+ });
103
+ test('should import the session manager and replay aggregate for new session', async () => {
104
+ jest.mocked(_config.getConfigurationValue).mockReturnValue(true);
105
+ jest.mocked(_agentSession.setupAgentSession).mockReturnValue({
106
+ isNew: true
107
+ });
108
+ const instrument = new _instrumentBase.InstrumentBase(agentIdentifier, aggregator, _features.FEATURE_NAMES.sessionReplay);
109
+ const aggregateArgs = {
110
+ [_faker.faker.datatype.uuid()]: _faker.faker.lorem.sentence()
111
+ };
112
+ instrument.importAggregator(aggregateArgs);
113
+ const windowLoadCallback = jest.mocked(_load.onWindowLoad).mock.calls[0][0];
114
+ await windowLoadCallback();
115
+ expect(_config.getConfigurationValue).toHaveBeenCalledWith(agentIdentifier, 'privacy.cookies_enabled');
116
+ expect(_agentSession.setupAgentSession).toHaveBeenCalledWith(agentIdentifier);
117
+ expect(_lazyFeatureLoader.lazyFeatureLoader).toHaveBeenCalledWith(_features.FEATURE_NAMES.sessionReplay, 'aggregate');
118
+ expect(mockAggregate).toHaveBeenCalledWith(agentIdentifier, aggregator, aggregateArgs);
119
+ });
120
+ test('should import the session manager and replay aggregate when a recording is active', async () => {
121
+ jest.mocked(_config.getConfigurationValue).mockReturnValue(true);
122
+ jest.mocked(_agentSession.setupAgentSession).mockReturnValue({
123
+ isNew: false,
124
+ state: {
125
+ sessionReplay: 1
126
+ }
127
+ });
128
+ const instrument = new _instrumentBase.InstrumentBase(agentIdentifier, aggregator, _features.FEATURE_NAMES.sessionReplay);
129
+ const aggregateArgs = {
130
+ [_faker.faker.datatype.uuid()]: _faker.faker.lorem.sentence()
131
+ };
132
+ instrument.importAggregator(aggregateArgs);
133
+ const windowLoadCallback = jest.mocked(_load.onWindowLoad).mock.calls[0][0];
134
+ await windowLoadCallback();
135
+ expect(_config.getConfigurationValue).toHaveBeenCalledWith(agentIdentifier, 'privacy.cookies_enabled');
136
+ expect(_agentSession.setupAgentSession).toHaveBeenCalledWith(agentIdentifier);
137
+ expect(_lazyFeatureLoader.lazyFeatureLoader).toHaveBeenCalledWith(_features.FEATURE_NAMES.sessionReplay, 'aggregate');
138
+ expect(mockAggregate).toHaveBeenCalledWith(agentIdentifier, aggregator, aggregateArgs);
139
+ });
140
+ test('should not import session aggregate when session is not new and a recording is not active', async () => {
141
+ jest.mocked(_config.getConfigurationValue).mockReturnValue(true);
142
+ jest.mocked(_agentSession.setupAgentSession).mockReturnValue({
143
+ isNew: false,
144
+ state: {
145
+ sessionReplay: 0
146
+ }
147
+ });
148
+ const instrument = new _instrumentBase.InstrumentBase(agentIdentifier, aggregator, _features.FEATURE_NAMES.sessionReplay);
149
+ const aggregateArgs = {
150
+ [_faker.faker.datatype.uuid()]: _faker.faker.lorem.sentence()
151
+ };
152
+ instrument.importAggregator(aggregateArgs);
153
+ const windowLoadCallback = jest.mocked(_load.onWindowLoad).mock.calls[0][0];
154
+ await windowLoadCallback();
155
+ expect(_config.getConfigurationValue).toHaveBeenCalledWith(agentIdentifier, 'privacy.cookies_enabled');
156
+ expect(_agentSession.setupAgentSession).toHaveBeenCalledWith(agentIdentifier);
157
+ expect(_drain.drain).toHaveBeenCalledWith(agentIdentifier, _features.FEATURE_NAMES.sessionReplay);
158
+ expect(_lazyFeatureLoader.lazyFeatureLoader).not.toHaveBeenCalled();
159
+ expect(mockAggregate).not.toHaveBeenCalled();
160
+ });
161
+ test('feature still imports by default even when setupAgentSession throws an error', async () => {
162
+ jest.mocked(_config.getConfigurationValue).mockReturnValue(true);
163
+ jest.mocked(_agentSession.setupAgentSession).mockImplementation(() => {
164
+ throw new Error(_faker.faker.lorem.sentence());
165
+ });
166
+ const instrument = new _instrumentBase.InstrumentBase(agentIdentifier, aggregator, featureName);
167
+ const aggregateArgs = {
168
+ [_faker.faker.datatype.uuid()]: _faker.faker.lorem.sentence()
169
+ };
170
+ instrument.abortHandler = jest.fn();
171
+ instrument.importAggregator(aggregateArgs);
172
+ const windowLoadCallback = jest.mocked(_load.onWindowLoad).mock.calls[0][0];
173
+ await windowLoadCallback();
174
+ expect(_load.onWindowLoad).toHaveBeenCalledWith(expect.any(Function), true);
175
+ expect(instrument.abortHandler).not.toHaveBeenCalled();
176
+ expect(_console.warn).toHaveBeenCalledWith(expect.stringContaining('A problem occurred when starting up session manager'), expect.any(Error));
177
+ expect(_lazyFeatureLoader.lazyFeatureLoader).toHaveBeenCalled();
178
+ expect(mockAggregate).toHaveBeenCalled();
179
+ });
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+
3
+ var _faker = require("@faker-js/faker");
4
+ var _features = require("../../loaders/features/features");
5
+ var _lazyFeatureLoader = require("./lazy-feature-loader");
6
+ // Use enableAutomock to make it easier to mock all the things that get imported by the aggregators
7
+ jest.enableAutomock();
8
+ // Unmock the file under test and the constants file
9
+ jest.unmock('../../loaders/features/features');
10
+ jest.unmock('./lazy-feature-loader');
11
+ test.each(Object.keys(_features.FEATURE_NAMES))('should import the aggregate for feature %s', async key => {
12
+ const featureName = _features.FEATURE_NAMES[key];
13
+ const randomId = _faker.faker.datatype.uuid();
14
+ jest.setMock("../".concat(featureName, "/aggregate"), {
15
+ id: randomId,
16
+ featureName
17
+ });
18
+ const result = await (0, _lazyFeatureLoader.lazyFeatureLoader)(featureName, 'aggregate');
19
+ expect(result.id).toEqual(randomId);
20
+ expect(result.featureName).toEqual(featureName);
21
+ });
22
+ test('should throw an error when the featureName is not supported', async () => {
23
+ const featureName = _faker.faker.datatype.uuid();
24
+ expect(() => (0, _lazyFeatureLoader.lazyFeatureLoader)(featureName, 'aggregate')).toThrow();
25
+ });
26
+ test('should return undefined when the featurePart is not supported', async () => {
27
+ const featureName = _faker.faker.datatype.uuid();
28
+ const featurePart = _faker.faker.datatype.uuid();
29
+ expect((0, _lazyFeatureLoader.lazyFeatureLoader)(featureName, featurePart)).toBeUndefined();
30
+ });
@@ -34,7 +34,6 @@ class Agent {
34
34
  });
35
35
  this.features = {};
36
36
  this.desiredFeatures = new Set(options.features || []); // expected to be a list of static Instrument/InstrumentBase classes, see "spa.js" for example
37
-
38
37
  // For Now... ALL agents must make the rum call whether the page_view_event feature was enabled or not.
39
38
  // NR1 creates an index on the rum call, and if not seen for a few days, will remove the browser app!
40
39
  // Future work is being planned to evaluate removing this behavior from the backend, but for now we must ensure this call is made
@@ -158,7 +158,7 @@ function setAPI(agentIdentifier, forceDrain) {
158
158
  };
159
159
 
160
160
  // theres no window.load event on non-browser scopes, lazy load immediately
161
- if (_globalScope.isWorkerScope) lazyLoad();
161
+ if (!_globalScope.isBrowserScope) lazyLoad();
162
162
  // try to stay out of the way of the window.load event, lazy load once that has finished.
163
163
  else (0, _load.onWindowLoad)(() => lazyLoad(), true);
164
164
  function lazyLoad() {
@@ -42,6 +42,5 @@ function configure(agentIdentifier) {
42
42
  (0, _nreum.gosNREUMInitializedAgents)(agentIdentifier, api, 'api');
43
43
  (0, _nreum.gosNREUMInitializedAgents)(agentIdentifier, exposed, 'exposed');
44
44
  (0, _nreum.addToNREUM)('activatedFeatures', _featureFlags.activatedFeatures);
45
- (0, _nreum.addToNREUM)('setToken', flags => (0, _featureFlags.activateFeatures)(flags, agentIdentifier));
46
45
  return api;
47
46
  }
@@ -11,6 +11,8 @@ function getFeatureDependencyNames(feature) {
11
11
  return [_features.FEATURE_NAMES.jserrors];
12
12
  case _features.FEATURE_NAMES.sessionTrace:
13
13
  return [_features.FEATURE_NAMES.ajax, _features.FEATURE_NAMES.pageViewEvent];
14
+ case _features.FEATURE_NAMES.sessionReplay:
15
+ return [_features.FEATURE_NAMES.sessionTrace];
14
16
  case _features.FEATURE_NAMES.pageViewTiming:
15
17
  return [_features.FEATURE_NAMES.pageViewEvent];
16
18
  // this could change if we disconnect window load timings
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Exposes the version of the agent
8
8
  */
9
- export const VERSION = "1.233.1";
9
+ export const VERSION = "1.235.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.233.1";
9
+ export const VERSION = "1.235.0";
10
10
 
11
11
  /**
12
12
  * Exposes the build type of the agent
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @file Keeps an object alive that is passed to all feature aggregate modules.
3
+ * The purpose is to have a way for communication and signals to relay across features at runtime.
4
+ * This object can hold any arbitrary values and should be treated as on-the-fly dynamic.
5
+ */
6
+
7
+ let onReplayReady;
8
+ const sessionReplayInitialized = new Promise(resolve => onReplayReady = resolve);
9
+ export const sharedChannel = Object.freeze({
10
+ onReplayReady,
11
+ sessionReplayInitialized
12
+ });
@@ -89,7 +89,7 @@ describe('event-emitter context', () => {
89
89
  });
90
90
  });
91
91
  describe('event-emitter buffer', () => {
92
- it('it should create a new buffer for the given group', async () => {
92
+ test('it should create a new buffer for the given group', async () => {
93
93
  const {
94
94
  ee
95
95
  } = await import('./contextual-ee');
@@ -101,7 +101,7 @@ describe('event-emitter buffer', () => {
101
101
  }));
102
102
  expect(ee.isBuffering(eventType)).toEqual(true);
103
103
  });
104
- it('it should default group to "feature"', async () => {
104
+ test('it should default group to "feature"', async () => {
105
105
  const {
106
106
  ee
107
107
  } = await import('./contextual-ee');
@@ -112,7 +112,7 @@ describe('event-emitter buffer', () => {
112
112
  }));
113
113
  expect(ee.isBuffering(eventType)).toEqual(true);
114
114
  });
115
- it('it should not create buffer if event-emitter is aborted', async () => {
115
+ test('it should not create buffer if event-emitter is aborted', async () => {
116
116
  const {
117
117
  ee
118
118
  } = await import('./contextual-ee');
@@ -152,7 +152,7 @@ describe('event-emitter abort', () => {
152
152
  });
153
153
  });
154
154
  describe('event-emitter emit', () => {
155
- it('should execute the listener', async () => {
155
+ test('should execute the listener', async () => {
156
156
  const {
157
157
  ee
158
158
  } = await import('./contextual-ee');
@@ -163,7 +163,7 @@ describe('event-emitter emit', () => {
163
163
  ee.emit(eventType, eventArgs);
164
164
  expect(mockListener).toHaveBeenCalledWith(eventArgs[0], eventArgs[1], eventArgs[2]);
165
165
  });
166
- it('should not execute the listener after removal', async () => {
166
+ test('should not execute the listener after removal', async () => {
167
167
  const {
168
168
  ee
169
169
  } = await import('./contextual-ee');
@@ -176,7 +176,7 @@ describe('event-emitter emit', () => {
176
176
  ee.emit(eventType, eventArgs);
177
177
  expect(mockListener).toHaveBeenCalledTimes(1);
178
178
  });
179
- it('should return early if global event-emitter is aborted', async () => {
179
+ test('should return early if global event-emitter is aborted', async () => {
180
180
  const {
181
181
  ee
182
182
  } = await import('./contextual-ee');
@@ -191,7 +191,7 @@ describe('event-emitter emit', () => {
191
191
  ee.emit(eventType, eventArgs);
192
192
  expect(mockListener).toHaveBeenCalledTimes(0);
193
193
  });
194
- it('should still emit if global event-emitter is aborted but force flag is true', async () => {
194
+ test('should still emit if global event-emitter is aborted but force flag is true', async () => {
195
195
  const {
196
196
  ee
197
197
  } = await import('./contextual-ee');
@@ -207,7 +207,7 @@ describe('event-emitter emit', () => {
207
207
  scopeEE.emit(eventType, eventArgs, {}, true);
208
208
  expect(mockScopeListener).toHaveBeenCalledTimes(1);
209
209
  });
210
- it('should bubble the event if bubble flag is true', async () => {
210
+ test('should bubble the event if bubble flag is true', async () => {
211
211
  const {
212
212
  ee
213
213
  } = await import('./contextual-ee');
@@ -222,7 +222,7 @@ describe('event-emitter emit', () => {
222
222
  expect(mockScopeListener).toHaveBeenCalledTimes(1);
223
223
  expect(mockListener).toHaveBeenCalledTimes(1);
224
224
  });
225
- it('should not bubble the event if bubble flag is false', async () => {
225
+ test('should not bubble the event if bubble flag is false', async () => {
226
226
  const {
227
227
  ee
228
228
  } = await import('./contextual-ee');
@@ -237,7 +237,7 @@ describe('event-emitter emit', () => {
237
237
  expect(mockScopeListener).toHaveBeenCalledTimes(1);
238
238
  expect(mockListener).not.toHaveBeenCalled();
239
239
  });
240
- it('should buffer the event on the scoped event-emitter', async () => {
240
+ test('should buffer the event on the scoped event-emitter', async () => {
241
241
  const {
242
242
  ee
243
243
  } = await import('./contextual-ee');
@@ -1,7 +1,7 @@
1
1
  import { setConfiguration } from '../config/state/init';
2
2
  import { HarvestScheduler } from './harvest-scheduler';
3
3
  describe('runHarvest', () => {
4
- it('should re-schedule harvest even if there is no accumulated data', () => {
4
+ test('should re-schedule harvest even if there is no accumulated data', () => {
5
5
  setConfiguration('asdf', {});
6
6
  const scheduler = new HarvestScheduler('events', {
7
7
  getPayload: jest.fn()
@@ -17,7 +17,7 @@ describe('runHarvest', () => {
17
17
  expect(scheduler.opts.getPayload()).toBeFalsy();
18
18
  expect(scheduler.scheduleHarvest).toHaveBeenCalledTimes(1);
19
19
  });
20
- it('should also re-schedule harvest if there is accumulated data', () => {
20
+ test('should also re-schedule harvest if there is accumulated data', () => {
21
21
  setConfiguration('asdf', {});
22
22
  const scheduler = new HarvestScheduler('events', {
23
23
  getPayload: jest.fn().mockImplementation(() => 'payload')
@@ -8,6 +8,7 @@ import { SharedContext } from '../context/shared-context';
8
8
  import { Harvest, getSubmitMethod } from './harvest';
9
9
  import { subscribeToEOL } from '../unload/eol';
10
10
  import { getConfigurationValue } from '../config/config';
11
+ import { SESSION_EVENTS } from '../session/session-entity';
11
12
 
12
13
  /**
13
14
  * Periodically invokes harvest calls and handles retries
@@ -37,9 +38,18 @@ export class HarvestScheduler extends SharedContext {
37
38
  // unload if EOL mechanism fires
38
39
  subscribeToEOL(this.unload.bind(this), getConfigurationValue(this.sharedContext.agentIdentifier, 'allow_bfcache')); // TO DO: remove feature flag after rls stable
39
40
 
40
- // unload if session resets
41
- this.sharedContext?.ee.on('session-reset', this.unload.bind(this));
41
+ /* Flush all buffered data if session resets and give up retries. This should be synchronous to ensure that the correct `session` value is sent.
42
+ Since session-reset generates a new session ID and the ID is grabbed at send-time, any delays or retries would cause the payload to be sent under
43
+ the wrong session ID. */
44
+ this.sharedContext?.ee.on(SESSION_EVENTS.RESET, () => this.runHarvest({
45
+ forceNoRetry: true
46
+ }));
42
47
  }
48
+
49
+ /**
50
+ * This function is only meant for the last outgoing harvest cycle of a page. It trickles down to using sendBeacon, which should not be used
51
+ * to send payloads while the page is still active, due to limitations on how much data can be buffered in the API at any one time.
52
+ */
43
53
  unload() {
44
54
  if (this.aborted) return;
45
55
  // If opts.onUnload is defined, these are special actions to execute before attempting to send the final payload.
@@ -111,7 +121,7 @@ export class HarvestScheduler extends SharedContext {
111
121
  payload,
112
122
  opts,
113
123
  submitMethod,
114
- cbFinished: onHarvestFinished,
124
+ cbFinished: cbRanAfterSend,
115
125
  customUrl: this.opts.customUrl,
116
126
  raw: this.opts.raw
117
127
  });
@@ -120,8 +130,14 @@ export class HarvestScheduler extends SharedContext {
120
130
  this.scheduleHarvest();
121
131
  }
122
132
  return;
123
- function onHarvestFinished(result) {
124
- if (result.blocked) scheduler.onHarvestBlocked(opts, result);else scheduler.onHarvestFinished(opts, result);
133
+
134
+ /**
135
+ * This is executed immediately after harvest sends the data via XHR, or if there's nothing to send. Note that this excludes on unloading / sendBeacon.
136
+ * @param {Object} result
137
+ */
138
+ function cbRanAfterSend(result) {
139
+ if (opts?.forceNoRetry) result.retry = false; // discard unsent data rather than re-queuing for next harvest attempt
140
+ scheduler.onHarvestFinished(opts, result);
125
141
  }
126
142
  }
127
143
  onHarvestFinished(opts, result) {
@@ -0,0 +1,222 @@
1
+ import { submitData } from '../util/submit-data';
2
+ import { Harvest } from './harvest';
3
+ jest.mock('../context/shared-context', () => ({
4
+ __esModule: true,
5
+ SharedContext: function () {
6
+ this.sharedContext = {
7
+ agentIdentifier: 'abcd'
8
+ };
9
+ }
10
+ }));
11
+ jest.mock('../config/config', () => ({
12
+ __esModule: true,
13
+ getConfigurationValue: jest.fn(),
14
+ getInfo: jest.fn().mockReturnValue({
15
+ errorBeacon: 'example.com',
16
+ licenseKey: 'abcd'
17
+ }),
18
+ getRuntime: jest.fn().mockReturnValue({
19
+ bytesSent: {},
20
+ queryBytesSent: {}
21
+ })
22
+ }));
23
+ jest.mock('../util/submit-data', () => ({
24
+ __esModule: true,
25
+ submitData: {
26
+ xhr: jest.fn(() => ({
27
+ addEventListener: jest.fn()
28
+ })),
29
+ beacon: jest.fn(),
30
+ img: jest.fn()
31
+ }
32
+ }));
33
+ describe('sendX', () => {
34
+ test.each([null, undefined, false])('should not send request when body is empty and sendEmptyBody is %s', sendEmptyBody => {
35
+ const sendCallback = jest.fn();
36
+ const harvester = new Harvest();
37
+ harvester.on('jserrors', () => ({
38
+ body: {},
39
+ qs: {}
40
+ }));
41
+ harvester.sendX({
42
+ endpoint: 'jserrors',
43
+ cbFinished: sendCallback
44
+ });
45
+ expect(sendCallback).toHaveBeenCalledWith({
46
+ sent: false
47
+ });
48
+ expect(submitData.xhr).not.toHaveBeenCalled();
49
+ expect(submitData.img).not.toHaveBeenCalled();
50
+ expect(submitData.beacon).not.toHaveBeenCalled();
51
+ });
52
+ test('should send request when body is empty and sendEmptyBody is true', () => {
53
+ const harvester = new Harvest();
54
+ harvester.on('jserrors', () => ({
55
+ body: {},
56
+ qs: {}
57
+ }));
58
+ harvester.sendX({
59
+ endpoint: 'jserrors',
60
+ opts: {
61
+ sendEmptyBody: true
62
+ },
63
+ cbFinished: jest.fn()
64
+ });
65
+ expect(submitData.xhr).toHaveBeenCalledWith(expect.objectContaining({
66
+ url: expect.stringContaining('https://example.com/jserrors/1/abcd?'),
67
+ body: undefined
68
+ }));
69
+ expect(submitData.img).not.toHaveBeenCalled();
70
+ expect(submitData.beacon).not.toHaveBeenCalled();
71
+ });
72
+ test.each([null, undefined, []])('should remove %s values from the body and query string when sending', emptyValue => {
73
+ const harvester = new Harvest();
74
+ harvester.on('jserrors', () => ({
75
+ body: {
76
+ bar: 'foo',
77
+ empty: emptyValue
78
+ },
79
+ qs: {
80
+ foo: 'bar',
81
+ empty: emptyValue
82
+ }
83
+ }));
84
+ harvester.sendX({
85
+ endpoint: 'jserrors',
86
+ cbFinished: jest.fn()
87
+ });
88
+ expect(submitData.xhr).toHaveBeenCalledWith(expect.objectContaining({
89
+ url: expect.stringContaining('&foo=bar'),
90
+ body: JSON.stringify({
91
+ bar: 'foo'
92
+ })
93
+ }));
94
+ expect(submitData.xhr).toHaveBeenCalledWith(expect.objectContaining({
95
+ url: expect.not.stringContaining('&empty'),
96
+ body: expect.not.stringContaining('empty')
97
+ }));
98
+ expect(submitData.img).not.toHaveBeenCalled();
99
+ expect(submitData.beacon).not.toHaveBeenCalled();
100
+ });
101
+ test.each([1, false, true])('should not remove value %s (when it doesn\'t have a length) from the body and query string when sending', nonStringValue => {
102
+ const harvester = new Harvest();
103
+ harvester.on('jserrors', () => ({
104
+ body: {
105
+ bar: 'foo',
106
+ nonString: nonStringValue
107
+ },
108
+ qs: {
109
+ foo: 'bar',
110
+ nonString: nonStringValue
111
+ }
112
+ }));
113
+ harvester.sendX({
114
+ endpoint: 'jserrors',
115
+ cbFinished: jest.fn()
116
+ });
117
+ expect(submitData.xhr).toHaveBeenCalledWith(expect.objectContaining({
118
+ url: expect.stringContaining("&nonString=".concat(nonStringValue)),
119
+ body: JSON.stringify({
120
+ bar: 'foo',
121
+ nonString: nonStringValue
122
+ })
123
+ }));
124
+ expect(submitData.img).not.toHaveBeenCalled();
125
+ expect(submitData.beacon).not.toHaveBeenCalled();
126
+ });
127
+ });
128
+ describe('send', () => {
129
+ test.each([null, undefined, false])('should not send request when body is empty and sendEmptyBody is %s', sendEmptyBody => {
130
+ const sendCallback = jest.fn();
131
+ const harvester = new Harvest();
132
+ harvester.send({
133
+ endpoint: 'rum',
134
+ payload: {
135
+ qs: {},
136
+ body: {}
137
+ },
138
+ opts: {
139
+ sendEmptyBody
140
+ },
141
+ cbFinished: sendCallback
142
+ });
143
+ expect(sendCallback).toHaveBeenCalledWith({
144
+ sent: false
145
+ });
146
+ expect(submitData.xhr).not.toHaveBeenCalled();
147
+ expect(submitData.img).not.toHaveBeenCalled();
148
+ expect(submitData.beacon).not.toHaveBeenCalled();
149
+ });
150
+ test('should send request when body is empty and sendEmptyBody is true', () => {
151
+ const harvester = new Harvest();
152
+ harvester.send({
153
+ endpoint: 'rum',
154
+ payload: {
155
+ qs: {},
156
+ body: {}
157
+ },
158
+ opts: {
159
+ sendEmptyBody: true
160
+ }
161
+ });
162
+ expect(submitData.xhr).toHaveBeenCalledWith(expect.objectContaining({
163
+ url: expect.stringContaining('https://example.com/1/abcd?'),
164
+ body: undefined
165
+ }));
166
+ expect(submitData.img).not.toHaveBeenCalled();
167
+ expect(submitData.beacon).not.toHaveBeenCalled();
168
+ });
169
+ test.each([null, undefined, []])('should remove %s values from the body and query string when sending', emptyValue => {
170
+ const harvester = new Harvest();
171
+ harvester.send({
172
+ endpoint: 'rum',
173
+ payload: {
174
+ qs: {
175
+ foo: 'bar',
176
+ empty: emptyValue
177
+ },
178
+ body: {
179
+ bar: 'foo',
180
+ empty: emptyValue
181
+ }
182
+ }
183
+ });
184
+ expect(submitData.xhr).toHaveBeenCalledWith(expect.objectContaining({
185
+ url: expect.stringContaining('&foo=bar'),
186
+ body: JSON.stringify({
187
+ bar: 'foo'
188
+ })
189
+ }));
190
+ expect(submitData.xhr).toHaveBeenCalledWith(expect.objectContaining({
191
+ url: expect.not.stringContaining('&empty'),
192
+ body: expect.not.stringContaining('empty')
193
+ }));
194
+ expect(submitData.img).not.toHaveBeenCalled();
195
+ expect(submitData.beacon).not.toHaveBeenCalled();
196
+ });
197
+ test.each([1, false, true])('should not remove value %s (when it doesn\'t have a length) from the body and query string when sending', nonStringValue => {
198
+ const harvester = new Harvest();
199
+ harvester.send({
200
+ endpoint: 'rum',
201
+ payload: {
202
+ qs: {
203
+ foo: 'bar',
204
+ nonString: nonStringValue
205
+ },
206
+ body: {
207
+ bar: 'foo',
208
+ nonString: nonStringValue
209
+ }
210
+ }
211
+ });
212
+ expect(submitData.xhr).toHaveBeenCalledWith(expect.objectContaining({
213
+ url: expect.stringContaining("&nonString=".concat(nonStringValue)),
214
+ body: JSON.stringify({
215
+ bar: 'foo',
216
+ nonString: nonStringValue
217
+ })
218
+ }));
219
+ expect(submitData.img).not.toHaveBeenCalled();
220
+ expect(submitData.beacon).not.toHaveBeenCalled();
221
+ });
222
+ });