posthog-node 4.7.0 → 4.8.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.
- package/CHANGELOG.md +8 -0
- package/lib/index.cjs.js +61 -5
- package/lib/index.cjs.js.map +1 -1
- package/lib/index.d.ts +5 -1
- package/lib/index.esm.js +59 -6
- package/lib/index.esm.js.map +1 -1
- package/lib/posthog-core/src/index.d.ts +1 -1
- package/lib/posthog-core/src/types.d.ts +1 -0
- package/lib/posthog-core/src/utils.d.ts +1 -0
- package/lib/posthog-node/src/feature-flags.d.ts +9 -0
- package/lib/posthog-node/src/posthog-node.d.ts +3 -0
- package/lib/posthog-node/test/test-utils.d.ts +2 -1
- package/package.json +1 -1
- package/src/feature-flags.ts +45 -3
- package/src/posthog-node.ts +17 -5
- package/test/feature-flags.spec.ts +36 -0
- package/test/posthog-node.spec.ts +34 -2
- package/test/test-utils.ts +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Next
|
|
2
2
|
|
|
3
|
+
# 4.8.1 – 2025-02-26
|
|
4
|
+
|
|
5
|
+
1. Supports gracefully handling quotaLimited responses from the PostHog API for feature flag evaluation
|
|
6
|
+
|
|
7
|
+
# 4.8.0 - 2025-02-26
|
|
8
|
+
|
|
9
|
+
1. Add guardrails and exponential error backoff in the feature flag local evaluation poller to prevent high rates of 401/403 traffic towards `/local_evaluation`
|
|
10
|
+
|
|
3
11
|
# 4.7.0 - 2025-02-20
|
|
4
12
|
|
|
5
13
|
## Added
|
package/lib/index.cjs.js
CHANGED
|
@@ -7,7 +7,7 @@ var node_fs = require('node:fs');
|
|
|
7
7
|
var node_readline = require('node:readline');
|
|
8
8
|
var node_path = require('node:path');
|
|
9
9
|
|
|
10
|
-
var version = "4.
|
|
10
|
+
var version = "4.8.1";
|
|
11
11
|
|
|
12
12
|
var PostHogPersistedProperty;
|
|
13
13
|
(function (PostHogPersistedProperty) {
|
|
@@ -958,6 +958,11 @@ class PostHogFetchNetworkError extends Error {
|
|
|
958
958
|
function isPostHogFetchError(err) {
|
|
959
959
|
return typeof err === 'object' && (err instanceof PostHogFetchHttpError || err instanceof PostHogFetchNetworkError);
|
|
960
960
|
}
|
|
961
|
+
var QuotaLimitedFeature;
|
|
962
|
+
(function (QuotaLimitedFeature) {
|
|
963
|
+
QuotaLimitedFeature["FeatureFlags"] = "feature_flags";
|
|
964
|
+
QuotaLimitedFeature["Recordings"] = "recordings";
|
|
965
|
+
})(QuotaLimitedFeature || (QuotaLimitedFeature = {}));
|
|
961
966
|
class PostHogCoreStateless {
|
|
962
967
|
constructor(apiKey, options) {
|
|
963
968
|
this.flushPromise = null;
|
|
@@ -1201,6 +1206,14 @@ class PostHogCoreStateless {
|
|
|
1201
1206
|
extraPayload['geoip_disable'] = true;
|
|
1202
1207
|
}
|
|
1203
1208
|
const decideResponse = await this.getDecide(distinctId, groups, personProperties, groupProperties, extraPayload);
|
|
1209
|
+
// Add check for quota limitation on feature flags
|
|
1210
|
+
if (decideResponse?.quotaLimited?.includes(QuotaLimitedFeature.FeatureFlags)) {
|
|
1211
|
+
console.warn('[FEATURE FLAGS] Feature flags quota limit exceeded - feature flags unavailable. Learn more about billing limits at https://posthog.com/docs/billing/limits-alerts');
|
|
1212
|
+
return {
|
|
1213
|
+
flags: undefined,
|
|
1214
|
+
payloads: undefined,
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1204
1217
|
const flags = decideResponse?.featureFlags;
|
|
1205
1218
|
const payloads = decideResponse?.featureFlagPayloads;
|
|
1206
1219
|
let parsedPayloads = payloads;
|
|
@@ -1495,6 +1508,8 @@ class FeatureFlagsPoller {
|
|
|
1495
1508
|
...options
|
|
1496
1509
|
}) {
|
|
1497
1510
|
this.debugMode = false;
|
|
1511
|
+
this.lastRequestWasAuthenticationError = false;
|
|
1512
|
+
this.authenticationErrorCount = 0;
|
|
1498
1513
|
this.pollingInterval = pollingInterval;
|
|
1499
1514
|
this.personalApiKey = personalApiKey;
|
|
1500
1515
|
this.featureFlags = [];
|
|
@@ -1506,7 +1521,6 @@ class FeatureFlagsPoller {
|
|
|
1506
1521
|
this.projectApiKey = projectApiKey;
|
|
1507
1522
|
this.host = host;
|
|
1508
1523
|
this.poller = undefined;
|
|
1509
|
-
// NOTE: as any is required here as the AbortSignal typing is slightly misaligned but works just fine
|
|
1510
1524
|
this.fetch = options.fetch || fetch$1;
|
|
1511
1525
|
this.onError = options.onError;
|
|
1512
1526
|
this.customHeaders = customHeaders;
|
|
@@ -1726,16 +1740,45 @@ class FeatureFlagsPoller {
|
|
|
1726
1740
|
await this._loadFeatureFlags();
|
|
1727
1741
|
}
|
|
1728
1742
|
}
|
|
1743
|
+
/**
|
|
1744
|
+
* If a client is misconfigured with an invalid or improper API key, the polling interval is doubled each time
|
|
1745
|
+
* until a successful request is made, up to a maximum of 60 seconds.
|
|
1746
|
+
*
|
|
1747
|
+
* @returns The polling interval to use for the next request.
|
|
1748
|
+
*/
|
|
1749
|
+
getPollingInterval() {
|
|
1750
|
+
if (!this.lastRequestWasAuthenticationError) {
|
|
1751
|
+
return this.pollingInterval;
|
|
1752
|
+
}
|
|
1753
|
+
return Math.min(SIXTY_SECONDS, this.pollingInterval * 2 ** this.authenticationErrorCount);
|
|
1754
|
+
}
|
|
1729
1755
|
async _loadFeatureFlags() {
|
|
1730
1756
|
if (this.poller) {
|
|
1731
1757
|
clearTimeout(this.poller);
|
|
1732
1758
|
this.poller = undefined;
|
|
1733
1759
|
}
|
|
1734
|
-
this.poller = setTimeout(() => this._loadFeatureFlags(), this.
|
|
1760
|
+
this.poller = setTimeout(() => this._loadFeatureFlags(), this.getPollingInterval());
|
|
1735
1761
|
try {
|
|
1736
1762
|
const res = await this._requestFeatureFlagDefinitions();
|
|
1737
1763
|
if (res && res.status === 401) {
|
|
1738
|
-
|
|
1764
|
+
this.lastRequestWasAuthenticationError = true;
|
|
1765
|
+
this.authenticationErrorCount += 1;
|
|
1766
|
+
throw new ClientError(`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`);
|
|
1767
|
+
}
|
|
1768
|
+
if (res && res.status === 403) {
|
|
1769
|
+
this.lastRequestWasAuthenticationError = true;
|
|
1770
|
+
this.authenticationErrorCount += 1;
|
|
1771
|
+
throw new ClientError(`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`);
|
|
1772
|
+
}
|
|
1773
|
+
if (res && res.status === 402) {
|
|
1774
|
+
// Quota limited - clear all flags
|
|
1775
|
+
console.warn('[FEATURE FLAGS] Feature flags quota limit exceeded - unsetting all local flags. Learn more about billing limits at https://posthog.com/docs/billing/limits-alerts');
|
|
1776
|
+
this.featureFlags = [];
|
|
1777
|
+
this.featureFlagsByKey = {};
|
|
1778
|
+
this.groupTypeMapping = {};
|
|
1779
|
+
this.cohorts = {};
|
|
1780
|
+
this.loadedSuccessfullyOnce = false;
|
|
1781
|
+
return;
|
|
1739
1782
|
}
|
|
1740
1783
|
if (res && res.status !== 200) {
|
|
1741
1784
|
// something else went wrong, or the server is down.
|
|
@@ -1751,6 +1794,8 @@ class FeatureFlagsPoller {
|
|
|
1751
1794
|
this.groupTypeMapping = responseJson.group_type_mapping || {};
|
|
1752
1795
|
this.cohorts = responseJson.cohorts || [];
|
|
1753
1796
|
this.loadedSuccessfullyOnce = true;
|
|
1797
|
+
this.lastRequestWasAuthenticationError = false;
|
|
1798
|
+
this.authenticationErrorCount = 0;
|
|
1754
1799
|
} catch (err) {
|
|
1755
1800
|
// if an error that is not an instance of ClientError is thrown
|
|
1756
1801
|
// we silently ignore the error when reloading feature flags
|
|
@@ -2939,7 +2984,11 @@ class ErrorTracking {
|
|
|
2939
2984
|
}
|
|
2940
2985
|
}
|
|
2941
2986
|
|
|
2987
|
+
// Standard local evaluation rate limit is 600 per minute (10 per second),
|
|
2988
|
+
// so the fastest a poller should ever be set is 100ms.
|
|
2989
|
+
const MINIMUM_POLLING_INTERVAL = 100;
|
|
2942
2990
|
const THIRTY_SECONDS = 30 * 1000;
|
|
2991
|
+
const SIXTY_SECONDS = 60 * 1000;
|
|
2943
2992
|
const MAX_CACHE_SIZE = 50 * 1000;
|
|
2944
2993
|
// The actual exported Nodejs API.
|
|
2945
2994
|
class PostHog extends PostHogCoreStateless {
|
|
@@ -2947,9 +2996,13 @@ class PostHog extends PostHogCoreStateless {
|
|
|
2947
2996
|
super(apiKey, options);
|
|
2948
2997
|
this._memoryStorage = new PostHogMemoryStorage();
|
|
2949
2998
|
this.options = options;
|
|
2999
|
+
this.options.featureFlagsPollingInterval = typeof options.featureFlagsPollingInterval === 'number' ? Math.max(options.featureFlagsPollingInterval, MINIMUM_POLLING_INTERVAL) : THIRTY_SECONDS;
|
|
2950
3000
|
if (options.personalApiKey) {
|
|
3001
|
+
if (options.personalApiKey.includes('phc_')) {
|
|
3002
|
+
throw new Error('Your Personal API key is invalid. These keys are prefixed with "phx_" and can be created in PostHog project settings.');
|
|
3003
|
+
}
|
|
2951
3004
|
this.featureFlagsPoller = new FeatureFlagsPoller({
|
|
2952
|
-
pollingInterval:
|
|
3005
|
+
pollingInterval: this.options.featureFlagsPollingInterval,
|
|
2953
3006
|
personalApiKey: options.personalApiKey,
|
|
2954
3007
|
projectApiKey: apiKey,
|
|
2955
3008
|
timeout: options.requestTimeout ?? 10000,
|
|
@@ -3404,8 +3457,11 @@ function setupExpressErrorHandler(_posthog, app) {
|
|
|
3404
3457
|
});
|
|
3405
3458
|
}
|
|
3406
3459
|
|
|
3460
|
+
exports.MINIMUM_POLLING_INTERVAL = MINIMUM_POLLING_INTERVAL;
|
|
3407
3461
|
exports.PostHog = PostHog;
|
|
3408
3462
|
exports.PostHogSentryIntegration = PostHogSentryIntegration;
|
|
3463
|
+
exports.SIXTY_SECONDS = SIXTY_SECONDS;
|
|
3464
|
+
exports.THIRTY_SECONDS = THIRTY_SECONDS;
|
|
3409
3465
|
exports.createEventProcessor = createEventProcessor;
|
|
3410
3466
|
exports.sentryIntegration = sentryIntegration;
|
|
3411
3467
|
exports.setupExpressErrorHandler = setupExpressErrorHandler;
|