@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,317 +0,0 @@
1
- import { Aggregate as SessionReplayAgg, AVG_COMPRESSION, MAX_PAYLOAD_SIZE, IDEAL_PAYLOAD_SIZE } from '.'
2
- import { Aggregator } from '../../../common/aggregate/aggregator'
3
- import { SESSION_EVENTS, SessionEntity, MODE } from '../../../common/session/session-entity'
4
- import { setConfiguration } from '../../../common/config/config'
5
- import { configure } from '../../../loaders/configure/configure'
6
-
7
- class LocalMemory {
8
- constructor (initialState = {}) {
9
- this.state = initialState
10
- }
11
-
12
- get (key) {
13
- try {
14
- return this.state[key]
15
- } catch (err) {
16
- // Error is ignored
17
- return ''
18
- }
19
- }
20
-
21
- set (key, value) {
22
- try {
23
- if (value === undefined || value === null) return this.remove(key)
24
- this.state[key] = value
25
- } catch (err) {
26
- // Error is ignored
27
- }
28
- }
29
-
30
- remove (key) {
31
- try {
32
- delete this.state[key]
33
- } catch (err) {
34
- // Error is ignored
35
- }
36
- }
37
- }
38
-
39
- let sr, session
40
- const agentIdentifier = 'abcd'
41
- const info = { licenseKey: 1234, applicationID: 9876 }
42
- const init = { session_replay: { enabled: true, sampling_rate: 100, error_sampling_rate: 0 } }
43
-
44
- const anyQuery = {
45
- browser_monitoring_key: info.licenseKey,
46
- type: 'SessionReplay',
47
- app_id: Number(info.applicationID),
48
- protocol_version: '0',
49
- attributes: expect.any(String)
50
- }
51
-
52
- describe('Session Replay', () => {
53
- beforeEach(async () => {
54
- primeSessionAndReplay()
55
- })
56
- afterEach(async () => {
57
- sr.abort()
58
- jest.clearAllMocks()
59
- })
60
-
61
- describe('Session Replay Session Behavior', () => {
62
- test('When Session Ends', async () => {
63
- const xhrMockClass = () => ({
64
- open: jest.fn(),
65
- send: jest.fn(),
66
- setRequestHeader: jest.fn(),
67
- addEventListener: jest.fn()
68
- })
69
- global.XMLHttpRequest = jest.fn().mockImplementation(xhrMockClass)
70
-
71
- setConfiguration(agentIdentifier, { ...init })
72
- sr.ee.emit('rumresp-sr', [true])
73
- await wait(1)
74
- expect(sr.initialized).toBeTruthy()
75
- expect(sr.recording).toBeTruthy()
76
- sr.ee.emit(SESSION_EVENTS.RESET)
77
- expect(global.XMLHttpRequest).toHaveBeenCalled()
78
- expect(sr.recording).toBeFalsy()
79
- expect(sr.blocked).toBeTruthy()
80
- })
81
-
82
- test('When Session Is Paused/Resumed', async () => {
83
- setConfiguration(agentIdentifier, { ...init })
84
- sr.ee.emit('rumresp-sr', [true])
85
- await wait(1)
86
- expect(sr.initialized).toBeTruthy()
87
- expect(sr.recording).toBeTruthy()
88
- sr.ee.emit(SESSION_EVENTS.PAUSE)
89
- expect(sr.recording).toBeFalsy()
90
- sr.ee.emit(SESSION_EVENTS.RESUME)
91
- expect(sr.recording).toBeTruthy()
92
- })
93
-
94
- test('Session SR mode matches SR mode -- FULL', async () => {
95
- setConfiguration(agentIdentifier, { ...init })
96
- sr.ee.emit('rumresp-sr', [true])
97
- await wait(1)
98
- expect(session.state.sessionReplay).toEqual(sr.mode)
99
- })
100
-
101
- test('Session SR mode matches SR mode -- ERROR', async () => {
102
- setConfiguration(agentIdentifier, { session_replay: { sampling_rate: 0, error_sampling_rate: 100 } })
103
- sr.ee.emit('rumresp-sr', [true])
104
- await wait(1)
105
- expect(session.state.sessionReplay).toEqual(sr.mode)
106
- })
107
-
108
- test('Session SR mode matches SR mode -- OFF', async () => {
109
- setConfiguration(agentIdentifier, { session_replay: { sampling_rate: 0, error_sampling_rate: 0 } })
110
- sr.ee.emit('rumresp-sr', [true])
111
- await wait(1)
112
- expect(session.state.sessionReplay).toEqual(sr.mode)
113
- })
114
- })
115
-
116
- describe('Session Replay Initialization Behavior', () => {
117
- test('Waits for SR', async () => {
118
- setConfiguration(agentIdentifier, { ...init })
119
- // do not emit sr flag
120
- await wait(1000)
121
- expect(sr.initialized).toEqual(false)
122
- expect(sr.recording).toEqual(false)
123
-
124
- // emit a false flag
125
- sr.ee.emit('rumresp-sr', [false])
126
- await wait(1)
127
- expect(sr.initialized).toEqual(true)
128
- expect(sr.recording).toEqual(false)
129
- })
130
-
131
- test('Does not run if cookies_enabled is false', async () => {
132
- setConfiguration(agentIdentifier, { ...init, privacy: { cookies_enabled: false } })
133
- sr = new SessionReplayAgg(agentIdentifier, new Aggregator({}))
134
- sr.ee.emit('rumresp-sr', [true])
135
- await wait(1)
136
- expect(sr.initialized).toEqual(false)
137
- expect(sr.recording).toEqual(false)
138
- })
139
-
140
- test('Does not run if session_trace is disabled', async () => {
141
- setConfiguration(agentIdentifier, { ...init, session_trace: { enabled: false } })
142
- sr = new SessionReplayAgg(agentIdentifier, new Aggregator({}))
143
- sr.ee.emit('rumresp-sr', [true])
144
- await wait(1)
145
- expect(sr.initialized).toEqual(false)
146
- expect(sr.recording).toEqual(false)
147
- })
148
- })
149
-
150
- describe('Session Replay Sample -> Mode Behaviors', () => {
151
- test('New Session -- Full 1 Error 1 === FULL', async () => {
152
- setConfiguration(agentIdentifier, { session_replay: { error_sampling_rate: 100, sampling_rate: 100 } })
153
- sr.ee.emit('rumresp-sr', [true])
154
- await wait(1)
155
- expect(sr.mode).toEqual(MODE.FULL)
156
- })
157
-
158
- test('New Session -- Full 1 Error 0 === FULL', async () => {
159
- setConfiguration(agentIdentifier, { session_replay: { error_sampling_rate: 0, sampling_rate: 100 } })
160
- sr.ee.emit('rumresp-sr', [true])
161
- await wait(1)
162
- expect(sr.mode).toEqual(MODE.FULL)
163
- })
164
-
165
- test('New Session -- Full 0 Error 1 === ERROR', async () => {
166
- setConfiguration(agentIdentifier, { session_replay: { error_sampling_rate: 100, sampling_rate: 0 } })
167
- sr.ee.emit('rumresp-sr', [true])
168
- await wait(1)
169
- expect(sr.mode).toEqual(MODE.ERROR)
170
- })
171
-
172
- test('New Session -- Full 0 Error 0 === OFF', async () => {
173
- setConfiguration(agentIdentifier, { session_replay: { error_sampling_rate: 0, sampling_rate: 0 } })
174
- sr.ee.emit('rumresp-sr', [true])
175
- await wait(1)
176
- expect(sr.mode).toEqual(MODE.OFF)
177
- })
178
-
179
- test('Existing Session -- Should inherit mode from session entity and ignore samples', async () => {
180
- const storage = new LocalMemory({ NRBA_SESSION: { value: 'abcdefghijklmnop', expiresAt: Date.now() + 10000, inactiveAt: Date.now() + 10000, updatedAt: Date.now(), sessionReplay: MODE.FULL, sessionTraceMode: MODE.FULL, custom: {} } })
181
- session = new SessionEntity({ agentIdentifier, key: 'SESSION', storage })
182
- expect(session.isNew).toBeFalsy()
183
- primeSessionAndReplay(session)
184
- // configure to get "error" sample ---> but should inherit FULL from session manager
185
- setConfiguration(agentIdentifier, { session_replay: { error_sampling_rate: 100, sampling_rate: 0 } })
186
- sr.ee.emit('rumresp-sr', [true])
187
- await wait(1)
188
- expect(sr.mode).toEqual(MODE.FULL)
189
- })
190
- })
191
-
192
- describe('Session Replay Error Mode Behaviors', () => {
193
- test('An error BEFORE rrweb import starts running in FULL from beginning', async () => {
194
- setConfiguration(agentIdentifier, { session_replay: { error_sampling_rate: 100, sampling_rate: 0 } })
195
- sr.ee.emit('errorAgg')
196
- sr.ee.emit('rumresp-sr', [true])
197
- await wait(1)
198
- expect(sr.mode).toEqual(MODE.FULL)
199
- expect(sr.scheduler.started).toEqual(true)
200
- })
201
-
202
- test('An error AFTER rrweb import changes mode and starts harvester', async () => {
203
- setConfiguration(agentIdentifier, { session_replay: { error_sampling_rate: 100, sampling_rate: 0 } })
204
- sr.ee.emit('rumresp-sr', [true])
205
- await wait(1)
206
- expect(sr.mode).toEqual(MODE.ERROR)
207
- expect(sr.scheduler.started).toEqual(false)
208
- sr.ee.emit('errorAgg')
209
- expect(sr.mode).toEqual(MODE.FULL)
210
- expect(sr.scheduler.started).toEqual(true)
211
- })
212
- })
213
-
214
- describe('Session Replay Payload Validation', () => {
215
- test('Payload', async () => {
216
- setConfiguration(agentIdentifier, { ...init })
217
- sr.ee.emit('rumresp-sr', [true])
218
- await wait(1)
219
- const harvestContents = sr.getHarvestContents()
220
- // query attrs
221
- expect(harvestContents.qs).toMatchObject(anyQuery)
222
-
223
- expect(harvestContents.body).toEqual(expect.any(Array))
224
-
225
- expect(harvestContents.body.length).toBeGreaterThan(0)
226
- })
227
- })
228
-
229
- describe('Session Replay Harvest Behaviors', () => {
230
- test('Compressed payload is provided to harvester', async () => {
231
- const { gunzipSync, strFromU8 } = await import('fflate')
232
- setConfiguration(agentIdentifier, { ...init })
233
- sr.ee.emit('rumresp-sr', [true])
234
- await wait(1)
235
- const [harvestContents] = sr.prepareHarvest()
236
- expect(harvestContents.qs).toMatchObject(anyQuery)
237
- expect(harvestContents.qs.attributes.includes('content_encoding=gzip')).toEqual(true)
238
- expect(harvestContents.body).toEqual(expect.any(Uint8Array))
239
- expect(JSON.parse(strFromU8(gunzipSync(harvestContents.body)))).toMatchObject(expect.any(Array))
240
- })
241
-
242
- test('Uncompressed payload is provided to harvester', async () => {
243
- jest.doMock('fflate', () => ({
244
- __esModule: true,
245
- gzipSync: jest.fn().mockImplementation(() => { throw new Error() })
246
- }))
247
-
248
- setConfiguration(agentIdentifier, { ...init })
249
- sr.shouldCompress = false
250
- sr.ee.emit('rumresp-sr', [true])
251
- await wait(1)
252
-
253
- const [harvestContents] = sr.prepareHarvest()
254
- expect(harvestContents.qs).toMatchObject({
255
- protocol_version: '0',
256
- browser_monitoring_key: info.licenseKey
257
- })
258
- expect(harvestContents.qs.attributes.includes('content_encoding')).toEqual(false)
259
- expect(harvestContents.body).toEqual(expect.any(Array))
260
- })
261
-
262
- test('Clears the event buffer when staged for harvesting', async () => {
263
- setConfiguration(agentIdentifier, { ...init })
264
- sr.shouldCompress = false
265
- sr.ee.emit('rumresp-sr', [true])
266
- await wait(1)
267
-
268
- sr.prepareHarvest()
269
- expect(sr.events.length).toEqual(0)
270
- })
271
-
272
- test('Harvests early if exceeds limit', async () => {
273
- let after = 0
274
- const spy = jest.spyOn(sr.scheduler, 'runHarvest').mockImplementation(() => { after = Date.now() })
275
- setConfiguration(agentIdentifier, { ...init })
276
- sr.payloadBytesEstimation = IDEAL_PAYLOAD_SIZE / AVG_COMPRESSION
277
- const before = Date.now()
278
- sr.ee.emit('rumresp-sr', [true])
279
- await wait(1)
280
- expect(spy).toHaveBeenCalled()
281
- expect(after - before).toBeLessThan(sr.harvestTimeSeconds * 1000)
282
- })
283
-
284
- test('Aborts if exceeds total limit', async () => {
285
- const spy = jest.spyOn(sr.scheduler, 'runHarvest')
286
- setConfiguration(agentIdentifier, { ...init })
287
- sr.payloadBytesEstimation = (MAX_PAYLOAD_SIZE + 1) / AVG_COMPRESSION
288
- sr.ee.emit('rumresp-sr', [true])
289
- await wait(1)
290
- expect(spy).not.toHaveBeenCalled()
291
- expect(sr.blocked).toEqual(true)
292
- expect(sr.mode).toEqual(MODE.OFF)
293
- })
294
-
295
- test('Aborts if 429 response', async () => {
296
- setConfiguration(agentIdentifier, { ...init })
297
- sr.ee.emit('rumresp-sr', [true])
298
- await wait(1)
299
- expect(sr.mode).toEqual(MODE.FULL)
300
- sr.onHarvestFinished({ status: 429 })
301
- expect(sr.blocked).toEqual(true)
302
- expect(sr.mode).toEqual(MODE.OFF)
303
- })
304
- })
305
- })
306
-
307
- function wait (ms = 0) {
308
- return new Promise((resolve) => {
309
- setTimeout(resolve, ms)
310
- })
311
- }
312
-
313
- function primeSessionAndReplay (sess = new SessionEntity({ agentIdentifier, key: 'SESSION', storage: new LocalMemory() })) {
314
- session = sess
315
- configure(agentIdentifier, { info, runtime: { session }, init: {} }, 'test', true)
316
- sr = new SessionReplayAgg(agentIdentifier, new Aggregator({}))
317
- }
@@ -1,17 +0,0 @@
1
- import { faker } from '@faker-js/faker'
2
- import { InteractionNode } from './interaction-node'
3
-
4
- test('finishing node with cancelled parent should not throw an error', () => {
5
- const interaction = {
6
- remaining: 0,
7
- onNodeAdded: jest.fn(),
8
- checkFinish: jest.fn()
9
- }
10
- const interactionRootNode = new InteractionNode(interaction, null, 'interaction', faker.date.past().getUTCSeconds())
11
- const interactionNode = interactionRootNode.child('test', faker.date.past().getUTCSeconds(), 'test', false)
12
-
13
- interactionRootNode.cancel()
14
- interactionNode.finish()
15
-
16
- expect(interactionRootNode.children.length).toEqual(0)
17
- })
@@ -1,194 +0,0 @@
1
- import { faker } from '@faker-js/faker'
2
-
3
- let agentIdentifier
4
- let agentSession
5
- let mockEE
6
-
7
- beforeEach(() => {
8
- agentIdentifier = faker.datatype.uuid()
9
- mockEE = { [faker.datatype.uuid()]: faker.lorem.sentence() }
10
- agentSession = {
11
- state: {
12
- [faker.datatype.uuid()]: faker.lorem.sentence()
13
- }
14
- }
15
-
16
- jest.doMock('../../common/config/config', () => ({
17
- __esModule: true,
18
- getConfigurationValue: jest.fn(),
19
- getConfiguration: jest.fn().mockImplementation(() => ({})),
20
- getInfo: jest.fn(),
21
- getRuntime: jest.fn().mockImplementation(() => ({ session: agentSession })),
22
- setInfo: jest.fn()
23
- }))
24
- jest.doMock('../../common/drain/drain', () => ({
25
- __esModule: true,
26
- drain: jest.fn()
27
- }))
28
- jest.doMock('../../common/event-emitter/contextual-ee', () => ({
29
- __esModule: true,
30
- ee: {
31
- get: jest.fn().mockReturnValue(mockEE)
32
- }
33
- }))
34
- jest.mock('../../common/event-emitter/register-handler', () => ({
35
- __esModule: true,
36
- registerHandler: jest.fn()
37
- }))
38
- jest.doMock('../../common/constants/runtime', () => ({
39
- __esModule: true,
40
- isBrowserScope: true
41
- }))
42
- jest.doMock('../../common/session/session-entity', () => ({
43
- __esModule: true,
44
- SessionEntity: jest.fn().mockReturnValue({
45
- state: agentSession.state,
46
- syncCustomAttribute: jest.fn()
47
- })
48
- }))
49
- jest.doMock('../../common/storage/local-storage.js', () => ({
50
- __esModule: true,
51
- LocalStorage: jest.fn()
52
- }))
53
- jest.doMock('../../common/storage/first-party-cookies', () => ({
54
- __esModule: true,
55
- FirstPartyCookies: jest.fn()
56
- }))
57
- })
58
-
59
- afterEach(() => {
60
- jest.resetModules()
61
- })
62
-
63
- test('should register handlers and drain the session feature', async () => {
64
- const { setupAgentSession } = await import('./agent-session')
65
- const result = setupAgentSession(agentIdentifier)
66
-
67
- const { registerHandler } = await import('../../common/event-emitter/register-handler')
68
- const { drain } = await import('../../common/drain/drain')
69
-
70
- expect(result).toEqual(expect.objectContaining(agentSession))
71
- expect(registerHandler).toHaveBeenNthCalledWith(1, 'api-setCustomAttribute', expect.any(Function), 'session', mockEE)
72
- expect(registerHandler).toHaveBeenNthCalledWith(2, 'api-setUserId', expect.any(Function), 'session', mockEE)
73
- expect(drain).toHaveBeenCalledWith(agentIdentifier, 'session')
74
- })
75
-
76
- test('should not drain the session feature more than once', async () => {
77
- const { setupAgentSession } = await import('./agent-session')
78
- const result1 = setupAgentSession(agentIdentifier)
79
- const result2 = setupAgentSession(agentIdentifier)
80
-
81
- const { registerHandler } = await import('../../common/event-emitter/register-handler')
82
- const { drain } = await import('../../common/drain/drain')
83
-
84
- expect(result1).toEqual(expect.objectContaining(agentSession))
85
- expect(result2).toEqual(expect.objectContaining(agentSession))
86
- expect(registerHandler).toHaveBeenCalledTimes(2)
87
- expect(drain).toHaveBeenCalledTimes(1)
88
- })
89
-
90
- test('should use the local storage class and instantiate a new session when cookies are enabled', async () => {
91
- const expiresMs = faker.datatype.number()
92
- const inactiveMs = faker.datatype.number()
93
- const { getConfiguration } = await import('../../common/config/config')
94
- jest.mocked(getConfiguration).mockReturnValue({
95
- session: {
96
- expiresMs,
97
- inactiveMs
98
- }
99
- })
100
-
101
- const { LocalStorage } = await import('../../common/storage/local-storage')
102
- const { SessionEntity } = await import('../../common/session/session-entity')
103
- const { setupAgentSession } = await import('./agent-session')
104
- setupAgentSession(agentIdentifier)
105
-
106
- expect(LocalStorage).toHaveBeenCalledTimes(1)
107
- expect(SessionEntity).toHaveBeenCalledWith({
108
- agentIdentifier,
109
- key: 'SESSION',
110
- storage: expect.any(LocalStorage),
111
- expiresMs,
112
- inactiveMs
113
- })
114
- })
115
-
116
- test('should use the first party cookie storage class and instantiate a new session when cookies are enabled and a domain is set', async () => {
117
- const expiresMs = faker.datatype.number()
118
- const inactiveMs = faker.datatype.number()
119
- const domain = faker.internet.domainName()
120
- const { getConfiguration } = await import('../../common/config/config')
121
- jest.mocked(getConfiguration).mockReturnValue({
122
- session: {
123
- expiresMs,
124
- inactiveMs,
125
- domain
126
- }
127
- })
128
-
129
- const { FirstPartyCookies } = await import('../../common/storage/first-party-cookies')
130
- const { SessionEntity } = await import('../../common/session/session-entity')
131
- const { setupAgentSession } = await import('./agent-session')
132
- setupAgentSession(agentIdentifier)
133
-
134
- expect(FirstPartyCookies).toHaveBeenCalledTimes(1)
135
- expect(SessionEntity).toHaveBeenCalledWith({
136
- agentIdentifier,
137
- key: 'SESSION',
138
- storage: expect.any(FirstPartyCookies),
139
- expiresMs,
140
- inactiveMs
141
- })
142
- })
143
-
144
- test('should set custom session data', async () => {
145
- const { getInfo } = await import('../../common/config/config')
146
- const agentInfo = {
147
- [faker.datatype.uuid()]: faker.lorem.sentence(),
148
- jsAttributes: {
149
- [faker.datatype.uuid()]: faker.lorem.sentence()
150
- }
151
- }
152
- jest.mocked(getInfo).mockReturnValue(agentInfo)
153
- agentSession.state.custom = {
154
- [faker.datatype.uuid()]: faker.lorem.sentence()
155
- }
156
-
157
- const { setupAgentSession } = await import('./agent-session')
158
- setupAgentSession(agentIdentifier)
159
-
160
- expect(agentInfo.jsAttributes).toEqual(expect.objectContaining(agentSession.state.custom))
161
- })
162
-
163
- test('should not set custom session data in worker scope', async () => {
164
- const globalScope = await import('../../common/constants/runtime')
165
- jest.replaceProperty(globalScope, 'isBrowserScope', false)
166
-
167
- const { setInfo } = await import('../../common/config/config')
168
- const { setupAgentSession } = await import('./agent-session')
169
- setupAgentSession(agentIdentifier)
170
-
171
- expect(setInfo).not.toHaveBeenCalled()
172
- })
173
-
174
- test('should sync custom attributes', async () => {
175
- const { registerHandler } = await import('../../common/event-emitter/register-handler')
176
- agentSession.syncCustomAttribute = jest.fn()
177
-
178
- const customProps = [
179
- [faker.date.recent(), faker.datatype.uuid(), faker.lorem.sentence()],
180
- [faker.date.recent(), faker.datatype.uuid(), faker.lorem.sentence()]
181
- ]
182
-
183
- const { setupAgentSession } = await import('./agent-session')
184
- const retVal = setupAgentSession(agentIdentifier)
185
-
186
- const setCustomAttributeHandler = jest.mocked(registerHandler).mock.calls[0][1]
187
- const setUserIdHandler = jest.mocked(registerHandler).mock.calls[1][1]
188
-
189
- setCustomAttributeHandler(...customProps[0])
190
- setUserIdHandler(...customProps[1])
191
-
192
- expect(retVal.syncCustomAttribute).toHaveBeenNthCalledWith(1, customProps[0][1], customProps[0][2])
193
- expect(retVal.syncCustomAttribute).toHaveBeenNthCalledWith(2, customProps[1][1], customProps[1][2])
194
- })
@@ -1,123 +0,0 @@
1
- import { faker } from '@faker-js/faker'
2
- import { AggregateBase } from './aggregate-base'
3
- import { registerHandler } from '../../common/event-emitter/register-handler'
4
- import { getInfo, isConfigured, getRuntime } from '../../common/config/config'
5
- import { configure } from '../../loaders/configure/configure'
6
- import { gosCDN } from '../../common/window/nreum'
7
-
8
- jest.enableAutomock()
9
- jest.unmock('./aggregate-base')
10
- jest.mock('./feature-base', () => ({
11
- __esModule: true,
12
- FeatureBase: jest.fn(function (...args) {
13
- this.agentIdentifier = args[0]
14
- this.aggregator = args[1]
15
- this.featureName = args[2]
16
- })
17
- }))
18
- jest.mock('../../common/event-emitter/register-handler', () => ({
19
- __esModule: true,
20
- registerHandler: jest.fn()
21
- }))
22
- jest.mock('../../common/config/config', () => ({
23
- __esModule: true,
24
- getInfo: jest.fn(),
25
- isConfigured: jest.fn().mockReturnValue(false),
26
- getRuntime: jest.fn()
27
- }))
28
- jest.mock('../../loaders/configure/configure', () => ({
29
- __esModule: true,
30
- configure: jest.fn()
31
- }))
32
- jest.mock('../../common/window/nreum', () => ({
33
- __esModule: true,
34
- gosCDN: jest.fn().mockReturnValue({}),
35
- gosNREUM: jest.fn().mockReturnValue({})
36
- }))
37
-
38
- let agentIdentifier
39
- let aggregator
40
- let featureName
41
-
42
- beforeEach(() => {
43
- agentIdentifier = faker.datatype.uuid()
44
- aggregator = {}
45
- featureName = faker.datatype.uuid()
46
- })
47
-
48
- test('should merge info, jsattributes, and runtime objects', () => {
49
- const mockInfo1 = {
50
- [faker.datatype.uuid()]: faker.lorem.sentence(),
51
- jsAttributes: {
52
- [faker.datatype.uuid()]: faker.lorem.sentence()
53
- }
54
- }
55
- jest.mocked(gosCDN).mockReturnValue({ info: mockInfo1 })
56
-
57
- const mockInfo2 = {
58
- jsAttributes: {
59
- [faker.datatype.uuid()]: faker.lorem.sentence()
60
- }
61
- }
62
- jest.mocked(getInfo).mockReturnValue(mockInfo2)
63
-
64
- const mockRuntime = {
65
- [faker.datatype.uuid()]: faker.lorem.sentence()
66
- }
67
- jest.mocked(getRuntime).mockReturnValue(mockRuntime)
68
-
69
- new AggregateBase(agentIdentifier, aggregator, featureName)
70
-
71
- expect(isConfigured).toHaveBeenCalledWith(agentIdentifier)
72
- expect(gosCDN).toHaveBeenCalledTimes(3)
73
- expect(getInfo).toHaveBeenCalledWith(agentIdentifier)
74
- expect(getRuntime).toHaveBeenCalledWith(agentIdentifier)
75
- expect(configure).toHaveBeenCalledWith(agentIdentifier, {
76
- info: {
77
- ...mockInfo1,
78
- jsAttributes: {
79
- ...mockInfo1.jsAttributes,
80
- ...mockInfo2.jsAttributes
81
- }
82
- },
83
- runtime: mockRuntime
84
- })
85
- })
86
-
87
- test('should only configure the agent once', () => {
88
- jest.mocked(isConfigured).mockReturnValue(true)
89
-
90
- new AggregateBase(agentIdentifier, aggregator, featureName)
91
-
92
- expect(isConfigured).toHaveBeenCalledWith(agentIdentifier)
93
- expect(gosCDN).not.toHaveBeenCalled()
94
- expect(getInfo).not.toHaveBeenCalled()
95
- expect(getRuntime).not.toHaveBeenCalled()
96
- expect(configure).not.toHaveBeenCalled()
97
- })
98
-
99
- test('should resolve waitForFlags correctly based on flags', async () => {
100
- const flagNames = [faker.datatype.uuid(), faker.datatype.uuid()]
101
- const aggregateBase = new AggregateBase(agentIdentifier, aggregator, featureName)
102
- aggregateBase.ee = {
103
- [faker.datatype.uuid()]: faker.lorem.sentence()
104
- }
105
- aggregateBase.feature = {
106
- [faker.datatype.uuid()]: faker.lorem.sentence()
107
- }
108
-
109
- const flagWait = aggregateBase.waitForFlags(flagNames)
110
- jest.mocked(registerHandler).mock.calls[0][1](true)
111
- jest.mocked(registerHandler).mock.calls[1][1](false)
112
-
113
- expect(registerHandler).toHaveBeenCalledWith(`rumresp-${flagNames[0]}`, expect.any(Function), featureName, aggregateBase.ee)
114
- expect(registerHandler).toHaveBeenCalledWith(`rumresp-${flagNames[1]}`, expect.any(Function), featureName, aggregateBase.ee)
115
- await expect(flagWait).resolves.toEqual([true, false])
116
- })
117
-
118
- test('should not register any handlers when flagNames is empty', async () => {
119
- const aggregateBase = new AggregateBase(agentIdentifier, aggregator, featureName)
120
-
121
- await expect(aggregateBase.waitForFlags()).resolves.toEqual([])
122
- expect(registerHandler).not.toHaveBeenCalled()
123
- })
@@ -1,45 +0,0 @@
1
- import { faker } from '@faker-js/faker'
2
- import { FeatureBase } from './feature-base'
3
- import { getRuntime } from '../../common/config/config'
4
- import { ee } from '../../common/event-emitter/contextual-ee'
5
-
6
- jest.enableAutomock()
7
- jest.unmock('./feature-base')
8
- jest.mock('../../common/config/config', () => ({
9
- __esModule: true,
10
- getRuntime: jest.fn().mockReturnValue({
11
- isolatedBacklog: true
12
- })
13
- }))
14
- jest.mock('../../common/event-emitter/contextual-ee', () => ({
15
- __esModule: true,
16
- ee: {
17
- get: jest.fn()
18
- }
19
- }))
20
-
21
- let agentIdentifier
22
- let aggregator
23
- let featureName
24
-
25
- beforeEach(() => {
26
- agentIdentifier = faker.datatype.uuid()
27
- aggregator = {}
28
- featureName = faker.datatype.uuid()
29
- })
30
-
31
- it('should set instance defaults', () => {
32
- const mockEE = { [faker.datatype.uuid()]: faker.lorem.sentence() }
33
- jest.mocked(ee.get).mockReturnValue(mockEE)
34
-
35
- const feature = new FeatureBase(agentIdentifier, aggregator, featureName)
36
-
37
- expect(feature.agentIdentifier).toEqual(agentIdentifier)
38
- expect(feature.aggregator).toEqual(aggregator)
39
- expect(feature.featureName).toEqual(featureName)
40
- expect(feature.blocked).toEqual(false)
41
- expect(feature.ee).toEqual(mockEE)
42
-
43
- expect(getRuntime).toHaveBeenCalledWith(agentIdentifier)
44
- expect(ee.get).toHaveBeenCalledWith(agentIdentifier, true)
45
- })