@posthog/core 1.1.0 → 1.2.1

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 (128) hide show
  1. package/dist/error-tracking/chunk-ids.js +1 -1
  2. package/dist/error-tracking/chunk-ids.mjs +1 -1
  3. package/dist/error-tracking/coercers/error-event-coercer.js +4 -5
  4. package/dist/error-tracking/coercers/error-event-coercer.mjs +4 -5
  5. package/dist/error-tracking/coercers/event-coercer.js +1 -2
  6. package/dist/error-tracking/coercers/event-coercer.mjs +1 -2
  7. package/dist/error-tracking/coercers/object-coercer.js +1 -2
  8. package/dist/error-tracking/coercers/object-coercer.mjs +1 -2
  9. package/dist/error-tracking/coercers/primitive-coercer.js +1 -2
  10. package/dist/error-tracking/coercers/primitive-coercer.mjs +1 -2
  11. package/dist/error-tracking/coercers/promise-rejection-event.js +4 -5
  12. package/dist/error-tracking/coercers/promise-rejection-event.mjs +4 -5
  13. package/dist/error-tracking/coercers/string-coercer.js +3 -4
  14. package/dist/error-tracking/coercers/string-coercer.mjs +3 -4
  15. package/dist/error-tracking/coercers/utils.js +2 -4
  16. package/dist/error-tracking/coercers/utils.mjs +2 -4
  17. package/dist/error-tracking/error-properties-builder.d.ts +6 -6
  18. package/dist/error-tracking/error-properties-builder.d.ts.map +1 -1
  19. package/dist/error-tracking/error-properties-builder.js +17 -27
  20. package/dist/error-tracking/error-properties-builder.mjs +16 -26
  21. package/dist/error-tracking/parsers/index.js +2 -4
  22. package/dist/error-tracking/parsers/index.mjs +2 -4
  23. package/dist/error-tracking/parsers/node.js +3 -5
  24. package/dist/error-tracking/parsers/node.mjs +3 -5
  25. package/dist/error-tracking/utils.js +4 -4
  26. package/dist/error-tracking/utils.mjs +4 -4
  27. package/dist/eventemitter.js +4 -4
  28. package/dist/eventemitter.mjs +4 -4
  29. package/dist/featureFlagUtils.js +20 -45
  30. package/dist/featureFlagUtils.mjs +20 -45
  31. package/dist/gzip.js +1 -2
  32. package/dist/gzip.mjs +1 -2
  33. package/dist/index.d.ts +4 -366
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +54 -1225
  36. package/dist/index.mjs +5 -1190
  37. package/dist/posthog-core-stateless.d.ts +204 -0
  38. package/dist/posthog-core-stateless.d.ts.map +1 -0
  39. package/dist/posthog-core-stateless.js +675 -0
  40. package/dist/posthog-core-stateless.mjs +632 -0
  41. package/dist/posthog-core.d.ts +171 -0
  42. package/dist/posthog-core.d.ts.map +1 -0
  43. package/dist/posthog-core.js +554 -0
  44. package/dist/posthog-core.mjs +520 -0
  45. package/dist/testing/PostHogCoreTestClient.d.ts +2 -1
  46. package/dist/testing/PostHogCoreTestClient.d.ts.map +1 -1
  47. package/dist/testing/PostHogCoreTestClient.js +9 -11
  48. package/dist/testing/PostHogCoreTestClient.mjs +8 -10
  49. package/dist/testing/test-utils.js +1 -1
  50. package/dist/testing/test-utils.mjs +1 -1
  51. package/dist/utils/bucketed-rate-limiter.js +8 -12
  52. package/dist/utils/bucketed-rate-limiter.mjs +8 -12
  53. package/dist/utils/index.js +3 -3
  54. package/dist/utils/index.mjs +3 -3
  55. package/dist/utils/type-utils.js +1 -1
  56. package/dist/utils/type-utils.mjs +1 -1
  57. package/dist/vendor/uuidv7.js +12 -16
  58. package/dist/vendor/uuidv7.mjs +12 -16
  59. package/package.json +3 -2
  60. package/src/__tests__/featureFlagUtils.spec.ts +427 -0
  61. package/src/__tests__/gzip.spec.ts +69 -0
  62. package/src/__tests__/posthog.ai.spec.ts +110 -0
  63. package/src/__tests__/posthog.capture.spec.ts +91 -0
  64. package/src/__tests__/posthog.core.spec.ts +135 -0
  65. package/src/__tests__/posthog.debug.spec.ts +36 -0
  66. package/src/__tests__/posthog.enqueue.spec.ts +93 -0
  67. package/src/__tests__/posthog.featureflags.spec.ts +1106 -0
  68. package/src/__tests__/posthog.featureflags.v1.spec.ts +922 -0
  69. package/src/__tests__/posthog.flush.spec.ts +237 -0
  70. package/src/__tests__/posthog.gdpr.spec.ts +50 -0
  71. package/src/__tests__/posthog.groups.spec.ts +96 -0
  72. package/src/__tests__/posthog.identify.spec.ts +194 -0
  73. package/src/__tests__/posthog.init.spec.ts +110 -0
  74. package/src/__tests__/posthog.listeners.spec.ts +51 -0
  75. package/src/__tests__/posthog.register.spec.ts +47 -0
  76. package/src/__tests__/posthog.reset.spec.ts +76 -0
  77. package/src/__tests__/posthog.sessions.spec.ts +63 -0
  78. package/src/__tests__/posthog.setProperties.spec.ts +102 -0
  79. package/src/__tests__/posthog.shutdown.spec.ts +88 -0
  80. package/src/__tests__/utils.spec.ts +36 -0
  81. package/src/error-tracking/chunk-ids.ts +58 -0
  82. package/src/error-tracking/coercers/dom-exception-coercer.ts +38 -0
  83. package/src/error-tracking/coercers/error-coercer.ts +36 -0
  84. package/src/error-tracking/coercers/error-event-coercer.ts +24 -0
  85. package/src/error-tracking/coercers/event-coercer.ts +19 -0
  86. package/src/error-tracking/coercers/index.ts +8 -0
  87. package/src/error-tracking/coercers/object-coercer.ts +76 -0
  88. package/src/error-tracking/coercers/primitive-coercer.ts +19 -0
  89. package/src/error-tracking/coercers/promise-rejection-event.spec.ts +77 -0
  90. package/src/error-tracking/coercers/promise-rejection-event.ts +53 -0
  91. package/src/error-tracking/coercers/string-coercer.spec.ts +26 -0
  92. package/src/error-tracking/coercers/string-coercer.ts +31 -0
  93. package/src/error-tracking/coercers/utils.ts +33 -0
  94. package/src/error-tracking/error-properties-builder.coerce.spec.ts +202 -0
  95. package/src/error-tracking/error-properties-builder.parse.spec.ts +30 -0
  96. package/src/error-tracking/error-properties-builder.ts +167 -0
  97. package/src/error-tracking/index.ts +5 -0
  98. package/src/error-tracking/parsers/base.ts +29 -0
  99. package/src/error-tracking/parsers/chrome.ts +53 -0
  100. package/src/error-tracking/parsers/gecko.ts +38 -0
  101. package/src/error-tracking/parsers/index.ts +104 -0
  102. package/src/error-tracking/parsers/node.ts +111 -0
  103. package/src/error-tracking/parsers/opera.ts +18 -0
  104. package/src/error-tracking/parsers/react-native.ts +0 -0
  105. package/src/error-tracking/parsers/safari.ts +33 -0
  106. package/src/error-tracking/parsers/winjs.ts +12 -0
  107. package/src/error-tracking/types.ts +107 -0
  108. package/src/error-tracking/utils.ts +39 -0
  109. package/src/eventemitter.ts +27 -0
  110. package/src/featureFlagUtils.ts +192 -0
  111. package/src/gzip.ts +29 -0
  112. package/src/index.ts +8 -0
  113. package/src/posthog-core-stateless.ts +1226 -0
  114. package/src/posthog-core.ts +958 -0
  115. package/src/testing/PostHogCoreTestClient.ts +91 -0
  116. package/src/testing/index.ts +2 -0
  117. package/src/testing/test-utils.ts +47 -0
  118. package/src/types.ts +544 -0
  119. package/src/utils/bucketed-rate-limiter.spec.ts +33 -0
  120. package/src/utils/bucketed-rate-limiter.ts +85 -0
  121. package/src/utils/index.ts +98 -0
  122. package/src/utils/number-utils.spec.ts +89 -0
  123. package/src/utils/number-utils.ts +30 -0
  124. package/src/utils/promise-queue.spec.ts +55 -0
  125. package/src/utils/promise-queue.ts +30 -0
  126. package/src/utils/string-utils.ts +23 -0
  127. package/src/utils/type-utils.ts +134 -0
  128. package/src/vendor/uuidv7.ts +479 -0
@@ -0,0 +1,427 @@
1
+ import {
2
+ getFlagValuesFromFlags,
3
+ getPayloadsFromFlags,
4
+ getFlagDetailsFromFlagsAndPayloads,
5
+ getFeatureFlagValue,
6
+ normalizeFlagsResponse,
7
+ } from '@/featureFlagUtils'
8
+ import { PostHogFlagsResponse, FeatureFlagDetail } from '@/types'
9
+
10
+ describe('featureFlagUtils', () => {
11
+ describe('getFeatureFlagValue', () => {
12
+ it('should return variant if present', () => {
13
+ const flag: FeatureFlagDetail = {
14
+ key: 'test-flag',
15
+ enabled: true,
16
+ variant: 'test-variant',
17
+ reason: undefined,
18
+ metadata: { id: 1, version: undefined, description: undefined, payload: undefined },
19
+ }
20
+ expect(getFeatureFlagValue(flag)).toBe('test-variant')
21
+ })
22
+
23
+ it('should return enabled if no variant', () => {
24
+ const flag1: FeatureFlagDetail = {
25
+ key: 'test-flag-1',
26
+ enabled: true,
27
+ variant: undefined,
28
+ reason: undefined,
29
+ metadata: { id: 1, version: undefined, description: undefined, payload: undefined },
30
+ }
31
+ const flag2: FeatureFlagDetail = {
32
+ key: 'test-flag-2',
33
+ enabled: false,
34
+ variant: undefined,
35
+ reason: undefined,
36
+ metadata: { id: 2, version: undefined, description: undefined, payload: undefined },
37
+ }
38
+ expect(getFeatureFlagValue(flag1)).toBe(true)
39
+ expect(getFeatureFlagValue(flag2)).toBe(false)
40
+ })
41
+
42
+ it('should return undefined if neither variant nor enabled', () => {
43
+ const flag: FeatureFlagDetail = {
44
+ key: 'test-flag',
45
+ enabled: false,
46
+ variant: undefined,
47
+ reason: undefined,
48
+ metadata: { id: 1, version: undefined, description: undefined, payload: undefined },
49
+ }
50
+ expect(getFeatureFlagValue(flag)).toBe(false)
51
+ })
52
+ })
53
+
54
+ describe('getFlagValuesFromFlags', () => {
55
+ it('should extract flag values from flags', () => {
56
+ const flags: Record<string, FeatureFlagDetail> = {
57
+ 'flag-1': {
58
+ key: 'flag-1',
59
+ enabled: true,
60
+ variant: undefined,
61
+ reason: undefined,
62
+ metadata: { id: 1, version: undefined, description: undefined, payload: undefined },
63
+ },
64
+ 'flag-2': {
65
+ key: 'flag-2',
66
+ enabled: false,
67
+ variant: undefined,
68
+ reason: undefined,
69
+ metadata: { id: 2, version: undefined, description: undefined, payload: undefined },
70
+ },
71
+ 'flag-3': {
72
+ key: 'flag-3',
73
+ enabled: true,
74
+ variant: 'test-variant',
75
+ reason: undefined,
76
+ metadata: { id: 3, version: undefined, description: undefined, payload: undefined },
77
+ },
78
+ }
79
+
80
+ expect(getFlagValuesFromFlags(flags)).toEqual({
81
+ 'flag-1': true,
82
+ 'flag-2': false,
83
+ 'flag-3': 'test-variant',
84
+ })
85
+ })
86
+
87
+ it('should handle empty flags object', () => {
88
+ expect(getFlagValuesFromFlags({})).toEqual({})
89
+ })
90
+ })
91
+
92
+ describe('getPayloadsFromFlags', () => {
93
+ it('should extract payloads from enabled flags with metadata', () => {
94
+ const flags: Record<string, FeatureFlagDetail> = {
95
+ 'flag-with-object-payload': {
96
+ key: 'flag-with-object-payload',
97
+ enabled: true,
98
+ variant: undefined,
99
+ reason: undefined,
100
+ metadata: { id: 1, version: undefined, description: undefined, payload: '{"key": "value"}' },
101
+ },
102
+ 'flag-with-single-item-array-payload': {
103
+ key: 'flag-with-single-item-array-payload',
104
+ enabled: true,
105
+ variant: undefined,
106
+ reason: undefined,
107
+ metadata: { id: 1, version: undefined, description: undefined, payload: '[5]' },
108
+ },
109
+ 'flag-with-array-payload': {
110
+ key: 'flag-with-array-payload',
111
+ enabled: true,
112
+ variant: undefined,
113
+ reason: undefined,
114
+ metadata: { id: 1, version: undefined, description: undefined, payload: '[1, 2, 3]' },
115
+ },
116
+ 'disabled-flag': {
117
+ key: 'disabled-flag',
118
+ enabled: false,
119
+ variant: undefined,
120
+ reason: undefined,
121
+ metadata: { id: 2, version: undefined, description: undefined, payload: undefined },
122
+ },
123
+ 'enabled-flag-no-payload': {
124
+ key: 'enabled-flag-no-payload',
125
+ enabled: true,
126
+ variant: undefined,
127
+ reason: undefined,
128
+ metadata: { id: 3, version: undefined, description: undefined, payload: undefined },
129
+ },
130
+ }
131
+
132
+ expect(getPayloadsFromFlags(flags)).toEqual({
133
+ 'flag-with-object-payload': { key: 'value' },
134
+ 'flag-with-single-item-array-payload': [5],
135
+ 'flag-with-array-payload': [1, 2, 3],
136
+ })
137
+ })
138
+
139
+ it('should handle empty flags object', () => {
140
+ expect(getPayloadsFromFlags({})).toEqual({})
141
+ })
142
+
143
+ it('should handle flags with no payloads', () => {
144
+ const flags: Record<string, FeatureFlagDetail> = {
145
+ 'flag-1': {
146
+ key: 'flag-1',
147
+ enabled: true,
148
+ variant: undefined,
149
+ reason: undefined,
150
+ metadata: { id: 1, version: undefined, description: undefined, payload: undefined },
151
+ },
152
+ 'flag-2': {
153
+ key: 'flag-2',
154
+ enabled: true,
155
+ variant: undefined,
156
+ reason: undefined,
157
+ metadata: { id: 2, version: undefined, description: undefined, payload: undefined },
158
+ },
159
+ }
160
+
161
+ expect(getPayloadsFromFlags(flags)).toEqual({})
162
+ })
163
+ })
164
+
165
+ describe('getFlagDetailsFromFlagsAndPayloads', () => {
166
+ it('should convert v1 flags and payloads to flag details', () => {
167
+ const flagsResponse: PostHogFlagsResponse = {
168
+ featureFlags: {
169
+ 'flag-1': true,
170
+ 'flag-2': 'variant-1',
171
+ 'flag-3': false,
172
+ },
173
+ featureFlagPayloads: {
174
+ 'flag-1': { key: 'value1' },
175
+ 'flag-2': { key: 'value2' },
176
+ },
177
+ flags: {},
178
+ errorsWhileComputingFlags: false,
179
+ }
180
+
181
+ const result = getFlagDetailsFromFlagsAndPayloads(flagsResponse)
182
+
183
+ expect(result).toEqual({
184
+ 'flag-1': {
185
+ key: 'flag-1',
186
+ enabled: true,
187
+ variant: undefined,
188
+ reason: undefined,
189
+ metadata: {
190
+ id: undefined,
191
+ version: undefined,
192
+ payload: '{"key":"value1"}',
193
+ description: undefined,
194
+ },
195
+ },
196
+ 'flag-2': {
197
+ key: 'flag-2',
198
+ enabled: true,
199
+ variant: 'variant-1',
200
+ reason: undefined,
201
+ metadata: {
202
+ id: undefined,
203
+ version: undefined,
204
+ payload: '{"key":"value2"}',
205
+ description: undefined,
206
+ },
207
+ },
208
+ 'flag-3': {
209
+ key: 'flag-3',
210
+ enabled: false,
211
+ variant: undefined,
212
+ reason: undefined,
213
+ metadata: {
214
+ id: undefined,
215
+ version: undefined,
216
+ payload: undefined,
217
+ description: undefined,
218
+ },
219
+ },
220
+ })
221
+ })
222
+
223
+ it('should handle empty flags and payloads', () => {
224
+ const flagsResponse: PostHogFlagsResponse = {
225
+ featureFlags: {},
226
+ featureFlagPayloads: {},
227
+ flags: {},
228
+ errorsWhileComputingFlags: false,
229
+ }
230
+
231
+ expect(getFlagDetailsFromFlagsAndPayloads(flagsResponse)).toEqual({})
232
+ })
233
+ })
234
+
235
+ describe('normalizeFlagsResponse', () => {
236
+ it('should convert v4 response to v1 format', () => {
237
+ const v4Response: PostHogFlagsResponse = {
238
+ flags: {
239
+ 'flag-1': {
240
+ key: 'flag-1',
241
+ enabled: true,
242
+ variant: undefined,
243
+ reason: undefined,
244
+ metadata: {
245
+ id: undefined,
246
+ version: undefined,
247
+ payload: '{"key":"value1"}',
248
+ description: undefined,
249
+ },
250
+ },
251
+ 'flag-2': {
252
+ key: 'flag-2',
253
+ enabled: true,
254
+ variant: 'variant-1',
255
+ reason: undefined,
256
+ metadata: {
257
+ id: undefined,
258
+ version: undefined,
259
+ payload: '{"key":"value2"}',
260
+ description: undefined,
261
+ },
262
+ },
263
+ 'flag-3': {
264
+ key: 'flag-3',
265
+ enabled: false,
266
+ variant: undefined,
267
+ reason: undefined,
268
+ metadata: {
269
+ id: undefined,
270
+ version: undefined,
271
+ payload: undefined,
272
+ description: undefined,
273
+ },
274
+ },
275
+ },
276
+ errorsWhileComputingFlags: false,
277
+ featureFlags: {},
278
+ featureFlagPayloads: {},
279
+ }
280
+
281
+ const result = normalizeFlagsResponse(v4Response)
282
+
283
+ expect(result).toEqual({
284
+ featureFlags: {
285
+ 'flag-1': true,
286
+ 'flag-2': 'variant-1',
287
+ 'flag-3': false,
288
+ },
289
+ featureFlagPayloads: {
290
+ 'flag-1': { key: 'value1' },
291
+ 'flag-2': { key: 'value2' },
292
+ },
293
+ flags: v4Response.flags,
294
+ errorsWhileComputingFlags: false,
295
+ })
296
+ })
297
+
298
+ it('should convert v1 response to v4 format', () => {
299
+ const v1Response: Omit<PostHogFlagsResponse, 'flags'> = {
300
+ featureFlags: {
301
+ 'flag-1': true,
302
+ 'flag-2': 'variant-1',
303
+ 'flag-3': false,
304
+ },
305
+ featureFlagPayloads: {
306
+ 'flag-1': { key: 'value1' },
307
+ 'flag-2': { key: 'value2' },
308
+ },
309
+ errorsWhileComputingFlags: false,
310
+ }
311
+
312
+ const result = normalizeFlagsResponse(v1Response)
313
+
314
+ expect(result).toEqual({
315
+ featureFlags: {
316
+ 'flag-1': true,
317
+ 'flag-2': 'variant-1',
318
+ 'flag-3': false,
319
+ },
320
+ featureFlagPayloads: {
321
+ 'flag-1': { key: 'value1' },
322
+ 'flag-2': { key: 'value2' },
323
+ },
324
+ flags: {
325
+ 'flag-1': {
326
+ key: 'flag-1',
327
+ enabled: true,
328
+ variant: undefined,
329
+ reason: undefined,
330
+ metadata: {
331
+ id: undefined,
332
+ version: undefined,
333
+ payload: '{"key":"value1"}',
334
+ description: undefined,
335
+ },
336
+ },
337
+ 'flag-2': {
338
+ key: 'flag-2',
339
+ enabled: true,
340
+ variant: 'variant-1',
341
+ reason: undefined,
342
+ metadata: {
343
+ id: undefined,
344
+ version: undefined,
345
+ payload: '{"key":"value2"}',
346
+ description: undefined,
347
+ },
348
+ },
349
+ 'flag-3': {
350
+ key: 'flag-3',
351
+ enabled: false,
352
+ variant: undefined,
353
+ reason: undefined,
354
+ metadata: {
355
+ id: undefined,
356
+ version: undefined,
357
+ payload: undefined,
358
+ description: undefined,
359
+ },
360
+ },
361
+ },
362
+ errorsWhileComputingFlags: false,
363
+ })
364
+ })
365
+
366
+ it('should handle empty flags and payloads', () => {
367
+ const v1Response: Omit<PostHogFlagsResponse, 'flags'> = {
368
+ featureFlags: {},
369
+ featureFlagPayloads: {},
370
+ errorsWhileComputingFlags: false,
371
+ }
372
+
373
+ const result = normalizeFlagsResponse(v1Response)
374
+
375
+ expect(result).toEqual({
376
+ featureFlags: {},
377
+ featureFlagPayloads: {},
378
+ flags: {},
379
+ errorsWhileComputingFlags: false,
380
+ })
381
+ })
382
+
383
+ it('should preserve additional fields', () => {
384
+ const v1Response: Omit<PostHogFlagsResponse, 'flags'> = {
385
+ featureFlags: {
386
+ 'flag-1': true,
387
+ },
388
+ featureFlagPayloads: {
389
+ 'flag-1': { key: 'value1' },
390
+ },
391
+ errorsWhileComputingFlags: false,
392
+ sessionRecording: true,
393
+ quotaLimited: ['feature_flags'],
394
+ requestId: 'test-request-id',
395
+ }
396
+
397
+ const result = normalizeFlagsResponse(v1Response)
398
+
399
+ expect(result).toEqual({
400
+ featureFlags: {
401
+ 'flag-1': true,
402
+ },
403
+ featureFlagPayloads: {
404
+ 'flag-1': { key: 'value1' },
405
+ },
406
+ flags: {
407
+ 'flag-1': {
408
+ key: 'flag-1',
409
+ enabled: true,
410
+ variant: undefined,
411
+ reason: undefined,
412
+ metadata: {
413
+ id: undefined,
414
+ version: undefined,
415
+ payload: '{"key":"value1"}',
416
+ description: undefined,
417
+ },
418
+ },
419
+ },
420
+ errorsWhileComputingFlags: false,
421
+ sessionRecording: true,
422
+ quotaLimited: ['feature_flags'],
423
+ requestId: 'test-request-id',
424
+ })
425
+ })
426
+ })
427
+ })
@@ -0,0 +1,69 @@
1
+ import { isGzipSupported, gzipCompress } from '@/gzip'
2
+ import { gzip } from 'node:zlib'
3
+ import { randomBytes, randomUUID } from 'node:crypto'
4
+ import { promisify } from 'node:util'
5
+
6
+ const RANDOM_TEST_INPUT = JSON.stringify({
7
+ abc: randomBytes(16),
8
+ def: randomBytes(64),
9
+ })
10
+ const API_TEST_INPUT = JSON.stringify({
11
+ api_key: 'TEST_API_KEY',
12
+ batch: [
13
+ {
14
+ event: 'custom-event',
15
+ distinct_id: 'user-distinct-id',
16
+ library: 'posthog-core-tests',
17
+ library_version: '2.0.0-alpha',
18
+ properties: {
19
+ $lib: 'posthog-core-tests',
20
+ $lib_version: '2.0.0-alpha',
21
+ $session_id: 'session.id',
22
+ },
23
+ timestamp: new Date().toISOString(),
24
+ uuid: randomUUID(),
25
+ type: 'capture',
26
+ },
27
+ ],
28
+ sent_at: new Date().toISOString(),
29
+ })
30
+
31
+ describe('gzip', () => {
32
+ describe('isGzipSupported', () => {
33
+ it('should return true if CompressStream exists', () => {
34
+ expect(globalThis.CompressionStream).toBeDefined()
35
+ expect(isGzipSupported()).toBe(true)
36
+ })
37
+ it('should return false if CompressStream not available', () => {
38
+ const CompressionStream = globalThis.CompressionStream
39
+ delete (globalThis as any).CompressionStream
40
+ expect(isGzipSupported()).toBe(false)
41
+ ;(globalThis as any).CompressionStream = CompressionStream
42
+ })
43
+ })
44
+ describe('gzipCompress', () => {
45
+ it('compressed random data should match node', async () => {
46
+ const compressed = await gzipCompress(RANDOM_TEST_INPUT)
47
+ expect(compressed).not.toBe(null)
48
+ if (!compressed) {
49
+ return
50
+ }
51
+ const webCompress = Buffer.from(await compressed.arrayBuffer())
52
+ const nodeCompress = await promisify(gzip)(RANDOM_TEST_INPUT)
53
+ expect(webCompress).not.toBeFalsy()
54
+ expect(webCompress).toEqual(nodeCompress)
55
+ })
56
+
57
+ it('compressed mock request should match node', async () => {
58
+ const compressed = await gzipCompress(API_TEST_INPUT)
59
+ expect(compressed).not.toBe(null)
60
+ if (!compressed) {
61
+ return
62
+ }
63
+ const webCompress = Buffer.from(await compressed.arrayBuffer())
64
+ const nodeCompress = await promisify(gzip)(API_TEST_INPUT)
65
+ expect(webCompress).not.toBeFalsy()
66
+ expect(webCompress).toEqual(nodeCompress)
67
+ })
68
+ })
69
+ })
@@ -0,0 +1,110 @@
1
+ import {
2
+ parseBody,
3
+ waitForPromises,
4
+ createTestClient,
5
+ PostHogCoreTestClient,
6
+ PostHogCoreTestClientMocks,
7
+ } from '@/testing'
8
+
9
+ describe('PostHog Core', () => {
10
+ let posthog: PostHogCoreTestClient
11
+ let mocks: PostHogCoreTestClientMocks
12
+
13
+ jest.useFakeTimers()
14
+
15
+ beforeEach(() => {
16
+ ;[posthog, mocks] = createTestClient('TEST_API_KEY', { flushAt: 1 })
17
+ })
18
+
19
+ describe('ai', () => {
20
+ it('should capture feedback', async () => {
21
+ jest.setSystemTime(new Date('2022-01-01'))
22
+
23
+ posthog.captureTraceFeedback('trace-id', 'feedback')
24
+
25
+ await waitForPromises()
26
+ expect(mocks.fetch).toHaveBeenCalledTimes(1)
27
+ const body = parseBody(mocks.fetch.mock.calls[0])
28
+
29
+ expect(body).toMatchObject({
30
+ batch: [
31
+ {
32
+ event: '$ai_feedback',
33
+ properties: {
34
+ $ai_feedback_text: 'feedback',
35
+ $ai_trace_id: 'trace-id',
36
+ },
37
+ },
38
+ ],
39
+ })
40
+ })
41
+
42
+ it('should convert numeric traceId in captureTraceFeedback', async () => {
43
+ jest.setSystemTime(new Date('2022-01-01'))
44
+
45
+ posthog.captureTraceFeedback(10, 'feedback')
46
+
47
+ await waitForPromises()
48
+ expect(mocks.fetch).toHaveBeenCalledTimes(1)
49
+ const body = parseBody(mocks.fetch.mock.calls[0])
50
+
51
+ expect(body).toMatchObject({
52
+ batch: [
53
+ {
54
+ event: '$ai_feedback',
55
+ properties: {
56
+ $ai_feedback_text: 'feedback',
57
+ $ai_trace_id: '10',
58
+ },
59
+ },
60
+ ],
61
+ })
62
+ })
63
+
64
+ it('should capture a metric', async () => {
65
+ jest.setSystemTime(new Date('2022-01-01'))
66
+
67
+ posthog.captureTraceMetric('trace-id', 'metric-name', 'good')
68
+
69
+ await waitForPromises()
70
+ expect(mocks.fetch).toHaveBeenCalledTimes(1)
71
+ const body = parseBody(mocks.fetch.mock.calls[0])
72
+
73
+ expect(body).toMatchObject({
74
+ batch: [
75
+ {
76
+ event: '$ai_metric',
77
+ properties: {
78
+ $ai_metric_name: 'metric-name',
79
+ $ai_metric_value: 'good',
80
+ $ai_trace_id: 'trace-id',
81
+ },
82
+ },
83
+ ],
84
+ })
85
+ })
86
+
87
+ it('should convert numeric arguments in captureTraceMetric', async () => {
88
+ jest.setSystemTime(new Date('2022-01-01'))
89
+
90
+ posthog.captureTraceMetric(10, 'metric-name', 1)
91
+
92
+ await waitForPromises()
93
+ expect(mocks.fetch).toHaveBeenCalledTimes(1)
94
+ const body = parseBody(mocks.fetch.mock.calls[0])
95
+
96
+ expect(body).toMatchObject({
97
+ batch: [
98
+ {
99
+ event: '$ai_metric',
100
+ properties: {
101
+ $ai_metric_name: 'metric-name',
102
+ $ai_metric_value: '1',
103
+ $ai_trace_id: '10',
104
+ },
105
+ },
106
+ ],
107
+ })
108
+ })
109
+ })
110
+ })
@@ -0,0 +1,91 @@
1
+ import {
2
+ parseBody,
3
+ waitForPromises,
4
+ createTestClient,
5
+ PostHogCoreTestClient,
6
+ PostHogCoreTestClientMocks,
7
+ } from '@/testing'
8
+ import { uuidv7 } from '@/vendor/uuidv7'
9
+
10
+ describe('PostHog Core', () => {
11
+ let posthog: PostHogCoreTestClient
12
+ let mocks: PostHogCoreTestClientMocks
13
+
14
+ jest.useFakeTimers()
15
+
16
+ beforeEach(() => {
17
+ ;[posthog, mocks] = createTestClient('TEST_API_KEY', { flushAt: 1 })
18
+ })
19
+
20
+ describe('capture', () => {
21
+ it('should capture an event', async () => {
22
+ jest.setSystemTime(new Date('2022-01-01'))
23
+
24
+ posthog.capture('custom-event')
25
+
26
+ await waitForPromises()
27
+ expect(mocks.fetch).toHaveBeenCalledTimes(1)
28
+ const [url, options] = mocks.fetch.mock.calls[0]
29
+ expect(url).toMatch(/^https:\/\/us\.i\.posthog\.com\/batch\//)
30
+ expect(options.method).toBe('POST')
31
+ const body = parseBody(mocks.fetch.mock.calls[0])
32
+
33
+ expect(body).toEqual({
34
+ api_key: 'TEST_API_KEY',
35
+ batch: [
36
+ {
37
+ event: 'custom-event',
38
+ distinct_id: posthog.getDistinctId(),
39
+ library: 'posthog-core-tests',
40
+ library_version: '2.0.0-alpha',
41
+ properties: {
42
+ $lib: 'posthog-core-tests',
43
+ $lib_version: '2.0.0-alpha',
44
+ $session_id: expect.any(String),
45
+ },
46
+ timestamp: '2022-01-01T00:00:00.000Z',
47
+ uuid: expect.any(String),
48
+ type: 'capture',
49
+ },
50
+ ],
51
+ sent_at: expect.any(String),
52
+ })
53
+ })
54
+
55
+ it('should allow overriding the timestamp', async () => {
56
+ jest.setSystemTime(new Date('2022-01-01'))
57
+
58
+ posthog.capture('custom-event', { foo: 'bar' }, { timestamp: new Date('2021-01-02') })
59
+ await waitForPromises()
60
+ const body = parseBody(mocks.fetch.mock.calls[0])
61
+ expect(body).toMatchObject({
62
+ api_key: 'TEST_API_KEY',
63
+ batch: [
64
+ {
65
+ event: 'custom-event',
66
+ timestamp: '2021-01-02T00:00:00.000Z',
67
+ },
68
+ ],
69
+ })
70
+ })
71
+
72
+ it('should allow overriding the uuid', async () => {
73
+ jest.setSystemTime(new Date('2022-01-01'))
74
+
75
+ const id = uuidv7()
76
+
77
+ posthog.capture('custom-event', { foo: 'bar' }, { uuid: id })
78
+ await waitForPromises()
79
+ const body = parseBody(mocks.fetch.mock.calls[0])
80
+
81
+ expect(body).toMatchObject({
82
+ batch: [
83
+ {
84
+ event: 'custom-event',
85
+ uuid: expect.any(String),
86
+ },
87
+ ],
88
+ })
89
+ })
90
+ })
91
+ })