@newrelic/browser-agent 1.242.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 (111) hide show
  1. package/CHANGELOG.md +1465 -0
  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/session/session-entity.js +20 -2
  5. package/dist/cjs/common/wrap/wrap-function.js +1 -1
  6. package/dist/cjs/features/ajax/aggregate/index.js +1 -1
  7. package/dist/cjs/features/session_replay/aggregate/index.js +84 -54
  8. package/dist/cjs/features/utils/feature-base.js +1 -2
  9. package/dist/cjs/loaders/api/api.js +2 -2
  10. package/dist/cjs/loaders/api/apiAsync.js +0 -37
  11. package/dist/cjs/loaders/configure/configure.js +1 -1
  12. package/dist/cjs/loaders/configure/public-path.js +6 -3
  13. package/dist/esm/common/constants/env.cdn.js +1 -1
  14. package/dist/esm/common/constants/env.npm.js +1 -1
  15. package/dist/esm/common/session/session-entity.js +18 -1
  16. package/dist/esm/common/wrap/wrap-function.js +1 -1
  17. package/dist/esm/features/ajax/aggregate/index.js +1 -1
  18. package/dist/esm/features/session_replay/aggregate/index.js +83 -54
  19. package/dist/esm/features/utils/feature-base.js +1 -2
  20. package/dist/esm/loaders/api/api.js +2 -2
  21. package/dist/esm/loaders/api/apiAsync.js +1 -36
  22. package/dist/esm/loaders/configure/configure.js +1 -1
  23. package/dist/esm/loaders/configure/public-path.js +6 -3
  24. package/dist/types/common/session/session-entity.d.ts +5 -0
  25. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  26. package/dist/types/features/session_replay/aggregate/index.d.ts +11 -14
  27. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  28. package/dist/types/features/utils/feature-base.d.ts.map +1 -1
  29. package/dist/types/loaders/api/api.d.ts.map +1 -1
  30. package/dist/types/loaders/api/apiAsync.d.ts.map +1 -1
  31. package/dist/types/loaders/configure/public-path.d.ts +1 -1
  32. package/dist/types/loaders/configure/public-path.d.ts.map +1 -1
  33. package/package.json +2 -2
  34. package/src/common/session/session-entity.js +20 -1
  35. package/src/common/wrap/wrap-function.js +1 -1
  36. package/src/features/ajax/aggregate/index.js +2 -2
  37. package/src/features/session_replay/aggregate/index.js +81 -37
  38. package/src/features/utils/feature-base.js +1 -2
  39. package/src/loaders/api/api.js +1 -2
  40. package/src/loaders/api/apiAsync.js +1 -39
  41. package/src/loaders/configure/configure.js +1 -1
  42. package/src/loaders/configure/public-path.js +6 -3
  43. package/src/common/aggregate/aggregator.test.js +0 -107
  44. package/src/common/config/state/configurable.test.js +0 -73
  45. package/src/common/config/state/info.test.js +0 -31
  46. package/src/common/config/state/init.test.js +0 -68
  47. package/src/common/config/state/loader-config.test.js +0 -21
  48. package/src/common/config/state/runtime.test.js +0 -21
  49. package/src/common/constants/env.cdn.test.js +0 -7
  50. package/src/common/constants/env.npm.test.js +0 -7
  51. package/src/common/constants/env.test.js +0 -7
  52. package/src/common/constants/runtime.test.js +0 -176
  53. package/src/common/deny-list/deny-list.test.js +0 -104
  54. package/src/common/dom/query-selector.test.js +0 -24
  55. package/src/common/drain/drain.test.js +0 -74
  56. package/src/common/event-emitter/contextual-ee.component-test.js +0 -293
  57. package/src/common/event-emitter/handle.test.js +0 -56
  58. package/src/common/event-emitter/register-handler.test.js +0 -61
  59. package/src/common/harvest/harvest-scheduler.test.js +0 -492
  60. package/src/common/harvest/harvest.test.js +0 -813
  61. package/src/common/ids/id.test.js +0 -92
  62. package/src/common/ids/unique-id.test.js +0 -58
  63. package/src/common/session/session-entity.component-test.js +0 -346
  64. package/src/common/storage/local-storage.test.js +0 -17
  65. package/src/common/timer/interaction-timer.component-test.js +0 -212
  66. package/src/common/timer/timer.test.js +0 -99
  67. package/src/common/timing/nav-timing.test.js +0 -161
  68. package/src/common/url/canonicalize-url.test.js +0 -45
  69. package/src/common/url/clean-url.test.js +0 -25
  70. package/src/common/url/encode.test.js +0 -81
  71. package/src/common/url/location.test.js +0 -15
  72. package/src/common/url/parse-url.test.js +0 -110
  73. package/src/common/url/protocol.test.js +0 -17
  74. package/src/common/util/console.test.js +0 -34
  75. package/src/common/util/data-size.test.js +0 -56
  76. package/src/common/util/feature-flags.test.js +0 -94
  77. package/src/common/util/get-or-set.test.js +0 -58
  78. package/src/common/util/invoke.test.js +0 -65
  79. package/src/common/util/map-own.test.js +0 -52
  80. package/src/common/util/obfuscate.component-test.js +0 -173
  81. package/src/common/util/stringify.test.js +0 -49
  82. package/src/common/util/submit-data.test.js +0 -183
  83. package/src/common/util/traverse.test.js +0 -50
  84. package/src/common/vitals/cumulative-layout-shift.test.js +0 -71
  85. package/src/common/vitals/first-contentful-paint.test.js +0 -124
  86. package/src/common/vitals/first-input-delay.test.js +0 -88
  87. package/src/common/vitals/first-paint.test.js +0 -127
  88. package/src/common/vitals/interaction-to-next-paint.test.js +0 -74
  89. package/src/common/vitals/largest-contentful-paint.test.js +0 -94
  90. package/src/common/vitals/long-task.test.js +0 -122
  91. package/src/common/vitals/time-to-first-byte.test.js +0 -147
  92. package/src/common/vitals/vital-metric.test.js +0 -171
  93. package/src/common/wrap/wrap-promise.component-test.js +0 -110
  94. package/src/features/ajax/instrument/distributed-tracing.test.js +0 -375
  95. package/src/features/jserrors/aggregate/canonical-function-name.test.js +0 -13
  96. package/src/features/jserrors/aggregate/compute-stack-trace.test.js +0 -414
  97. package/src/features/jserrors/aggregate/format-stack-trace.test.js +0 -39
  98. package/src/features/jserrors/aggregate/string-hash-code.test.js +0 -12
  99. package/src/features/metrics/aggregate/framework-detection.test.js +0 -332
  100. package/src/features/page_view_timing/aggregate/index.component-test.js +0 -86
  101. package/src/features/session_replay/aggregate/index.component-test.js +0 -317
  102. package/src/features/spa/aggregate/interaction-node.test.js +0 -17
  103. package/src/features/utils/agent-session.test.js +0 -194
  104. package/src/features/utils/aggregate-base.test.js +0 -123
  105. package/src/features/utils/feature-base.test.js +0 -45
  106. package/src/features/utils/handler-cache.test.js +0 -72
  107. package/src/features/utils/instrument-base.test.js +0 -216
  108. package/src/features/utils/lazy-feature-loader.test.js +0 -37
  109. package/src/loaders/api/api.component-test.js +0 -45
  110. package/src/loaders/api/api.test.js +0 -85
  111. 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
- })