posthog-node 4.7.0 → 4.8.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 CHANGED
@@ -1,5 +1,9 @@
1
1
  # Next
2
2
 
3
+ # 4.8.0 - 2025-02-26
4
+
5
+ 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`
6
+
3
7
  # 4.7.0 - 2025-02-20
4
8
 
5
9
  ## 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.7.0";
10
+ var version = "4.8.0";
11
11
 
12
12
  var PostHogPersistedProperty;
13
13
  (function (PostHogPersistedProperty) {
@@ -1495,6 +1495,8 @@ class FeatureFlagsPoller {
1495
1495
  ...options
1496
1496
  }) {
1497
1497
  this.debugMode = false;
1498
+ this.lastRequestWasAuthenticationError = false;
1499
+ this.authenticationErrorCount = 0;
1498
1500
  this.pollingInterval = pollingInterval;
1499
1501
  this.personalApiKey = personalApiKey;
1500
1502
  this.featureFlags = [];
@@ -1506,7 +1508,6 @@ class FeatureFlagsPoller {
1506
1508
  this.projectApiKey = projectApiKey;
1507
1509
  this.host = host;
1508
1510
  this.poller = undefined;
1509
- // NOTE: as any is required here as the AbortSignal typing is slightly misaligned but works just fine
1510
1511
  this.fetch = options.fetch || fetch$1;
1511
1512
  this.onError = options.onError;
1512
1513
  this.customHeaders = customHeaders;
@@ -1726,16 +1727,35 @@ class FeatureFlagsPoller {
1726
1727
  await this._loadFeatureFlags();
1727
1728
  }
1728
1729
  }
1730
+ /**
1731
+ * If a client is misconfigured with an invalid or improper API key, the polling interval is doubled each time
1732
+ * until a successful request is made, up to a maximum of 60 seconds.
1733
+ *
1734
+ * @returns The polling interval to use for the next request.
1735
+ */
1736
+ getPollingInterval() {
1737
+ if (!this.lastRequestWasAuthenticationError) {
1738
+ return this.pollingInterval;
1739
+ }
1740
+ return Math.min(SIXTY_SECONDS, this.pollingInterval * 2 ** this.authenticationErrorCount);
1741
+ }
1729
1742
  async _loadFeatureFlags() {
1730
1743
  if (this.poller) {
1731
1744
  clearTimeout(this.poller);
1732
1745
  this.poller = undefined;
1733
1746
  }
1734
- this.poller = setTimeout(() => this._loadFeatureFlags(), this.pollingInterval);
1747
+ this.poller = setTimeout(() => this._loadFeatureFlags(), this.getPollingInterval());
1735
1748
  try {
1736
1749
  const res = await this._requestFeatureFlagDefinitions();
1737
1750
  if (res && res.status === 401) {
1738
- throw new ClientError(`Your personalApiKey is invalid. Are you sure you're not using your Project API key? More information: https://posthog.com/docs/api/overview`);
1751
+ this.lastRequestWasAuthenticationError = true;
1752
+ this.authenticationErrorCount += 1;
1753
+ 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`);
1754
+ }
1755
+ if (res && res.status === 403) {
1756
+ this.lastRequestWasAuthenticationError = true;
1757
+ this.authenticationErrorCount += 1;
1758
+ 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`);
1739
1759
  }
1740
1760
  if (res && res.status !== 200) {
1741
1761
  // something else went wrong, or the server is down.
@@ -1751,6 +1771,8 @@ class FeatureFlagsPoller {
1751
1771
  this.groupTypeMapping = responseJson.group_type_mapping || {};
1752
1772
  this.cohorts = responseJson.cohorts || [];
1753
1773
  this.loadedSuccessfullyOnce = true;
1774
+ this.lastRequestWasAuthenticationError = false;
1775
+ this.authenticationErrorCount = 0;
1754
1776
  } catch (err) {
1755
1777
  // if an error that is not an instance of ClientError is thrown
1756
1778
  // we silently ignore the error when reloading feature flags
@@ -2939,7 +2961,11 @@ class ErrorTracking {
2939
2961
  }
2940
2962
  }
2941
2963
 
2964
+ // Standard local evaluation rate limit is 600 per minute (10 per second),
2965
+ // so the fastest a poller should ever be set is 100ms.
2966
+ const MINIMUM_POLLING_INTERVAL = 100;
2942
2967
  const THIRTY_SECONDS = 30 * 1000;
2968
+ const SIXTY_SECONDS = 60 * 1000;
2943
2969
  const MAX_CACHE_SIZE = 50 * 1000;
2944
2970
  // The actual exported Nodejs API.
2945
2971
  class PostHog extends PostHogCoreStateless {
@@ -2947,9 +2973,13 @@ class PostHog extends PostHogCoreStateless {
2947
2973
  super(apiKey, options);
2948
2974
  this._memoryStorage = new PostHogMemoryStorage();
2949
2975
  this.options = options;
2976
+ this.options.featureFlagsPollingInterval = typeof options.featureFlagsPollingInterval === 'number' ? Math.max(options.featureFlagsPollingInterval, MINIMUM_POLLING_INTERVAL) : THIRTY_SECONDS;
2950
2977
  if (options.personalApiKey) {
2978
+ if (options.personalApiKey.includes('phc_')) {
2979
+ throw new Error('Your Personal API key is invalid. These keys are prefixed with "phx_" and can be created in PostHog project settings.');
2980
+ }
2951
2981
  this.featureFlagsPoller = new FeatureFlagsPoller({
2952
- pollingInterval: typeof options.featureFlagsPollingInterval === 'number' ? options.featureFlagsPollingInterval : THIRTY_SECONDS,
2982
+ pollingInterval: this.options.featureFlagsPollingInterval,
2953
2983
  personalApiKey: options.personalApiKey,
2954
2984
  projectApiKey: apiKey,
2955
2985
  timeout: options.requestTimeout ?? 10000,
@@ -3404,8 +3434,11 @@ function setupExpressErrorHandler(_posthog, app) {
3404
3434
  });
3405
3435
  }
3406
3436
 
3437
+ exports.MINIMUM_POLLING_INTERVAL = MINIMUM_POLLING_INTERVAL;
3407
3438
  exports.PostHog = PostHog;
3408
3439
  exports.PostHogSentryIntegration = PostHogSentryIntegration;
3440
+ exports.SIXTY_SECONDS = SIXTY_SECONDS;
3441
+ exports.THIRTY_SECONDS = THIRTY_SECONDS;
3409
3442
  exports.createEventProcessor = createEventProcessor;
3410
3443
  exports.sentryIntegration = sentryIntegration;
3411
3444
  exports.setupExpressErrorHandler = setupExpressErrorHandler;