@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
@@ -68,22 +68,47 @@ export class HarvestScheduler extends SharedContext {
68
68
  if (this.aborted) return
69
69
  var scheduler = this
70
70
 
71
- if (this.opts.getPayload) { // Ajax & PVT
72
- var submitMethod = getSubmitMethod(this.endpoint, opts)
71
+ let harvests = []
72
+ let submitMethod
73
+
74
+ if (this.opts.getPayload) { // Ajax & PVT & SR
75
+ submitMethod = getSubmitMethod(this.endpoint, opts)
73
76
  if (!submitMethod) return false
74
77
 
75
- var retry = submitMethod.method === submitData.xhr
78
+ const retry = submitMethod.method === submitData.xhr
76
79
  var payload = this.opts.getPayload({ retry: retry })
77
- if (payload) {
78
- payload = Object.prototype.toString.call(payload) === '[object Array]' ? payload : [payload]
79
- for (var i = 0; i < payload.length; i++) {
80
- this.harvest.send(this.endpoint, payload[i], opts, submitMethod, onHarvestFinished)
81
- }
82
- }
80
+
81
+ if (!payload) return
82
+
83
+ payload = Object.prototype.toString.call(payload) === '[object Array]' ? payload : [payload]
84
+ harvests.push(...payload)
85
+ }
86
+
87
+ /** sendX is used for features that do not supply a preformatted payload via "getPayload" */
88
+ let send = args => this.harvest.sendX(args)
89
+ if (harvests.length) {
90
+ /** _send is the underlying method for sending in the harvest, if sending raw we can bypass the other helpers completely which format the payloads */
91
+ if (this.opts.raw) send = args => this.harvest._send(args)
92
+ /** send is used to formated the payloads from "getPayload" and obfuscate before sending */
93
+ else send = args => this.harvest.send(args)
83
94
  } else {
84
- this.harvest.sendX(this.endpoint, opts, onHarvestFinished)
95
+ // force it to run at least once in sendX mode
96
+ harvests.push(undefined)
85
97
  }
86
98
 
99
+ harvests.forEach(payload => {
100
+ send({
101
+ endpoint: this.endpoint,
102
+ payload,
103
+ opts,
104
+ submitMethod,
105
+ cbFinished: onHarvestFinished,
106
+ includeBaseParams: this.opts.includeBaseParams,
107
+ customUrl: this.opts.customUrl,
108
+ gzip: this.opts.gzip
109
+ })
110
+ })
111
+
87
112
  if (this.started) {
88
113
  this.scheduleHarvest()
89
114
  }
@@ -8,7 +8,7 @@ import { obj as encodeObj, param as encodeParam } from '../url/encode'
8
8
  import { stringify } from '../util/stringify'
9
9
  import { submitData } from '../util/submit-data'
10
10
  import { getLocation } from '../url/location'
11
- import { getInfo, getConfigurationValue, getRuntime, getConfiguration } from '../config/config'
11
+ import { getInfo, getConfigurationValue, getRuntime } from '../config/config'
12
12
  import { cleanURL } from '../url/clean-url'
13
13
  import { now } from '../timing/now'
14
14
  import { eventListenerOpts } from '../event-listener/event-listener-opts'
@@ -18,6 +18,23 @@ import { SharedContext } from '../context/shared-context'
18
18
  import { VERSION } from '../constants/env'
19
19
  import { isBrowserScope, isWorkerScope } from '../util/global-scope'
20
20
 
21
+ /**
22
+ * @typedef {object} NetworkSendSpec
23
+ * @param {string} endpoint The endpoint to use (jserrors, events, resources etc.)
24
+ * @param {object} payload Object representing payload.
25
+ * @param {object} payload.qs Map of values that should be sent as part of the request query string.
26
+ * @param {string} payload.body String that should be sent as the body of the request.
27
+ * @param {string} payload.body.e Special case of body used for browser interactions.
28
+ * @param {object} opts Additional options for sending data
29
+ * @param {boolean} opts.needResponse Specify whether the caller expects a response data.
30
+ * @param {boolean} opts.unload Specify whether the call is a final harvest during page unload.
31
+ * @param {boolean} opts.sendEmptyBody Specify whether the call should be made even if the body is empty. Useful for rum calls.
32
+ * @param {function} submitMethod The submit method to use {@link ../util/submit-data}
33
+ * @param {string} customUrl Override the beacon url the data is sent to; must include protocol if defined
34
+ * @param {boolean} gzip Enabled gzip compression on the body of the request before it is sent
35
+ * @param {boolean} includeBaseParams Enables the use of base query parameters in the beacon url {@see Harvest.baseQueryString}
36
+ */
37
+
21
38
  const haveSendBeacon = !!navigator.sendBeacon // only the web window obj has sendBeacon at this time, so 'false' for other envs
22
39
 
23
40
  export class Harvest extends SharedContext {
@@ -34,55 +51,48 @@ export class Harvest extends SharedContext {
34
51
  /**
35
52
  * Initiate a harvest from multiple sources. An event that corresponds to the endpoint
36
53
  * name is emitted, which gives any listeners the opportunity to provide payload data.
37
- *
38
- * @param {string} endpoint - The endpoint of the harvest (jserrors, events, resources etc.)
39
- *
40
- * @param {object} opts
41
- * @param {bool} opts.needResponse - Specify whether the caller expects a response data.
42
- * @param {bool} opts.unload - Specify whether the call is a final harvest during page unload.
54
+ * @param {NetworkSendSpec} spec Specification for sending data
43
55
  */
44
- sendX (endpoint, opts, cbFinished) {
56
+ sendX (spec) {
57
+ const { endpoint, opts } = spec
45
58
  var submitMethod = getSubmitMethod(endpoint, opts)
46
59
  if (!submitMethod) return false
47
60
  var options = {
48
61
  retry: submitMethod.method === submitData.xhr
49
62
  }
50
- return this.obfuscator.shouldObfuscate() ? this.obfuscateAndSend(endpoint, this.createPayload(endpoint, options), opts, submitMethod, cbFinished) : this._send(endpoint, this.createPayload(endpoint, options), opts, submitMethod, cbFinished)
63
+ const payload = this.createPayload(endpoint, options)
64
+ var caller = this.obfuscator.shouldObfuscate() ? this.obfuscateAndSend.bind(this) : this._send.bind(this)
65
+ return caller({ ...spec, payload, submitMethod })
51
66
  }
52
67
 
53
68
  /**
54
- * Initiate a harvest call.
55
- *
56
- * @param {string} endpoint - The endpoint of the harvest (jserrors, events, resources etc.)
57
- * @param {object} nr - The loader singleton.
58
- *
59
- * @param {object} singlePayload - Object representing payload.
60
- * @param {object} singlePayload.qs - Map of values that should be sent as part of the request query string.
61
- * @param {string} singlePayload.body - String that should be sent as the body of the request.
62
- * @param {string} singlePayload.body.e - Special case of body used for browser interactions.
63
- *
64
- * @param {object} opts
65
- * @param {bool} opts.needResponse - Specify whether the caller expects a response data.
66
- * @param {bool} opts.unload - Specify whether the call is a final harvest during page unload.
67
- */
68
- send (endpoint, singlePayload, opts, submitMethod, cbFinished) {
69
+ * Initiate a harvest call.
70
+ * @param {NetworkSendSpec} spec Specification for sending data
71
+ */
72
+ send (spec) {
73
+ const { payload = {} } = spec
69
74
  var makeBody = createAccumulator()
70
75
  var makeQueryString = createAccumulator()
71
- if (singlePayload.body) mapOwn(singlePayload.body, makeBody)
72
- if (singlePayload.qs) mapOwn(singlePayload.qs, makeQueryString)
76
+ if (payload.body) mapOwn(payload.body, makeBody)
77
+ if (payload.qs) mapOwn(payload.qs, makeQueryString)
73
78
 
74
- var payload = { body: makeBody(), qs: makeQueryString() }
75
- var caller = this.obfuscator.shouldObfuscate() ? (...args) => this.obfuscateAndSend(...args) : (...args) => this._send(...args)
79
+ var newPayload = { body: makeBody(), qs: makeQueryString() }
80
+ var caller = this.obfuscator.shouldObfuscate() ? this.obfuscateAndSend.bind(this) : this._send.bind(this)
76
81
 
77
- return caller(endpoint, payload, opts, submitMethod, cbFinished)
82
+ return caller({ ...spec, payload: newPayload })
78
83
  }
79
84
 
80
- obfuscateAndSend (endpoint, payload, opts, submitMethod, cbFinished) {
85
+ /**
86
+ * Apply obfuscation rules to the payload and then initial the harvest network call.
87
+ * @param {NetworkSendSpec} spec Specification for sending data
88
+ */
89
+ obfuscateAndSend (spec) {
90
+ const { payload = {} } = spec
81
91
  applyFnToProps(payload, (...args) => this.obfuscator.obfuscateString(...args), 'string', ['e'])
82
- return this._send(endpoint, payload, opts, submitMethod, cbFinished)
92
+ return this._send({ ...spec, payload })
83
93
  }
84
94
 
85
- _send (endpoint, payload, opts, submitMethod, cbFinished) {
95
+ _send ({ endpoint, payload = {}, opts = {}, submitMethod, cbFinished, customUrl, gzip, includeBaseParams = true }) {
86
96
  var info = getInfo(this.sharedContext.agentIdentifier)
87
97
  if (!info.errorBeacon) return false
88
98
 
@@ -95,11 +105,10 @@ export class Harvest extends SharedContext {
95
105
  return false
96
106
  }
97
107
 
98
- if (!opts) opts = {}
99
-
100
- var url = this.getScheme() + '://' + info.errorBeacon + '/' + endpoint + '/1/' + info.licenseKey + this.baseQueryString()
101
- if (payload.qs) url += encodeObj(payload.qs, agentRuntime.maxBytes)
108
+ var url = customUrl || this.getScheme() + '://' + info.errorBeacon + '/' + endpoint + '/1/' + info.licenseKey + '?'
102
109
 
110
+ var baseParams = includeBaseParams ? this.baseQueryString() : ''
111
+ var params = payload.qs ? encodeObj(payload.qs, agentRuntime.maxBytes) : ''
103
112
  if (!submitMethod) {
104
113
  submitMethod = getSubmitMethod(endpoint, opts)
105
114
  }
@@ -107,29 +116,43 @@ export class Harvest extends SharedContext {
107
116
  var useBody = submitMethod.useBody
108
117
 
109
118
  var body
110
- var fullUrl = url
111
- if (useBody && endpoint === 'events') {
112
- body = payload.body.e
113
- } else if (useBody) {
114
- body = stringify(payload.body)
115
- } else {
116
- fullUrl = url + encodeObj(payload.body, agentRuntime.maxBytes)
117
- }
119
+ var fullUrl = url + baseParams + params
120
+
121
+ if (!gzip) {
122
+ if (useBody && endpoint === 'events') {
123
+ body = payload.body.e
124
+ } else if (useBody) {
125
+ body = stringify(payload.body)
126
+ } else {
127
+ fullUrl = fullUrl + encodeObj(payload.body, agentRuntime.maxBytes)
128
+ }
129
+ } else body = payload.body
118
130
 
119
131
  // Get bytes harvested per endpoint as a supportability metric. See metrics aggregator (on unload).
120
132
  agentRuntime.bytesSent[endpoint] = (agentRuntime.bytesSent[endpoint] || 0) + body?.length || 0
121
133
  // Get query bytes harvested per endpoint as a supportability metric. See metrics aggregator (on unload).
122
134
  agentRuntime.queryBytesSent[endpoint] = (agentRuntime.queryBytesSent[endpoint] || 0) + fullUrl.split('?').slice(-1)[0]?.length || 0
123
135
 
136
+ const headers = []
137
+
138
+ if (gzip) {
139
+ headers.push({ key: 'content-type', value: 'application/json' })
140
+ headers.push({ key: 'X-Browser-Monitoring-Key', value: info.licenseKey })
141
+ headers.push({ key: 'Content-Encoding', value: 'gzip' })
142
+ } else {
143
+ headers.push({ key: 'content-type', value: 'text/plain' })
144
+ }
145
+
124
146
  /* Since workers don't support sendBeacon right now, or Image(), they can only use XHR method.
125
147
  Because they still do permit synch XHR, the idea is that at final harvest time (worker is closing),
126
148
  we just make a BLOCKING request--trivial impact--with the remaining data as a temp fill-in for sendBeacon. */
127
- var result = method(fullUrl, body, opts.unload && isWorkerScope)
149
+
150
+ var result = method({ url: fullUrl, body, sync: opts.unload && isWorkerScope, headers })
128
151
 
129
152
  if (cbFinished && method === submitData.xhr) {
130
153
  var xhr = result
131
154
  xhr.addEventListener('load', function () {
132
- var result = { sent: true }
155
+ var result = { sent: true, status: this.status }
133
156
  if (this.status === 429) {
134
157
  result.retry = true
135
158
  result.delay = this.tooManyRequestsDelay
@@ -147,7 +170,7 @@ export class Harvest extends SharedContext {
147
170
  // if beacon request failed, retry with an alternative method -- will not happen for workers
148
171
  if (!result && method === submitData.beacon) {
149
172
  method = submitData.img
150
- result = method(url + encodeObj(payload.body, agentRuntime.maxBytes))
173
+ result = method({ url: fullUrl + encodeObj(payload.body, agentRuntime.maxBytes) })
151
174
  }
152
175
 
153
176
  return result
@@ -162,14 +185,14 @@ export class Harvest extends SharedContext {
162
185
  var ref = this.obfuscator.shouldObfuscate() ? this.obfuscator.obfuscateString(location) : location
163
186
 
164
187
  return ([
165
- '?a=' + info.applicationID,
188
+ 'a=' + info.applicationID,
166
189
  encodeParam('sa', (info.sa ? '' + info.sa : '')),
167
190
  encodeParam('v', VERSION),
168
191
  transactionNameParam(info),
169
192
  encodeParam('ct', runtime.customTransaction),
170
193
  '&rst=' + now(),
171
194
  '&ck=0', // ck param DEPRECATED - still expected by backend
172
- '&s=' + (runtime.session?.value || '0'), // the 0 id encaps all untrackable and default traffic
195
+ '&s=' + (runtime.session?.state.value || '0'), // the 0 id encaps all untrackable and default traffic
173
196
  encodeParam('ref', ref),
174
197
  encodeParam('ptid', (runtime.ptid ? '' + runtime.ptid : ''))
175
198
  ].join(''))
@@ -241,7 +264,7 @@ function createAccumulator () {
241
264
  var accumulator = {}
242
265
  var hasData = false
243
266
  return function (key, val) {
244
- if (val && val.length) {
267
+ if (val !== null && val !== undefined && val.length) {
245
268
  accumulator[key] = val
246
269
  hasData = true
247
270
  }
@@ -8,21 +8,29 @@ import { DEFAULT_EXPIRES_MS, DEFAULT_INACTIVE_MS, PREFIX } from './constants'
8
8
  import { LocalMemory } from '../storage/local-memory'
9
9
  import { InteractionTimer } from '../timer/interaction-timer'
10
10
  import { wrapEvents } from '../wrap'
11
- import { Configurable } from '../config/state/configurable'
11
+ import { getModeledObject } from '../config/state/configurable'
12
12
  import { handle } from '../event-emitter/handle'
13
13
  import { SUPPORTABILITY_METRIC_CHANNEL } from '../../features/metrics/constants'
14
14
  import { FEATURE_NAMES } from '../../loaders/features/features'
15
15
 
16
+ // this is what can be stored in local storage (not enforced but probably should be)
17
+ // these values should sync between local storage and the parent class props
16
18
  const model = {
17
19
  value: '',
18
20
  inactiveAt: 0,
19
21
  expiresAt: 0,
20
22
  updatedAt: Date.now(),
21
- sessionReplayActive: false,
23
+ sessionReplay: 0,
22
24
  sessionTraceActive: false,
23
25
  custom: {}
24
26
  }
25
27
 
28
+ export const SESSION_EVENTS = {
29
+ PAUSE: 'session-pause',
30
+ RESET: 'session-reset',
31
+ RESUME: 'session-resume'
32
+ }
33
+
26
34
  export class SessionEntity {
27
35
  /**
28
36
  * Create a self-managing Session Entity. This entity is scoped to the agent identifier which triggered it, allowing for multiple simultaneous session objects to exist.
@@ -39,14 +47,15 @@ export class SessionEntity {
39
47
  if (!isBrowserScope) this.storage = new LocalMemory()
40
48
  else this.storage = storageAPI
41
49
 
50
+ this.state = {}
51
+
42
52
  this.sync(model)
43
53
 
44
54
  this.agentIdentifier = agentIdentifier
45
-
46
55
  // key is intended to act as the k=v pair
47
56
  this.key = key
48
57
  // value is intended to act as the primary value of the k=v pair
49
- this.value = value
58
+ this.state.value = value
50
59
 
51
60
  this.expiresMs = expiresMs
52
61
  this.inactiveMs = inactiveMs
@@ -66,7 +75,7 @@ export class SessionEntity {
66
75
  // the set-up of the timer used to expire the session "naturally" at a certain time
67
76
  // this gets ignored if the value is falsy, allowing for session entities that do not expire
68
77
  if (expiresMs) {
69
- this.expiresAt = initialRead?.expiresAt || this.getFutureTimestamp(expiresMs)
78
+ this.state.expiresAt = initialRead?.expiresAt || this.getFutureTimestamp(expiresMs)
70
79
  this.expiresTimer = new Timer({
71
80
  // When the inactive timer ends, collect a SM and reset the session
72
81
  onEnd: () => {
@@ -74,16 +83,16 @@ export class SessionEntity {
74
83
  this.collectSM('duration', this)
75
84
  this.reset()
76
85
  }
77
- }, this.expiresAt - Date.now())
86
+ }, this.state.expiresAt - Date.now())
78
87
  } else {
79
- this.expiresAt = Infinity
88
+ this.state.expiresAt = Infinity
80
89
  }
81
90
 
82
91
  // the set-up of the timer used to expire the session due to "inactivity" at a certain time
83
92
  // this gets ignored if the value is falsy, allowing for session entities that do not expire
84
93
  // this gets "refreshed" when "activity" is observed
85
94
  if (inactiveMs) {
86
- this.inactiveAt = initialRead?.inactiveAt || this.getFutureTimestamp(inactiveMs)
95
+ this.state.inactiveAt = initialRead?.inactiveAt || this.getFutureTimestamp(inactiveMs)
87
96
  this.inactiveTimer = new InteractionTimer({
88
97
  // When the inactive timer ends, collect a SM and reset the session
89
98
  onEnd: () => {
@@ -93,21 +102,25 @@ export class SessionEntity {
93
102
  },
94
103
  // When the inactive timer refreshes, it will update the storage values with an update timestamp
95
104
  onRefresh: this.refresh.bind(this),
105
+ onResume: () => { this.ee.emit(SESSION_EVENTS.RESUME) },
96
106
  // When the inactive timer pauses, update the storage values with an update timestamp
97
- onPause: () => this.write(new Configurable(this.read(), model)),
107
+ onPause: () => {
108
+ if (this.initialized) this.ee.emit(SESSION_EVENTS.PAUSE)
109
+ this.write(getModeledObject(this.state, model))
110
+ },
98
111
  ee: this.ee,
99
112
  refreshEvents: ['click', 'keydown', 'scroll']
100
- }, this.inactiveAt - Date.now())
113
+ }, this.state.inactiveAt - Date.now())
101
114
  } else {
102
- this.inactiveAt = Infinity
115
+ this.state.inactiveAt = Infinity
103
116
  }
104
117
 
105
118
  // The fact that the session is "new" or pre-existing is used in some places in the agent. Session Replay and Trace
106
119
  // can use this info to inform whether to trust a new sampling decision vs continue a previous tracking effort.
107
- this.isNew = !Object.keys(initialRead).length
120
+ if (this.isNew === undefined) this.isNew = !Object.keys(initialRead).length
108
121
  // if its a "new" session, we write to storage API with the default values. These values may change over the lifespan of the agent run.
109
- // we can use configurable here to help us know and manage what values are being used. -- see "model" above
110
- if (this.isNew) this.write(new Configurable(this, model), true)
122
+ // we can use a modeled object here to help us know and manage what values are being used. -- see "model" above
123
+ if (this.isNew) this.write(getModeledObject(this.state, model), true)
111
124
  else this.sync(initialRead)
112
125
 
113
126
  this.initialized = true
@@ -119,7 +132,7 @@ export class SessionEntity {
119
132
  }
120
133
 
121
134
  sync (data) {
122
- Object.assign(this, data)
135
+ Object.assign(this.state, data)
123
136
  }
124
137
 
125
138
  /**
@@ -167,7 +180,8 @@ export class SessionEntity {
167
180
  if (!data || typeof data !== 'object') return
168
181
  // everytime we update, we can update a timestamp for sanity
169
182
  data.updatedAt = Date.now()
170
- this.sync(data)
183
+ this.sync(data) // update the parent class "state" properties with the local storage values
184
+ //
171
185
  // TODO - compression would need happen here if we decide to do it
172
186
  this.storage.set(this.lookupKey, stringify(data))
173
187
  return data
@@ -184,13 +198,11 @@ export class SessionEntity {
184
198
  // * stop recording (stn and sr)...
185
199
  // * delete the session and start over
186
200
  try {
187
- if (this.initialized) this.ee.emit('session-reset')
188
-
201
+ if (this.initialized) this.ee.emit(SESSION_EVENTS.RESET)
189
202
  this.storage.remove(this.lookupKey)
190
203
  this.inactiveTimer?.abort?.()
191
204
  this.expiresTimer?.clear?.()
192
- delete this.custom
193
- delete this.value
205
+ delete this.isNew
194
206
 
195
207
  this.setup({
196
208
  agentIdentifier: this.agentIdentifier,
@@ -211,8 +223,7 @@ export class SessionEntity {
211
223
  refresh () {
212
224
  // read here & invalidate
213
225
  const existingData = this.read()
214
- this.inactiveAt = this.getFutureTimestamp(this.inactiveMs)
215
- this.write({ ...existingData, inactiveAt: this.inactiveAt })
226
+ this.write({ ...existingData, inactiveAt: this.getFutureTimestamp(this.inactiveMs) })
216
227
  }
217
228
 
218
229
  /**
@@ -228,7 +239,7 @@ export class SessionEntity {
228
239
  * @returns {boolean}
229
240
  */
230
241
  isInvalid (data) {
231
- const requiredKeys = ['value', 'expiresAt', 'inactiveAt']
242
+ const requiredKeys = Object.keys(model)
232
243
  return !requiredKeys.every(x => Object.keys(data).includes(x))
233
244
  }
234
245