posthog-node 2.0.2 → 2.1.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.
@@ -1,3 +1,4 @@
1
+ /// <reference types="node" />
1
2
  export declare type PosthogCoreOptions = {
2
3
  host?: string;
3
4
  flushAt?: number;
@@ -30,7 +31,8 @@ export declare type PostHogFetchOptions = {
30
31
  headers: {
31
32
  [key: string]: string;
32
33
  };
33
- body: string;
34
+ body?: string;
35
+ signal?: AbortSignal;
34
36
  };
35
37
  export declare type PostHogFetchResponse = {
36
38
  status: number;
@@ -1,6 +1,6 @@
1
1
  /// <reference types="node" />
2
2
  import { FeatureFlagCondition, PostHogFeatureFlag } from './types';
3
- import { ResponseData } from 'undici/types/dispatcher';
3
+ import { PostHogFetchOptions, PostHogFetchResponse } from 'posthog-core/src';
4
4
  declare class ClientError extends Error {
5
5
  constructor(message: string);
6
6
  }
@@ -13,6 +13,7 @@ declare type FeatureFlagsPollerOptions = {
13
13
  host: string;
14
14
  pollingInterval: number;
15
15
  timeout?: number;
16
+ fetch?: (url: string, options: PostHogFetchOptions) => Promise<PostHogFetchResponse>;
16
17
  };
17
18
  declare class FeatureFlagsPoller {
18
19
  pollingInterval: number;
@@ -24,7 +25,8 @@ declare class FeatureFlagsPoller {
24
25
  timeout?: number;
25
26
  host: FeatureFlagsPollerOptions['host'];
26
27
  poller?: NodeJS.Timeout;
27
- constructor({ pollingInterval, personalApiKey, projectApiKey, timeout, host }: FeatureFlagsPollerOptions);
28
+ fetch: (url: string, options: PostHogFetchOptions) => Promise<PostHogFetchResponse>;
29
+ constructor({ pollingInterval, personalApiKey, projectApiKey, timeout, host, ...options }: FeatureFlagsPollerOptions);
28
30
  getFeatureFlag(key: string, distinctId: string, groups?: Record<string, string>, personProperties?: Record<string, string>, groupProperties?: Record<string, Record<string, string>>): Promise<string | boolean | undefined>;
29
31
  getAllFlags(distinctId: string, groups?: Record<string, string>, personProperties?: Record<string, string>, groupProperties?: Record<string, Record<string, string>>): Promise<{
30
32
  response: Record<string, string | boolean>;
@@ -41,7 +43,7 @@ declare class FeatureFlagsPoller {
41
43
  }[];
42
44
  loadFeatureFlags(forceReload?: boolean): Promise<void>;
43
45
  _loadFeatureFlags(): Promise<void>;
44
- _requestFeatureFlagDefinitions(): Promise<ResponseData>;
46
+ _requestFeatureFlagDefinitions(): Promise<PostHogFetchResponse>;
45
47
  stopPoller(): void;
46
48
  }
47
49
  declare function matchProperty(property: FeatureFlagCondition['properties'][number], propertyValues: Record<string, any>): boolean;
@@ -0,0 +1,2 @@
1
+ import { PostHogFetchOptions, PostHogFetchResponse } from 'posthog-core/src';
2
+ export declare const fetch: (url: string, options: PostHogFetchOptions) => Promise<PostHogFetchResponse>;
@@ -1,4 +1,4 @@
1
- import { PosthogCoreOptions } from '../../posthog-core/src';
1
+ import { PosthogCoreOptions, PostHogFetchOptions, PostHogFetchResponse } from '../../posthog-core/src';
2
2
  import { EventMessageV1, GroupIdentifyMessage, IdentifyMessageV1, PostHogNodeV1 } from './types';
3
3
  export declare type PostHogOptions = PosthogCoreOptions & {
4
4
  persistence?: 'memory';
@@ -6,6 +6,7 @@ export declare type PostHogOptions = PosthogCoreOptions & {
6
6
  featureFlagsPollingInterval?: number;
7
7
  requestTimeout?: number;
8
8
  maxCacheSize?: number;
9
+ fetch?: (url: string, options: PostHogFetchOptions) => Promise<PostHogFetchResponse>;
9
10
  };
10
11
  export declare class PostHog implements PostHogNodeV1 {
11
12
  private _sharedClient;
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "posthog-node",
3
- "version": "2.0.2",
3
+ "version": "2.1.0",
4
4
  "description": "PostHog Node.js integration",
5
5
  "repository": "PostHog/posthog-node",
6
6
  "scripts": {
7
7
  "prepublish": "cd .. && yarn build"
8
8
  },
9
9
  "engines": {
10
- "node": ">=10"
10
+ "node": ">=14.17.0"
11
11
  },
12
12
  "license": "MIT",
13
13
  "author": {
@@ -19,7 +19,7 @@
19
19
  "module": "lib/index.esm.js",
20
20
  "types": "lib/index.d.ts",
21
21
  "dependencies": {
22
- "undici": "^5.8.0"
22
+ "axios": "^0.27.0"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/node": "^18.0.0",
@@ -1,8 +1,9 @@
1
1
  import { createHash } from 'crypto'
2
- import { request } from 'undici'
3
2
  import { FeatureFlagCondition, PostHogFeatureFlag } from './types'
4
3
  import { version } from '../package.json'
5
- import { ResponseData } from 'undici/types/dispatcher'
4
+ import { PostHogFetchOptions, PostHogFetchResponse } from 'posthog-core/src'
5
+ import { safeSetTimeout } from 'posthog-core/src/utils'
6
+ import { fetch } from './fetch'
6
7
 
7
8
  // eslint-disable-next-line
8
9
  const LONG_SCALE = 0xfffffffffffffff
@@ -35,6 +36,7 @@ type FeatureFlagsPollerOptions = {
35
36
  host: string
36
37
  pollingInterval: number
37
38
  timeout?: number
39
+ fetch?: (url: string, options: PostHogFetchOptions) => Promise<PostHogFetchResponse>
38
40
  }
39
41
 
40
42
  class FeatureFlagsPoller {
@@ -47,8 +49,16 @@ class FeatureFlagsPoller {
47
49
  timeout?: number
48
50
  host: FeatureFlagsPollerOptions['host']
49
51
  poller?: NodeJS.Timeout
50
-
51
- constructor({ pollingInterval, personalApiKey, projectApiKey, timeout, host }: FeatureFlagsPollerOptions) {
52
+ fetch: (url: string, options: PostHogFetchOptions) => Promise<PostHogFetchResponse>
53
+
54
+ constructor({
55
+ pollingInterval,
56
+ personalApiKey,
57
+ projectApiKey,
58
+ timeout,
59
+ host,
60
+ ...options
61
+ }: FeatureFlagsPollerOptions) {
52
62
  this.pollingInterval = pollingInterval
53
63
  this.personalApiKey = personalApiKey
54
64
  this.featureFlags = []
@@ -58,6 +68,8 @@ class FeatureFlagsPoller {
58
68
  this.projectApiKey = projectApiKey
59
69
  this.host = host
60
70
  this.poller = undefined
71
+ // NOTE: as any is required here as the AbortSignal typing is slightly misaligned but works just fine
72
+ this.fetch = options.fetch || fetch
61
73
 
62
74
  void this.loadFeatureFlags()
63
75
  }
@@ -275,12 +287,12 @@ class FeatureFlagsPoller {
275
287
  try {
276
288
  const res = await this._requestFeatureFlagDefinitions()
277
289
 
278
- if (res && res.statusCode === 401) {
290
+ if (res && res.status === 401) {
279
291
  throw new ClientError(
280
292
  `Your personalApiKey is invalid. Are you sure you're not using your Project API key? More information: https://posthog.com/docs/api/overview`
281
293
  )
282
294
  }
283
- const responseJson = await res.body.json()
295
+ const responseJson = await res.json()
284
296
  if (!('flags' in responseJson)) {
285
297
  console.error(`Invalid response when getting feature flags: ${JSON.stringify(responseJson)}`)
286
298
  }
@@ -297,31 +309,35 @@ class FeatureFlagsPoller {
297
309
  }
298
310
  }
299
311
 
300
- async _requestFeatureFlagDefinitions(): Promise<ResponseData> {
312
+ async _requestFeatureFlagDefinitions(): Promise<PostHogFetchResponse> {
301
313
  const url = `${this.host}/api/feature_flag/local_evaluation?token=${this.projectApiKey}`
302
- const headers = {
303
- 'Content-Type': 'application/json',
304
- Authorization: `Bearer ${this.personalApiKey}`,
305
- 'user-agent': `posthog-node/${version}`,
306
- }
307
314
 
308
- const options: Parameters<typeof request>[1] = {
315
+ const options: PostHogFetchOptions = {
309
316
  method: 'GET',
310
- headers: headers,
317
+ headers: {
318
+ 'Content-Type': 'application/json',
319
+ Authorization: `Bearer ${this.personalApiKey}`,
320
+ 'user-agent': `posthog-node/${version}`,
321
+ },
311
322
  }
312
323
 
324
+ let abortTimeout = null
325
+
313
326
  if (this.timeout && typeof this.timeout === 'number') {
314
- options.bodyTimeout = this.timeout
327
+ const controller = new AbortController()
328
+ abortTimeout = safeSetTimeout(() => {
329
+ controller.abort()
330
+ }, this.timeout)
331
+ options.signal = controller.signal
315
332
  }
316
333
 
317
- let res
318
334
  try {
319
- res = await request(url, options)
335
+ return await this.fetch(url, options)
320
336
  } catch (err) {
321
337
  throw new Error(`Request failed with error: ${err}`)
338
+ } finally {
339
+ clearTimeout(abortTimeout)
322
340
  }
323
-
324
- return res
325
341
  }
326
342
 
327
343
  stopPoller(): void {
package/src/fetch.ts ADDED
@@ -0,0 +1,20 @@
1
+ import axios from 'axios'
2
+ import { PostHogFetchOptions, PostHogFetchResponse } from 'posthog-core/src'
3
+
4
+ // NOTE: We use axios as a reliable, well supported request library but follow the Fetch API (roughly)
5
+ // So that alternative implementations can be used if desired
6
+ export const fetch = async (url: string, options: PostHogFetchOptions): Promise<PostHogFetchResponse> => {
7
+ const res = await axios.request({
8
+ url,
9
+ headers: options.headers,
10
+ method: options.method.toLowerCase(),
11
+ data: options.body,
12
+ signal: options.signal,
13
+ })
14
+
15
+ return {
16
+ status: res.status,
17
+ text: () => res.data,
18
+ json: () => res.data,
19
+ }
20
+ }
@@ -1,5 +1,5 @@
1
1
  import { version } from '../package.json'
2
- import undici from 'undici'
2
+
3
3
  import {
4
4
  PostHogCore,
5
5
  PosthogCoreOptions,
@@ -10,6 +10,7 @@ import {
10
10
  import { PostHogMemoryStorage } from '../../posthog-core/src/storage-memory'
11
11
  import { EventMessageV1, GroupIdentifyMessage, IdentifyMessageV1, PostHogNodeV1 } from './types'
12
12
  import { FeatureFlagsPoller } from './feature-flags'
13
+ import { fetch } from './fetch'
13
14
 
14
15
  export type PostHogOptions = PosthogCoreOptions & {
15
16
  persistence?: 'memory'
@@ -20,6 +21,7 @@ export type PostHogOptions = PosthogCoreOptions & {
20
21
  requestTimeout?: number
21
22
  // Maximum size of cache that deduplicates $feature_flag_called calls per user.
22
23
  maxCacheSize?: number
24
+ fetch?: (url: string, options: PostHogFetchOptions) => Promise<PostHogFetchResponse>
23
25
  }
24
26
 
25
27
  const THIRTY_SECONDS = 30 * 1000
@@ -28,7 +30,7 @@ const MAX_CACHE_SIZE = 50 * 1000
28
30
  class PostHogClient extends PostHogCore {
29
31
  private _memoryStorage = new PostHogMemoryStorage()
30
32
 
31
- constructor(apiKey: string, options: PostHogOptions = {}) {
33
+ constructor(apiKey: string, private options: PostHogOptions = {}) {
32
34
  options.captureMode = options?.captureMode || 'json'
33
35
  options.preloadFeatureFlags = false // Don't preload as this makes no sense without a distinctId
34
36
  options.sendFeatureFlagEvent = false // Let `posthog-node` handle this on its own, since we're dealing with multiple distinctIDs
@@ -50,7 +52,7 @@ class PostHogClient extends PostHogCore {
50
52
  }
51
53
 
52
54
  fetch(url: string, options: PostHogFetchOptions): Promise<PostHogFetchResponse> {
53
- return undici.fetch(url, options)
55
+ return this.options.fetch ? this.options.fetch(url, options) : fetch(url, options)
54
56
  }
55
57
 
56
58
  getLibraryId(): string {
@@ -84,6 +86,7 @@ export class PostHog implements PostHogNodeV1 {
84
86
  projectApiKey: apiKey,
85
87
  timeout: options.requestTimeout,
86
88
  host: this._sharedClient.host,
89
+ fetch: options.fetch,
87
90
  })
88
91
  }
89
92
  this.distinctIdHasSentFlagCalls = {}