@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
@@ -32,6 +32,7 @@ export class Aggregate extends AggregateBase {
32
32
  super(agentIdentifier, aggregator, FEATURE_NAME)
33
33
 
34
34
  this.stackReported = {}
35
+ this.observedAt = {}
35
36
  this.pageviewReported = {}
36
37
  this.errorCache = {}
37
38
  this.currentBody
@@ -171,6 +172,7 @@ export class Aggregate extends AggregateBase {
171
172
  if (!this.stackReported[bucketHash]) {
172
173
  this.stackReported[bucketHash] = true
173
174
  params.stack_trace = truncateSize(stackInfo.stackString)
175
+ this.observedAt[bucketHash] = agentRuntime.offset + time
174
176
  } else {
175
177
  params.browser_stack_hash = stringHashCode(stackInfo.stackString)
176
178
  }
@@ -186,6 +188,9 @@ export class Aggregate extends AggregateBase {
186
188
  this.pageviewReported[bucketHash] = true
187
189
  }
188
190
 
191
+ if (agentRuntime?.session?.state?.sessionReplay) params.hasReplay = true
192
+ params.firstOccurrenceTimestamp = this.observedAt[bucketHash]
193
+
189
194
  var type = internal ? 'ierr' : 'err'
190
195
  var newMetrics = { time: time }
191
196
 
@@ -38,7 +38,7 @@ export class Instrument extends InstrumentBase {
38
38
  return true
39
39
  })
40
40
  this.thrown = true
41
- notice(err, undefined, thisInstrument.ee)
41
+ handle('err', [err, now()], undefined, FEATURE_NAMES.jserrors, thisInstrument.ee)
42
42
  }
43
43
  })
44
44
  thisInstrument.ee.on('fn-end', function () {
@@ -89,7 +89,7 @@ export class Instrument extends InstrumentBase {
89
89
 
90
90
  try {
91
91
  if (this.skipNext) this.skipNext -= 1
92
- else notice(errorObj || new UncaughtException(message, filename, lineno), true, this.ee)
92
+ else handle('err', [errorObj || new UncaughtException(message, filename, lineno), now()], undefined, FEATURE_NAMES.jserrors, this.ee)
93
93
  } catch (e) {
94
94
  try {
95
95
  handle('ierr', [e, now(), true], undefined, FEATURE_NAMES.jserrors, this.ee)
@@ -113,19 +113,6 @@ function UncaughtException (message, filename, lineno) {
113
113
  this.line = lineno
114
114
  }
115
115
 
116
- /**
117
- * Adds a timestamp and emits the 'err' event, which the error aggregator listens for
118
- * @param {Error} err
119
- * @param {boolean} doNotStamp
120
- * @param {ContextualEE} ee
121
- */
122
- function notice (err, doNotStamp, ee) {
123
- // by default add timestamp, unless specifically told not to
124
- // this is to preserve existing behavior
125
- var time = (!doNotStamp) ? now() : null
126
- handle('err', [err, time], undefined, FEATURE_NAMES.jserrors, ee)
127
- }
128
-
129
116
  /**
130
117
  * Attempts to cast an unhandledPromiseRejection reason (reject(...)) to an Error object
131
118
  * @param {*} reason - The reason property from an unhandled promise rejection
@@ -0,0 +1,7 @@
1
+ export const endpointMap = {
2
+ rum: '1',
3
+ events: 'Events',
4
+ ins: 'Ins',
5
+ jserrors: 'Jserrors',
6
+ resources: 'Resources'
7
+ }
@@ -13,6 +13,7 @@ import { windowAddEventListener } from '../../../common/event-listener/event-lis
13
13
  import { isBrowserScope } from '../../../common/util/global-scope'
14
14
  import { AggregateBase } from '../../utils/aggregate-base'
15
15
  import { stringify } from '../../../common/util/stringify'
16
+ import { endpointMap } from './endpoint-map'
16
17
 
17
18
  export class Aggregate extends AggregateBase {
18
19
  static featureName = FEATURE_NAME
@@ -125,7 +126,7 @@ export class Aggregate extends AggregateBase {
125
126
  // Capture per-agent bytes sent for each endpoint (see harvest) and RUM call (see page_view_event aggregator).
126
127
  Object.keys(agentRuntime.bytesSent).forEach(endpoint => {
127
128
  this.storeSupportabilityMetrics(
128
- `PageSession/Endpoint/${endpoint.charAt(0).toUpperCase() + endpoint.slice(1)}/BytesSent`,
129
+ `PageSession/Endpoint/${endpointMap[endpoint]}/BytesSent`,
129
130
  agentRuntime.bytesSent[endpoint]
130
131
  )
131
132
  })
@@ -133,7 +134,7 @@ export class Aggregate extends AggregateBase {
133
134
  // Capture per-agent query bytes sent for each endpoint (see harvest) and RUM call (see page_view_event aggregator).
134
135
  Object.keys(agentRuntime.bytesSent).forEach(endpoint => {
135
136
  this.storeSupportabilityMetrics(
136
- `PageSession/Endpoint/${endpoint.charAt(0).toUpperCase() + endpoint.slice(1)}/QueryBytesSent`,
137
+ `PageSession/Endpoint/${endpointMap[endpoint]}/QueryBytesSent`,
137
138
  agentRuntime.queryBytesSent[endpoint]
138
139
  )
139
140
  })
@@ -1,4 +1,3 @@
1
-
2
1
  /**
3
2
  * This file is used as a replacement for the polyfill-detection.es5.js
4
3
  * file when running the polyfills webpack build.
@@ -1,5 +1,4 @@
1
1
  import { InstrumentBase } from '../../utils/instrument-base'
2
- import { insertSupportMetrics } from './workers-helper'
3
2
  import { FEATURE_NAME, SUPPORTABILITY_METRIC_CHANNEL } from '../constants'
4
3
  import { handle } from '../../../common/event-emitter/handle'
5
4
  import { FEATURE_NAMES } from '../../../loaders/features/features'
@@ -8,7 +7,6 @@ export class Instrument extends InstrumentBase {
8
7
  static featureName = FEATURE_NAME
9
8
  constructor (agentIdentifier, aggregator, auto = true) {
10
9
  super(agentIdentifier, aggregator, FEATURE_NAME, auto)
11
- insertSupportMetrics(tag => handle(SUPPORTABILITY_METRIC_CHANNEL, [tag], undefined, FEATURE_NAMES.metrics, this.ee))
12
10
  this.importAggregator()
13
11
  }
14
12
  }
@@ -2,22 +2,19 @@ import { handle } from '../../../common/event-emitter/handle'
2
2
  import { FEATURE_NAMES } from '../../../loaders/features/features'
3
3
  import { isiOS } from '../../../common/browser-version/ios-version'
4
4
  import { onTTFB } from 'web-vitals'
5
- import { mapOwn } from '../../../common/util/map-own'
6
- import { param, fromArray } from '../../../common/url/encode'
7
5
  import { addPT, addPN } from '../../../common/timing/nav-timing'
8
6
  import { stringify } from '../../../common/util/stringify'
9
7
  import { paintMetrics } from '../../../common/metrics/paint-metrics'
10
- import { submitData } from '../../../common/util/submit-data'
11
8
  import { getConfigurationValue, getInfo, getRuntime } from '../../../common/config/config'
12
- import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler'
9
+ import { Harvest } from '../../../common/harvest/harvest'
13
10
  import * as CONSTANTS from '../constants'
14
11
  import { getActivatedFeaturesFlags } from './initialized-features'
15
12
  import { globalScope, isBrowserScope } from '../../../common/util/global-scope'
16
13
  import { drain } from '../../../common/drain/drain'
14
+ import { activateFeatures } from '../../../common/util/feature-flags'
15
+ import { warn } from '../../../common/util/console'
17
16
  import { AggregateBase } from '../../utils/aggregate-base'
18
17
 
19
- const jsonp = 'NREUM.setToken'
20
-
21
18
  export class Aggregate extends AggregateBase {
22
19
  static featureName = CONSTANTS.FEATURE_NAME
23
20
  constructor (agentIdentifier, aggregator) {
@@ -55,10 +52,12 @@ export class Aggregate extends AggregateBase {
55
52
 
56
53
  sendRum () {
57
54
  const info = getInfo(this.agentIdentifier)
55
+ const agentRuntime = getRuntime(this.agentIdentifier)
56
+ const harvester = new Harvest(this)
57
+
58
58
  if (!info.beacon) return
59
59
  if (info.queueTime) this.aggregator.store('measures', 'qt', { value: info.queueTime })
60
60
  if (info.applicationTime) this.aggregator.store('measures', 'ap', { value: info.applicationTime })
61
- const agentRuntime = getRuntime(this.agentIdentifier)
62
61
 
63
62
  // These 3 values should've been recorded after load and before this func runs. They are part of the minimum required for PageView events to be created.
64
63
  // Following PR #428, which demands that all agents send RUM call, these need to be sent even outside of the main window context where PerformanceTiming
@@ -67,27 +66,27 @@ export class Aggregate extends AggregateBase {
67
66
  this.aggregator.store('measures', 'fe', { value: isBrowserScope ? agentRuntime[CONSTANTS.FBTWL] : 0 })
68
67
  this.aggregator.store('measures', 'dc', { value: isBrowserScope ? agentRuntime[CONSTANTS.FBTDC] : 0 })
69
68
 
70
- var measuresMetrics = this.aggregator.get('measures')
71
-
72
- var measuresQueryString = mapOwn(measuresMetrics, function (metricName, measure) {
73
- return '&' + metricName + '=' + measure.params.value
74
- }).join('')
75
-
76
- // currently we only have one version of our protocol
77
- // in the future we may add more
78
- var protocol = '1'
79
-
80
- var scheduler = new HarvestScheduler('page_view_event', {}, this)
81
-
82
- var chunksForQueryString = [scheduler.harvest.baseQueryString()]
83
-
84
- chunksForQueryString.push(measuresQueryString)
69
+ const queryParameters = {
70
+ tt: info.ttGuid,
71
+ us: info.user,
72
+ ac: info.account,
73
+ pr: info.product,
74
+ af: getActivatedFeaturesFlags(this.agentIdentifier).join(','),
75
+ ...(
76
+ Object.entries(this.aggregator.get('measures') || {}).reduce((aggregator, [metricName, measure]) => {
77
+ aggregator[metricName] = measure.params?.value
78
+ return aggregator
79
+ }, {})
80
+ ),
81
+ xx: info.extra,
82
+ ua: info.userAttributes,
83
+ at: info.atts
84
+ }
85
85
 
86
- chunksForQueryString.push(param('tt', info.ttGuid))
87
- chunksForQueryString.push(param('us', info.user))
88
- chunksForQueryString.push(param('ac', info.account))
89
- chunksForQueryString.push(param('pr', info.product))
90
- chunksForQueryString.push(param('af', getActivatedFeaturesFlags(this.agentIdentifier).join(',')))
86
+ let body
87
+ if (typeof info.jsAttributes === 'object' && Object.keys(info.jsAttributes).length > 0) {
88
+ body = { ja: info.jsAttributes }
89
+ }
91
90
 
92
91
  if (globalScope.performance) {
93
92
  if (typeof PerformanceNavigationTiming !== 'undefined') { // Navigation Timing level 2 API that replaced PerformanceTiming & PerformanceNavigation
@@ -96,13 +95,13 @@ export class Aggregate extends AggregateBase {
96
95
  timing: addPT(agentRuntime.offset, navTimingEntry, {}),
97
96
  navigation: addPN(navTimingEntry, {})
98
97
  })
99
- chunksForQueryString.push(param('perf', stringify(perf)))
98
+ queryParameters.perf = stringify(perf)
100
99
  } else if (typeof PerformanceTiming !== 'undefined') { // Safari pre-15 did not support level 2 timing
101
100
  const perf = ({
102
101
  timing: addPT(agentRuntime.offset, globalScope.performance.timing, {}, true),
103
102
  navigation: addPN(globalScope.performance.navigation, {})
104
103
  })
105
- chunksForQueryString.push(param('perf', stringify(perf)))
104
+ queryParameters.perf = stringify(perf)
106
105
  }
107
106
  }
108
107
 
@@ -112,35 +111,33 @@ export class Aggregate extends AggregateBase {
112
111
  if (!entry.startTime || entry.startTime <= 0) return
113
112
 
114
113
  if (entry.name === 'first-paint') {
115
- chunksForQueryString.push(param('fp', String(Math.floor(entry.startTime))))
114
+ queryParameters.fp = String(Math.floor(entry.startTime))
116
115
  } else if (entry.name === 'first-contentful-paint') {
117
- chunksForQueryString.push(param('fcp', String(Math.floor(entry.startTime))))
116
+ queryParameters.fcp = String(Math.floor(entry.startTime))
118
117
  }
119
118
  paintMetrics[entry.name] = Math.floor(entry.startTime) // this is consumed by Spa module
120
119
  })
121
120
  } catch (e) {}
122
121
 
123
- chunksForQueryString.push(param('xx', info.extra))
124
- chunksForQueryString.push(param('ua', info.userAttributes))
125
- chunksForQueryString.push(param('at', info.atts))
126
-
127
- var customJsAttributes = stringify(info.jsAttributes)
128
- chunksForQueryString.push(param('ja', customJsAttributes === '{}' ? null : customJsAttributes))
129
-
130
- var queryString = fromArray(chunksForQueryString, agentRuntime.maxBytes)
131
-
132
- // Capture bytes sent to RUM call endpoint (currently `1`) as a supportability metric. See metrics aggregator (on unload).
133
- agentRuntime.bytesSent[protocol] = 0 // Set to zero for now until RUM is moved to POST
134
-
135
- // Capture query bytes sent to RUM call endpoint (currently `1`) as a supportability metric. See metrics aggregator (on unload).
136
- agentRuntime.queryBytesSent[protocol] = (agentRuntime.queryBytesSent[protocol] || 0) + queryString?.length || 0
122
+ harvester.send({
123
+ endpoint: 'rum',
124
+ payload: { qs: queryParameters, body },
125
+ opts: { needResponse: true, sendEmptyBody: true },
126
+ cbFinished: ({ status, responseText }) => {
127
+ if (status >= 400) {
128
+ // Adding retry logic for the rum call will be a separate change
129
+ this.ee.abort()
130
+ return
131
+ }
137
132
 
138
- const isValidJsonp = submitData.jsonp({
139
- url: this.getScheme() + '://' + info.beacon + '/' + protocol + '/' + info.licenseKey + '?' + queryString,
140
- jsonp
133
+ try {
134
+ activateFeatures(JSON.parse(responseText), this.agentIdentifier)
135
+ drain(this.agentIdentifier, this.featureName)
136
+ } catch (err) {
137
+ this.ee.abort()
138
+ warn('RUM call failed. Agent shutting down.')
139
+ }
140
+ }
141
141
  })
142
- // Usually `drain` is invoked automatically after processing feature flags contained in the JSONP callback from
143
- // ingest (see `activateFeatures`), so when JSONP cannot execute (as with module workers), we drain manually.
144
- if (!isValidJsonp) drain(this.agentIdentifier, CONSTANTS.FEATURE_NAME)
145
142
  }
146
143
  }
@@ -1,4 +1,3 @@
1
-
2
1
  import { handle } from '../../../common/event-emitter/handle'
3
2
  import { isiOS } from '../../../common/browser-version/ios-version'
4
3
  import { InstrumentBase } from '../../utils/instrument-base'
@@ -0,0 +1,368 @@
1
+ import { Aggregate as SessionReplayAgg, AVG_COMPRESSION, MAX_PAYLOAD_SIZE, IDEAL_PAYLOAD_SIZE } from '.'
2
+ import { Aggregator } from '../../../common/aggregate/aggregator'
3
+ import { SESSION_EVENTS, SessionEntity, MODE } from '../../../common/session/session-entity'
4
+ import { setConfiguration } from '../../../common/config/config'
5
+ import { configure } from '../../../loaders/configure/configure'
6
+
7
+ class LocalMemory {
8
+ constructor (initialState = {}) {
9
+ this.state = initialState
10
+ }
11
+
12
+ get (key) {
13
+ try {
14
+ return this.state[key]
15
+ } catch (err) {
16
+ return ''
17
+ }
18
+ }
19
+
20
+ set (key, value) {
21
+ try {
22
+ if (value === undefined || value === null) return this.remove(key)
23
+ this.state[key] = value
24
+ } catch (err) {
25
+ return
26
+ }
27
+ }
28
+
29
+ remove (key) {
30
+ try {
31
+ delete this.state[key]
32
+ } catch (err) {
33
+ return
34
+ }
35
+ }
36
+ }
37
+
38
+ let sr, session
39
+ const agentIdentifier = 'abcd'
40
+ const info = { licenseKey: 1234, applicationID: 9876 }
41
+ const init = { session_replay: { enabled: true, sampleRate: 1, errorSampleRate: 0 } }
42
+
43
+ describe('Session Replay', () => {
44
+ beforeEach(async () => {
45
+ primeSessionAndReplay()
46
+ })
47
+ afterEach(async () => {
48
+ sr.abort()
49
+ jest.clearAllMocks()
50
+ })
51
+
52
+ describe('Session Replay Session Behavior', () => {
53
+ test('When Session Ends', async () => {
54
+ const xhrMockClass = () => ({
55
+ open: jest.fn(),
56
+ send: jest.fn(),
57
+ setRequestHeader: jest.fn(),
58
+ addEventListener: jest.fn()
59
+ })
60
+ global.XMLHttpRequest = jest.fn().mockImplementation(xhrMockClass)
61
+
62
+ setConfiguration(agentIdentifier, { ...init })
63
+ sr.ee.emit('rumresp-sr', [true])
64
+ await wait(1)
65
+ expect(sr.initialized).toBeTruthy()
66
+ expect(sr.recording).toBeTruthy()
67
+ sr.ee.emit(SESSION_EVENTS.RESET)
68
+ expect(global.XMLHttpRequest).toHaveBeenCalled()
69
+ expect(sr.recording).toBeFalsy()
70
+ expect(sr.blocked).toBeTruthy()
71
+ })
72
+
73
+ test('When Session Is Paused/Resumed', async () => {
74
+ setConfiguration(agentIdentifier, { ...init })
75
+ sr.ee.emit('rumresp-sr', [true])
76
+ await wait(1)
77
+ expect(sr.initialized).toBeTruthy()
78
+ expect(sr.recording).toBeTruthy()
79
+ sr.ee.emit(SESSION_EVENTS.PAUSE)
80
+ expect(sr.recording).toBeFalsy()
81
+ sr.ee.emit(SESSION_EVENTS.RESUME)
82
+ expect(sr.recording).toBeTruthy()
83
+ })
84
+
85
+ test('Session SR mode matches SR mode -- FULL', async () => {
86
+ setConfiguration(agentIdentifier, { ...init })
87
+ sr.ee.emit('rumresp-sr', [true])
88
+ await wait(1)
89
+ expect(session.state.sessionReplay).toEqual(sr.mode)
90
+ })
91
+
92
+ test('Session SR mode matches SR mode -- ERROR', async () => {
93
+ setConfiguration(agentIdentifier, { session_replay: { sampleRate: 0, errorSampleRate: 1 } })
94
+ sr.ee.emit('rumresp-sr', [true])
95
+ await wait(1)
96
+ expect(session.state.sessionReplay).toEqual(sr.mode)
97
+ return
98
+ })
99
+
100
+ test('Session SR mode matches SR mode -- OFF', async () => {
101
+ setConfiguration(agentIdentifier, { session_replay: { sampleRate: 0, errorSampleRate: 0 } })
102
+ sr.ee.emit('rumresp-sr', [true])
103
+ await wait(1)
104
+ expect(session.state.sessionReplay).toEqual(sr.mode)
105
+ return
106
+ })
107
+ })
108
+
109
+ describe('Session Replay Initialization Behavior', () => {
110
+ test('Waits for SR', async () => {
111
+ setConfiguration(agentIdentifier, { ...init })
112
+ // do not emit sr flag
113
+ await wait(1000)
114
+ expect(sr.initialized).toEqual(false)
115
+ expect(sr.recording).toEqual(false)
116
+
117
+ // emit a false flag
118
+ sr.ee.emit('rumresp-sr', [false])
119
+ await wait(1)
120
+ expect(sr.initialized).toEqual(true)
121
+ expect(sr.recording).toEqual(false)
122
+ return
123
+ })
124
+
125
+ test('Does not run if cookies_enabled is false', async () => {
126
+ setConfiguration(agentIdentifier, { ...init, privacy: { cookies_enabled: false } })
127
+ sr = new SessionReplayAgg(agentIdentifier, new Aggregator({}))
128
+ sr.ee.emit('rumresp-sr', [true])
129
+ await wait(1)
130
+ expect(sr.initialized).toEqual(false)
131
+ expect(sr.recording).toEqual(false)
132
+ return
133
+ })
134
+
135
+ test('Does not run if session_trace is disabled', async () => {
136
+ setConfiguration(agentIdentifier, { ...init, session_trace: { enabled: false } })
137
+ sr = new SessionReplayAgg(agentIdentifier, new Aggregator({}))
138
+ sr.ee.emit('rumresp-sr', [true])
139
+ await wait(1)
140
+ expect(sr.initialized).toEqual(false)
141
+ expect(sr.recording).toEqual(false)
142
+ return
143
+ })
144
+ })
145
+
146
+ describe('Session Replay Sample -> Mode Behaviors', () => {
147
+ test('New Session -- Full 1 Error 1 === FULL', async () => {
148
+ setConfiguration(agentIdentifier, { session_replay: { errorSampleRate: 1, sampleRate: 1 } })
149
+ sr.ee.emit('rumresp-sr', [true])
150
+ await wait(1)
151
+ expect(sr.mode).toEqual(MODE.FULL)
152
+ return
153
+ })
154
+
155
+ test('New Session -- Full 1 Error 0 === FULL', async () => {
156
+ setConfiguration(agentIdentifier, { session_replay: { errorSampleRate: 0, sampleRate: 1 } })
157
+ sr.ee.emit('rumresp-sr', [true])
158
+ await wait(1)
159
+ expect(sr.mode).toEqual(MODE.FULL)
160
+ return
161
+ })
162
+
163
+ test('New Session -- Full 0 Error 1 === ERROR', async () => {
164
+ setConfiguration(agentIdentifier, { session_replay: { errorSampleRate: 1, sampleRate: 0 } })
165
+ sr.ee.emit('rumresp-sr', [true])
166
+ await wait(1)
167
+ expect(sr.mode).toEqual(MODE.ERROR)
168
+ return
169
+ })
170
+
171
+ test('New Session -- Full 0 Error 0 === OFF', async () => {
172
+ setConfiguration(agentIdentifier, { session_replay: { errorSampleRate: 0, sampleRate: 0 } })
173
+ sr.ee.emit('rumresp-sr', [true])
174
+ await wait(1)
175
+ expect(sr.mode).toEqual(MODE.OFF)
176
+ return
177
+ })
178
+
179
+ test('Existing Session -- Should inherit mode from session entity and ignore samples', async () => {
180
+ const storage = new LocalMemory({ NRBA_SESSION: { value: 'abcdefghijklmnop', expiresAt: Date.now() + 10000, inactiveAt: Date.now() + 10000, updatedAt: Date.now(), sessionReplay: MODE.FULL, sessionTraceMode: MODE.FULL, custom: {} } })
181
+ session = new SessionEntity({ agentIdentifier, key: 'SESSION', storage })
182
+ expect(session.isNew).toBeFalsy()
183
+ primeSessionAndReplay(session)
184
+ // configure to get "error" sample ---> but should inherit FULL from session manager
185
+ setConfiguration(agentIdentifier, { session_replay: { errorSampleRate: 1, sampleRate: 0 } })
186
+ sr.ee.emit('rumresp-sr', [true])
187
+ await wait(1)
188
+ expect(sr.mode).toEqual(MODE.FULL)
189
+ return
190
+ })
191
+ })
192
+
193
+ describe('Session Replay Error Mode Behaviors', () => {
194
+ test('An error BEFORE rrweb import starts running in FULL from beginning', async () => {
195
+ setConfiguration(agentIdentifier, { session_replay: { errorSampleRate: 1, sampleRate: 0 } })
196
+ sr.ee.emit('errorAgg')
197
+ sr.ee.emit('rumresp-sr', [true])
198
+ await wait(1)
199
+ expect(sr.mode).toEqual(MODE.FULL)
200
+ expect(sr.scheduler.started).toEqual(true)
201
+ return
202
+ })
203
+
204
+ test('An error AFTER rrweb import changes mode and starts harvester', async () => {
205
+ setConfiguration(agentIdentifier, { session_replay: { errorSampleRate: 1, sampleRate: 0 } })
206
+ sr.ee.emit('rumresp-sr', [true])
207
+ await wait(1)
208
+ expect(sr.mode).toEqual(MODE.ERROR)
209
+ expect(sr.scheduler.started).toEqual(false)
210
+ sr.ee.emit('errorAgg')
211
+ expect(sr.mode).toEqual(MODE.FULL)
212
+ expect(sr.scheduler.started).toEqual(true)
213
+ return
214
+ })
215
+ })
216
+
217
+ describe('Session Replay Payload Validation', () => {
218
+ test('Payload', async () => {
219
+ setConfiguration(agentIdentifier, { ...init })
220
+ sr.ee.emit('rumresp-sr', [true])
221
+ await wait(1)
222
+ const harvestContents = sr.getHarvestContents()
223
+ // query attrs
224
+ expect(harvestContents.qs).toMatchObject({
225
+ protocol_version: '0',
226
+ content_encoding: 'gzip',
227
+ browser_monitoring_key: info.licenseKey
228
+ })
229
+
230
+ expect(harvestContents.body).toMatchObject({
231
+ type: 'SessionReplay',
232
+ appId: info.applicationID,
233
+ timestamp: expect.any(Number),
234
+ blob: expect.any(String),
235
+ attributes: {
236
+ session: session.state.value,
237
+ hasSnapshot: expect.any(Boolean),
238
+ hasError: expect.any(Boolean),
239
+ agentVersion: expect.any(String),
240
+ isFirstChunk: expect.any(Boolean),
241
+ 'nr.rrweb.version': expect.any(String)
242
+ }
243
+ })
244
+
245
+ expect(JSON.parse(harvestContents.body.blob).length).toBeGreaterThan(0)
246
+ })
247
+ })
248
+
249
+ describe('Session Replay Harvest Behaviors', () => {
250
+ test('Compressed payload is provided to harvester', async () => {
251
+ const { gunzipSync, strFromU8 } = await import('fflate')
252
+ setConfiguration(agentIdentifier, { ...init })
253
+ sr.ee.emit('rumresp-sr', [true])
254
+ await wait(1)
255
+ const [harvestContents] = sr.prepareHarvest()
256
+ expect(harvestContents.qs).toMatchObject({
257
+ protocol_version: '0',
258
+ content_encoding: 'gzip',
259
+ browser_monitoring_key: info.licenseKey
260
+ })
261
+ expect(harvestContents.body).toEqual(expect.any(Uint8Array))
262
+ expect(JSON.parse(strFromU8(gunzipSync(harvestContents.body)))).toMatchObject({
263
+ type: 'SessionReplay',
264
+ appId: info.applicationID,
265
+ timestamp: expect.any(Number),
266
+ blob: expect.any(String),
267
+ attributes: {
268
+ session: session.state.value,
269
+ hasSnapshot: expect.any(Boolean),
270
+ hasError: expect.any(Boolean),
271
+ agentVersion: expect.any(String),
272
+ isFirstChunk: expect.any(Boolean),
273
+ 'nr.rrweb.version': expect.any(String)
274
+ }
275
+ })
276
+ })
277
+
278
+ test('Uncompressed payload is provided to harvester', async () => {
279
+ jest.doMock('fflate', () => ({
280
+ __esModule: true,
281
+ gzipSync: jest.fn().mockImplementation(() => { throw new Error() })
282
+ }))
283
+
284
+ setConfiguration(agentIdentifier, { ...init })
285
+ sr.shouldCompress = false
286
+ sr.ee.emit('rumresp-sr', [true])
287
+ await wait(1)
288
+
289
+ const [harvestContents] = sr.prepareHarvest()
290
+ expect(harvestContents.qs).toMatchObject({
291
+ protocol_version: '0',
292
+ // content_encoding is omitted when the payload is not compressed
293
+ browser_monitoring_key: info.licenseKey
294
+ })
295
+ expect(harvestContents.qs.content_encoding).toBeUndefined()
296
+ expect(harvestContents.body).toMatchObject({
297
+ type: 'SessionReplay',
298
+ appId: info.applicationID,
299
+ timestamp: expect.any(Number),
300
+ blob: expect.any(String),
301
+ attributes: {
302
+ session: session.state.value,
303
+ hasSnapshot: expect.any(Boolean),
304
+ hasError: expect.any(Boolean),
305
+ agentVersion: expect.any(String),
306
+ isFirstChunk: expect.any(Boolean),
307
+ 'nr.rrweb.version': expect.any(String)
308
+ }
309
+ })
310
+ })
311
+
312
+ test('Clears the event buffer when staged for harvesting', async () => {
313
+ setConfiguration(agentIdentifier, { ...init })
314
+ sr.shouldCompress = false
315
+ sr.ee.emit('rumresp-sr', [true])
316
+ await wait(1)
317
+
318
+ sr.prepareHarvest()
319
+ expect(sr.events.length).toEqual(0)
320
+ })
321
+
322
+ test('Harvests early if exceeds limit', async () => {
323
+ let after = 0
324
+ const spy = jest.spyOn(sr.scheduler, 'runHarvest').mockImplementation(() => { after = Date.now() })
325
+ setConfiguration(agentIdentifier, { ...init })
326
+ sr.payloadBytesEstimation = IDEAL_PAYLOAD_SIZE / AVG_COMPRESSION
327
+ const before = Date.now()
328
+ sr.ee.emit('rumresp-sr', [true])
329
+ await wait(1)
330
+ expect(spy).toHaveBeenCalled()
331
+ expect(after - before).toBeLessThan(sr.harvestTimeSeconds * 1000)
332
+ })
333
+
334
+ test('Aborts if exceeds total limit', async () => {
335
+ const spy = jest.spyOn(sr.scheduler, 'runHarvest')
336
+ setConfiguration(agentIdentifier, { ...init })
337
+ sr.payloadBytesEstimation = (MAX_PAYLOAD_SIZE + 1) / AVG_COMPRESSION
338
+ const before = Date.now()
339
+ sr.ee.emit('rumresp-sr', [true])
340
+ await wait(1)
341
+ expect(spy).not.toHaveBeenCalled()
342
+ expect(sr.blocked).toEqual(true)
343
+ expect(sr.mode).toEqual(MODE.OFF)
344
+ })
345
+
346
+ test('Aborts if 429 response', async () => {
347
+ setConfiguration(agentIdentifier, { ...init })
348
+ sr.ee.emit('rumresp-sr', [true])
349
+ await wait(1)
350
+ expect(sr.mode).toEqual(MODE.FULL)
351
+ sr.onHarvestFinished({ status: 429 })
352
+ expect(sr.blocked).toEqual(true)
353
+ expect(sr.mode).toEqual(MODE.OFF)
354
+ })
355
+ })
356
+ })
357
+
358
+ function wait (ms = 0) {
359
+ return new Promise((resolve) => {
360
+ setTimeout(resolve, ms)
361
+ })
362
+ }
363
+
364
+ function primeSessionAndReplay (sess = new SessionEntity({ agentIdentifier, key: 'SESSION', storage: new LocalMemory() })) {
365
+ session = sess
366
+ configure(agentIdentifier, { info, runtime: { session } }, 'test', true)
367
+ sr = new SessionReplayAgg(agentIdentifier, new Aggregator({}))
368
+ }