@openobserve/browser-core 0.2.12-beta.26 → 0.2.12-beta.28

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 (88) hide show
  1. package/cjs/boot/init.js +1 -1
  2. package/cjs/domain/configuration/endpointBuilder.js +1 -9
  3. package/cjs/domain/configuration/endpointBuilder.js.map +1 -1
  4. package/cjs/domain/tags.js +1 -1
  5. package/cjs/domain/telemetry/telemetry.js +1 -1
  6. package/cjs/transport/httpRequest.js +0 -4
  7. package/cjs/transport/httpRequest.js.map +1 -1
  8. package/esm/boot/init.js +1 -1
  9. package/esm/domain/configuration/endpointBuilder.js +1 -9
  10. package/esm/domain/configuration/endpointBuilder.js.map +1 -1
  11. package/esm/domain/tags.js +1 -1
  12. package/esm/domain/telemetry/telemetry.js +1 -1
  13. package/esm/transport/httpRequest.js +0 -4
  14. package/esm/transport/httpRequest.js.map +1 -1
  15. package/package.json +2 -2
  16. package/src/boot/displayAlreadyInitializedError.spec.ts +18 -0
  17. package/src/boot/init.spec.ts +50 -0
  18. package/src/browser/addEventListener.spec.ts +141 -0
  19. package/src/browser/cookie.spec.ts +53 -0
  20. package/src/browser/fetchObservable.spec.ts +334 -0
  21. package/src/browser/pageMayExitObservable.spec.ts +56 -0
  22. package/src/browser/xhrObservable.spec.ts +405 -0
  23. package/src/domain/allowedTrackingOrigins.spec.ts +210 -0
  24. package/src/domain/bufferedData.spec.ts +34 -0
  25. package/src/domain/configuration/configuration.spec.ts +236 -0
  26. package/src/domain/configuration/endpointBuilder.spec.ts +173 -0
  27. package/src/domain/configuration/endpointBuilder.ts +1 -9
  28. package/src/domain/configuration/transportConfiguration.spec.ts +130 -0
  29. package/src/domain/connectivity/connectivity.spec.ts +50 -0
  30. package/src/domain/console/consoleObservable.spec.ts +151 -0
  31. package/src/domain/context/contextManager.spec.ts +152 -0
  32. package/src/domain/context/contextUtils.spec.ts +26 -0
  33. package/src/domain/context/storeContextManager.spec.ts +103 -0
  34. package/src/domain/contexts/accountContext.spec.ts +99 -0
  35. package/src/domain/contexts/globalContext.spec.ts +88 -0
  36. package/src/domain/contexts/userContext.spec.ts +156 -0
  37. package/src/domain/error/error.spec.ts +294 -0
  38. package/src/domain/error/trackRuntimeError.spec.ts +343 -0
  39. package/src/domain/eventRateLimiter/createEventRateLimiter.spec.ts +119 -0
  40. package/src/domain/extension/extensionUtils.spec.ts +47 -0
  41. package/src/domain/report/reportObservable.spec.ts +71 -0
  42. package/src/domain/session/oldCookiesMigration.spec.ts +76 -0
  43. package/src/domain/session/sessionManager.spec.ts +684 -0
  44. package/src/domain/session/sessionState.spec.ts +88 -0
  45. package/src/domain/session/sessionStore.spec.ts +647 -0
  46. package/src/domain/session/sessionStoreOperations.spec.ts +234 -0
  47. package/src/domain/session/storeStrategies/sessionInCookie.spec.ts +228 -0
  48. package/src/domain/session/storeStrategies/sessionInLocalStorage.spec.ts +75 -0
  49. package/src/domain/synthetics/syntheticsWorkerValues.spec.ts +82 -0
  50. package/src/domain/tags.spec.ts +74 -0
  51. package/src/domain/telemetry/telemetry.spec.ts +494 -0
  52. package/src/domain/trackingConsent.spec.ts +44 -0
  53. package/src/tools/abstractHooks.spec.ts +76 -0
  54. package/src/tools/abstractLifeCycle.spec.ts +46 -0
  55. package/src/tools/boundedBuffer.spec.ts +40 -0
  56. package/src/tools/catchUserErrors.spec.ts +20 -0
  57. package/src/tools/encoder.spec.ts +112 -0
  58. package/src/tools/experimentalFeatures.spec.ts +60 -0
  59. package/src/tools/getZoneJsOriginalValue.spec.ts +36 -0
  60. package/src/tools/instrumentMethod.spec.ts +381 -0
  61. package/src/tools/matchOption.spec.ts +41 -0
  62. package/src/tools/mergeInto.spec.ts +198 -0
  63. package/src/tools/monitor.spec.ts +172 -0
  64. package/src/tools/observable.spec.ts +259 -0
  65. package/src/tools/queueMicrotask.spec.ts +24 -0
  66. package/src/tools/readBytesFromStream.spec.ts +62 -0
  67. package/src/tools/requestIdleCallback.spec.ts +65 -0
  68. package/src/tools/serialisation/jsonStringify.spec.ts +76 -0
  69. package/src/tools/serialisation/sanitize.spec.ts +284 -0
  70. package/src/tools/stackTrace/capturedExceptions.specHelper.ts +316 -0
  71. package/src/tools/stackTrace/computeStackTrace.spec.ts +1007 -0
  72. package/src/tools/stackTrace/handlingStack.spec.ts +20 -0
  73. package/src/tools/taskQueue.spec.ts +60 -0
  74. package/src/tools/timer.spec.ts +76 -0
  75. package/src/tools/utils/browserDetection.spec.ts +120 -0
  76. package/src/tools/utils/byteUtils.spec.ts +29 -0
  77. package/src/tools/utils/functionUtils.spec.ts +229 -0
  78. package/src/tools/utils/numberUtils.spec.ts +27 -0
  79. package/src/tools/utils/stringUtils.spec.ts +74 -0
  80. package/src/tools/utils/typeUtils.spec.ts +25 -0
  81. package/src/tools/utils/urlPolyfill.spec.ts +55 -0
  82. package/src/tools/valueHistory.spec.ts +180 -0
  83. package/src/transport/batch.spec.ts +261 -0
  84. package/src/transport/eventBridge.spec.ts +90 -0
  85. package/src/transport/flushController.spec.ts +267 -0
  86. package/src/transport/httpRequest.spec.ts +400 -0
  87. package/src/transport/httpRequest.ts +3 -7
  88. package/src/transport/sendWithRetryStrategy.spec.ts +393 -0
@@ -0,0 +1,74 @@
1
+ import { display } from '../tools/display'
2
+ import type { Configuration } from './configuration'
3
+ import { buildTag, buildTags, supportUnicodePropertyEscapes, TAG_SIZE_LIMIT } from './tags'
4
+
5
+ const LARGE_VALUE = Array(TAG_SIZE_LIMIT + 10).join('a')
6
+
7
+ describe('buildTags', () => {
8
+ it('build tags from init configuration', () => {
9
+ expect(
10
+ buildTags({
11
+ service: 'foo',
12
+ env: 'bar',
13
+ version: 'baz',
14
+ datacenter: 'us1.prod.dog',
15
+ } as Configuration)
16
+ ).toEqual(['sdk_version:test', 'env:bar', 'service:foo', 'version:baz', 'datacenter:us1.prod.dog'])
17
+ })
18
+ })
19
+
20
+ describe('buildTag warning', () => {
21
+ let displaySpy: jasmine.Spy<typeof display.warn>
22
+ beforeEach(() => {
23
+ if (!supportUnicodePropertyEscapes()) {
24
+ pending('Unicode property escapes are not supported')
25
+ }
26
+
27
+ displaySpy = spyOn(display, 'warn')
28
+ })
29
+ ;(
30
+ [
31
+ [(s: string) => buildTag(s), 'key only'],
32
+ [(s: string) => buildTag(s, 'value'), 'tag key'],
33
+ [(s: string) => buildTag('env', s), 'tag value'],
34
+ ] as const
35
+ ).forEach(([tagBuilder, description]) => {
36
+ describe(description, () => {
37
+ it('shows a warning when the tag contains uppercase letters', () => {
38
+ tagBuilder('BaR')
39
+ expectWarning()
40
+ })
41
+
42
+ it('shows a warning when the tag is too large', () => {
43
+ tagBuilder(LARGE_VALUE)
44
+ expectWarning()
45
+ })
46
+
47
+ it('shows a warning when the tag contains forbidden characters', () => {
48
+ tagBuilder('b#r')
49
+ expectWarning()
50
+ })
51
+
52
+ it('shows a warning when using non latin uppercase letters like in Greek', () => {
53
+ tagBuilder('Δοκιμή')
54
+ expectWarning()
55
+ })
56
+
57
+ it('do not shows a warning when non latin characters are neither uppercase or lowercase (p{Lo}) like Japanese', () => {
58
+ tagBuilder('てすと')
59
+ expect(displaySpy).not.toHaveBeenCalled()
60
+ })
61
+
62
+ it('forbids to craft multiple tags by passing a value with a comma', () => {
63
+ expect(tagBuilder('foo,bar')).toContain('foo_bar')
64
+ expectWarning()
65
+ })
66
+ })
67
+ })
68
+
69
+ function expectWarning() {
70
+ expect(displaySpy).toHaveBeenCalledOnceWith(
71
+ jasmine.stringMatching("Tag .* doesn't meet tag requirements and will be sanitized")
72
+ )
73
+ }
74
+ })
@@ -0,0 +1,494 @@
1
+ import { NO_ERROR_STACK_PRESENT_MESSAGE } from '../error/error'
2
+ import { callMonitored } from '../../tools/monitor'
3
+ import type { ExperimentalFeature } from '../../tools/experimentalFeatures'
4
+ import { resetExperimentalFeatures, addExperimentalFeatures } from '../../tools/experimentalFeatures'
5
+ import type { Configuration } from '../configuration'
6
+ import { INTAKE_SITE_US1_FED, INTAKE_SITE_US1 } from '../intakeSites'
7
+ import { setNavigatorOnLine, setNavigatorConnection, createHooks, waitNextMicrotask } from '../../../test'
8
+ import type { Context } from '../../tools/serialisation/context'
9
+ import { Observable } from '../../tools/observable'
10
+ import type { StackTrace } from '../../tools/stackTrace/computeStackTrace'
11
+ import { HookNames } from '../../tools/abstractHooks'
12
+ import {
13
+ addTelemetryError,
14
+ resetTelemetry,
15
+ scrubCustomerFrames,
16
+ formatError,
17
+ addTelemetryConfiguration,
18
+ addTelemetryUsage,
19
+ TelemetryService,
20
+ startTelemetryCollection,
21
+ addTelemetryMetrics,
22
+ addTelemetryDebug,
23
+ TelemetryMetrics,
24
+ } from './telemetry'
25
+ import type { TelemetryEvent } from './telemetryEvent.types'
26
+ import { StatusType, TelemetryType } from './rawTelemetryEvent.types'
27
+
28
+ function startAndSpyTelemetry(
29
+ configuration?: Partial<Configuration>,
30
+ {
31
+ metricSampleRate,
32
+ maxTelemetryEventsPerPage,
33
+ }: { metricSampleRate?: number; maxTelemetryEventsPerPage?: number } = {}
34
+ ) {
35
+ const observable = new Observable<TelemetryEvent & Context>()
36
+
37
+ const events: TelemetryEvent[] = []
38
+ observable.subscribe((event) => events.push(event))
39
+ const hooks = createHooks()
40
+ const telemetry = startTelemetryCollection(
41
+ TelemetryService.RUM,
42
+ {
43
+ telemetrySampleRate: 100,
44
+ telemetryUsageSampleRate: 100,
45
+ ...configuration,
46
+ } as Configuration,
47
+ hooks,
48
+ observable,
49
+ metricSampleRate,
50
+ maxTelemetryEventsPerPage
51
+ )
52
+
53
+ return {
54
+ getTelemetryEvents: async () => {
55
+ await waitNextMicrotask()
56
+ return events
57
+ },
58
+ telemetry,
59
+ hooks,
60
+ }
61
+ }
62
+
63
+ describe('telemetry', () => {
64
+ afterEach(() => {
65
+ resetTelemetry()
66
+ })
67
+
68
+ it('collects "monitor" errors', async () => {
69
+ const { getTelemetryEvents } = startAndSpyTelemetry()
70
+ callMonitored(() => {
71
+ throw new Error('message')
72
+ })
73
+
74
+ expect(await getTelemetryEvents()).toEqual([
75
+ jasmine.objectContaining({
76
+ telemetry: jasmine.objectContaining({
77
+ type: TelemetryType.LOG,
78
+ status: StatusType.error,
79
+ }),
80
+ }),
81
+ ])
82
+ })
83
+
84
+ describe('addTelemetryConfiguration', () => {
85
+ afterEach(() => {
86
+ resetExperimentalFeatures()
87
+ })
88
+
89
+ it('should collects configuration when sampled', async () => {
90
+ const { getTelemetryEvents } = startAndSpyTelemetry({
91
+ telemetrySampleRate: 100,
92
+ telemetryConfigurationSampleRate: 100,
93
+ })
94
+
95
+ addTelemetryConfiguration({})
96
+
97
+ expect(await getTelemetryEvents()).toEqual([
98
+ jasmine.objectContaining({
99
+ telemetry: jasmine.objectContaining({
100
+ type: TelemetryType.CONFIGURATION,
101
+ configuration: jasmine.anything(),
102
+ }),
103
+ }),
104
+ ])
105
+ })
106
+
107
+ it('should not notify configuration when not sampled', async () => {
108
+ const { getTelemetryEvents } = startAndSpyTelemetry({
109
+ telemetrySampleRate: 100,
110
+ telemetryConfigurationSampleRate: 0,
111
+ })
112
+
113
+ addTelemetryConfiguration({})
114
+
115
+ expect(await getTelemetryEvents()).toEqual([])
116
+ })
117
+
118
+ it('should not notify configuration when telemetrySampleRate is 0', async () => {
119
+ const { getTelemetryEvents } = startAndSpyTelemetry({
120
+ telemetrySampleRate: 0,
121
+ telemetryConfigurationSampleRate: 100,
122
+ })
123
+
124
+ addTelemetryConfiguration({})
125
+
126
+ expect(await getTelemetryEvents()).toEqual([])
127
+ })
128
+ })
129
+
130
+ describe('addTelemetryUsage', () => {
131
+ it('should collects usage when sampled', async () => {
132
+ const { getTelemetryEvents } = startAndSpyTelemetry({ telemetrySampleRate: 100, telemetryUsageSampleRate: 100 })
133
+
134
+ addTelemetryUsage({ feature: 'set-tracking-consent', tracking_consent: 'granted' })
135
+
136
+ expect(await getTelemetryEvents()).toEqual([
137
+ jasmine.objectContaining({
138
+ telemetry: jasmine.objectContaining({
139
+ type: TelemetryType.USAGE,
140
+ usage: jasmine.anything(),
141
+ }),
142
+ }),
143
+ ])
144
+ })
145
+
146
+ it('should not notify usage when not sampled', async () => {
147
+ const { getTelemetryEvents } = startAndSpyTelemetry({ telemetrySampleRate: 100, telemetryUsageSampleRate: 0 })
148
+
149
+ addTelemetryUsage({ feature: 'set-tracking-consent', tracking_consent: 'granted' })
150
+
151
+ expect(await getTelemetryEvents()).toEqual([])
152
+ })
153
+
154
+ it('should not notify usage when telemetrySampleRate is 0', async () => {
155
+ const { getTelemetryEvents } = startAndSpyTelemetry({ telemetrySampleRate: 0, telemetryUsageSampleRate: 100 })
156
+
157
+ addTelemetryUsage({ feature: 'set-tracking-consent', tracking_consent: 'granted' })
158
+
159
+ expect(await getTelemetryEvents()).toEqual([])
160
+ })
161
+ })
162
+
163
+ describe('addTelemetryMetrics', () => {
164
+ it('should collect metrics when sampled', async () => {
165
+ const { getTelemetryEvents } = startAndSpyTelemetry({ telemetrySampleRate: 100 }, { metricSampleRate: 100 })
166
+
167
+ addTelemetryMetrics(TelemetryMetrics.CUSTOMER_DATA_METRIC_NAME, { speed: 1000 })
168
+
169
+ expect(await getTelemetryEvents()).toEqual([
170
+ jasmine.objectContaining({
171
+ telemetry: jasmine.objectContaining({
172
+ type: TelemetryType.LOG,
173
+ message: TelemetryMetrics.CUSTOMER_DATA_METRIC_NAME,
174
+ status: StatusType.debug,
175
+ }),
176
+ }),
177
+ ])
178
+ })
179
+
180
+ it('should not notify metrics when telemetry not sampled', async () => {
181
+ const { getTelemetryEvents } = startAndSpyTelemetry({ telemetrySampleRate: 0 }, { metricSampleRate: 100 })
182
+
183
+ addTelemetryMetrics(TelemetryMetrics.CUSTOMER_DATA_METRIC_NAME, { speed: 1000 })
184
+
185
+ expect(await getTelemetryEvents()).toEqual([])
186
+ })
187
+
188
+ it('should not notify metrics when metric not sampled', async () => {
189
+ const { getTelemetryEvents } = startAndSpyTelemetry({ telemetrySampleRate: 100 }, { metricSampleRate: 0 })
190
+
191
+ addTelemetryMetrics(TelemetryMetrics.CUSTOMER_DATA_METRIC_NAME, { speed: 1000 })
192
+
193
+ expect(await getTelemetryEvents()).toEqual([])
194
+ })
195
+ })
196
+
197
+ it('should contains feature flags', async () => {
198
+ addExperimentalFeatures(['foo' as ExperimentalFeature])
199
+ const { getTelemetryEvents } = startAndSpyTelemetry()
200
+ callMonitored(() => {
201
+ throw new Error('message')
202
+ })
203
+
204
+ expect((await getTelemetryEvents())[0].experimental_features).toEqual(['foo'])
205
+ })
206
+
207
+ it('should contains runtime env', async () => {
208
+ const { getTelemetryEvents } = startAndSpyTelemetry()
209
+ callMonitored(() => {
210
+ throw new Error('message')
211
+ })
212
+
213
+ expect((await getTelemetryEvents())[0].telemetry.runtime_env).toEqual({
214
+ is_local_file: jasmine.any(Boolean),
215
+ is_worker: jasmine.any(Boolean),
216
+ })
217
+ })
218
+
219
+ it('should contain connectivity information', async () => {
220
+ setNavigatorOnLine(false)
221
+ setNavigatorConnection({ type: 'wifi', effectiveType: '4g' })
222
+
223
+ const { getTelemetryEvents } = startAndSpyTelemetry()
224
+ callMonitored(() => {
225
+ throw new Error('message')
226
+ })
227
+
228
+ expect((await getTelemetryEvents())[0].telemetry.connectivity).toEqual({
229
+ status: 'not_connected',
230
+ interfaces: ['wifi'],
231
+ effective_type: '4g',
232
+ })
233
+ })
234
+
235
+ it('should collect pre start events', async () => {
236
+ addTelemetryUsage({ feature: 'set-tracking-consent', tracking_consent: 'granted' })
237
+
238
+ const { getTelemetryEvents } = startAndSpyTelemetry({ telemetrySampleRate: 100, telemetryUsageSampleRate: 100 })
239
+
240
+ expect((await getTelemetryEvents()).length).toBe(1)
241
+ })
242
+
243
+ it('should collect ddtags', async () => {
244
+ const { getTelemetryEvents } = startAndSpyTelemetry({
245
+ service: 'foo',
246
+ env: 'bar',
247
+ version: '123',
248
+ })
249
+
250
+ addTelemetryUsage({ feature: 'set-tracking-consent', tracking_consent: 'granted' })
251
+
252
+ expect((await getTelemetryEvents())[0].ddtags).toEqual('sdk_version:test,env:bar,service:foo,version:123')
253
+ })
254
+
255
+ describe('assemble telemetry hook', () => {
256
+ it('should add default telemetry event attributes', async () => {
257
+ const { getTelemetryEvents, hooks } = startAndSpyTelemetry()
258
+
259
+ hooks.register(HookNames.AssembleTelemetry, () => ({ foo: 'bar' }))
260
+
261
+ callMonitored(() => {
262
+ throw new Error('foo')
263
+ })
264
+
265
+ expect((await getTelemetryEvents())[0].foo).toEqual('bar')
266
+ })
267
+
268
+ it('should add context progressively', async () => {
269
+ const { hooks, getTelemetryEvents } = startAndSpyTelemetry()
270
+ hooks.register(HookNames.AssembleTelemetry, () => ({
271
+ application: {
272
+ id: 'bar',
273
+ },
274
+ }))
275
+ callMonitored(() => {
276
+ throw new Error('foo')
277
+ })
278
+ hooks.register(HookNames.AssembleTelemetry, () => ({
279
+ session: {
280
+ id: '123',
281
+ },
282
+ }))
283
+ callMonitored(() => {
284
+ throw new Error('bar')
285
+ })
286
+
287
+ const events = await getTelemetryEvents()
288
+ expect(events[0].application!.id).toEqual('bar')
289
+ expect(events[1].application!.id).toEqual('bar')
290
+ expect(events[1].session!.id).toEqual('123')
291
+ })
292
+
293
+ it('should apply telemetry hook on events collected before telemetry is started', async () => {
294
+ addTelemetryDebug('debug 1')
295
+
296
+ const { hooks, getTelemetryEvents } = startAndSpyTelemetry()
297
+
298
+ hooks.register(HookNames.AssembleTelemetry, () => ({
299
+ application: {
300
+ id: 'bar',
301
+ },
302
+ }))
303
+
304
+ const events = await getTelemetryEvents()
305
+ expect(events[0].application!.id).toEqual('bar')
306
+ })
307
+ })
308
+
309
+ describe('sampling', () => {
310
+ it('should notify when sampled', async () => {
311
+ spyOn(Math, 'random').and.callFake(() => 0)
312
+ const { getTelemetryEvents } = startAndSpyTelemetry({ telemetrySampleRate: 50 })
313
+
314
+ callMonitored(() => {
315
+ throw new Error('message')
316
+ })
317
+
318
+ expect((await getTelemetryEvents()).length).toBe(1)
319
+ })
320
+
321
+ it('should not notify when not sampled', async () => {
322
+ spyOn(Math, 'random').and.callFake(() => 1)
323
+ const { getTelemetryEvents } = startAndSpyTelemetry({ telemetrySampleRate: 50 })
324
+
325
+ callMonitored(() => {
326
+ throw new Error('message')
327
+ })
328
+
329
+ expect(await getTelemetryEvents()).toEqual([])
330
+ })
331
+ })
332
+
333
+ describe('deduplicating', () => {
334
+ it('should discard already sent telemetry', async () => {
335
+ const { getTelemetryEvents } = startAndSpyTelemetry()
336
+ const fooError = new Error('foo')
337
+ const barError = new Error('bar')
338
+
339
+ addTelemetryError(fooError)
340
+ addTelemetryError(fooError)
341
+ addTelemetryError(barError)
342
+
343
+ const events = await getTelemetryEvents()
344
+ expect(events.length).toBe(2)
345
+ expect(events[0].telemetry.message).toEqual('foo')
346
+ expect(events[1].telemetry.message).toEqual('bar')
347
+ })
348
+
349
+ it('should not consider a discarded event for the maxTelemetryEventsPerPage', async () => {
350
+ const { getTelemetryEvents } = startAndSpyTelemetry(undefined, { maxTelemetryEventsPerPage: 2 })
351
+
352
+ addTelemetryUsage({ feature: 'stop-session' })
353
+ addTelemetryUsage({ feature: 'stop-session' })
354
+ addTelemetryUsage({ feature: 'start-session-replay-recording' })
355
+
356
+ const events = await getTelemetryEvents()
357
+ expect(events.length).toBe(2)
358
+ expect((events[0].telemetry.usage as any).feature).toEqual('stop-session')
359
+ expect((events[1].telemetry.usage as any).feature).toEqual('start-session-replay-recording')
360
+ })
361
+ })
362
+
363
+ describe('maxTelemetryEventsPerPage', () => {
364
+ it('should be enforced', async () => {
365
+ const { getTelemetryEvents } = startAndSpyTelemetry(undefined, { maxTelemetryEventsPerPage: 2 })
366
+
367
+ addTelemetryUsage({ feature: 'stop-session' })
368
+ addTelemetryUsage({ feature: 'start-session-replay-recording' })
369
+ addTelemetryUsage({ feature: 'start-view' })
370
+
371
+ const events = await getTelemetryEvents()
372
+ expect(events.length).toBe(2)
373
+ expect((events[0].telemetry.usage as any).feature).toEqual('stop-session')
374
+ expect((events[1].telemetry.usage as any).feature).toEqual('start-session-replay-recording')
375
+ })
376
+
377
+ it('should be enforced separately for different kinds of telemetry', async () => {
378
+ const { getTelemetryEvents } = startAndSpyTelemetry(undefined, {
379
+ metricSampleRate: 100,
380
+ maxTelemetryEventsPerPage: 2,
381
+ })
382
+
383
+ // Group 1. These are all distinct kinds of telemetry, so these should all be sent.
384
+ addTelemetryDebug('debug 1')
385
+ addTelemetryError(new Error('error 1'))
386
+ addTelemetryMetrics(TelemetryMetrics.SEGMENT_METRICS_TELEMETRY_NAME, { bandwidth: 500 })
387
+ addTelemetryMetrics(TelemetryMetrics.CUSTOMER_DATA_METRIC_NAME, { speed: 1000 })
388
+ addTelemetryUsage({ feature: 'stop-session' })
389
+
390
+ // Group 2. Again, these should all be sent.
391
+ addTelemetryDebug('debug 2')
392
+ addTelemetryError(new Error('error 2'))
393
+ addTelemetryMetrics(TelemetryMetrics.SEGMENT_METRICS_TELEMETRY_NAME, { latency: 50 })
394
+ addTelemetryMetrics(TelemetryMetrics.CUSTOMER_DATA_METRIC_NAME, { jank: 50 })
395
+ addTelemetryUsage({ feature: 'start-session-replay-recording' })
396
+
397
+ // Group 3. Each of these events should hit the limit for their respective kind of
398
+ // telemetry, so none of them should be sent.
399
+ addTelemetryDebug('debug 3')
400
+ addTelemetryError(new Error('error 3'))
401
+ addTelemetryMetrics(TelemetryMetrics.SEGMENT_METRICS_TELEMETRY_NAME, { packet_loss: 99 })
402
+ addTelemetryMetrics(TelemetryMetrics.CUSTOMER_DATA_METRIC_NAME, { latency: 500 })
403
+ addTelemetryUsage({ feature: 'start-view' })
404
+
405
+ expect((await getTelemetryEvents()).map((event) => event.telemetry)).toEqual([
406
+ // Group 1.
407
+ jasmine.objectContaining({ message: 'debug 1' }),
408
+ jasmine.objectContaining({ message: 'error 1' }),
409
+ jasmine.objectContaining({ message: TelemetryMetrics.SEGMENT_METRICS_TELEMETRY_NAME, bandwidth: 500 }),
410
+ jasmine.objectContaining({ message: TelemetryMetrics.CUSTOMER_DATA_METRIC_NAME, speed: 1000 }),
411
+ jasmine.objectContaining({ usage: jasmine.objectContaining({ feature: 'stop-session' }) }),
412
+
413
+ // Group 2.
414
+ jasmine.objectContaining({ message: 'debug 2' }),
415
+ jasmine.objectContaining({ message: 'error 2' }),
416
+ jasmine.objectContaining({ message: TelemetryMetrics.SEGMENT_METRICS_TELEMETRY_NAME, latency: 50 }),
417
+ jasmine.objectContaining({ message: TelemetryMetrics.CUSTOMER_DATA_METRIC_NAME, jank: 50 }),
418
+ jasmine.objectContaining({ usage: jasmine.objectContaining({ feature: 'start-session-replay-recording' }) }),
419
+ ])
420
+ })
421
+ })
422
+
423
+ describe('excluded sites', () => {
424
+ ;[
425
+ { site: INTAKE_SITE_US1_FED, enabled: false },
426
+ { site: INTAKE_SITE_US1, enabled: true },
427
+ ].forEach(({ site, enabled }) => {
428
+ it(`should be ${enabled ? 'enabled' : 'disabled'} on ${site}`, async () => {
429
+ const { getTelemetryEvents } = startAndSpyTelemetry({ site })
430
+
431
+ callMonitored(() => {
432
+ throw new Error('message')
433
+ })
434
+
435
+ const events = await getTelemetryEvents()
436
+ if (enabled) {
437
+ expect(events.length).toBe(1)
438
+ } else {
439
+ expect(events.length).toBe(0)
440
+ }
441
+ })
442
+ })
443
+ })
444
+ })
445
+
446
+ describe('formatError', () => {
447
+ it('formats error instances', () => {
448
+ expect(formatError(new Error('message'))).toEqual({
449
+ message: 'message',
450
+ error: {
451
+ kind: 'Error',
452
+ stack: jasmine.stringMatching(/^Error: message(\n|$)/) as unknown as string,
453
+ },
454
+ })
455
+ })
456
+
457
+ it('formats strings', () => {
458
+ expect(formatError('message')).toEqual({
459
+ message: 'Uncaught "message"',
460
+ error: {
461
+ stack: NO_ERROR_STACK_PRESENT_MESSAGE,
462
+ },
463
+ })
464
+ })
465
+
466
+ it('formats objects', () => {
467
+ expect(formatError({ foo: 'bar' })).toEqual({
468
+ message: 'Uncaught {"foo":"bar"}',
469
+ error: {
470
+ stack: NO_ERROR_STACK_PRESENT_MESSAGE,
471
+ },
472
+ })
473
+ })
474
+ })
475
+
476
+ describe('scrubCustomerFrames', () => {
477
+ it('should remove stack trace frames that are related to customer files', () => {
478
+ ;[
479
+ { scrub: false, url: 'https://cloud.openobserve.ai/openobserve-rum-v4.js' },
480
+ { scrub: false, url: 'https://cloud.openobserve.ai/openobserve-rum-v5.js' },
481
+ { scrub: false, url: 'https://cloud.openobserve.ai/openobserve-logs-staging.js' },
482
+ { scrub: false, url: 'https://cloud.openobserve.ai/openobserve-rum-canary.js' },
483
+ { scrub: false, url: 'http://localhost/index.html' },
484
+ { scrub: false, url: undefined },
485
+ { scrub: false, url: '<anonymous>' },
486
+ { scrub: true, url: 'https://foo.bar/path?qux=qix' },
487
+ ].forEach(({ url, scrub }) => {
488
+ const candidate: Partial<StackTrace> = {
489
+ stack: [{ url }],
490
+ }
491
+ expect(scrubCustomerFrames(candidate as StackTrace).stack.length).toBe(scrub ? 0 : 1, `for url: ${url!}`)
492
+ })
493
+ })
494
+ })
@@ -0,0 +1,44 @@
1
+ import { TrackingConsent, createTrackingConsentState } from './trackingConsent'
2
+
3
+ describe('createTrackingConsentState', () => {
4
+ it('creates a tracking consent state', () => {
5
+ const trackingConsentState = createTrackingConsentState()
6
+ expect(trackingConsentState).toBeDefined()
7
+ })
8
+
9
+ it('defaults to not granted', () => {
10
+ const trackingConsentState = createTrackingConsentState()
11
+ expect(trackingConsentState.isGranted()).toBeFalse()
12
+ })
13
+
14
+ it('can be created with a default consent state', () => {
15
+ const trackingConsentState = createTrackingConsentState(TrackingConsent.GRANTED)
16
+ expect(trackingConsentState.isGranted()).toBeTrue()
17
+ })
18
+
19
+ it('can be updated to granted', () => {
20
+ const trackingConsentState = createTrackingConsentState()
21
+ trackingConsentState.update(TrackingConsent.GRANTED)
22
+ expect(trackingConsentState.isGranted()).toBeTrue()
23
+ })
24
+
25
+ it('notifies when the consent is updated', () => {
26
+ const spy = jasmine.createSpy()
27
+ const trackingConsentState = createTrackingConsentState()
28
+ trackingConsentState.observable.subscribe(spy)
29
+ trackingConsentState.update(TrackingConsent.GRANTED)
30
+ expect(spy).toHaveBeenCalledTimes(1)
31
+ })
32
+
33
+ it('can init a consent state if not defined yet', () => {
34
+ const trackingConsentState = createTrackingConsentState()
35
+ trackingConsentState.tryToInit(TrackingConsent.GRANTED)
36
+ expect(trackingConsentState.isGranted()).toBeTrue()
37
+ })
38
+
39
+ it('does not init a consent state if already defined', () => {
40
+ const trackingConsentState = createTrackingConsentState(TrackingConsent.GRANTED)
41
+ trackingConsentState.tryToInit(TrackingConsent.NOT_GRANTED)
42
+ expect(trackingConsentState.isGranted()).toBeTrue()
43
+ })
44
+ })
@@ -0,0 +1,76 @@
1
+ import type { RelativeTime } from '@datadog/browser-core'
2
+ import { createHooks } from '../../test'
3
+ import { DISCARDED, HookNames } from './abstractHooks'
4
+
5
+ describe('startHooks', () => {
6
+ let hooks: ReturnType<typeof createHooks>
7
+ const hookParams = { eventType: 'error', startTime: 1011 as RelativeTime } as any
8
+
9
+ beforeEach(() => {
10
+ hooks = createHooks()
11
+ })
12
+
13
+ it('unregister a hook callback', () => {
14
+ const callback = jasmine.createSpy().and.returnValue({ service: 'foo' })
15
+
16
+ const { unregister } = hooks.register(HookNames.Assemble, callback)
17
+ unregister()
18
+
19
+ const result = hooks.triggerHook(HookNames.Assemble, hookParams)
20
+
21
+ expect(callback).not.toHaveBeenCalled()
22
+ expect(result).toBeUndefined()
23
+ })
24
+
25
+ describe('assemble hook', () => {
26
+ it('combines results from multiple callbacks', () => {
27
+ const callback1 = jasmine.createSpy().and.returnValue({ type: 'action', service: 'foo' })
28
+ const callback2 = jasmine.createSpy().and.returnValue({ type: 'action', version: 'bar' })
29
+
30
+ hooks.register(HookNames.Assemble, callback1)
31
+ hooks.register(HookNames.Assemble, callback2)
32
+
33
+ const defaultRumEventAttributes = hooks.triggerHook(HookNames.Assemble, hookParams)
34
+
35
+ expect(defaultRumEventAttributes).toEqual({ type: 'action', service: 'foo', version: 'bar' })
36
+ expect(callback1).toHaveBeenCalled()
37
+ expect(callback2).toHaveBeenCalled()
38
+ })
39
+
40
+ it('does not combine undefined results from callbacks', () => {
41
+ const callback1 = jasmine.createSpy().and.returnValue({ type: 'action', service: 'foo' })
42
+ const callback2 = jasmine.createSpy().and.returnValue(undefined)
43
+
44
+ hooks.register(HookNames.Assemble, callback1)
45
+ hooks.register(HookNames.Assemble, callback2)
46
+
47
+ const defaultRumEventAttributes = hooks.triggerHook(HookNames.Assemble, hookParams)
48
+
49
+ expect(defaultRumEventAttributes).toEqual({ type: 'action', service: 'foo' })
50
+ expect(callback1).toHaveBeenCalled()
51
+ expect(callback2).toHaveBeenCalled()
52
+ })
53
+
54
+ it('returns DISCARDED if one callbacks returns DISCARDED', () => {
55
+ const callback1 = jasmine.createSpy().and.returnValue({ type: 'action', service: 'foo' })
56
+ const callback2 = jasmine.createSpy().and.returnValue(DISCARDED)
57
+ const callback3 = jasmine.createSpy().and.returnValue({ type: 'action', version: 'bar' })
58
+
59
+ hooks.register(HookNames.Assemble, callback1)
60
+ hooks.register(HookNames.Assemble, callback2)
61
+ hooks.register(HookNames.Assemble, callback3)
62
+
63
+ const defaultRumEventAttributes = hooks.triggerHook(HookNames.Assemble, hookParams)
64
+
65
+ expect(defaultRumEventAttributes).toEqual(DISCARDED)
66
+ expect(callback1).toHaveBeenCalled()
67
+ expect(callback2).toHaveBeenCalled()
68
+ expect(callback3).not.toHaveBeenCalled()
69
+ })
70
+
71
+ it('returns undefined when no registered hooks', () => {
72
+ const result = hooks.triggerHook(HookNames.Assemble, hookParams)
73
+ expect(result).toBeUndefined()
74
+ })
75
+ })
76
+ })