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.
- package/CHANGELOG.md +6 -1
- package/lib/index.cjs.js +87 -24
- package/lib/index.cjs.js.map +1 -1
- package/lib/index.d.ts +17 -0
- package/lib/index.esm.js +86 -23
- package/lib/index.esm.js.map +1 -1
- package/lib/posthog-core/src/types.d.ts +3 -1
- package/lib/posthog-node/src/feature-flags.d.ts +5 -3
- package/lib/posthog-node/src/fetch.d.ts +2 -0
- package/lib/posthog-node/src/posthog-node.d.ts +2 -1
- package/package.json +3 -3
- package/src/feature-flags.ts +35 -19
- package/src/fetch.ts +20 -0
- package/src/posthog-node.ts +6 -3
- package/test/feature-flags.spec.ts +113 -115
- package/test/posthog-node.spec.ts +41 -40
|
@@ -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
|
|
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 {
|
|
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
|
-
|
|
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<
|
|
46
|
+
_requestFeatureFlagDefinitions(): Promise<PostHogFetchResponse>;
|
|
45
47
|
stopPoller(): void;
|
|
46
48
|
}
|
|
47
49
|
declare function matchProperty(property: FeatureFlagCondition['properties'][number], propertyValues: Record<string, any>): boolean;
|
|
@@ -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
|
|
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
|
+
"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
|
-
"
|
|
22
|
+
"axios": "^0.27.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/node": "^18.0.0",
|
package/src/feature-flags.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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<
|
|
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:
|
|
315
|
+
const options: PostHogFetchOptions = {
|
|
309
316
|
method: 'GET',
|
|
310
|
-
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/src/posthog-node.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { version } from '../package.json'
|
|
2
|
-
|
|
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
|
|
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 = {}
|