@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
@@ -1,34 +1,45 @@
1
+ import { faker } from '@faker-js/faker'
2
+ import * as globalScopeModule from '../util/global-scope'
3
+ import * as cleanUrlModule from './clean-url'
4
+ import { canonicalizeUrl } from './canonicalize-url'
5
+
6
+ jest.mock('../util/global-scope')
7
+ jest.mock('./clean-url')
8
+
9
+ beforeEach(() => {
10
+ jest.spyOn(cleanUrlModule, 'cleanURL').mockImplementation(input => input)
11
+ jest.replaceProperty(globalScopeModule, 'initialLocation', faker.internet.url())
12
+ })
13
+
1
14
  afterEach(() => {
2
- jest.resetModules()
15
+ jest.resetAllMocks()
3
16
  })
4
17
 
5
18
  test.each([null, undefined, 34])('returns empty string when url argument is %s', async (url) => {
6
- const { canonicalizeUrl } = await import('./canonicalize-url')
7
19
  expect(canonicalizeUrl(url)).toEqual('')
8
20
  })
9
21
 
10
- test('strips URLs of query strings and fragments', async () => {
11
- jest.doMock('../util/global-scope', () => ({
12
- initialLocation: 'http://different-domain.com/'
13
- }))
14
- const { canonicalizeUrl } = await import('./canonicalize-url')
15
- expect(canonicalizeUrl('http://example.com/path?query=string#fragment')).toBe('http://example.com/path')
16
- expect(canonicalizeUrl('https://www.example.com/path/to/file.html?param=value')).toBe('https://www.example.com/path/to/file.html')
17
- expect(canonicalizeUrl('https://www.example.com/?param=value#fragment')).toBe('https://www.example.com/')
22
+ test('uses cleanURL to clean the input and initial location URLs', () => {
23
+ const url = faker.internet.url()
24
+ canonicalizeUrl(url)
25
+
26
+ expect(cleanUrlModule.cleanURL).toHaveBeenCalledWith(globalScopeModule.initialLocation)
27
+ expect(cleanUrlModule.cleanURL).toHaveBeenCalledWith(url)
28
+ expect(cleanUrlModule.cleanURL).toHaveBeenCalledTimes(2)
18
29
  })
19
30
 
20
- test('returns <inline> when matching the page URL of the loader', async () => {
21
- jest.doMock('../util/global-scope', () => ({
22
- initialLocation: 'http://example.com/'
23
- }))
24
- const { canonicalizeUrl } = await import('./canonicalize-url')
25
- expect(canonicalizeUrl('http://example.com/')).toEqual('<inline>')
31
+ test('returns <inline> when input and initial page urls are the same', async () => {
32
+ expect(canonicalizeUrl(globalScopeModule.initialLocation)).toEqual('<inline>')
33
+ })
34
+
35
+ test('returns input url when it does not match initial page url', async () => {
36
+ const url = faker.internet.url()
37
+
38
+ expect(canonicalizeUrl(url)).toEqual(url)
26
39
  })
27
40
 
28
41
  test('does not identify sub-paths of the loader origin as <inline>', async () => {
29
- jest.doMock('../util/global-scope', () => ({
30
- initialLocation: 'http://example.com/'
31
- }))
32
- const { canonicalizeUrl } = await import('./canonicalize-url')
33
- expect(canonicalizeUrl('http://example.com/path/to/script.js')).not.toEqual('<inline>')
42
+ const url = globalScopeModule.initialLocation + '/path/to/script.js'
43
+
44
+ expect(canonicalizeUrl(url)).not.toEqual('<inline>')
34
45
  })
@@ -48,11 +48,11 @@ export function obj (payload, maxBytes) {
48
48
  var next
49
49
  var i
50
50
 
51
- if (typeof dataArray === 'string') {
51
+ if (typeof dataArray === 'string' || (!Array.isArray(dataArray) && dataArray !== null && dataArray !== undefined && dataArray.toString().length)) {
52
52
  next = '&' + feature + '=' + qs(dataArray)
53
53
  total += next.length
54
54
  result += next
55
- } else if (dataArray.length) {
55
+ } else if (Array.isArray(dataArray) && dataArray.length) {
56
56
  total += 9
57
57
  for (i = 0; i < dataArray.length; i++) {
58
58
  next = qs(stringify(dataArray[i]))
@@ -0,0 +1,34 @@
1
+ import { warn } from './console'
2
+
3
+ beforeEach(() => {
4
+ console.warn = jest.fn()
5
+ })
6
+
7
+ afterEach(() => {
8
+ jest.restoreAllMocks()
9
+ })
10
+
11
+ describe('warn', () => {
12
+ test('should not call console.warn if it is not a function', () => {
13
+ const spy = jest.spyOn(console, 'warn')
14
+ console.warn = undefined
15
+ warn('test message')
16
+ expect(spy).not.toHaveBeenCalled()
17
+ })
18
+
19
+ test('should call console.warn with a prefixed message', () => {
20
+ warn('test message')
21
+ expect(console.warn).toHaveBeenCalledWith('New Relic: test message')
22
+ })
23
+
24
+ test('should call console.warn with secondary argument if provided', () => {
25
+ const secondary = 'secondary value'
26
+ warn('test message', secondary)
27
+ expect(console.warn).toHaveBeenCalledWith(secondary)
28
+ })
29
+
30
+ test('should not call console.warn with secondary argument if not provided', () => {
31
+ warn('test message')
32
+ expect(console.warn).toHaveBeenCalledTimes(1)
33
+ })
34
+ })
@@ -1,24 +1,29 @@
1
+ import { faker } from '@faker-js/faker'
2
+ import * as stringifyModule from './stringify'
1
3
  import { dataSize } from './data-size'
2
4
 
5
+ jest.mock('./stringify')
6
+
3
7
  describe('dataSize', () => {
4
8
  test('returns length of string', () => {
5
- const str = 'Hello, world!'
9
+ const str = faker.lorem.sentence()
6
10
  expect(dataSize(str)).toBe(str.length)
7
11
  })
8
12
 
9
13
  test('returns undefined for non-object, number, or empty string', () => {
10
14
  expect(dataSize(Infinity)).toBeUndefined()
11
- expect(dataSize(12345)).toBeUndefined() // might not actually be by design, but this is how it works today
15
+ expect(dataSize(NaN)).toBeUndefined()
16
+ expect(dataSize(12345)).toBeUndefined()
12
17
  expect(dataSize('')).toBeUndefined()
13
18
  })
14
19
 
15
20
  test('returns byte length of ArrayBuffer object', () => {
16
- const buffer = new ArrayBuffer(8)
17
- expect(dataSize(buffer)).toBe(8)
21
+ const buffer = new ArrayBuffer(faker.datatype.number({ min: 10, max: 100 }))
22
+ expect(dataSize(buffer)).toBe(buffer.byteLength)
18
23
  })
19
24
 
20
25
  test('returns size of Blob object', () => {
21
- const blob = new Blob(['Hello, world!'], { type: 'text/plain' })
26
+ const blob = new Blob([faker.lorem.sentence()], { type: 'text/plain' })
22
27
  expect(dataSize(blob)).toBe(blob.size)
23
28
  })
24
29
 
@@ -27,24 +32,26 @@ describe('dataSize', () => {
27
32
  expect(dataSize(formData)).toBeUndefined()
28
33
  })
29
34
 
30
- test('returns length of JSON string representation of object', () => {
31
- const obj = {
32
- str: 'Hello, world!',
33
- num: 12345,
34
- nestedObj: {
35
- arr: [1, 2, 3]
36
- }
35
+ test('uses stringify to get the length of an object', () => {
36
+ const input = {
37
+ [faker.datatype.uuid()]: faker.lorem.sentence()
37
38
  }
38
- const expectedSize = JSON.stringify(obj).length
39
- expect(dataSize(obj)).toBe(expectedSize)
39
+ const expectedSize = faker.datatype.number({ min: 1000, max: 10000 })
40
+
41
+ jest.spyOn(stringifyModule, 'stringify').mockReturnValue({ length: expectedSize })
42
+
43
+ expect(dataSize(input)).toBe(expectedSize)
40
44
  })
41
45
 
42
- test('returns undefined for object with toJSON method that throws an error', () => {
43
- const obj = {
44
- toJSON: () => {
45
- throw new Error('Error in toJSON')
46
- }
46
+ test('should not throw an exception if stringify throws an exception', () => {
47
+ const input = {
48
+ [faker.datatype.uuid()]: faker.lorem.sentence()
47
49
  }
48
- expect(dataSize(obj)).toBeUndefined()
50
+ const expectedSize = faker.datatype.number({ min: 1000, max: 10000 })
51
+
52
+ jest.spyOn(stringifyModule, 'stringify').mockImplementation(() => { throw new Error(faker.lorem.sentence()) })
53
+
54
+ expect(() => dataSize(input)).not.toThrow()
55
+ expect(dataSize(input)).toBeUndefined()
49
56
  })
50
57
  })
@@ -2,7 +2,6 @@
2
2
  * Copyright 2020 New Relic Corporation. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
- import { mapOwn } from './map-own'
6
5
  import { ee } from '../event-emitter/contextual-ee'
7
6
  import { handle } from '../event-emitter/handle'
8
7
  import { drain } from '../drain/drain'
@@ -13,24 +12,37 @@ const bucketMap = {
13
12
  err: [FEATURE_NAMES.jserrors, FEATURE_NAMES.metrics],
14
13
  ins: [FEATURE_NAMES.pageAction],
15
14
  spa: [FEATURE_NAMES.spa],
16
- sr: [FEATURE_NAMES.sessionReplay]
15
+ sr: [FEATURE_NAMES.sessionReplay, FEATURE_NAMES.sessionTrace]
17
16
  }
18
17
 
18
+ /** Note that this function only processes each unique flag ONCE, with the first occurrence of each flag and numeric value determining its switch on/off setting. */
19
19
  export function activateFeatures (flags, agentIdentifier) {
20
- var sharedEE = ee.get(agentIdentifier)
20
+ const sharedEE = ee.get(agentIdentifier)
21
21
  if (!(flags && typeof flags === 'object')) return
22
- mapOwn(flags, function (flag, val) {
23
- if (!val) {
24
- return (bucketMap[flag] || []).forEach(feat => {
25
- handle('block-' + flag, [], undefined, feat, sharedEE)
22
+
23
+ Object.entries(flags).forEach(([flag, num]) => {
24
+ if (activatedFeatures[flag] !== undefined) return
25
+
26
+ if (bucketMap[flag]) {
27
+ bucketMap[flag].forEach(feat => {
28
+ if (!num) handle('block-' + flag, [], undefined, feat, sharedEE)
29
+ else handle('feat-' + flag, [], undefined, feat, sharedEE)
30
+
31
+ handle('rumresp-' + flag, [Boolean(num)], undefined, feat, sharedEE) // this is a duplicate of feat-/block- but makes awaiting for 1 event easier than 2
26
32
  })
27
- }
33
+ } else if (num) handle('feat-' + flag, [], undefined, undefined, sharedEE) // not sure what other flags are overlooked, but there's a test for ones not in the map --
34
+ // ^^^ THIS DOESN'T ACTUALLY DO ANYTHHING AS UNDEFINED/FEATURE GROUP ISN'T DRAINED
35
+
36
+ activatedFeatures[flag] = Boolean(num)
37
+ })
28
38
 
29
- if (activatedFeatures[flag]) {
30
- return
39
+ // Let the features waiting on their respective flags know that RUM response was received and that any missing flags are interpreted as bad entitlement / "off".
40
+ // Hence, those features will not be hanging forever if their flags aren't included in the response.
41
+ Object.keys(bucketMap).forEach(flag => {
42
+ if (activatedFeatures[flag] === undefined) {
43
+ bucketMap[flag]?.forEach(feat => handle('rumresp-' + flag, [false], undefined, feat, sharedEE))
44
+ activatedFeatures[flag] = false
31
45
  }
32
- handle('feat-' + flag, [], undefined, bucketMap[flag], sharedEE)
33
- activatedFeatures[flag] = true
34
46
  })
35
47
  drain(agentIdentifier, FEATURE_NAMES.pageViewEvent)
36
48
  }
@@ -0,0 +1,98 @@
1
+ import { faker } from '@faker-js/faker'
2
+ import * as eventEmitterModule from '../event-emitter/contextual-ee'
3
+ import * as handleModule from '../event-emitter/handle'
4
+ import * as drainModule from '../drain/drain'
5
+ import { activateFeatures, activatedFeatures } from './feature-flags'
6
+ import { FEATURE_NAMES } from '../../loaders/features/features'
7
+
8
+ jest.mock('../event-emitter/handle')
9
+ jest.mock('../drain/drain')
10
+ jest.mock('../event-emitter/contextual-ee', () => ({
11
+ __esModule: true,
12
+ ee: {
13
+ get: jest.fn(() => ({
14
+ foo: `bar_${Math.random()}`
15
+ }))
16
+ }
17
+ }))
18
+
19
+ let agentIdentifier
20
+
21
+ beforeEach(() => {
22
+ agentIdentifier = faker.datatype.uuid()
23
+ })
24
+
25
+ afterEach(() => {
26
+ Object.keys(activatedFeatures)
27
+ .forEach(key => delete activatedFeatures[key])
28
+ })
29
+
30
+ test.each([
31
+ null,
32
+ undefined
33
+ ])('should not do anything when flags is %s', (input) => {
34
+ activateFeatures(input, agentIdentifier)
35
+
36
+ expect(handleModule.handle).not.toHaveBeenCalled()
37
+ expect(drainModule.drain).not.toHaveBeenCalled()
38
+ expect(activatedFeatures).toEqual({})
39
+ })
40
+
41
+ const bucketMap = {
42
+ stn: [FEATURE_NAMES.sessionTrace],
43
+ err: [FEATURE_NAMES.jserrors, FEATURE_NAMES.metrics],
44
+ ins: [FEATURE_NAMES.pageAction],
45
+ spa: [FEATURE_NAMES.spa],
46
+ sr: [FEATURE_NAMES.sessionReplay, FEATURE_NAMES.sessionTrace]
47
+ }
48
+
49
+ test('emits the right events when feature flag = 1', () => {
50
+ const flags = {}
51
+ Object.keys(bucketMap).forEach(flag => flags[flag] = 1)
52
+ activateFeatures(flags, agentIdentifier)
53
+
54
+ const sharedEE = jest.mocked(eventEmitterModule.ee.get).mock.results[0].value
55
+
56
+ // each flag gets emitted to each of its mapped features, and a feat- AND a rumresp- for every emit, so (1+2+1+1+2)*2 = 14
57
+ expect(handleModule.handle).toHaveBeenCalledTimes(14)
58
+ expect(handleModule.handle).toHaveBeenNthCalledWith(1, 'feat-stn', [], undefined, FEATURE_NAMES.sessionTrace, sharedEE)
59
+ expect(handleModule.handle).toHaveBeenLastCalledWith('rumresp-sr', [true], undefined, FEATURE_NAMES.sessionTrace, sharedEE)
60
+ expect(drainModule.drain).toHaveBeenCalledWith(agentIdentifier, 'page_view_event')
61
+
62
+ Object.keys(flags).forEach(flag => flags[flag] = true)
63
+ expect(activatedFeatures).toEqual(flags)
64
+ })
65
+
66
+ test('emits the right events when feature flag = 0', () => {
67
+ const flags = {}
68
+ Object.keys(bucketMap).forEach(flag => flags[flag] = 0)
69
+ activateFeatures(flags, agentIdentifier)
70
+
71
+ const sharedEE = jest.mocked(eventEmitterModule.ee.get).mock.results[0].value
72
+
73
+ // each flag gets emitted to each of its mapped features, and a block- AND a rumresp- for every emit, so (1+2+1+1+2)*2 = 14
74
+ expect(handleModule.handle).toHaveBeenCalledTimes(14)
75
+ expect(handleModule.handle).toHaveBeenNthCalledWith(1, 'block-stn', [], undefined, FEATURE_NAMES.sessionTrace, sharedEE)
76
+ expect(handleModule.handle).toHaveBeenLastCalledWith('rumresp-sr', [false], undefined, FEATURE_NAMES.sessionTrace, sharedEE)
77
+ expect(drainModule.drain).toHaveBeenCalledWith(agentIdentifier, 'page_view_event')
78
+
79
+ Object.keys(flags).forEach(flag => flags[flag] = false)
80
+ expect(activatedFeatures).toEqual(flags)
81
+ })
82
+
83
+ test('only the first activate of the same feature is respected', () => {
84
+ const flags = { stn: 1 }
85
+ activateFeatures(flags, agentIdentifier)
86
+ flags.stn = 0
87
+ activateFeatures(flags, agentIdentifier)
88
+
89
+ const sharedEE1 = jest.mocked(eventEmitterModule.ee.get).mock.results[0].value
90
+ const sharedEE2 = jest.mocked(eventEmitterModule.ee.get).mock.results[1].value
91
+
92
+ expect(handleModule.handle).toHaveBeenNthCalledWith(1, 'feat-stn', [], undefined, 'session_trace', sharedEE1)
93
+ expect(handleModule.handle).toHaveBeenNthCalledWith(2, 'rumresp-stn', [true], undefined, 'session_trace', sharedEE1)
94
+ expect(handleModule.handle).not.toHaveBeenNthCalledWith(1, 'feat-stn', [], undefined, 'session_trace', sharedEE2)
95
+ expect(drainModule.drain).toHaveBeenCalledWith(agentIdentifier, 'page_view_event')
96
+ expect(drainModule.drain).toHaveBeenCalledTimes(2)
97
+ expect(activatedFeatures.stn).toBeTruthy()
98
+ })
@@ -5,7 +5,14 @@
5
5
 
6
6
  var has = Object.prototype.hasOwnProperty
7
7
 
8
- // Always returns the current value of obj[prop], even if it has to set it first
8
+ /**
9
+ * Always returns the current value of obj[prop], even if it has to set it first. Sets properties as non-enumerable if possible.
10
+ *
11
+ * @param {Object} obj - The object to get or set the property on.
12
+ * @param {string} prop - The name of the property.
13
+ * @param {Function} getVal - A function that returns the value to be set if the property does not exist.
14
+ * @returns {*} The value of the property in the object.
15
+ */
9
16
  export function getOrSet (obj, prop, getVal) {
10
17
  // If the value exists return it.
11
18
  if (has.call(obj, prop)) return obj[prop]
@@ -0,0 +1,58 @@
1
+ import { getOrSet } from './get-or-set'
2
+
3
+ test('should return the current value of an existing property', () => {
4
+ const obj = { foo: 'bar' }
5
+ const prop = 'foo'
6
+ const getVal = jest.fn()
7
+
8
+ const result = getOrSet(obj, prop, getVal)
9
+
10
+ expect(result).toBe('bar')
11
+ expect(getVal).not.toHaveBeenCalled()
12
+ })
13
+
14
+ test('should set and return the value from getVal if the property does not exist', () => {
15
+ const obj = {}
16
+ const prop = 'foo'
17
+ const getVal = jest.fn().mockReturnValue('baz')
18
+
19
+ const result = getOrSet(obj, prop, getVal)
20
+
21
+ expect(result).toBe('baz')
22
+ expect(getVal).toHaveBeenCalled()
23
+ expect(obj.foo).toBe('baz')
24
+ })
25
+
26
+ test('should set the property as non-enumerable if Object.defineProperty is supported', () => {
27
+ const obj = {}
28
+ const prop = 'foo'
29
+ const getVal = jest.fn().mockReturnValue('baz')
30
+
31
+ jest.spyOn(Object, 'defineProperty')
32
+
33
+ const result = getOrSet(obj, prop, getVal)
34
+
35
+ expect(result).toBe('baz')
36
+ expect(Object.defineProperty).toHaveBeenCalledWith(obj, prop, {
37
+ value: 'baz',
38
+ writable: true,
39
+ enumerable: false
40
+ })
41
+ })
42
+
43
+ test('should set the property from getVal if Object.defineProperty and Object.keys are not supported', () => {
44
+ const obj = {}
45
+ const prop = 'foo'
46
+ const getVal = jest.fn(() => 'baz')
47
+
48
+ const originalFn = Object.defineProperty
49
+ Object.defineProperty = null
50
+
51
+ const result = getOrSet(obj, prop, getVal)
52
+
53
+ expect(result).toBe('baz')
54
+ expect(obj.foo).toBe('baz')
55
+ expect(Object.prototype.propertyIsEnumerable.call(obj, 'foo')).toBe(true)
56
+
57
+ Object.defineProperty = originalFn
58
+ })
@@ -21,29 +21,3 @@ export let globalScope = (() => {
21
21
  })()
22
22
 
23
23
  export const initialLocation = '' + globalScope.location
24
-
25
- /**
26
- * The below methods are only used for testing and should be removed once the
27
- * reliant tests are moved to Jest.
28
- * tests/browser/protocol.browser.js
29
- * tests/browser/obfuscate.browser.js
30
- */
31
- export function setScope (obj) {
32
- globalScope = { ...obj }
33
- }
34
- export function resetScope () {
35
- if (isBrowserScope) {
36
- globalScope = window
37
- } else if (isWorkerScope) {
38
- if (typeof globalThis !== 'undefined' && globalThis instanceof WorkerGlobalScope) {
39
- globalScope = globalThis
40
- } else if (self instanceof WorkerGlobalScope) {
41
- globalScope = self
42
- }
43
- } else {
44
- throw new Error('New Relic browser agent shutting down due to error: Unable to locate global scope. This is possibly due to code redefining browser global variables like "self" and "window".')
45
- }
46
- }
47
- export function getGlobalScope () {
48
- return globalScope
49
- }
@@ -0,0 +1,87 @@
1
+ /*
2
+ The global-scope module contains exports that are defined once at the time
3
+ of importing the module. For this reason, the module must be dynamically
4
+ imported in each test case.
5
+
6
+ A scope must always exist or importing the module will throw an error. Use
7
+ `enableWorkerScope` to enable the worker scope. Be sure to call `disableWorkerScope`
8
+ before any calls to `expect` or the test will fail with an error from Jest.
9
+ */
10
+
11
+ import { faker } from '@faker-js/faker'
12
+
13
+ afterEach(() => {
14
+ jest.restoreAllMocks()
15
+ jest.resetModules()
16
+ })
17
+
18
+ test('should indicate a browser scope', async () => {
19
+ jest.spyOn(global, 'window', 'get').mockReturnValue({ document: {} })
20
+
21
+ const globalScopeModule = await import('./global-scope')
22
+
23
+ expect(globalScopeModule.isBrowserScope).toBe(true)
24
+ expect(globalScopeModule.isWorkerScope).toBe(false)
25
+ expect(globalScopeModule.globalScope).toBe(global.window)
26
+ })
27
+
28
+ test('should indicate a worker scope', async () => {
29
+ enableWorkerScope()
30
+ const globalScopeModule = await import('./global-scope')
31
+ const mockedGlobalThis = global.globalThis
32
+ disableWorkerScope()
33
+
34
+ expect(globalScopeModule.isBrowserScope).toBe(false)
35
+ expect(globalScopeModule.isWorkerScope).toBe(true)
36
+ expect(globalScopeModule.globalScope).toBe(mockedGlobalThis)
37
+ })
38
+
39
+ test('should return the self global', async () => {
40
+ enableWorkerScope()
41
+ jest.replaceProperty(global, 'globalThis', null)
42
+ jest.spyOn(global, 'self', 'get').mockReturnValue(new global.WorkerGlobalScope())
43
+
44
+ const globalScopeModule = await import('./global-scope')
45
+ const mockedGlobalSelf = global.self
46
+ disableWorkerScope()
47
+
48
+ expect(globalScopeModule.isBrowserScope).toBe(false)
49
+ expect(globalScopeModule.isWorkerScope).toBe(true)
50
+ expect(globalScopeModule.globalScope).toBe(mockedGlobalSelf)
51
+ })
52
+
53
+ test('should throw an error when a scope cannot be defined', async () => {
54
+ jest.spyOn(global, 'window', 'get').mockReturnValue(undefined)
55
+
56
+ await expect(() => import('./global-scope')).rejects.toThrow()
57
+ })
58
+
59
+ test('should immediately store the current location', async () => {
60
+ const url = faker.internet.url()
61
+ jest.spyOn(window, 'location', 'get').mockReturnValue(url)
62
+
63
+ const globalScopeModule = await import('./global-scope')
64
+
65
+ expect(globalScopeModule.initialLocation).toBe(url)
66
+ })
67
+
68
+ function enableWorkerScope () {
69
+ jest.spyOn(global, 'window', 'get').mockReturnValue(undefined)
70
+
71
+ class WorkerNavigator {}
72
+ class WorkerGlobalScope {
73
+ navigator = new WorkerNavigator()
74
+ }
75
+ global.WorkerGlobalScope = WorkerGlobalScope
76
+ global.WorkerNavigator = WorkerNavigator
77
+
78
+ jest.spyOn(global, 'navigator', 'get').mockReturnValue(new global.WorkerNavigator())
79
+ jest.replaceProperty(global, 'globalThis', new WorkerGlobalScope())
80
+ }
81
+
82
+ function disableWorkerScope () {
83
+ delete global.WorkerGlobalScope
84
+ delete global.WorkerNavigator
85
+
86
+ jest.restoreAllMocks()
87
+ }