posthog-node 4.17.0 → 4.17.2

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 (46) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/lib/edge/{index.cjs.js → index.cjs} +43 -11
  3. package/lib/edge/index.cjs.map +1 -0
  4. package/lib/edge/{index.esm.js → index.mjs} +43 -11
  5. package/lib/edge/index.mjs.map +1 -0
  6. package/lib/index.d.ts +69 -57
  7. package/lib/node/{index.cjs.js → index.cjs} +43 -11
  8. package/lib/node/index.cjs.map +1 -0
  9. package/lib/node/{index.esm.js → index.mjs} +43 -11
  10. package/lib/node/index.mjs.map +1 -0
  11. package/package.json +17 -17
  12. package/lib/edge/index.cjs.js.map +0 -1
  13. package/lib/edge/index.esm.js.map +0 -1
  14. package/lib/node/index.cjs.js.map +0 -1
  15. package/lib/node/index.esm.js.map +0 -1
  16. package/src/client.ts +0 -650
  17. package/src/entrypoints/index.edge.ts +0 -15
  18. package/src/entrypoints/index.node.ts +0 -17
  19. package/src/exports.ts +0 -3
  20. package/src/extensions/error-tracking/autocapture.ts +0 -65
  21. package/src/extensions/error-tracking/context-lines.node.ts +0 -392
  22. package/src/extensions/error-tracking/error-conversion.ts +0 -279
  23. package/src/extensions/error-tracking/get-module.node.ts +0 -57
  24. package/src/extensions/error-tracking/index.ts +0 -69
  25. package/src/extensions/error-tracking/reduceable-cache.ts +0 -39
  26. package/src/extensions/error-tracking/stack-parser.ts +0 -212
  27. package/src/extensions/error-tracking/type-checking.ts +0 -40
  28. package/src/extensions/error-tracking/types.ts +0 -69
  29. package/src/extensions/express.ts +0 -37
  30. package/src/extensions/feature-flags/crypto-helpers.ts +0 -36
  31. package/src/extensions/feature-flags/crypto.ts +0 -22
  32. package/src/extensions/feature-flags/feature-flags.ts +0 -865
  33. package/src/extensions/feature-flags/lazy.ts +0 -55
  34. package/src/extensions/sentry-integration.ts +0 -204
  35. package/src/fetch.ts +0 -39
  36. package/src/storage-memory.ts +0 -13
  37. package/src/types.ts +0 -275
  38. package/test/crypto.spec.ts +0 -36
  39. package/test/extensions/error-conversion.spec.ts +0 -44
  40. package/test/extensions/sentry-integration.spec.ts +0 -163
  41. package/test/feature-flags.decide.spec.ts +0 -381
  42. package/test/feature-flags.spec.ts +0 -4686
  43. package/test/lazy.spec.ts +0 -71
  44. package/test/posthog-node.spec.ts +0 -1341
  45. package/test/test-utils.ts +0 -111
  46. package/tsconfig.json +0 -8
@@ -1,163 +0,0 @@
1
- import { PostHog } from '../../src/entrypoints/index.node'
2
- import { PostHogSentryIntegration } from '../../src/extensions/sentry-integration'
3
- jest.mock('../../src/fetch')
4
- import fetch from '../../src/fetch'
5
- import { waitForPromises } from 'posthog-core/test/test-utils/test-utils'
6
-
7
- jest.mock('../../package.json', () => ({ version: '1.2.3' }))
8
-
9
- const mockedFetch = jest.mocked(fetch, true)
10
-
11
- const getLastBatchEvents = (): any[] | undefined => {
12
- expect(mockedFetch).toHaveBeenCalledWith('http://example.com/batch/', expect.objectContaining({ method: 'POST' }))
13
-
14
- // reverse mock calls array to get the last call
15
- const call = mockedFetch.mock.calls.reverse().find((x) => (x[0] as string).includes('/batch/'))
16
- if (!call) {
17
- return undefined
18
- }
19
- return JSON.parse((call[1] as any).body as any).batch
20
- }
21
-
22
- const createMockSentryException = (): any => ({
23
- exception: {
24
- values: [
25
- {
26
- type: 'Error',
27
- value: 'example error',
28
- stacktrace: {
29
- frames: [],
30
- },
31
- mechanism: { type: 'generic', handled: true },
32
- },
33
- ],
34
- },
35
- event_id: '80a7023ac32c47f7acb0adaed600d149',
36
- platform: 'node',
37
- contexts: {},
38
- server_name: 'localhost',
39
- timestamp: 1704203482.356,
40
- environment: 'production',
41
- level: 'error',
42
- tags: { posthog_distinct_id: 'EXAMPLE_APP_GLOBAL' },
43
- breadcrumbs: [
44
- {
45
- timestamp: 1704203481.422,
46
- category: 'console',
47
- level: 'log',
48
- message: '⚡: Server is running at http://localhost:8010',
49
- },
50
- {
51
- timestamp: 1704203481.658,
52
- category: 'console',
53
- level: 'log',
54
- message:
55
- "PostHog Debug error [ClientError: Your personalApiKey is invalid. Are you sure you're not using your Project API key? More information: https://posthog.com/docs/api/overview]",
56
- },
57
- ],
58
- sdkProcessingMetadata: {
59
- propagationContext: { traceId: 'ea26146e5a354cb0b3b1daebb3f90e33', spanId: '8d642089c3daa272' },
60
- },
61
- })
62
-
63
- describe('PostHogSentryIntegration', () => {
64
- let posthog: PostHog
65
- let posthogSentry: PostHogSentryIntegration
66
-
67
- jest.useFakeTimers()
68
-
69
- beforeEach(() => {
70
- posthog = new PostHog('TEST_API_KEY', {
71
- host: 'http://example.com',
72
- fetchRetryCount: 0,
73
- })
74
-
75
- posthogSentry = new PostHogSentryIntegration(posthog)
76
-
77
- mockedFetch.mockResolvedValue({
78
- status: 200,
79
- text: () => Promise.resolve('ok'),
80
- json: () =>
81
- Promise.resolve({
82
- status: 'ok',
83
- }),
84
- } as any)
85
- })
86
-
87
- afterEach(async () => {
88
- // ensure clean shutdown & no test interdependencies
89
- await posthog.shutdown()
90
- })
91
-
92
- it('should forward sentry exceptions to posthog', async () => {
93
- expect(mockedFetch).toHaveBeenCalledTimes(0)
94
-
95
- const mockSentry = {
96
- getClient: () => ({
97
- getDsn: () => ({
98
- projectId: 123,
99
- }),
100
- }),
101
- }
102
-
103
- let processorFunction: any
104
-
105
- posthogSentry.setupOnce(
106
- (fn) => (processorFunction = fn),
107
- () => mockSentry
108
- )
109
-
110
- processorFunction(createMockSentryException())
111
-
112
- await waitForPromises() // First flush
113
- jest.runOnlyPendingTimers() // Flush timer
114
- await waitForPromises() // Second flush
115
- const batchEvents = getLastBatchEvents()
116
-
117
- expect(batchEvents).toEqual([
118
- {
119
- distinct_id: 'EXAMPLE_APP_GLOBAL',
120
- event: '$exception',
121
- properties: {
122
- $exception_level: 'error',
123
- $exception_list: [
124
- {
125
- mechanism: { handled: true, type: 'generic' },
126
- stacktrace: { frames: [], type: 'raw' },
127
- type: 'Error',
128
- value: 'example error',
129
- },
130
- ],
131
- $exception_message: 'example error',
132
- $exception_type: 'Error',
133
- $exception_personURL: 'http://example.com/project/TEST_API_KEY/person/EXAMPLE_APP_GLOBAL',
134
- $sentry_event_id: '80a7023ac32c47f7acb0adaed600d149',
135
- $sentry_exception: {
136
- values: [
137
- {
138
- type: 'Error',
139
- value: 'example error',
140
- stacktrace: { frames: [] },
141
- mechanism: { type: 'generic', handled: true },
142
- },
143
- ],
144
- },
145
- $sentry_exception_message: 'example error',
146
- $sentry_exception_type: 'Error',
147
- $sentry_tags: {
148
- posthog_distinct_id: 'EXAMPLE_APP_GLOBAL',
149
- 'PostHog Person URL': 'http://example.com/project/TEST_API_KEY/person/EXAMPLE_APP_GLOBAL',
150
- },
151
- $lib: 'posthog-node',
152
- $lib_version: '1.2.3',
153
- $geoip_disable: true,
154
- },
155
- type: 'capture',
156
- library: 'posthog-node',
157
- library_version: '1.2.3',
158
- timestamp: expect.any(String),
159
- uuid: expect.any(String),
160
- },
161
- ])
162
- })
163
- })
@@ -1,381 +0,0 @@
1
- import { PostHog } from '../src/entrypoints/index.node'
2
- import { PostHogOptions } from '../src/types'
3
- import fetch from '../src/fetch'
4
- import { apiImplementation, apiImplementationV4 } from './test-utils'
5
- import { waitForPromises } from 'posthog-core/test/test-utils/test-utils'
6
- import { PostHogV4DecideResponse } from 'posthog-core/src/types'
7
- jest.mock('../src/fetch')
8
-
9
- jest.spyOn(console, 'debug').mockImplementation()
10
-
11
- const mockedFetch = jest.mocked(fetch, true)
12
-
13
- const posthogImmediateResolveOptions: PostHogOptions = {
14
- fetchRetryCount: 0,
15
- }
16
-
17
- describe('decide v4', () => {
18
- describe('getFeatureFlag v4', () => {
19
- it('returns undefined if the flag is not found', async () => {
20
- const decideResponse: PostHogV4DecideResponse = {
21
- flags: {},
22
- errorsWhileComputingFlags: false,
23
- requestId: '0152a345-295f-4fba-adac-2e6ea9c91082',
24
- }
25
- mockedFetch.mockImplementation(apiImplementationV4(decideResponse))
26
-
27
- const posthog = new PostHog('TEST_API_KEY', {
28
- host: 'http://example.com',
29
- ...posthogImmediateResolveOptions,
30
- })
31
- let capturedMessage: any
32
- posthog.on('capture', (message) => {
33
- capturedMessage = message
34
- })
35
-
36
- const result = await posthog.getFeatureFlag('non-existent-flag', 'some-distinct-id')
37
-
38
- expect(result).toBe(undefined)
39
- expect(mockedFetch).toHaveBeenCalledWith('http://example.com/flags/?v=2', expect.any(Object))
40
-
41
- await waitForPromises()
42
- expect(capturedMessage).toMatchObject({
43
- distinct_id: 'some-distinct-id',
44
- event: '$feature_flag_called',
45
- library: posthog.getLibraryId(),
46
- library_version: posthog.getLibraryVersion(),
47
- properties: {
48
- '$feature/non-existent-flag': undefined,
49
- $feature_flag: 'non-existent-flag',
50
- $feature_flag_response: undefined,
51
- $feature_flag_request_id: '0152a345-295f-4fba-adac-2e6ea9c91082',
52
- $groups: undefined,
53
- $lib: posthog.getLibraryId(),
54
- $lib_version: posthog.getLibraryVersion(),
55
- locally_evaluated: false,
56
- },
57
- })
58
- })
59
-
60
- it.each([
61
- {
62
- key: 'variant-flag',
63
- expectedResponse: 'variant-value',
64
- expectedReason: 'Matched condition set 3',
65
- expectedId: 2,
66
- expectedVersion: 23,
67
- },
68
- {
69
- key: 'boolean-flag',
70
- expectedResponse: true,
71
- expectedReason: 'Matched condition set 1',
72
- expectedId: 1,
73
- expectedVersion: 12,
74
- },
75
- {
76
- key: 'non-matching-flag',
77
- expectedResponse: false,
78
- expectedReason: 'Did not match any condition',
79
- expectedId: 3,
80
- expectedVersion: 2,
81
- },
82
- ])(
83
- 'captures a feature flag called event with extra metadata when the flag is found',
84
- async ({ key, expectedResponse, expectedReason, expectedId, expectedVersion }) => {
85
- const decideResponse: PostHogV4DecideResponse = {
86
- flags: {
87
- 'variant-flag': {
88
- key: 'variant-flag',
89
- enabled: true,
90
- variant: 'variant-value',
91
- reason: {
92
- code: 'variant',
93
- condition_index: 2,
94
- description: 'Matched condition set 3',
95
- },
96
- metadata: {
97
- id: 2,
98
- version: 23,
99
- payload: '{"key": "value"}',
100
- description: 'description',
101
- },
102
- },
103
- 'boolean-flag': {
104
- key: 'boolean-flag',
105
- enabled: true,
106
- variant: undefined,
107
- reason: {
108
- code: 'boolean',
109
- condition_index: 1,
110
- description: 'Matched condition set 1',
111
- },
112
- metadata: {
113
- id: 1,
114
- version: 12,
115
- payload: undefined,
116
- description: 'description',
117
- },
118
- },
119
- 'non-matching-flag': {
120
- key: 'non-matching-flag',
121
- enabled: false,
122
- variant: undefined,
123
- reason: {
124
- code: 'boolean',
125
- condition_index: 1,
126
- description: 'Did not match any condition',
127
- },
128
- metadata: {
129
- id: 3,
130
- version: 2,
131
- payload: undefined,
132
- description: 'description',
133
- },
134
- },
135
- },
136
- errorsWhileComputingFlags: false,
137
- requestId: '0152a345-295f-4fba-adac-2e6ea9c91082',
138
- }
139
- mockedFetch.mockImplementation(apiImplementationV4(decideResponse))
140
-
141
- const posthog = new PostHog('TEST_API_KEY', {
142
- host: 'http://example.com',
143
- ...posthogImmediateResolveOptions,
144
- })
145
- let capturedMessage: any
146
- posthog.on('capture', (message) => {
147
- capturedMessage = message
148
- })
149
-
150
- const result = await posthog.getFeatureFlag(key, 'some-distinct-id')
151
-
152
- expect(result).toBe(expectedResponse)
153
- expect(mockedFetch).toHaveBeenCalledWith('http://example.com/flags/?v=2', expect.any(Object))
154
-
155
- await waitForPromises()
156
- expect(capturedMessage).toMatchObject({
157
- distinct_id: 'some-distinct-id',
158
- event: '$feature_flag_called',
159
- library: posthog.getLibraryId(),
160
- library_version: posthog.getLibraryVersion(),
161
- properties: {
162
- [`$feature/${key}`]: expectedResponse,
163
- $feature_flag: key,
164
- $feature_flag_response: expectedResponse,
165
- $feature_flag_id: expectedId,
166
- $feature_flag_version: expectedVersion,
167
- $feature_flag_reason: expectedReason,
168
- $feature_flag_request_id: '0152a345-295f-4fba-adac-2e6ea9c91082',
169
- $groups: undefined,
170
- $lib: posthog.getLibraryId(),
171
- $lib_version: posthog.getLibraryVersion(),
172
- locally_evaluated: false,
173
- },
174
- })
175
- }
176
- )
177
-
178
- describe('getFeatureFlagPayload v4', () => {
179
- it('returns payload', async () => {
180
- mockedFetch.mockImplementation(
181
- apiImplementationV4({
182
- flags: {
183
- 'flag-with-payload': {
184
- key: 'flag-with-payload',
185
- enabled: true,
186
- variant: undefined,
187
- reason: {
188
- code: 'boolean',
189
- condition_index: 1,
190
- description: 'Matched condition set 2',
191
- },
192
- metadata: {
193
- id: 1,
194
- version: 12,
195
- payload: '[0, 1, 2]',
196
- description: 'description',
197
- },
198
- },
199
- },
200
- errorsWhileComputingFlags: false,
201
- })
202
- )
203
-
204
- const posthog = new PostHog('TEST_API_KEY', {
205
- host: 'http://example.com',
206
- ...posthogImmediateResolveOptions,
207
- })
208
- let capturedMessage: any
209
- posthog.on('capture', (message) => {
210
- capturedMessage = message
211
- })
212
-
213
- const result = await posthog.getFeatureFlagPayload('flag-with-payload', 'some-distinct-id')
214
-
215
- expect(result).toEqual([0, 1, 2])
216
- expect(mockedFetch).toHaveBeenCalledWith('http://example.com/flags/?v=2', expect.any(Object))
217
-
218
- await waitForPromises()
219
- expect(capturedMessage).toBeUndefined()
220
- })
221
- })
222
- })
223
-
224
- describe('error handling', () => {
225
- let posthog: PostHog
226
- describe.each([
227
- {
228
- case: 'JSON error response',
229
- mock: apiImplementationV4({
230
- status: 400,
231
- json: () => Promise.resolve({ error: 'error response' }),
232
- }),
233
- },
234
- {
235
- case: 'undefined response',
236
- mock: apiImplementationV4({
237
- status: 400,
238
- json: () => Promise.resolve(undefined),
239
- }),
240
- },
241
- {
242
- case: 'null response',
243
- mock: apiImplementationV4({
244
- status: 400,
245
- json: () => Promise.resolve(null),
246
- }),
247
- },
248
- {
249
- case: 'empty response',
250
- mock: apiImplementationV4({
251
- status: 400,
252
- json: () => Promise.resolve({}),
253
- }),
254
- },
255
- {
256
- case: 'network error',
257
- mock: () => Promise.reject(new Error('Network error')),
258
- },
259
- {
260
- case: 'invalid JSON',
261
- mock: apiImplementationV4({
262
- status: 500,
263
- json: () => Promise.reject(new Error('Invalid JSON')),
264
- }),
265
- },
266
- ])('when $case', ({ mock }) => {
267
- beforeEach(() => {
268
- posthog = new PostHog('TEST_API_KEY', {
269
- host: 'http://example.com',
270
- ...posthogImmediateResolveOptions,
271
- })
272
- mockedFetch.mockImplementation(mock)
273
- })
274
-
275
- it('getFeatureFlag returns undefined', async () => {
276
- expect(await posthog.getFeatureFlag('error-flag', 'some-distinct-id')).toBe(undefined)
277
- })
278
-
279
- it('isFeatureEnabled returns undefined', async () => {
280
- expect(await posthog.isFeatureEnabled('error-flag', 'some-distinct-id')).toBe(undefined)
281
- })
282
-
283
- it('getFeatureFlagPayload returns undefined', async () => {
284
- expect(await posthog.getFeatureFlagPayload('error-flag', 'some-distinct-id')).toBe(undefined)
285
- })
286
-
287
- it('getAllFlags returns empty object', async () => {
288
- expect(await posthog.getAllFlags('some-distinct-id')).toEqual({})
289
- })
290
-
291
- it('getAllFlagsAndPayloads returns object with empty flags and payloads', async () => {
292
- expect(await posthog.getAllFlagsAndPayloads('some-distinct-id')).toEqual({
293
- featureFlags: {},
294
- featureFlagPayloads: {},
295
- })
296
- })
297
-
298
- it('captures no events', async () => {
299
- let capturedMessage: any
300
- posthog.on('capture', (message) => {
301
- capturedMessage = message
302
- })
303
-
304
- await posthog.getFeatureFlag('error-flag', 'some-distinct-id')
305
- await waitForPromises()
306
- expect(capturedMessage).toBeUndefined()
307
- })
308
- })
309
- })
310
- })
311
-
312
- describe('decide v3', () => {
313
- describe('getFeatureFlag v3', () => {
314
- it('returns undefined if the flag is not found', async () => {
315
- mockedFetch.mockImplementation(apiImplementation({ decideFlags: {} }))
316
-
317
- const posthog = new PostHog('TEST_API_KEY', {
318
- host: 'http://example.com',
319
- ...posthogImmediateResolveOptions,
320
- })
321
- let capturedMessage: any
322
- posthog.on('capture', (message) => {
323
- capturedMessage = message
324
- })
325
-
326
- const result = await posthog.getFeatureFlag('non-existent-flag', 'some-distinct-id')
327
-
328
- expect(result).toBe(undefined)
329
- expect(mockedFetch).toHaveBeenCalledWith('http://example.com/flags/?v=2', expect.any(Object))
330
-
331
- await waitForPromises()
332
- expect(capturedMessage).toMatchObject({
333
- distinct_id: 'some-distinct-id',
334
- event: '$feature_flag_called',
335
- library: posthog.getLibraryId(),
336
- library_version: posthog.getLibraryVersion(),
337
- properties: {
338
- '$feature/non-existent-flag': undefined,
339
- $feature_flag: 'non-existent-flag',
340
- $feature_flag_response: undefined,
341
- $groups: undefined,
342
- $lib: posthog.getLibraryId(),
343
- $lib_version: posthog.getLibraryVersion(),
344
- locally_evaluated: false,
345
- },
346
- })
347
- })
348
- })
349
-
350
- describe('getFeatureFlagPayload v3', () => {
351
- it('returns payload', async () => {
352
- mockedFetch.mockImplementation(
353
- apiImplementation({
354
- decideFlags: {
355
- 'flag-with-payload': true,
356
- },
357
- decideFlagPayloads: {
358
- 'flag-with-payload': [0, 1, 2],
359
- },
360
- })
361
- )
362
-
363
- const posthog = new PostHog('TEST_API_KEY', {
364
- host: 'http://example.com',
365
- ...posthogImmediateResolveOptions,
366
- })
367
- let capturedMessage: any = undefined
368
- posthog.on('capture', (message) => {
369
- capturedMessage = message
370
- })
371
-
372
- const result = await posthog.getFeatureFlagPayload('flag-with-payload', 'some-distinct-id')
373
-
374
- expect(result).toEqual([0, 1, 2])
375
- expect(mockedFetch).toHaveBeenCalledWith('http://example.com/flags/?v=2', expect.any(Object))
376
-
377
- await waitForPromises()
378
- expect(capturedMessage).toBeUndefined()
379
- })
380
- })
381
- })