@newrelic/browser-agent 1.241.0 → 1.243.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 (132) hide show
  1. package/CHANGELOG.md +1465 -0
  2. package/dist/cjs/cdn/polyfills/lite.js +13 -1
  3. package/dist/cjs/cdn/polyfills/pro.js +17 -1
  4. package/dist/cjs/cdn/polyfills/spa.js +18 -1
  5. package/dist/cjs/common/config/state/init.js +32 -5
  6. package/dist/cjs/common/constants/env.cdn.js +1 -1
  7. package/dist/cjs/common/constants/env.npm.js +1 -1
  8. package/dist/cjs/common/dom/query-selector.js +16 -0
  9. package/dist/cjs/common/session/session-entity.js +20 -2
  10. package/dist/cjs/common/wrap/wrap-function.js +1 -1
  11. package/dist/cjs/features/ajax/aggregate/index.js +1 -1
  12. package/dist/cjs/features/session_replay/aggregate/index.js +84 -50
  13. package/dist/cjs/features/utils/feature-base.js +1 -2
  14. package/dist/cjs/features/utils/instrument-base.js +1 -0
  15. package/dist/cjs/loaders/api/api.js +2 -2
  16. package/dist/cjs/loaders/api/apiAsync.js +0 -37
  17. package/dist/cjs/loaders/configure/configure.js +1 -1
  18. package/dist/cjs/loaders/configure/public-path.js +6 -3
  19. package/dist/esm/cdn/polyfills/lite.js +8 -1
  20. package/dist/esm/cdn/polyfills/pro.js +13 -2
  21. package/dist/esm/cdn/polyfills/spa.js +13 -1
  22. package/dist/esm/common/config/state/init.js +32 -5
  23. package/dist/esm/common/constants/env.cdn.js +1 -1
  24. package/dist/esm/common/constants/env.npm.js +1 -1
  25. package/dist/esm/common/dom/query-selector.js +9 -0
  26. package/dist/esm/common/session/session-entity.js +18 -1
  27. package/dist/esm/common/wrap/wrap-function.js +1 -1
  28. package/dist/esm/features/ajax/aggregate/index.js +1 -1
  29. package/dist/esm/features/session_replay/aggregate/index.js +83 -50
  30. package/dist/esm/features/utils/feature-base.js +1 -2
  31. package/dist/esm/features/utils/instrument-base.js +1 -0
  32. package/dist/esm/loaders/api/api.js +2 -2
  33. package/dist/esm/loaders/api/apiAsync.js +1 -36
  34. package/dist/esm/loaders/configure/configure.js +1 -1
  35. package/dist/esm/loaders/configure/public-path.js +6 -3
  36. package/dist/types/common/config/state/init.d.ts.map +1 -1
  37. package/dist/types/common/dom/query-selector.d.ts +2 -0
  38. package/dist/types/common/dom/query-selector.d.ts.map +1 -0
  39. package/dist/types/common/session/session-entity.d.ts +5 -0
  40. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  41. package/dist/types/features/session_replay/aggregate/index.d.ts +11 -14
  42. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  43. package/dist/types/features/utils/feature-base.d.ts.map +1 -1
  44. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  45. package/dist/types/loaders/api/api.d.ts.map +1 -1
  46. package/dist/types/loaders/api/apiAsync.d.ts.map +1 -1
  47. package/dist/types/loaders/configure/public-path.d.ts +1 -1
  48. package/dist/types/loaders/configure/public-path.d.ts.map +1 -1
  49. package/package.json +2 -2
  50. package/src/cdn/polyfills/lite.js +14 -1
  51. package/src/cdn/polyfills/pro.js +23 -2
  52. package/src/cdn/polyfills/spa.js +24 -1
  53. package/src/common/config/state/init.js +33 -4
  54. package/src/common/dom/query-selector.js +9 -0
  55. package/src/common/session/session-entity.js +20 -1
  56. package/src/common/wrap/wrap-function.js +1 -1
  57. package/src/features/ajax/aggregate/index.js +2 -2
  58. package/src/features/session_replay/aggregate/index.js +82 -34
  59. package/src/features/utils/feature-base.js +1 -2
  60. package/src/features/utils/instrument-base.js +1 -0
  61. package/src/loaders/api/api.js +1 -2
  62. package/src/loaders/api/apiAsync.js +1 -39
  63. package/src/loaders/configure/configure.js +1 -1
  64. package/src/loaders/configure/public-path.js +6 -3
  65. package/src/common/aggregate/aggregator.test.js +0 -107
  66. package/src/common/config/state/configurable.test.js +0 -73
  67. package/src/common/config/state/info.test.js +0 -31
  68. package/src/common/config/state/init.test.js +0 -28
  69. package/src/common/config/state/loader-config.test.js +0 -21
  70. package/src/common/config/state/runtime.test.js +0 -21
  71. package/src/common/constants/env.cdn.test.js +0 -7
  72. package/src/common/constants/env.npm.test.js +0 -7
  73. package/src/common/constants/env.test.js +0 -7
  74. package/src/common/constants/runtime.test.js +0 -176
  75. package/src/common/deny-list/deny-list.test.js +0 -104
  76. package/src/common/drain/drain.test.js +0 -74
  77. package/src/common/event-emitter/contextual-ee.component-test.js +0 -293
  78. package/src/common/event-emitter/handle.test.js +0 -56
  79. package/src/common/event-emitter/register-handler.test.js +0 -61
  80. package/src/common/harvest/harvest-scheduler.test.js +0 -492
  81. package/src/common/harvest/harvest.test.js +0 -813
  82. package/src/common/ids/id.test.js +0 -92
  83. package/src/common/ids/unique-id.test.js +0 -58
  84. package/src/common/session/session-entity.component-test.js +0 -346
  85. package/src/common/storage/local-storage.test.js +0 -17
  86. package/src/common/timer/interaction-timer.component-test.js +0 -212
  87. package/src/common/timer/timer.test.js +0 -99
  88. package/src/common/timing/nav-timing.test.js +0 -161
  89. package/src/common/url/canonicalize-url.test.js +0 -45
  90. package/src/common/url/clean-url.test.js +0 -25
  91. package/src/common/url/encode.test.js +0 -81
  92. package/src/common/url/location.test.js +0 -15
  93. package/src/common/url/parse-url.test.js +0 -110
  94. package/src/common/url/protocol.test.js +0 -17
  95. package/src/common/util/console.test.js +0 -34
  96. package/src/common/util/data-size.test.js +0 -56
  97. package/src/common/util/feature-flags.test.js +0 -94
  98. package/src/common/util/get-or-set.test.js +0 -58
  99. package/src/common/util/invoke.test.js +0 -65
  100. package/src/common/util/map-own.test.js +0 -52
  101. package/src/common/util/obfuscate.component-test.js +0 -173
  102. package/src/common/util/stringify.test.js +0 -49
  103. package/src/common/util/submit-data.test.js +0 -183
  104. package/src/common/util/traverse.test.js +0 -50
  105. package/src/common/vitals/cumulative-layout-shift.test.js +0 -71
  106. package/src/common/vitals/first-contentful-paint.test.js +0 -124
  107. package/src/common/vitals/first-input-delay.test.js +0 -88
  108. package/src/common/vitals/first-paint.test.js +0 -127
  109. package/src/common/vitals/interaction-to-next-paint.test.js +0 -74
  110. package/src/common/vitals/largest-contentful-paint.test.js +0 -94
  111. package/src/common/vitals/long-task.test.js +0 -122
  112. package/src/common/vitals/time-to-first-byte.test.js +0 -147
  113. package/src/common/vitals/vital-metric.test.js +0 -171
  114. package/src/common/wrap/wrap-promise.component-test.js +0 -110
  115. package/src/features/ajax/instrument/distributed-tracing.test.js +0 -375
  116. package/src/features/jserrors/aggregate/canonical-function-name.test.js +0 -13
  117. package/src/features/jserrors/aggregate/compute-stack-trace.test.js +0 -414
  118. package/src/features/jserrors/aggregate/format-stack-trace.test.js +0 -39
  119. package/src/features/jserrors/aggregate/string-hash-code.test.js +0 -12
  120. package/src/features/metrics/aggregate/framework-detection.test.js +0 -332
  121. package/src/features/page_view_timing/aggregate/index.component-test.js +0 -86
  122. package/src/features/session_replay/aggregate/index.component-test.js +0 -317
  123. package/src/features/spa/aggregate/interaction-node.test.js +0 -17
  124. package/src/features/utils/agent-session.test.js +0 -194
  125. package/src/features/utils/aggregate-base.test.js +0 -123
  126. package/src/features/utils/feature-base.test.js +0 -45
  127. package/src/features/utils/handler-cache.test.js +0 -72
  128. package/src/features/utils/instrument-base.test.js +0 -216
  129. package/src/features/utils/lazy-feature-loader.test.js +0 -37
  130. package/src/loaders/api/api.component-test.js +0 -45
  131. package/src/loaders/api/api.test.js +0 -85
  132. package/src/loaders/api/apiAsync.test.js +0 -17
@@ -1,813 +0,0 @@
1
- import { faker } from '@faker-js/faker'
2
-
3
- import * as encodeModule from '../url/encode'
4
- import * as submitDataModule from '../util/submit-data'
5
- import * as configModule from '../config/config'
6
- import { warn } from '../util/console'
7
- import { applyFnToProps } from '../util/traverse'
8
-
9
- import { Harvest } from './harvest'
10
-
11
- jest.enableAutomock()
12
- jest.unmock('./harvest')
13
-
14
- let harvestInstance
15
-
16
- beforeEach(() => {
17
- harvestInstance = new Harvest()
18
- })
19
-
20
- afterEach(() => {
21
- jest.clearAllMocks()
22
- })
23
-
24
- describe('sendX', () => {
25
- beforeEach(() => {
26
- jest.mocked(submitDataModule.getSubmitMethod).mockReturnValue(jest.fn())
27
- jest.spyOn(harvestInstance, '_send').mockImplementation(jest.fn())
28
- jest.spyOn(harvestInstance, 'obfuscateAndSend').mockImplementation(jest.fn())
29
- jest.spyOn(harvestInstance, 'createPayload').mockReturnValue({})
30
- })
31
-
32
- test('should pass spec settings on to _send method', async () => {
33
- const endpoint = faker.datatype.uuid()
34
- const spec = {
35
- endpoint,
36
- [faker.datatype.uuid()]: faker.lorem.sentence()
37
- }
38
-
39
- harvestInstance.sendX(spec)
40
-
41
- expect(harvestInstance._send).toHaveBeenCalledWith(expect.objectContaining(spec))
42
- })
43
-
44
- test('should create payload with retry true', async () => {
45
- const endpoint = faker.datatype.uuid()
46
- const spec = {
47
- endpoint,
48
- opts: {
49
- unload: false
50
- }
51
- }
52
- jest.mocked(submitDataModule.getSubmitMethod).mockReturnValue(submitDataModule.xhr)
53
-
54
- harvestInstance.sendX(spec)
55
-
56
- expect(harvestInstance.createPayload).toHaveBeenCalledWith(spec.endpoint, { retry: true, isFinalHarvest: false })
57
- })
58
-
59
- test('should not use obfuscateAndSend', async () => {
60
- jest.mocked(harvestInstance.obfuscator.shouldObfuscate).mockReturnValue(false)
61
-
62
- const endpoint = faker.datatype.uuid()
63
- harvestInstance.sendX({ endpoint })
64
-
65
- expect(harvestInstance._send).toHaveBeenCalledWith({
66
- endpoint,
67
- payload: {},
68
- submitMethod: expect.any(Function)
69
- })
70
- expect(harvestInstance.obfuscateAndSend).not.toHaveBeenCalled()
71
- })
72
-
73
- test('should use obfuscateAndSend', async () => {
74
- jest.mocked(harvestInstance.obfuscator.shouldObfuscate).mockReturnValue(true)
75
-
76
- const endpoint = faker.datatype.uuid()
77
- harvestInstance.sendX({ endpoint })
78
-
79
- expect(harvestInstance.obfuscateAndSend).toHaveBeenCalledWith({
80
- endpoint,
81
- payload: {},
82
- submitMethod: expect.any(Function)
83
- })
84
- expect(harvestInstance._send).not.toHaveBeenCalled()
85
- })
86
-
87
- test.each([undefined, {}])('should still call _send when spec is %s', async (spec) => {
88
- harvestInstance.sendX(spec)
89
-
90
- expect(harvestInstance._send).toHaveBeenCalledWith({
91
- payload: {},
92
- submitMethod: expect.any(Function)
93
- })
94
- })
95
- })
96
-
97
- describe('send', () => {
98
- beforeEach(() => {
99
- jest.spyOn(harvestInstance, '_send').mockImplementation(jest.fn())
100
- jest.spyOn(harvestInstance, 'obfuscateAndSend').mockImplementation(jest.fn())
101
- })
102
-
103
- test('should pass spec settings on to _send method', async () => {
104
- const endpoint = faker.datatype.uuid()
105
- const spec = {
106
- endpoint,
107
- [faker.datatype.uuid()]: faker.lorem.sentence()
108
- }
109
-
110
- harvestInstance.send(spec)
111
-
112
- expect(harvestInstance._send).toHaveBeenCalledWith(spec)
113
- })
114
-
115
- test('should not use obfuscateAndSend', async () => {
116
- jest.mocked(harvestInstance.obfuscator.shouldObfuscate).mockReturnValue(false)
117
-
118
- const endpoint = faker.datatype.uuid()
119
- harvestInstance.send({ endpoint })
120
-
121
- expect(harvestInstance._send).toHaveBeenCalledWith({
122
- endpoint
123
- })
124
- expect(harvestInstance.obfuscateAndSend).not.toHaveBeenCalled()
125
- })
126
-
127
- test('should use obfuscateAndSend', async () => {
128
- jest.mocked(harvestInstance.obfuscator.shouldObfuscate).mockReturnValue(true)
129
-
130
- const endpoint = faker.datatype.uuid()
131
- harvestInstance.send({ endpoint })
132
-
133
- expect(harvestInstance.obfuscateAndSend).toHaveBeenCalledWith({
134
- endpoint
135
- })
136
- expect(harvestInstance._send).not.toHaveBeenCalled()
137
- })
138
-
139
- test.each([undefined, {}])('should still call _send when spec is %s', async (spec) => {
140
- harvestInstance.send(spec)
141
-
142
- expect(harvestInstance._send).toHaveBeenCalledWith(spec || {})
143
- })
144
- })
145
-
146
- describe('_send', () => {
147
- let errorBeacon
148
- let submitMethod
149
- let spec
150
- let licenseKey
151
-
152
- beforeEach(() => {
153
- errorBeacon = faker.internet.domainName()
154
- licenseKey = faker.datatype.uuid()
155
- jest.mocked(configModule.getInfo).mockReturnValue({
156
- errorBeacon,
157
- licenseKey
158
- })
159
- jest.mocked(configModule.getRuntime).mockReturnValue({
160
- maxBytes: Infinity
161
- })
162
- jest.mocked(configModule.getConfiguration).mockReturnValue({
163
- ssl: undefined,
164
- proxy: {}
165
- })
166
-
167
- spec = {
168
- endpoint: faker.datatype.uuid(),
169
- payload: {
170
- body: {
171
- [faker.datatype.uuid()]: faker.lorem.sentence()
172
- },
173
- qs: {
174
- [faker.datatype.uuid()]: faker.lorem.sentence()
175
- }
176
- },
177
- opts: {}
178
- }
179
- submitMethod = jest.fn().mockReturnValue(true)
180
- jest.mocked(submitDataModule.getSubmitMethod).mockReturnValue(submitMethod)
181
- })
182
-
183
- test('should return false when info.errorBeacon is not defined', () => {
184
- jest.mocked(configModule.getInfo).mockReturnValue({})
185
-
186
- const result = harvestInstance._send(spec)
187
-
188
- expect(result).toEqual(false)
189
- expect(submitMethod).not.toHaveBeenCalled()
190
- })
191
-
192
- test('should return false when body is empty and sendEmptyBody is false', () => {
193
- jest.spyOn(harvestInstance, 'cleanPayload').mockReturnValue({ body: {}, qs: {} })
194
- spec.opts.sendEmptyBody = false
195
- spec.cbFinished = jest.fn()
196
-
197
- const result = harvestInstance._send(spec)
198
-
199
- expect(result).toEqual(false)
200
- expect(submitMethod).not.toHaveBeenCalled()
201
- expect(spec.cbFinished).toHaveBeenCalledWith({ sent: false })
202
- })
203
-
204
- test('should construct the rum url', () => {
205
- spec.endpoint = 'rum'
206
-
207
- const result = harvestInstance._send(spec)
208
-
209
- expect(result).toEqual(true)
210
- expect(submitMethod).toHaveBeenCalledWith({
211
- body: JSON.stringify(spec.payload.body),
212
- headers: [{ key: 'content-type', value: 'text/plain' }],
213
- sync: undefined,
214
- url: expect.stringContaining(`https://${errorBeacon}/1/${licenseKey}?`)
215
- })
216
- })
217
-
218
- test('should construct the non-rum url', () => {
219
- const result = harvestInstance._send(spec)
220
-
221
- expect(result).toEqual(true)
222
- expect(submitMethod).toHaveBeenCalledWith({
223
- body: JSON.stringify(spec.payload.body),
224
- headers: [{ key: 'content-type', value: 'text/plain' }],
225
- sync: undefined,
226
- url: expect.stringContaining(`https://${errorBeacon}/${spec.endpoint}/1/${licenseKey}?`)
227
- })
228
- })
229
-
230
- test('able to use and send to proxy when defined', () => {
231
- jest.mocked(configModule.getConfiguration).mockReturnValue({ proxy: { beacon: 'some_other_string' } })
232
- const result = harvestInstance._send(spec)
233
-
234
- expect(result).toEqual(true)
235
- expect(submitMethod).toHaveBeenCalledWith({
236
- body: JSON.stringify(spec.payload.body),
237
- headers: [{ key: 'content-type', value: 'text/plain' }],
238
- sync: undefined,
239
- url: expect.stringContaining(`https://some_other_string/${spec.endpoint}/1/${licenseKey}?`)
240
- })
241
- })
242
-
243
- test('should use the custom defined url', () => {
244
- spec.customUrl = faker.internet.url()
245
-
246
- const result = harvestInstance._send(spec)
247
-
248
- expect(result).toEqual(true)
249
- expect(submitMethod).toHaveBeenCalledWith({
250
- body: JSON.stringify(spec.payload.body),
251
- headers: [{ key: 'content-type', value: 'text/plain' }],
252
- sync: undefined,
253
- url: expect.stringContaining(`${spec.customUrl}?`)
254
- })
255
- })
256
-
257
- test('should not include the license key or base params in a raw url', () => {
258
- spec.raw = true
259
-
260
- const result = harvestInstance._send(spec)
261
- const queryString = Object.entries(spec.payload.qs)
262
- .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
263
- .join('&')
264
-
265
- expect(result).toEqual(true)
266
- expect(submitMethod).toHaveBeenCalledWith({
267
- body: JSON.stringify(spec.payload.body),
268
- headers: [{ key: 'content-type', value: 'text/plain' }],
269
- sync: undefined,
270
- url: `https://${errorBeacon}/${spec.endpoint}?${queryString}`
271
- })
272
- })
273
-
274
- test('should remove leading ampersand from encoded payload params', () => {
275
- const queryString = Object.entries(spec.payload.qs)
276
- .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
277
- .join('&')
278
-
279
- jest.mocked(encodeModule.obj).mockReturnValue(`&${queryString}`)
280
- spec.raw = true
281
-
282
- const result = harvestInstance._send(spec)
283
-
284
- expect(result).toEqual(true)
285
- expect(submitMethod).toHaveBeenCalledWith({
286
- body: JSON.stringify(spec.payload.body),
287
- headers: [{ key: 'content-type', value: 'text/plain' }],
288
- sync: undefined,
289
- url: `https://${errorBeacon}/${spec.endpoint}?${queryString}`
290
- })
291
- })
292
-
293
- test('should not alter body when gzip qs is present', () => {
294
- spec.payload.qs.attributes += '&content_encoding=gzip'
295
-
296
- const result = harvestInstance._send(spec)
297
-
298
- expect(result).toEqual(true)
299
- expect(submitMethod).toHaveBeenCalledWith({
300
- body: spec.payload.body,
301
- headers: [{ key: 'content-type', value: 'text/plain' }],
302
- sync: undefined,
303
- url: expect.stringContaining(`https://${errorBeacon}/${spec.endpoint}/1/${licenseKey}?`)
304
- })
305
- })
306
-
307
- test('should warn (once) if payload is large', () => {
308
- spec.payload.body = 'x'.repeat(1024 * 1024) // ~1mb string
309
-
310
- const result = harvestInstance._send(spec)
311
-
312
- expect(result).toEqual(true)
313
- expect(warn).toHaveBeenCalledWith(expect.stringContaining('The Browser Agent is attempting to send a very large payload'))
314
- expect(warn).toHaveBeenCalledTimes(1)
315
-
316
- const result2 = harvestInstance._send(spec)
317
- expect(result2).toEqual(true)
318
- expect(warn).toHaveBeenCalledTimes(1)
319
- })
320
-
321
- test('should set body to events when endpoint is events', () => {
322
- spec.endpoint = 'events'
323
- spec.payload.body.e = faker.lorem.sentence()
324
-
325
- const result = harvestInstance._send(spec)
326
-
327
- expect(result).toEqual(true)
328
- expect(submitMethod).toHaveBeenCalledWith({
329
- body: spec.payload.body.e,
330
- headers: [{ key: 'content-type', value: 'text/plain' }],
331
- sync: undefined,
332
- url: expect.stringContaining(`https://${errorBeacon}/${spec.endpoint}/1/${licenseKey}?`)
333
- })
334
- })
335
-
336
- test.each([
337
- null,
338
- undefined,
339
- {},
340
- []
341
- ])('should set body to empty string when %s', (inputBody) => {
342
- spec.opts.sendEmptyBody = true
343
- spec.payload.body = inputBody
344
-
345
- const result = harvestInstance._send(spec)
346
-
347
- expect(result).toEqual(true)
348
- expect(submitMethod).toHaveBeenCalledWith({
349
- body: '',
350
- headers: [{ key: 'content-type', value: 'text/plain' }],
351
- sync: undefined,
352
- url: expect.stringContaining(`https://${errorBeacon}/${spec.endpoint}/1/${licenseKey}?`)
353
- })
354
- })
355
-
356
- test('should add a callback to XHR and call cbCallback when not a final harvest', () => {
357
- jest.mocked(submitDataModule.getSubmitMethod).mockReturnValue(submitDataModule.xhr)
358
- spec.cbFinished = jest.fn()
359
-
360
- const result = harvestInstance._send(spec)
361
- const xhrAddEventListener = jest.mocked(submitDataModule.xhr).mock.results[0].value.addEventListener
362
- const xhrLoadHandler = jest.mocked(xhrAddEventListener).mock.calls[0][1]
363
-
364
- const xhrState = {
365
- status: faker.datatype.uuid()
366
- }
367
- xhrLoadHandler.call(xhrState)
368
-
369
- expect(xhrAddEventListener).toHaveBeenCalledWith('load', expect.any(Function), expect.any(Object))
370
- expect(result).toEqual(jest.mocked(submitDataModule.xhr).mock.results[0].value)
371
- expect(submitMethod).not.toHaveBeenCalled()
372
- expect(spec.cbFinished).toHaveBeenCalledWith({ ...xhrState, sent: true })
373
- })
374
-
375
- test('should set cbFinished state retry to true with delay when xhr has 429 status', () => {
376
- jest.mocked(submitDataModule.getSubmitMethod).mockReturnValue(submitDataModule.xhr)
377
- spec.cbFinished = jest.fn()
378
- harvestInstance.tooManyRequestsDelay = faker.datatype.number({ min: 100, max: 1000 })
379
-
380
- const result = harvestInstance._send(spec)
381
- const xhrAddEventListener = jest.mocked(submitDataModule.xhr).mock.results[0].value.addEventListener
382
- const xhrLoadHandler = jest.mocked(xhrAddEventListener).mock.calls[0][1]
383
-
384
- const xhrState = {
385
- status: 429
386
- }
387
- xhrLoadHandler.call(xhrState)
388
-
389
- expect(xhrAddEventListener).toHaveBeenCalledWith('load', expect.any(Function), expect.any(Object))
390
- expect(result).toEqual(jest.mocked(submitDataModule.xhr).mock.results[0].value)
391
- expect(submitMethod).not.toHaveBeenCalled()
392
- expect(spec.cbFinished).toHaveBeenCalledWith({
393
- ...xhrState,
394
- sent: true,
395
- retry: true,
396
- delay: harvestInstance.tooManyRequestsDelay
397
- })
398
- })
399
-
400
- test.each([
401
- 408, 500, 503
402
- ])('should set cbFinished state retry to true without delay when xhr has %s status', (statusCode) => {
403
- jest.mocked(submitDataModule.getSubmitMethod).mockReturnValue(submitDataModule.xhr)
404
- spec.cbFinished = jest.fn()
405
-
406
- const result = harvestInstance._send(spec)
407
- const xhrAddEventListener = jest.mocked(submitDataModule.xhr).mock.results[0].value.addEventListener
408
- const xhrLoadHandler = jest.mocked(xhrAddEventListener).mock.calls[0][1]
409
-
410
- const xhrState = {
411
- status: statusCode
412
- }
413
- xhrLoadHandler.call(xhrState)
414
-
415
- expect(xhrAddEventListener).toHaveBeenCalledWith('load', expect.any(Function), expect.any(Object))
416
- expect(result).toEqual(jest.mocked(submitDataModule.xhr).mock.results[0].value)
417
- expect(submitMethod).not.toHaveBeenCalled()
418
- expect(spec.cbFinished).toHaveBeenCalledWith({
419
- ...xhrState,
420
- sent: true,
421
- retry: true
422
- })
423
- })
424
-
425
- test('should include response in call to cbFinished when needResponse is true', () => {
426
- jest.mocked(submitDataModule.getSubmitMethod).mockReturnValue(submitDataModule.xhr)
427
- spec.cbFinished = jest.fn()
428
- spec.opts.needResponse = true
429
-
430
- const result = harvestInstance._send(spec)
431
- const xhrAddEventListener = jest.mocked(submitDataModule.xhr).mock.results[0].value.addEventListener
432
- const xhrLoadHandler = jest.mocked(xhrAddEventListener).mock.calls[0][1]
433
-
434
- const xhrState = {
435
- status: faker.datatype.uuid(),
436
- responseText: faker.lorem.sentence()
437
- }
438
- xhrLoadHandler.call(xhrState)
439
-
440
- expect(xhrAddEventListener).toHaveBeenCalledWith('load', expect.any(Function), expect.any(Object))
441
- expect(result).toEqual(jest.mocked(submitDataModule.xhr).mock.results[0].value)
442
- expect(submitMethod).not.toHaveBeenCalled()
443
- expect(spec.cbFinished).toHaveBeenCalledWith({
444
- ...xhrState,
445
- sent: true
446
- })
447
- })
448
-
449
- test('should not include response in call to cbFinished when needResponse is false', () => {
450
- jest.mocked(submitDataModule.getSubmitMethod).mockReturnValue(submitDataModule.xhr)
451
- spec.cbFinished = jest.fn()
452
- spec.opts.needResponse = false
453
-
454
- const result = harvestInstance._send(spec)
455
- const xhrAddEventListener = jest.mocked(submitDataModule.xhr).mock.results[0].value.addEventListener
456
- const xhrLoadHandler = jest.mocked(xhrAddEventListener).mock.calls[0][1]
457
-
458
- const xhrState = {
459
- status: faker.datatype.uuid(),
460
- responseText: faker.lorem.sentence()
461
- }
462
- xhrLoadHandler.call(xhrState)
463
-
464
- expect(xhrAddEventListener).toHaveBeenCalledWith('load', expect.any(Function), expect.any(Object))
465
- expect(result).toEqual(jest.mocked(submitDataModule.xhr).mock.results[0].value)
466
- expect(submitMethod).not.toHaveBeenCalled()
467
- expect(spec.cbFinished).toHaveBeenCalledWith({
468
- ...xhrState,
469
- responseText: undefined,
470
- sent: true
471
- })
472
- })
473
- })
474
-
475
- describe('obfuscateAndSend', () => {
476
- beforeEach(() => {
477
- jest.spyOn(harvestInstance, '_send').mockImplementation(jest.fn())
478
- })
479
-
480
- test('should apply obfuscation to payload', () => {
481
- const payload = {
482
- body: {
483
- foo: faker.lorem.sentence()
484
- },
485
- qs: {
486
- foo: faker.lorem.sentence()
487
- }
488
- }
489
-
490
- harvestInstance.obfuscateAndSend({ payload })
491
-
492
- expect(applyFnToProps).toHaveBeenCalledWith(payload, expect.any(Function), 'string', ['e'])
493
- expect(harvestInstance.obfuscator.obfuscateString).toHaveBeenCalledWith(payload)
494
- })
495
-
496
- test('should still call _send when spec is undefined', () => {
497
- harvestInstance.obfuscateAndSend()
498
-
499
- expect(harvestInstance._send).toHaveBeenCalled()
500
- })
501
-
502
- test.each([
503
- null,
504
- undefined
505
- ])('should still call _send when payload is %s', (payload) => {
506
- harvestInstance.obfuscateAndSend({ payload })
507
-
508
- expect(harvestInstance._send).toHaveBeenCalled()
509
- })
510
- })
511
-
512
- describe('baseQueryString', () => {
513
- beforeEach(() => {
514
- jest.mocked(configModule.getInfo).mockReturnValue({})
515
- jest.mocked(configModule.getRuntime).mockReturnValue({})
516
- })
517
-
518
- test('should construct a string of base query parameters', () => {
519
- const applicationID = faker.datatype.uuid()
520
- const sa = faker.datatype.uuid()
521
- jest.mocked(configModule.getInfo).mockReturnValue({
522
- applicationID,
523
- sa
524
- })
525
- const customTransaction = faker.datatype.uuid()
526
- const ptid = faker.datatype.uuid()
527
- jest.mocked(configModule.getRuntime).mockReturnValue({
528
- customTransaction,
529
- ptid
530
- })
531
-
532
- const results = harvestInstance.baseQueryString()
533
-
534
- expect(results).toContain(`a=${applicationID}`)
535
- expect(encodeModule.param).toHaveBeenCalledWith('sa', sa)
536
- expect(results).toContain(`&sa=${sa}`)
537
- expect(encodeModule.param).toHaveBeenCalledWith('v', expect.stringMatching(/\d{1,3}\.\d{1,3}\.\d{1,3}/))
538
- expect(results).toMatch(/&v=\d{1,3}\.\d{1,3}\.\d{1,3}/)
539
- expect(encodeModule.param).toHaveBeenCalledWith('t', 'Unnamed Transaction')
540
- expect(results).toContain('&t=Unnamed%20Transaction')
541
- expect(encodeModule.param).toHaveBeenCalledWith('ct', customTransaction)
542
- expect(results).toContain(`&ct=${customTransaction}`)
543
- expect(results).toMatch(/&rst=\d{1,9}/)
544
- expect(results).toContain('&ck=0')
545
- expect(results).toContain('&s=0')
546
- expect(encodeModule.param).toHaveBeenCalledWith('ref', location)
547
- expect(results).toContain(`&ref=${encodeURIComponent(location)}`)
548
- expect(encodeModule.param).toHaveBeenCalledWith('ptid', ptid)
549
- expect(results).toContain(`&ptid=${ptid}`)
550
- })
551
-
552
- test('should set t param to info.tNamePlain', () => {
553
- const tNamePlain = faker.datatype.uuid()
554
- jest.mocked(configModule.getInfo).mockReturnValue({
555
- tNamePlain
556
- })
557
-
558
- const results = harvestInstance.baseQueryString()
559
-
560
- expect(encodeModule.param).toHaveBeenCalledWith('t', tNamePlain)
561
- expect(results).toContain(`&t=${tNamePlain}`)
562
- })
563
-
564
- test('should set to param to info.transactionName and exclude t param', () => {
565
- const transactionName = faker.datatype.uuid()
566
- jest.mocked(configModule.getInfo).mockReturnValue({
567
- transactionName
568
- })
569
-
570
- const results = harvestInstance.baseQueryString()
571
-
572
- expect(encodeModule.param).not.toHaveBeenCalledWith('t', expect.any(String))
573
- expect(results).not.toContain('&t=')
574
- expect(encodeModule.param).toHaveBeenCalledWith('to', transactionName)
575
- expect(results).toContain(`&to=${transactionName}`)
576
- })
577
-
578
- test('should default sa to empty string', () => {
579
- const results = harvestInstance.baseQueryString()
580
-
581
- expect(encodeModule.param).toHaveBeenCalledWith('sa', '')
582
- expect(results).toContain('&sa=')
583
- })
584
-
585
- test('should default s to 0', () => {
586
- const results = harvestInstance.baseQueryString()
587
-
588
- expect(results).toContain('&s=0')
589
- })
590
-
591
- test('should obfuscate ref', () => {
592
- const obfuscatedLocation = faker.datatype.uuid()
593
- jest.mocked(harvestInstance.obfuscator.shouldObfuscate).mockReturnValue(true)
594
- jest.mocked(harvestInstance.obfuscator.obfuscateString).mockReturnValue(obfuscatedLocation)
595
-
596
- const results = harvestInstance.baseQueryString()
597
-
598
- expect(harvestInstance.obfuscator.obfuscateString).toHaveBeenCalledWith(location)
599
- expect(results).toContain(`&ref=${obfuscatedLocation}`)
600
- })
601
-
602
- test('should default ptid to empty string', () => {
603
- const results = harvestInstance.baseQueryString()
604
-
605
- expect(encodeModule.param).toHaveBeenCalledWith('ptid', '')
606
- expect(results).toContain('&ptid=')
607
- })
608
- })
609
-
610
- describe('createPayload', () => {
611
- test('should return empty body and qs values when no listeners exist', () => {
612
- const feature = faker.datatype.uuid()
613
- const results = harvestInstance.createPayload(feature)
614
-
615
- expect(results).toEqual({
616
- body: {},
617
- qs: {}
618
- })
619
- })
620
-
621
- test('should pass options to callback', () => {
622
- const feature = faker.datatype.uuid()
623
- const options = {
624
- [faker.datatype.uuid()]: faker.lorem.sentence()
625
- }
626
- const harvestCallback = jest.fn()
627
-
628
- harvestInstance.on(feature, harvestCallback)
629
- const results = harvestInstance.createPayload(feature, options)
630
-
631
- expect(results).toEqual({
632
- body: {},
633
- qs: {}
634
- })
635
- })
636
-
637
- test('should aggregate the body properties of the payload', () => {
638
- const feature = faker.datatype.uuid()
639
- const payloadA = {
640
- body: {
641
- [faker.datatype.uuid()]: {
642
- [faker.datatype.uuid()]: faker.lorem.sentence()
643
- }
644
- }
645
- }
646
- const payloadB = {
647
- body: {
648
- [faker.datatype.uuid()]: {
649
- [faker.datatype.uuid()]: faker.lorem.sentence()
650
- }
651
- }
652
- }
653
- const harvestCallbackA = jest.fn().mockReturnValue(payloadA)
654
- const harvestCallbackB = jest.fn().mockReturnValue(payloadB)
655
-
656
- harvestInstance.on(feature, harvestCallbackA)
657
- harvestInstance.on(feature, harvestCallbackB)
658
- const results = harvestInstance.createPayload(feature)
659
-
660
- expect(results).toEqual({
661
- body: {
662
- ...payloadA.body,
663
- ...payloadB.body
664
- },
665
- qs: {}
666
- })
667
- })
668
-
669
- test('should aggregate the qs properties of the payload', () => {
670
- const feature = faker.datatype.uuid()
671
- const payloadA = {
672
- qs: {
673
- [faker.datatype.uuid()]: {
674
- [faker.datatype.uuid()]: faker.lorem.sentence()
675
- }
676
- }
677
- }
678
- const payloadB = {
679
- qs: {
680
- [faker.datatype.uuid()]: {
681
- [faker.datatype.uuid()]: faker.lorem.sentence()
682
- }
683
- }
684
- }
685
- const harvestCallbackA = jest.fn().mockReturnValue(payloadA)
686
- const harvestCallbackB = jest.fn().mockReturnValue(payloadB)
687
-
688
- harvestInstance.on(feature, harvestCallbackA)
689
- harvestInstance.on(feature, harvestCallbackB)
690
- const results = harvestInstance.createPayload(feature)
691
-
692
- expect(results).toEqual({
693
- body: {},
694
- qs: {
695
- ...payloadA.qs,
696
- ...payloadB.qs
697
- }
698
- })
699
- })
700
-
701
- test('should not deep merge the body and qs properties', () => {
702
- const feature = faker.datatype.uuid()
703
- const bodyProp = faker.datatype.uuid()
704
- const qsProp = faker.datatype.uuid()
705
- const payloadA = {
706
- body: {
707
- [bodyProp]: {
708
- [faker.datatype.uuid()]: faker.lorem.sentence()
709
- }
710
- },
711
- qs: {
712
- [qsProp]: {
713
- [faker.datatype.uuid()]: faker.lorem.sentence()
714
- }
715
- }
716
- }
717
- const payloadB = {
718
- body: {
719
- [bodyProp]: {
720
- [faker.datatype.uuid()]: faker.lorem.sentence()
721
- }
722
- },
723
- qs: {
724
- [qsProp]: {
725
- [faker.datatype.uuid()]: faker.lorem.sentence()
726
- }
727
- }
728
- }
729
- const harvestCallbackA = jest.fn().mockReturnValue(payloadA)
730
- const harvestCallbackB = jest.fn().mockReturnValue(payloadB)
731
-
732
- harvestInstance.on(feature, harvestCallbackA)
733
- harvestInstance.on(feature, harvestCallbackB)
734
- const results = harvestInstance.createPayload(feature)
735
-
736
- expect(results).toEqual({
737
- body: payloadB.body,
738
- qs: payloadB.qs
739
- })
740
- })
741
- })
742
-
743
- describe('cleanPayload', () => {
744
- test('should remove undefined properties from body and qs', () => {
745
- const payload = {
746
- body: {
747
- foo: undefined,
748
- [faker.datatype.uuid()]: faker.lorem.sentence()
749
- },
750
- qs: {
751
- foo: undefined,
752
- [faker.datatype.uuid()]: faker.lorem.sentence()
753
- }
754
- }
755
-
756
- const results = harvestInstance.cleanPayload(payload)
757
-
758
- expect(Object.keys(results.body)).not.toContain('foo')
759
- expect(Object.keys(results.qs)).not.toContain('foo')
760
- })
761
-
762
- test('should remove null properties from body and qs', () => {
763
- const payload = {
764
- body: {
765
- foo: null,
766
- [faker.datatype.uuid()]: faker.lorem.sentence()
767
- },
768
- qs: {
769
- foo: null,
770
- [faker.datatype.uuid()]: faker.lorem.sentence()
771
- }
772
- }
773
-
774
- const results = harvestInstance.cleanPayload(payload)
775
-
776
- expect(Object.keys(results.body)).not.toContain('foo')
777
- expect(Object.keys(results.qs)).not.toContain('foo')
778
- })
779
-
780
- test('should remove empty string properties from body and qs', () => {
781
- const payload = {
782
- body: {
783
- foo: '',
784
- [faker.datatype.uuid()]: faker.lorem.sentence()
785
- },
786
- qs: {
787
- foo: '',
788
- [faker.datatype.uuid()]: faker.lorem.sentence()
789
- }
790
- }
791
-
792
- const results = harvestInstance.cleanPayload(payload)
793
-
794
- expect(Object.keys(results.body)).not.toContain('foo')
795
- expect(Object.keys(results.qs)).not.toContain('foo')
796
- })
797
-
798
- test.each([
799
- { [faker.datatype.uuid()]: { [faker.datatype.uuid()]: faker.datatype.number({ min: 100, max: 1000 }) } },
800
- { [faker.datatype.uuid()]: faker.datatype.number({ min: 100, max: 1000 }) },
801
- { [faker.datatype.uuid()]: new Uint8Array(faker.datatype.number({ min: 100, max: 1000 })) }
802
- ])('should retain %s properties in body and qs', (input) => {
803
- const payload = {
804
- body: input,
805
- qs: input
806
- }
807
-
808
- const results = harvestInstance.cleanPayload(payload)
809
-
810
- expect(results.body).toEqual(input)
811
- expect(results.qs).toEqual(input)
812
- })
813
- })