@newrelic/browser-agent 1.232.0 → 1.233.0

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