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/lib/index.d.ts CHANGED
@@ -108,6 +108,7 @@ type PostHogDecideResponse = {
108
108
  [key: string]: JsonType;
109
109
  };
110
110
  errorsWhileComputingFlags: boolean;
111
+ quotaLimited?: string[];
111
112
  sessionRecording?: boolean | {
112
113
  [key: string]: JsonType;
113
114
  };
@@ -406,6 +407,9 @@ type PostHogOptions = PostHogCoreOptions & {
406
407
  maxCacheSize?: number;
407
408
  fetch?: (url: string, options: PostHogFetchOptions) => Promise<PostHogFetchResponse>;
408
409
  };
410
+ declare const MINIMUM_POLLING_INTERVAL = 100;
411
+ declare const THIRTY_SECONDS: number;
412
+ declare const SIXTY_SECONDS: number;
409
413
  declare class PostHog extends PostHogCoreStateless implements PostHogNodeV1 {
410
414
  private _memoryStorage;
411
415
  private featureFlagsPoller?;
@@ -520,4 +524,4 @@ declare function setupExpressErrorHandler(_posthog: PostHog, app: {
520
524
  use: (middleware: ExpressMiddleware | ExpressErrorMiddleware) => unknown;
521
525
  }): void;
522
526
 
523
- export { PostHog, PostHogOptions, PostHogSentryIntegration, SentryIntegrationOptions, createEventProcessor, sentryIntegration, setupExpressErrorHandler };
527
+ export { MINIMUM_POLLING_INTERVAL, PostHog, PostHogOptions, PostHogSentryIntegration, SIXTY_SECONDS, SentryIntegrationOptions, THIRTY_SECONDS, createEventProcessor, sentryIntegration, setupExpressErrorHandler };
package/lib/index.esm.js CHANGED
@@ -3,7 +3,7 @@ import { createReadStream } from 'node:fs';
3
3
  import { createInterface } from 'node:readline';
4
4
  import { posix, dirname, sep } from 'node:path';
5
5
 
6
- var version = "4.7.0";
6
+ var version = "4.8.1";
7
7
 
8
8
  var PostHogPersistedProperty;
9
9
  (function (PostHogPersistedProperty) {
@@ -954,6 +954,11 @@ class PostHogFetchNetworkError extends Error {
954
954
  function isPostHogFetchError(err) {
955
955
  return typeof err === 'object' && (err instanceof PostHogFetchHttpError || err instanceof PostHogFetchNetworkError);
956
956
  }
957
+ var QuotaLimitedFeature;
958
+ (function (QuotaLimitedFeature) {
959
+ QuotaLimitedFeature["FeatureFlags"] = "feature_flags";
960
+ QuotaLimitedFeature["Recordings"] = "recordings";
961
+ })(QuotaLimitedFeature || (QuotaLimitedFeature = {}));
957
962
  class PostHogCoreStateless {
958
963
  constructor(apiKey, options) {
959
964
  this.flushPromise = null;
@@ -1197,6 +1202,14 @@ class PostHogCoreStateless {
1197
1202
  extraPayload['geoip_disable'] = true;
1198
1203
  }
1199
1204
  const decideResponse = await this.getDecide(distinctId, groups, personProperties, groupProperties, extraPayload);
1205
+ // Add check for quota limitation on feature flags
1206
+ if (decideResponse?.quotaLimited?.includes(QuotaLimitedFeature.FeatureFlags)) {
1207
+ 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');
1208
+ return {
1209
+ flags: undefined,
1210
+ payloads: undefined,
1211
+ };
1212
+ }
1200
1213
  const flags = decideResponse?.featureFlags;
1201
1214
  const payloads = decideResponse?.featureFlagPayloads;
1202
1215
  let parsedPayloads = payloads;
@@ -1491,6 +1504,8 @@ class FeatureFlagsPoller {
1491
1504
  ...options
1492
1505
  }) {
1493
1506
  this.debugMode = false;
1507
+ this.lastRequestWasAuthenticationError = false;
1508
+ this.authenticationErrorCount = 0;
1494
1509
  this.pollingInterval = pollingInterval;
1495
1510
  this.personalApiKey = personalApiKey;
1496
1511
  this.featureFlags = [];
@@ -1502,7 +1517,6 @@ class FeatureFlagsPoller {
1502
1517
  this.projectApiKey = projectApiKey;
1503
1518
  this.host = host;
1504
1519
  this.poller = undefined;
1505
- // NOTE: as any is required here as the AbortSignal typing is slightly misaligned but works just fine
1506
1520
  this.fetch = options.fetch || fetch$1;
1507
1521
  this.onError = options.onError;
1508
1522
  this.customHeaders = customHeaders;
@@ -1722,16 +1736,45 @@ class FeatureFlagsPoller {
1722
1736
  await this._loadFeatureFlags();
1723
1737
  }
1724
1738
  }
1739
+ /**
1740
+ * If a client is misconfigured with an invalid or improper API key, the polling interval is doubled each time
1741
+ * until a successful request is made, up to a maximum of 60 seconds.
1742
+ *
1743
+ * @returns The polling interval to use for the next request.
1744
+ */
1745
+ getPollingInterval() {
1746
+ if (!this.lastRequestWasAuthenticationError) {
1747
+ return this.pollingInterval;
1748
+ }
1749
+ return Math.min(SIXTY_SECONDS, this.pollingInterval * 2 ** this.authenticationErrorCount);
1750
+ }
1725
1751
  async _loadFeatureFlags() {
1726
1752
  if (this.poller) {
1727
1753
  clearTimeout(this.poller);
1728
1754
  this.poller = undefined;
1729
1755
  }
1730
- this.poller = setTimeout(() => this._loadFeatureFlags(), this.pollingInterval);
1756
+ this.poller = setTimeout(() => this._loadFeatureFlags(), this.getPollingInterval());
1731
1757
  try {
1732
1758
  const res = await this._requestFeatureFlagDefinitions();
1733
1759
  if (res && res.status === 401) {
1734
- 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`);
1760
+ this.lastRequestWasAuthenticationError = true;
1761
+ this.authenticationErrorCount += 1;
1762
+ 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`);
1763
+ }
1764
+ if (res && res.status === 403) {
1765
+ this.lastRequestWasAuthenticationError = true;
1766
+ this.authenticationErrorCount += 1;
1767
+ 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`);
1768
+ }
1769
+ if (res && res.status === 402) {
1770
+ // Quota limited - clear all flags
1771
+ 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');
1772
+ this.featureFlags = [];
1773
+ this.featureFlagsByKey = {};
1774
+ this.groupTypeMapping = {};
1775
+ this.cohorts = {};
1776
+ this.loadedSuccessfullyOnce = false;
1777
+ return;
1735
1778
  }
1736
1779
  if (res && res.status !== 200) {
1737
1780
  // something else went wrong, or the server is down.
@@ -1747,6 +1790,8 @@ class FeatureFlagsPoller {
1747
1790
  this.groupTypeMapping = responseJson.group_type_mapping || {};
1748
1791
  this.cohorts = responseJson.cohorts || [];
1749
1792
  this.loadedSuccessfullyOnce = true;
1793
+ this.lastRequestWasAuthenticationError = false;
1794
+ this.authenticationErrorCount = 0;
1750
1795
  } catch (err) {
1751
1796
  // if an error that is not an instance of ClientError is thrown
1752
1797
  // we silently ignore the error when reloading feature flags
@@ -2935,7 +2980,11 @@ class ErrorTracking {
2935
2980
  }
2936
2981
  }
2937
2982
 
2983
+ // Standard local evaluation rate limit is 600 per minute (10 per second),
2984
+ // so the fastest a poller should ever be set is 100ms.
2985
+ const MINIMUM_POLLING_INTERVAL = 100;
2938
2986
  const THIRTY_SECONDS = 30 * 1000;
2987
+ const SIXTY_SECONDS = 60 * 1000;
2939
2988
  const MAX_CACHE_SIZE = 50 * 1000;
2940
2989
  // The actual exported Nodejs API.
2941
2990
  class PostHog extends PostHogCoreStateless {
@@ -2943,9 +2992,13 @@ class PostHog extends PostHogCoreStateless {
2943
2992
  super(apiKey, options);
2944
2993
  this._memoryStorage = new PostHogMemoryStorage();
2945
2994
  this.options = options;
2995
+ this.options.featureFlagsPollingInterval = typeof options.featureFlagsPollingInterval === 'number' ? Math.max(options.featureFlagsPollingInterval, MINIMUM_POLLING_INTERVAL) : THIRTY_SECONDS;
2946
2996
  if (options.personalApiKey) {
2997
+ if (options.personalApiKey.includes('phc_')) {
2998
+ throw new Error('Your Personal API key is invalid. These keys are prefixed with "phx_" and can be created in PostHog project settings.');
2999
+ }
2947
3000
  this.featureFlagsPoller = new FeatureFlagsPoller({
2948
- pollingInterval: typeof options.featureFlagsPollingInterval === 'number' ? options.featureFlagsPollingInterval : THIRTY_SECONDS,
3001
+ pollingInterval: this.options.featureFlagsPollingInterval,
2949
3002
  personalApiKey: options.personalApiKey,
2950
3003
  projectApiKey: apiKey,
2951
3004
  timeout: options.requestTimeout ?? 10000,
@@ -3400,5 +3453,5 @@ function setupExpressErrorHandler(_posthog, app) {
3400
3453
  });
3401
3454
  }
3402
3455
 
3403
- export { PostHog, PostHogSentryIntegration, createEventProcessor, sentryIntegration, setupExpressErrorHandler };
3456
+ export { MINIMUM_POLLING_INTERVAL, PostHog, PostHogSentryIntegration, SIXTY_SECONDS, THIRTY_SECONDS, createEventProcessor, sentryIntegration, setupExpressErrorHandler };
3404
3457
  //# sourceMappingURL=index.esm.js.map