posthog-node 4.10.0 → 4.10.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.
- package/CHANGELOG.md +13 -3
- package/lib/index.cjs.js +162 -60
- package/lib/index.cjs.js.map +1 -1
- package/lib/index.d.ts +250 -13
- package/lib/index.esm.js +162 -60
- package/lib/index.esm.js.map +1 -1
- package/lib/posthog-core/src/index.d.ts +15 -1
- package/lib/posthog-core/src/types.d.ts +35 -12
- package/lib/posthog-node/src/feature-flags.d.ts +2 -2
- package/lib/posthog-node/src/posthog-node.d.ts +1 -1
- package/lib/posthog-node/test/test-utils.d.ts +2 -1
- package/package.json +1 -1
- package/src/extensions/sentry-integration.ts +13 -6
- package/src/feature-flags.ts +77 -50
- package/src/posthog-node.ts +8 -10
- package/test/extensions/sentry-integration.spec.ts +1 -1
- package/test/posthog-node.spec.ts +31 -0
- package/test/test-utils.ts +3 -0
|
@@ -1,18 +1,22 @@
|
|
|
1
|
-
import { PostHogFetchOptions, PostHogFetchResponse, PostHogAutocaptureElement, PostHogDecideResponse, PostHogCoreOptions, PostHogEventProperties, PostHogPersistedProperty, PostHogCaptureOptions, JsonType } from './types';
|
|
1
|
+
import { PostHogFetchOptions, PostHogFetchResponse, PostHogAutocaptureElement, PostHogDecideResponse, PostHogCoreOptions, PostHogEventProperties, PostHogPersistedProperty, PostHogCaptureOptions, JsonType, PostHogRemoteConfig } from './types';
|
|
2
2
|
import { RetriableOptions } from './utils';
|
|
3
3
|
import { LZString } from './lz-string';
|
|
4
4
|
import { SimpleEventEmitter } from './eventemitter';
|
|
5
|
+
import { SurveyResponse } from './surveys-types';
|
|
5
6
|
export * as utils from './utils';
|
|
6
7
|
export declare abstract class PostHogCoreStateless {
|
|
7
8
|
readonly apiKey: string;
|
|
8
9
|
readonly host: string;
|
|
9
10
|
readonly flushAt: number;
|
|
11
|
+
readonly preloadFeatureFlags: boolean;
|
|
12
|
+
readonly disableSurveys: boolean;
|
|
10
13
|
private maxBatchSize;
|
|
11
14
|
private maxQueueSize;
|
|
12
15
|
private flushInterval;
|
|
13
16
|
private flushPromise;
|
|
14
17
|
private requestTimeout;
|
|
15
18
|
private featureFlagsRequestTimeoutMs;
|
|
19
|
+
private remoteConfigRequestTimeoutMs;
|
|
16
20
|
private captureMode;
|
|
17
21
|
private removeDebugCallback?;
|
|
18
22
|
private disableGeoip;
|
|
@@ -25,6 +29,7 @@ export declare abstract class PostHogCoreStateless {
|
|
|
25
29
|
protected _retryOptions: RetriableOptions;
|
|
26
30
|
protected _initPromise: Promise<void>;
|
|
27
31
|
protected _isInitialized: boolean;
|
|
32
|
+
protected _remoteConfigResponsePromise?: Promise<PostHogRemoteConfig | undefined>;
|
|
28
33
|
abstract fetch(url: string, options: PostHogFetchOptions): Promise<PostHogFetchResponse>;
|
|
29
34
|
abstract getLibraryId(): string;
|
|
30
35
|
abstract getLibraryVersion(): string;
|
|
@@ -58,6 +63,7 @@ export declare abstract class PostHogCoreStateless {
|
|
|
58
63
|
*** GROUPS
|
|
59
64
|
***/
|
|
60
65
|
protected groupIdentifyStateless(groupType: string, groupKey: string | number, groupProperties?: PostHogEventProperties, options?: PostHogCaptureOptions, distinctId?: string, eventProperties?: PostHogEventProperties): void;
|
|
66
|
+
protected getRemoteConfig(): Promise<PostHogRemoteConfig | undefined>;
|
|
61
67
|
/***
|
|
62
68
|
*** FEATURE FLAGS
|
|
63
69
|
***/
|
|
@@ -79,6 +85,10 @@ export declare abstract class PostHogCoreStateless {
|
|
|
79
85
|
payloads: PostHogDecideResponse['featureFlagPayloads'] | undefined;
|
|
80
86
|
requestId: PostHogDecideResponse['requestId'] | undefined;
|
|
81
87
|
}>;
|
|
88
|
+
/***
|
|
89
|
+
*** SURVEYS
|
|
90
|
+
***/
|
|
91
|
+
getSurveysStateless(): Promise<SurveyResponse['surveys']>;
|
|
82
92
|
/***
|
|
83
93
|
*** QUEUEING AND FLUSHING
|
|
84
94
|
***/
|
|
@@ -170,10 +180,13 @@ export declare abstract class PostHogCore extends PostHogCoreStateless {
|
|
|
170
180
|
groupProperties(properties: {
|
|
171
181
|
[type: string]: Record<string, string>;
|
|
172
182
|
}): void;
|
|
183
|
+
private remoteConfigAsync;
|
|
173
184
|
/***
|
|
174
185
|
*** FEATURE FLAGS
|
|
175
186
|
***/
|
|
176
187
|
private decideAsync;
|
|
188
|
+
private cacheSessionReplay;
|
|
189
|
+
private _remoteConfigAsync;
|
|
177
190
|
private _decideAsync;
|
|
178
191
|
private setKnownFeatureFlags;
|
|
179
192
|
private setKnownFeatureFlagPayloads;
|
|
@@ -187,6 +200,7 @@ export declare abstract class PostHogCore extends PostHogCoreStateless {
|
|
|
187
200
|
};
|
|
188
201
|
isFeatureEnabled(key: string): boolean | undefined;
|
|
189
202
|
reloadFeatureFlags(cb?: (err?: Error, flags?: PostHogDecideResponse['featureFlags']) => void): void;
|
|
203
|
+
reloadRemoteConfigAsync(): Promise<PostHogRemoteConfig | undefined>;
|
|
190
204
|
reloadFeatureFlagsAsync(sendAnonDistinctId?: boolean): Promise<PostHogDecideResponse['featureFlags'] | undefined>;
|
|
191
205
|
onFeatureFlags(cb: (flags: PostHogDecideResponse['featureFlags']) => void): () => void;
|
|
192
206
|
onFeatureFlag(key: string, cb: (value: string | boolean) => void): () => void;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
+
import { Survey } from './surveys-types';
|
|
2
3
|
export type PostHogCoreOptions = {
|
|
3
4
|
/** PostHog API host, usually 'https://us.i.posthog.com' or 'https://eu.i.posthog.com' */
|
|
4
5
|
host?: string;
|
|
@@ -20,6 +21,18 @@ export type PostHogCoreOptions = {
|
|
|
20
21
|
sendFeatureFlagEvent?: boolean;
|
|
21
22
|
/** Whether to load feature flags when initialized or not */
|
|
22
23
|
preloadFeatureFlags?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Whether to load remote config when initialized or not
|
|
26
|
+
* Experimental support
|
|
27
|
+
* Default: false - Remote config is loaded by default
|
|
28
|
+
*/
|
|
29
|
+
disableRemoteConfig?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Whether to load surveys when initialized or not
|
|
32
|
+
* Experimental support
|
|
33
|
+
* Default: false - Surveys are loaded by default, but requires the `PostHogSurveyProvider` to be used
|
|
34
|
+
*/
|
|
35
|
+
disableSurveys?: boolean;
|
|
23
36
|
/** Option to bootstrap the library with given distinctId and feature flags */
|
|
24
37
|
bootstrap?: {
|
|
25
38
|
distinctId?: string;
|
|
@@ -35,6 +48,8 @@ export type PostHogCoreOptions = {
|
|
|
35
48
|
requestTimeout?: number;
|
|
36
49
|
/** Timeout in milliseconds for feature flag calls. Defaults to 10 seconds for stateful clients, and 3 seconds for stateless. */
|
|
37
50
|
featureFlagsRequestTimeoutMs?: number;
|
|
51
|
+
/** Timeout in milliseconds for remote config calls. Defaults to 3 seconds. */
|
|
52
|
+
remoteConfigRequestTimeoutMs?: number;
|
|
38
53
|
/** For Session Analysis how long before we expire a session (defaults to 30 mins) */
|
|
39
54
|
sessionExpirationTimeSeconds?: number;
|
|
40
55
|
/** Whether to post events to PostHog in JSON or compressed format. Defaults to 'json' */
|
|
@@ -61,7 +76,11 @@ export declare enum PostHogPersistedProperty {
|
|
|
61
76
|
InstalledAppBuild = "installed_app_build",
|
|
62
77
|
InstalledAppVersion = "installed_app_version",
|
|
63
78
|
SessionReplay = "session_replay",
|
|
64
|
-
DecideEndpointWasHit = "decide_endpoint_was_hit"
|
|
79
|
+
DecideEndpointWasHit = "decide_endpoint_was_hit",
|
|
80
|
+
SurveyLastSeenDate = "survey_last_seen_date",
|
|
81
|
+
SurveysSeen = "surveys_seen",
|
|
82
|
+
Surveys = "surveys",
|
|
83
|
+
RemoteConfig = "remote_config"
|
|
65
84
|
}
|
|
66
85
|
export type PostHogFetchOptions = {
|
|
67
86
|
method: 'GET' | 'POST' | 'PUT' | 'PATCH';
|
|
@@ -102,16 +121,20 @@ export type PostHogAutocaptureElement = {
|
|
|
102
121
|
} & {
|
|
103
122
|
[key: string]: any;
|
|
104
123
|
};
|
|
105
|
-
export
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
};
|
|
109
|
-
editorParams: {
|
|
110
|
-
toolbarVersion: string;
|
|
111
|
-
jsURL: string;
|
|
124
|
+
export interface PostHogRemoteConfig {
|
|
125
|
+
sessionRecording?: boolean | {
|
|
126
|
+
[key: string]: JsonType;
|
|
112
127
|
};
|
|
113
|
-
|
|
114
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Whether surveys are enabled
|
|
130
|
+
*/
|
|
131
|
+
surveys?: boolean | Survey[];
|
|
132
|
+
/**
|
|
133
|
+
* Indicates if the team has any flags enabled (if not we don't need to load them)
|
|
134
|
+
*/
|
|
135
|
+
hasFeatureFlags?: boolean;
|
|
136
|
+
}
|
|
137
|
+
export interface PostHogDecideResponse extends Omit<PostHogRemoteConfig, 'surveys' | 'hasFeatureFlags'> {
|
|
115
138
|
featureFlags: {
|
|
116
139
|
[key: string]: string | boolean;
|
|
117
140
|
};
|
|
@@ -119,12 +142,12 @@ export type PostHogDecideResponse = {
|
|
|
119
142
|
[key: string]: JsonType;
|
|
120
143
|
};
|
|
121
144
|
errorsWhileComputingFlags: boolean;
|
|
122
|
-
quotaLimited?: string[];
|
|
123
145
|
sessionRecording?: boolean | {
|
|
124
146
|
[key: string]: JsonType;
|
|
125
147
|
};
|
|
148
|
+
quotaLimited?: string[];
|
|
126
149
|
requestId?: string;
|
|
127
|
-
}
|
|
150
|
+
}
|
|
128
151
|
export type PostHogFlagsAndPayloadsResponse = {
|
|
129
152
|
featureFlags: PostHogDecideResponse['featureFlags'];
|
|
130
153
|
featureFlagPayloads: PostHogDecideResponse['featureFlagPayloads'];
|
|
@@ -37,8 +37,8 @@ declare class FeatureFlagsPoller {
|
|
|
37
37
|
customHeaders?: {
|
|
38
38
|
[key: string]: string;
|
|
39
39
|
};
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
shouldBeginExponentialBackoff: boolean;
|
|
41
|
+
backOffCount: number;
|
|
42
42
|
constructor({ pollingInterval, personalApiKey, projectApiKey, timeout, host, customHeaders, ...options }: FeatureFlagsPollerOptions);
|
|
43
43
|
debug(enabled?: boolean): void;
|
|
44
44
|
private logMsgIfDebug;
|
|
@@ -30,7 +30,7 @@ export declare class PostHog extends PostHogCoreStateless implements PostHogNode
|
|
|
30
30
|
enable(): Promise<void>;
|
|
31
31
|
disable(): Promise<void>;
|
|
32
32
|
debug(enabled?: boolean): void;
|
|
33
|
-
capture(
|
|
33
|
+
capture(props: EventMessage): void;
|
|
34
34
|
identify({ distinctId, properties, disableGeoip }: IdentifyMessage): void;
|
|
35
35
|
alias(data: {
|
|
36
36
|
distinctId: string;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
export declare const apiImplementation: ({ localFlags, decideFlags, decideFlagPayloads, decideStatus, localFlagsStatus, }: {
|
|
1
|
+
export declare const apiImplementation: ({ localFlags, decideFlags, decideFlagPayloads, decideStatus, localFlagsStatus, errorsWhileComputingFlags, }: {
|
|
2
2
|
localFlags?: any;
|
|
3
3
|
decideFlags?: any;
|
|
4
4
|
decideFlagPayloads?: any;
|
|
5
5
|
decideStatus?: number | undefined;
|
|
6
6
|
localFlagsStatus?: number | undefined;
|
|
7
|
+
errorsWhileComputingFlags?: boolean | undefined;
|
|
7
8
|
}) => (url: any) => Promise<any>;
|
|
8
9
|
export declare const anyLocalEvalCall: any[];
|
|
9
10
|
export declare const anyDecideCall: any[];
|
package/package.json
CHANGED
|
@@ -101,11 +101,18 @@ export function createEventProcessor(
|
|
|
101
101
|
|
|
102
102
|
const exceptions: _SentryException[] = event.exception?.values || []
|
|
103
103
|
|
|
104
|
-
exceptions.map((exception) => {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
104
|
+
const exceptionList = exceptions.map((exception) => ({
|
|
105
|
+
...exception,
|
|
106
|
+
stacktrace: exception.stacktrace
|
|
107
|
+
? {
|
|
108
|
+
...exception.stacktrace,
|
|
109
|
+
type: 'raw',
|
|
110
|
+
frames: (exception.stacktrace.frames || []).map((frame: any) => {
|
|
111
|
+
return { ...frame, platform: 'node:javascript' }
|
|
112
|
+
}),
|
|
113
|
+
}
|
|
114
|
+
: undefined,
|
|
115
|
+
}))
|
|
109
116
|
|
|
110
117
|
const properties: SentryExceptionProperties & {
|
|
111
118
|
// two properties added to match any exception auto-capture
|
|
@@ -121,7 +128,7 @@ export function createEventProcessor(
|
|
|
121
128
|
$exception_type: exceptions[0]?.type,
|
|
122
129
|
$exception_personURL: personUrl,
|
|
123
130
|
$exception_level: event.level,
|
|
124
|
-
$exception_list:
|
|
131
|
+
$exception_list: exceptionList,
|
|
125
132
|
// Sentry Exception Properties
|
|
126
133
|
$sentry_event_id: event.event_id,
|
|
127
134
|
$sentry_exception: event.exception,
|
package/src/feature-flags.ts
CHANGED
|
@@ -58,8 +58,8 @@ class FeatureFlagsPoller {
|
|
|
58
58
|
debugMode: boolean = false
|
|
59
59
|
onError?: (error: Error) => void
|
|
60
60
|
customHeaders?: { [key: string]: string }
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
shouldBeginExponentialBackoff: boolean = false
|
|
62
|
+
backOffCount: number = 0
|
|
63
63
|
|
|
64
64
|
constructor({
|
|
65
65
|
pollingInterval,
|
|
@@ -384,11 +384,11 @@ class FeatureFlagsPoller {
|
|
|
384
384
|
* @returns The polling interval to use for the next request.
|
|
385
385
|
*/
|
|
386
386
|
private getPollingInterval(): number {
|
|
387
|
-
if (!this.
|
|
387
|
+
if (!this.shouldBeginExponentialBackoff) {
|
|
388
388
|
return this.pollingInterval
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
-
return Math.min(SIXTY_SECONDS, this.pollingInterval * 2 ** this.
|
|
391
|
+
return Math.min(SIXTY_SECONDS, this.pollingInterval * 2 ** this.backOffCount)
|
|
392
392
|
}
|
|
393
393
|
|
|
394
394
|
async _loadFeatureFlags(): Promise<void> {
|
|
@@ -402,59 +402,86 @@ class FeatureFlagsPoller {
|
|
|
402
402
|
try {
|
|
403
403
|
const res = await this._requestFeatureFlagDefinitions()
|
|
404
404
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
`Your project key or personal API key is invalid. Setting next polling interval to ${this.getPollingInterval()}ms. More information: https://posthog.com/docs/api#rate-limiting`
|
|
410
|
-
)
|
|
405
|
+
// Handle undefined res case, this shouldn't happen, but it doesn't hurt to handle it anyway
|
|
406
|
+
if (!res) {
|
|
407
|
+
// Don't override existing flags when something goes wrong
|
|
408
|
+
return
|
|
411
409
|
}
|
|
412
410
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
411
|
+
// NB ON ERROR HANDLING & `loadedSuccessfullyOnce`:
|
|
412
|
+
//
|
|
413
|
+
// `loadedSuccessfullyOnce` indicates we've successfully loaded a valid set of flags at least once.
|
|
414
|
+
// If we set it to `true` in an error scenario (e.g. 402 Over Quota, 401 Invalid Key, etc.),
|
|
415
|
+
// any manual call to `loadFeatureFlags()` (without forceReload) will skip refetching entirely,
|
|
416
|
+
// leaving us stuck with zero or outdated flags. The poller does keep running, but we also want
|
|
417
|
+
// manual reloads to be possible as soon as the error condition is resolved.
|
|
418
|
+
//
|
|
419
|
+
// Therefore, on error statuses, we do *not* set `loadedSuccessfullyOnce = true`, ensuring that
|
|
420
|
+
// both the background poller and any subsequent manual calls can keep trying to load flags
|
|
421
|
+
// once the issue (quota, permission, rate limit, etc.) is resolved.
|
|
422
|
+
switch (res.status) {
|
|
423
|
+
case 401:
|
|
424
|
+
// Invalid API key
|
|
425
|
+
this.shouldBeginExponentialBackoff = true
|
|
426
|
+
this.backOffCount += 1
|
|
427
|
+
throw new ClientError(
|
|
428
|
+
`Your project key or personal API key is invalid. Setting next polling interval to ${this.getPollingInterval()}ms. More information: https://posthog.com/docs/api#rate-limiting`
|
|
429
|
+
)
|
|
420
430
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
431
|
+
case 402:
|
|
432
|
+
// Quota exceeded - clear all flags
|
|
433
|
+
console.warn(
|
|
434
|
+
'[FEATURE FLAGS] Feature flags quota limit exceeded - unsetting all local flags. Learn more about billing limits at https://posthog.com/docs/billing/limits-alerts'
|
|
435
|
+
)
|
|
436
|
+
this.featureFlags = []
|
|
437
|
+
this.featureFlagsByKey = {}
|
|
438
|
+
this.groupTypeMapping = {}
|
|
439
|
+
this.cohorts = {}
|
|
440
|
+
return
|
|
441
|
+
|
|
442
|
+
case 403:
|
|
443
|
+
// Permissions issue
|
|
444
|
+
this.shouldBeginExponentialBackoff = true
|
|
445
|
+
this.backOffCount += 1
|
|
446
|
+
throw new ClientError(
|
|
447
|
+
`Your personal API key does not have permission to fetch feature flag definitions for local evaluation. Setting next polling interval to ${this.getPollingInterval()}ms. Are you sure you're using the correct personal and Project API key pair? More information: https://posthog.com/docs/api/overview`
|
|
448
|
+
)
|
|
433
449
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
450
|
+
case 429:
|
|
451
|
+
// Rate limited
|
|
452
|
+
this.shouldBeginExponentialBackoff = true
|
|
453
|
+
this.backOffCount += 1
|
|
454
|
+
throw new ClientError(
|
|
455
|
+
`You are being rate limited. Setting next polling interval to ${this.getPollingInterval()}ms. More information: https://posthog.com/docs/api#rate-limiting`
|
|
456
|
+
)
|
|
439
457
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
458
|
+
case 200: {
|
|
459
|
+
// Process successful response
|
|
460
|
+
const responseJson = await res.json()
|
|
461
|
+
if (!('flags' in responseJson)) {
|
|
462
|
+
this.onError?.(new Error(`Invalid response when getting feature flags: ${JSON.stringify(responseJson)}`))
|
|
463
|
+
return
|
|
464
|
+
}
|
|
444
465
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
466
|
+
this.featureFlags = responseJson.flags || []
|
|
467
|
+
this.featureFlagsByKey = this.featureFlags.reduce(
|
|
468
|
+
(acc, curr) => ((acc[curr.key] = curr), acc),
|
|
469
|
+
<Record<string, PostHogFeatureFlag>>{}
|
|
470
|
+
)
|
|
471
|
+
this.groupTypeMapping = responseJson.group_type_mapping || {}
|
|
472
|
+
this.cohorts = responseJson.cohorts || {}
|
|
473
|
+
this.loadedSuccessfullyOnce = true
|
|
474
|
+
this.shouldBeginExponentialBackoff = false
|
|
475
|
+
this.backOffCount = 0
|
|
476
|
+
break
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
default:
|
|
480
|
+
// Something else went wrong, or the server is down.
|
|
481
|
+
// In this case, don't override existing flags
|
|
482
|
+
return
|
|
483
|
+
}
|
|
455
484
|
} catch (err) {
|
|
456
|
-
// if an error that is not an instance of ClientError is thrown
|
|
457
|
-
// we silently ignore the error when reloading feature flags
|
|
458
485
|
if (err instanceof ClientError) {
|
|
459
486
|
this.onError?.(err)
|
|
460
487
|
}
|
package/src/posthog-node.ts
CHANGED
|
@@ -116,16 +116,14 @@ export class PostHog extends PostHogCoreStateless implements PostHogNodeV1 {
|
|
|
116
116
|
this.featureFlagsPoller?.debug(enabled)
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
capture({
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
timestamp,
|
|
126
|
-
|
|
127
|
-
uuid,
|
|
128
|
-
}: EventMessage): void {
|
|
119
|
+
capture(props: EventMessage): void {
|
|
120
|
+
if (typeof props === 'string') {
|
|
121
|
+
this.logMsgIfDebug(() =>
|
|
122
|
+
console.warn('Called capture() with a string as the first argument when an object was expected.')
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
const { distinctId, event, properties, groups, sendFeatureFlags, timestamp, disableGeoip, uuid }: EventMessage =
|
|
126
|
+
props
|
|
129
127
|
const _capture = (props: EventMessage['properties']): void => {
|
|
130
128
|
super.captureStateless(distinctId, event, props, { timestamp, disableGeoip, uuid })
|
|
131
129
|
}
|
|
@@ -328,6 +328,17 @@ describe('PostHog Node.js', () => {
|
|
|
328
328
|
|
|
329
329
|
await client.shutdown()
|
|
330
330
|
})
|
|
331
|
+
|
|
332
|
+
it('should warn if capture is called with a string', () => {
|
|
333
|
+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
|
|
334
|
+
posthog.debug(true)
|
|
335
|
+
// @ts-expect-error - Testing the warning when passing a string instead of an object
|
|
336
|
+
posthog.capture('test-event')
|
|
337
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
338
|
+
'Called capture() with a string as the first argument when an object was expected.'
|
|
339
|
+
)
|
|
340
|
+
warnSpy.mockRestore()
|
|
341
|
+
})
|
|
331
342
|
})
|
|
332
343
|
|
|
333
344
|
describe('shutdown', () => {
|
|
@@ -1308,5 +1319,25 @@ describe('PostHog Node.js', () => {
|
|
|
1308
1319
|
})
|
|
1309
1320
|
)
|
|
1310
1321
|
})
|
|
1322
|
+
|
|
1323
|
+
it('should log error when decide response has errors', async () => {
|
|
1324
|
+
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
|
|
1325
|
+
|
|
1326
|
+
mockedFetch.mockImplementation(
|
|
1327
|
+
apiImplementation({
|
|
1328
|
+
decideFlags: { 'feature-1': true },
|
|
1329
|
+
decideFlagPayloads: {},
|
|
1330
|
+
errorsWhileComputingFlags: true,
|
|
1331
|
+
})
|
|
1332
|
+
)
|
|
1333
|
+
|
|
1334
|
+
await posthog.getFeatureFlag('feature-1', '123')
|
|
1335
|
+
|
|
1336
|
+
expect(errorSpy).toHaveBeenCalledWith(
|
|
1337
|
+
'[FEATURE FLAGS] Error while computing feature flags, some flags may be missing or incorrect. Learn more at https://posthog.com/docs/feature-flags/best-practices'
|
|
1338
|
+
)
|
|
1339
|
+
|
|
1340
|
+
errorSpy.mockRestore()
|
|
1341
|
+
})
|
|
1311
1342
|
})
|
|
1312
1343
|
})
|
package/test/test-utils.ts
CHANGED
|
@@ -4,12 +4,14 @@ export const apiImplementation = ({
|
|
|
4
4
|
decideFlagPayloads,
|
|
5
5
|
decideStatus = 200,
|
|
6
6
|
localFlagsStatus = 200,
|
|
7
|
+
errorsWhileComputingFlags = false,
|
|
7
8
|
}: {
|
|
8
9
|
localFlags?: any
|
|
9
10
|
decideFlags?: any
|
|
10
11
|
decideFlagPayloads?: any
|
|
11
12
|
decideStatus?: number
|
|
12
13
|
localFlagsStatus?: number
|
|
14
|
+
errorsWhileComputingFlags?: boolean
|
|
13
15
|
}) => {
|
|
14
16
|
return (url: any): Promise<any> => {
|
|
15
17
|
if ((url as any).includes('/decide/')) {
|
|
@@ -25,6 +27,7 @@ export const apiImplementation = ({
|
|
|
25
27
|
featureFlagPayloads: Object.fromEntries(
|
|
26
28
|
Object.entries(decideFlagPayloads || {}).map(([k, v]) => [k, JSON.stringify(v)])
|
|
27
29
|
),
|
|
30
|
+
errorsWhileComputingFlags,
|
|
28
31
|
})
|
|
29
32
|
}
|
|
30
33
|
},
|