posthog-node 5.8.0 → 5.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.
@@ -315,7 +315,7 @@ function makeUncaughtExceptionHandler(captureFn, onFatalFn) {
315
315
  });
316
316
  if (!calledFatalError && processWouldExit) {
317
317
  calledFatalError = true;
318
- onFatalFn();
318
+ onFatalFn(error);
319
319
  }
320
320
  }, {
321
321
  _posthogErrorHandler: true
@@ -326,7 +326,7 @@ function addUncaughtExceptionListener(captureFn, onFatalFn) {
326
326
  }
327
327
  function addUnhandledRejectionListener(captureFn) {
328
328
  global.process.on('unhandledRejection', reason => {
329
- captureFn(reason, {
329
+ return captureFn(reason, {
330
330
  mechanism: {
331
331
  type: 'onunhandledrejection',
332
332
  handled: false
@@ -343,8 +343,7 @@ let cachedFilenameChunkIds;
343
343
  function getFilenameToChunkIdMap(stackParser) {
344
344
  const chunkIdMap = globalThis._posthogChunkIds;
345
345
  if (!chunkIdMap) {
346
- console.error('No chunk id map found');
347
- return {};
346
+ return null;
348
347
  }
349
348
  const chunkIdKeys = Object.keys(chunkIdMap);
350
349
  if (cachedFilenameChunkIds && chunkIdKeys.length === lastKeysCount) {
@@ -615,7 +614,7 @@ function parseStackFrames(stackParser, error) {
615
614
  function applyChunkIds(frames, parser) {
616
615
  const filenameChunkIdMap = getFilenameToChunkIdMap(parser);
617
616
  frames.forEach(frame => {
618
- if (frame.filename) {
617
+ if (frame.filename && filenameChunkIdMap) {
619
618
  frame.chunk_id = filenameChunkIdMap[frame.filename];
620
619
  }
621
620
  });
@@ -644,6 +643,12 @@ function clampToRange(value, min, max, logger, fallbackValue) {
644
643
  }
645
644
 
646
645
  class BucketedRateLimiter {
646
+ stop() {
647
+ if (this._removeInterval) {
648
+ clearInterval(this._removeInterval);
649
+ this._removeInterval = void 0;
650
+ }
651
+ }
647
652
  constructor(_options){
648
653
  this._options = _options;
649
654
  this._buckets = {};
@@ -675,7 +680,7 @@ class BucketedRateLimiter {
675
680
  this._bucketSize = clampToRange(this._options.bucketSize, 0, 100, this._options._logger);
676
681
  this._refillRate = clampToRange(this._options.refillRate, 0, this._bucketSize, this._options._logger);
677
682
  this._refillInterval = clampToRange(this._options.refillInterval, 0, 86400000, this._options._logger);
678
- setInterval(()=>{
683
+ this._removeInterval = setInterval(()=>{
679
684
  this._refillBuckets();
680
685
  }, this._refillInterval);
681
686
  }
@@ -689,6 +694,22 @@ function safeSetTimeout(fn, timeout) {
689
694
 
690
695
  const SHUTDOWN_TIMEOUT = 2000;
691
696
  class ErrorTracking {
697
+ constructor(client, options, _logger) {
698
+ this.client = client;
699
+ this._exceptionAutocaptureEnabled = options.enableExceptionAutocapture || false;
700
+ this._logger = _logger;
701
+ // by default captures ten exceptions before rate limiting by exception type
702
+ // refills at a rate of one token / 10 second period
703
+ // e.g. will capture 1 exception rate limited exception every 10 seconds until burst ends
704
+ this._rateLimiter = new BucketedRateLimiter({
705
+ refillRate: 1,
706
+ bucketSize: 10,
707
+ refillInterval: 10000,
708
+ // ten seconds in milliseconds
709
+ _logger: this._logger
710
+ });
711
+ this.startAutocaptureIfEnabled();
712
+ }
692
713
  static async buildEventMessage(error, hint, distinctId, additionalProperties) {
693
714
  const properties = {
694
715
  ...additionalProperties
@@ -708,31 +729,16 @@ class ErrorTracking {
708
729
  }
709
730
  };
710
731
  }
711
- constructor(client, options, _logger) {
712
- this.client = client;
713
- this._exceptionAutocaptureEnabled = options.enableExceptionAutocapture || false;
714
- this._logger = _logger;
715
- // by default captures ten exceptions before rate limiting by exception type
716
- // refills at a rate of one token / 10 second period
717
- // e.g. will capture 1 exception rate limited exception every 10 seconds until burst ends
718
- this._rateLimiter = new BucketedRateLimiter({
719
- refillRate: 1,
720
- bucketSize: 10,
721
- refillInterval: 10000,
722
- // ten seconds in milliseconds
723
- _logger: this._logger
724
- });
725
- this.startAutocaptureIfEnabled();
726
- }
727
732
  startAutocaptureIfEnabled() {
728
733
  if (this.isEnabled()) {
729
734
  addUncaughtExceptionListener(this.onException.bind(this), this.onFatalError.bind(this));
730
735
  addUnhandledRejectionListener(this.onException.bind(this));
731
736
  }
732
737
  }
733
- onException(exception, hint) {
734
- return ErrorTracking.buildEventMessage(exception, hint).then(msg => {
735
- const exceptionProperties = msg.properties;
738
+ async onException(exception, hint) {
739
+ this.client.addPendingPromise((async () => {
740
+ const eventMessage = await ErrorTracking.buildEventMessage(exception, hint);
741
+ const exceptionProperties = eventMessage.properties;
736
742
  const exceptionType = exceptionProperties?.$exception_list[0].type ?? 'Exception';
737
743
  const isRateLimited = this._rateLimiter.consumeRateLimit(exceptionType);
738
744
  if (isRateLimited) {
@@ -741,15 +747,20 @@ class ErrorTracking {
741
747
  });
742
748
  return;
743
749
  }
744
- this.client.capture(msg);
745
- });
750
+ return this.client.capture(eventMessage);
751
+ })());
746
752
  }
747
- async onFatalError() {
753
+ async onFatalError(exception) {
754
+ console.error(exception);
748
755
  await this.client.shutdown(SHUTDOWN_TIMEOUT);
756
+ process.exit(1);
749
757
  }
750
758
  isEnabled() {
751
759
  return !this.client.isDisabled && this._exceptionAutocaptureEnabled;
752
760
  }
761
+ shutdown() {
762
+ this._rateLimiter.stop();
763
+ }
753
764
  }
754
765
 
755
766
  function setupExpressErrorHandler(_posthog, app) {
@@ -769,7 +780,7 @@ function setupExpressErrorHandler(_posthog, app) {
769
780
  });
770
781
  }
771
782
 
772
- var version = "5.8.0";
783
+ var version = "5.8.1";
773
784
 
774
785
  /**
775
786
  * A lazy value that is only computed when needed. Inspired by C#'s Lazy<T> class.
@@ -1734,146 +1745,43 @@ class PostHogBackendClient extends core.PostHogCoreStateless {
1734
1745
  if (typeof props === 'string') {
1735
1746
  this.logMsgIfDebug(() => console.warn('Called capture() with a string as the first argument when an object was expected.'));
1736
1747
  }
1737
- const {
1738
- distinctId,
1739
- event,
1740
- properties,
1741
- groups,
1742
- sendFeatureFlags,
1743
- timestamp,
1744
- disableGeoip,
1745
- uuid
1746
- } = props;
1747
- // Run before_send if configured
1748
- const eventMessage = this._runBeforeSend({
1748
+ this.addPendingPromise(this.prepareEventMessage(props).then(({
1749
1749
  distinctId,
1750
1750
  event,
1751
1751
  properties,
1752
- groups,
1753
- sendFeatureFlags,
1754
- timestamp,
1755
- disableGeoip,
1756
- uuid
1757
- });
1758
- if (!eventMessage) {
1759
- return;
1760
- }
1761
- const _capture = props => {
1762
- super.captureStateless(eventMessage.distinctId, eventMessage.event, props, {
1763
- timestamp: eventMessage.timestamp,
1764
- disableGeoip: eventMessage.disableGeoip,
1765
- uuid: eventMessage.uuid
1752
+ options
1753
+ }) => {
1754
+ return super.captureStateless(distinctId, event, properties, {
1755
+ timestamp: options.timestamp,
1756
+ disableGeoip: options.disableGeoip,
1757
+ uuid: options.uuid
1766
1758
  });
1767
- };
1768
- // :TRICKY: If we flush, or need to shut down, to not lose events we want this promise to resolve before we flush
1769
- const capturePromise = Promise.resolve().then(async () => {
1770
- if (sendFeatureFlags) {
1771
- // If we are sending feature flags, we evaluate them locally if the user prefers it, otherwise we fall back to remote evaluation
1772
- const sendFeatureFlagsOptions = typeof sendFeatureFlags === 'object' ? sendFeatureFlags : undefined;
1773
- return await this.getFeatureFlagsForEvent(distinctId, groups, disableGeoip, sendFeatureFlagsOptions);
1774
- }
1775
- if (event === '$feature_flag_called') {
1776
- // If we're capturing a $feature_flag_called event, we don't want to enrich the event with cached flags that may be out of date.
1777
- return {};
1778
- }
1779
- return {};
1780
- }).then(flags => {
1781
- // Derive the relevant flag properties to add
1782
- const additionalProperties = {};
1783
- if (flags) {
1784
- for (const [feature, variant] of Object.entries(flags)) {
1785
- additionalProperties[`$feature/${feature}`] = variant;
1786
- }
1759
+ }).catch(err => {
1760
+ if (err) {
1761
+ console.error(err);
1787
1762
  }
1788
- const activeFlags = Object.keys(flags || {}).filter(flag => flags?.[flag] !== false).sort();
1789
- if (activeFlags.length > 0) {
1790
- additionalProperties['$active_feature_flags'] = activeFlags;
1791
- }
1792
- return additionalProperties;
1793
- }).catch(() => {
1794
- // Something went wrong getting the flag info - we should capture the event anyways
1795
- return {};
1796
- }).then(additionalProperties => {
1797
- // No matter what - capture the event
1798
- _capture({
1799
- ...additionalProperties,
1800
- ...(eventMessage.properties || {}),
1801
- $groups: eventMessage.groups || groups
1802
- });
1803
- });
1804
- this.addPendingPromise(capturePromise);
1763
+ }));
1805
1764
  }
1806
1765
  async captureImmediate(props) {
1807
1766
  if (typeof props === 'string') {
1808
- this.logMsgIfDebug(() => console.warn('Called capture() with a string as the first argument when an object was expected.'));
1767
+ this.logMsgIfDebug(() => console.warn('Called captureImmediate() with a string as the first argument when an object was expected.'));
1809
1768
  }
1810
- const {
1769
+ return this.addPendingPromise(this.prepareEventMessage(props).then(({
1811
1770
  distinctId,
1812
1771
  event,
1813
1772
  properties,
1814
- groups,
1815
- sendFeatureFlags,
1816
- timestamp,
1817
- disableGeoip,
1818
- uuid
1819
- } = props;
1820
- // Run before_send if configured
1821
- const eventMessage = this._runBeforeSend({
1822
- distinctId,
1823
- event,
1824
- properties,
1825
- groups,
1826
- sendFeatureFlags,
1827
- timestamp,
1828
- disableGeoip,
1829
- uuid
1830
- });
1831
- if (!eventMessage) {
1832
- return;
1833
- }
1834
- const _capture = props => {
1835
- return super.captureStatelessImmediate(eventMessage.distinctId, eventMessage.event, props, {
1836
- timestamp: eventMessage.timestamp,
1837
- disableGeoip: eventMessage.disableGeoip,
1838
- uuid: eventMessage.uuid
1773
+ options
1774
+ }) => {
1775
+ return super.captureStatelessImmediate(distinctId, event, properties, {
1776
+ timestamp: options.timestamp,
1777
+ disableGeoip: options.disableGeoip,
1778
+ uuid: options.uuid
1839
1779
  });
1840
- };
1841
- const capturePromise = Promise.resolve().then(async () => {
1842
- if (sendFeatureFlags) {
1843
- // If we are sending feature flags, we evaluate them locally if the user prefers it, otherwise we fall back to remote evaluation
1844
- const sendFeatureFlagsOptions = typeof sendFeatureFlags === 'object' ? sendFeatureFlags : undefined;
1845
- return await this.getFeatureFlagsForEvent(distinctId, groups, disableGeoip, sendFeatureFlagsOptions);
1846
- }
1847
- if (event === '$feature_flag_called') {
1848
- // If we're capturing a $feature_flag_called event, we don't want to enrich the event with cached flags that may be out of date.
1849
- return {};
1850
- }
1851
- return {};
1852
- }).then(flags => {
1853
- // Derive the relevant flag properties to add
1854
- const additionalProperties = {};
1855
- if (flags) {
1856
- for (const [feature, variant] of Object.entries(flags)) {
1857
- additionalProperties[`$feature/${feature}`] = variant;
1858
- }
1780
+ }).catch(err => {
1781
+ if (err) {
1782
+ console.error(err);
1859
1783
  }
1860
- const activeFlags = Object.keys(flags || {}).filter(flag => flags?.[flag] !== false).sort();
1861
- if (activeFlags.length > 0) {
1862
- additionalProperties['$active_feature_flags'] = activeFlags;
1863
- }
1864
- return additionalProperties;
1865
- }).catch(() => {
1866
- // Something went wrong getting the flag info - we should capture the event anyways
1867
- return {};
1868
- }).then(additionalProperties => {
1869
- // No matter what - capture the event
1870
- _capture({
1871
- ...additionalProperties,
1872
- ...(eventMessage.properties || {}),
1873
- $groups: eventMessage.groups || groups
1874
- });
1875
- });
1876
- await capturePromise;
1784
+ }));
1877
1785
  }
1878
1786
  identify({
1879
1787
  distinctId,
@@ -2143,6 +2051,7 @@ class PostHogBackendClient extends core.PostHogCoreStateless {
2143
2051
  }
2144
2052
  async _shutdown(shutdownTimeoutMs) {
2145
2053
  this.featureFlagsPoller?.stopPoller();
2054
+ this.errorTracking.shutdown();
2146
2055
  return super._shutdown(shutdownTimeoutMs);
2147
2056
  }
2148
2057
  async _requestRemoteConfigPayload(flagKey) {
@@ -2161,7 +2070,7 @@ class PostHogBackendClient extends core.PostHogCoreStateless {
2161
2070
  let abortTimeout = null;
2162
2071
  if (this.options.requestTimeout && typeof this.options.requestTimeout === 'number') {
2163
2072
  const controller = new AbortController();
2164
- abortTimeout = safeSetTimeout(() => {
2073
+ abortTimeout = core.safeSetTimeout(() => {
2165
2074
  controller.abort();
2166
2075
  }, this.options.requestTimeout);
2167
2076
  options.signal = controller.signal;
@@ -2270,18 +2179,88 @@ class PostHogBackendClient extends core.PostHogCoreStateless {
2270
2179
  }
2271
2180
  captureException(error, distinctId, additionalProperties) {
2272
2181
  const syntheticException = new Error('PostHog syntheticException');
2273
- ErrorTracking.buildEventMessage(error, {
2182
+ this.addPendingPromise(ErrorTracking.buildEventMessage(error, {
2274
2183
  syntheticException
2275
- }, distinctId, additionalProperties).then(msg => {
2276
- this.capture(msg);
2277
- });
2184
+ }, distinctId, additionalProperties).then(msg => this.capture(msg)));
2278
2185
  }
2279
2186
  async captureExceptionImmediate(error, distinctId, additionalProperties) {
2280
2187
  const syntheticException = new Error('PostHog syntheticException');
2281
- const evtMsg = await ErrorTracking.buildEventMessage(error, {
2188
+ this.addPendingPromise(ErrorTracking.buildEventMessage(error, {
2282
2189
  syntheticException
2283
- }, distinctId, additionalProperties);
2284
- return await this.captureImmediate(evtMsg);
2190
+ }, distinctId, additionalProperties).then(msg => this.captureImmediate(msg)));
2191
+ }
2192
+ async prepareEventMessage(props) {
2193
+ const {
2194
+ distinctId,
2195
+ event,
2196
+ properties,
2197
+ groups,
2198
+ sendFeatureFlags,
2199
+ timestamp,
2200
+ disableGeoip,
2201
+ uuid
2202
+ } = props;
2203
+ // Run before_send if configured
2204
+ const eventMessage = this._runBeforeSend({
2205
+ distinctId,
2206
+ event,
2207
+ properties,
2208
+ groups,
2209
+ sendFeatureFlags,
2210
+ timestamp,
2211
+ disableGeoip,
2212
+ uuid
2213
+ });
2214
+ if (!eventMessage) {
2215
+ return Promise.reject(null);
2216
+ }
2217
+ // :TRICKY: If we flush, or need to shut down, to not lose events we want this promise to resolve before we flush
2218
+ const eventProperties = await Promise.resolve().then(async () => {
2219
+ if (sendFeatureFlags) {
2220
+ // If we are sending feature flags, we evaluate them locally if the user prefers it, otherwise we fall back to remote evaluation
2221
+ const sendFeatureFlagsOptions = typeof sendFeatureFlags === 'object' ? sendFeatureFlags : undefined;
2222
+ return await this.getFeatureFlagsForEvent(distinctId, groups, disableGeoip, sendFeatureFlagsOptions);
2223
+ }
2224
+ if (event === '$feature_flag_called') {
2225
+ // If we're capturing a $feature_flag_called event, we don't want to enrich the event with cached flags that may be out of date.
2226
+ return {};
2227
+ }
2228
+ return {};
2229
+ }).then(flags => {
2230
+ // Derive the relevant flag properties to add
2231
+ const additionalProperties = {};
2232
+ if (flags) {
2233
+ for (const [feature, variant] of Object.entries(flags)) {
2234
+ additionalProperties[`$feature/${feature}`] = variant;
2235
+ }
2236
+ }
2237
+ const activeFlags = Object.keys(flags || {}).filter(flag => flags?.[flag] !== false).sort();
2238
+ if (activeFlags.length > 0) {
2239
+ additionalProperties['$active_feature_flags'] = activeFlags;
2240
+ }
2241
+ return additionalProperties;
2242
+ }).catch(() => {
2243
+ // Something went wrong getting the flag info - we should capture the event anyways
2244
+ return {};
2245
+ }).then(additionalProperties => {
2246
+ // No matter what - capture the event
2247
+ const props = {
2248
+ ...additionalProperties,
2249
+ ...(eventMessage.properties || {}),
2250
+ $groups: eventMessage.groups || groups
2251
+ };
2252
+ return props;
2253
+ });
2254
+ return {
2255
+ distinctId: eventMessage.distinctId,
2256
+ event: eventMessage.event,
2257
+ properties: eventProperties,
2258
+ options: {
2259
+ timestamp: eventMessage.timestamp,
2260
+ disableGeoip: eventMessage.disableGeoip,
2261
+ uuid: eventMessage.uuid
2262
+ }
2263
+ };
2285
2264
  }
2286
2265
  _runBeforeSend(eventMessage) {
2287
2266
  const beforeSend = this.options.before_send;