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.
@@ -318,7 +318,7 @@ function makeUncaughtExceptionHandler(captureFn, onFatalFn) {
318
318
  });
319
319
  if (!calledFatalError && processWouldExit) {
320
320
  calledFatalError = true;
321
- onFatalFn();
321
+ onFatalFn(error);
322
322
  }
323
323
  }, {
324
324
  _posthogErrorHandler: true
@@ -329,7 +329,7 @@ function addUncaughtExceptionListener(captureFn, onFatalFn) {
329
329
  }
330
330
  function addUnhandledRejectionListener(captureFn) {
331
331
  global.process.on('unhandledRejection', reason => {
332
- captureFn(reason, {
332
+ return captureFn(reason, {
333
333
  mechanism: {
334
334
  type: 'onunhandledrejection',
335
335
  handled: false
@@ -346,8 +346,7 @@ let cachedFilenameChunkIds;
346
346
  function getFilenameToChunkIdMap(stackParser) {
347
347
  const chunkIdMap = globalThis._posthogChunkIds;
348
348
  if (!chunkIdMap) {
349
- console.error('No chunk id map found');
350
- return {};
349
+ return null;
351
350
  }
352
351
  const chunkIdKeys = Object.keys(chunkIdMap);
353
352
  if (cachedFilenameChunkIds && chunkIdKeys.length === lastKeysCount) {
@@ -618,7 +617,7 @@ function parseStackFrames(stackParser, error) {
618
617
  function applyChunkIds(frames, parser) {
619
618
  const filenameChunkIdMap = getFilenameToChunkIdMap(parser);
620
619
  frames.forEach(frame => {
621
- if (frame.filename) {
620
+ if (frame.filename && filenameChunkIdMap) {
622
621
  frame.chunk_id = filenameChunkIdMap[frame.filename];
623
622
  }
624
623
  });
@@ -647,6 +646,12 @@ function clampToRange(value, min, max, logger, fallbackValue) {
647
646
  }
648
647
 
649
648
  class BucketedRateLimiter {
649
+ stop() {
650
+ if (this._removeInterval) {
651
+ clearInterval(this._removeInterval);
652
+ this._removeInterval = void 0;
653
+ }
654
+ }
650
655
  constructor(_options){
651
656
  this._options = _options;
652
657
  this._buckets = {};
@@ -678,7 +683,7 @@ class BucketedRateLimiter {
678
683
  this._bucketSize = clampToRange(this._options.bucketSize, 0, 100, this._options._logger);
679
684
  this._refillRate = clampToRange(this._options.refillRate, 0, this._bucketSize, this._options._logger);
680
685
  this._refillInterval = clampToRange(this._options.refillInterval, 0, 86400000, this._options._logger);
681
- setInterval(()=>{
686
+ this._removeInterval = setInterval(()=>{
682
687
  this._refillBuckets();
683
688
  }, this._refillInterval);
684
689
  }
@@ -692,6 +697,22 @@ function safeSetTimeout(fn, timeout) {
692
697
 
693
698
  const SHUTDOWN_TIMEOUT = 2000;
694
699
  class ErrorTracking {
700
+ constructor(client, options, _logger) {
701
+ this.client = client;
702
+ this._exceptionAutocaptureEnabled = options.enableExceptionAutocapture || false;
703
+ this._logger = _logger;
704
+ // by default captures ten exceptions before rate limiting by exception type
705
+ // refills at a rate of one token / 10 second period
706
+ // e.g. will capture 1 exception rate limited exception every 10 seconds until burst ends
707
+ this._rateLimiter = new BucketedRateLimiter({
708
+ refillRate: 1,
709
+ bucketSize: 10,
710
+ refillInterval: 10000,
711
+ // ten seconds in milliseconds
712
+ _logger: this._logger
713
+ });
714
+ this.startAutocaptureIfEnabled();
715
+ }
695
716
  static async buildEventMessage(error, hint, distinctId, additionalProperties) {
696
717
  const properties = {
697
718
  ...additionalProperties
@@ -711,31 +732,16 @@ class ErrorTracking {
711
732
  }
712
733
  };
713
734
  }
714
- constructor(client, options, _logger) {
715
- this.client = client;
716
- this._exceptionAutocaptureEnabled = options.enableExceptionAutocapture || false;
717
- this._logger = _logger;
718
- // by default captures ten exceptions before rate limiting by exception type
719
- // refills at a rate of one token / 10 second period
720
- // e.g. will capture 1 exception rate limited exception every 10 seconds until burst ends
721
- this._rateLimiter = new BucketedRateLimiter({
722
- refillRate: 1,
723
- bucketSize: 10,
724
- refillInterval: 10000,
725
- // ten seconds in milliseconds
726
- _logger: this._logger
727
- });
728
- this.startAutocaptureIfEnabled();
729
- }
730
735
  startAutocaptureIfEnabled() {
731
736
  if (this.isEnabled()) {
732
737
  addUncaughtExceptionListener(this.onException.bind(this), this.onFatalError.bind(this));
733
738
  addUnhandledRejectionListener(this.onException.bind(this));
734
739
  }
735
740
  }
736
- onException(exception, hint) {
737
- return ErrorTracking.buildEventMessage(exception, hint).then(msg => {
738
- const exceptionProperties = msg.properties;
741
+ async onException(exception, hint) {
742
+ this.client.addPendingPromise((async () => {
743
+ const eventMessage = await ErrorTracking.buildEventMessage(exception, hint);
744
+ const exceptionProperties = eventMessage.properties;
739
745
  const exceptionType = exceptionProperties?.$exception_list[0].type ?? 'Exception';
740
746
  const isRateLimited = this._rateLimiter.consumeRateLimit(exceptionType);
741
747
  if (isRateLimited) {
@@ -744,15 +750,20 @@ class ErrorTracking {
744
750
  });
745
751
  return;
746
752
  }
747
- this.client.capture(msg);
748
- });
753
+ return this.client.capture(eventMessage);
754
+ })());
749
755
  }
750
- async onFatalError() {
756
+ async onFatalError(exception) {
757
+ console.error(exception);
751
758
  await this.client.shutdown(SHUTDOWN_TIMEOUT);
759
+ process.exit(1);
752
760
  }
753
761
  isEnabled() {
754
762
  return !this.client.isDisabled && this._exceptionAutocaptureEnabled;
755
763
  }
764
+ shutdown() {
765
+ this._rateLimiter.stop();
766
+ }
756
767
  }
757
768
 
758
769
  function setupExpressErrorHandler(_posthog, app) {
@@ -1175,7 +1186,7 @@ function snipLine(line, colno) {
1175
1186
  return newLine;
1176
1187
  }
1177
1188
 
1178
- var version = "5.8.0";
1189
+ var version = "5.8.1";
1179
1190
 
1180
1191
  /**
1181
1192
  * A lazy value that is only computed when needed. Inspired by C#'s Lazy<T> class.
@@ -2140,146 +2151,43 @@ class PostHogBackendClient extends core.PostHogCoreStateless {
2140
2151
  if (typeof props === 'string') {
2141
2152
  this.logMsgIfDebug(() => console.warn('Called capture() with a string as the first argument when an object was expected.'));
2142
2153
  }
2143
- const {
2144
- distinctId,
2145
- event,
2146
- properties,
2147
- groups,
2148
- sendFeatureFlags,
2149
- timestamp,
2150
- disableGeoip,
2151
- uuid
2152
- } = props;
2153
- // Run before_send if configured
2154
- const eventMessage = this._runBeforeSend({
2154
+ this.addPendingPromise(this.prepareEventMessage(props).then(({
2155
2155
  distinctId,
2156
2156
  event,
2157
2157
  properties,
2158
- groups,
2159
- sendFeatureFlags,
2160
- timestamp,
2161
- disableGeoip,
2162
- uuid
2163
- });
2164
- if (!eventMessage) {
2165
- return;
2166
- }
2167
- const _capture = props => {
2168
- super.captureStateless(eventMessage.distinctId, eventMessage.event, props, {
2169
- timestamp: eventMessage.timestamp,
2170
- disableGeoip: eventMessage.disableGeoip,
2171
- uuid: eventMessage.uuid
2158
+ options
2159
+ }) => {
2160
+ return super.captureStateless(distinctId, event, properties, {
2161
+ timestamp: options.timestamp,
2162
+ disableGeoip: options.disableGeoip,
2163
+ uuid: options.uuid
2172
2164
  });
2173
- };
2174
- // :TRICKY: If we flush, or need to shut down, to not lose events we want this promise to resolve before we flush
2175
- const capturePromise = Promise.resolve().then(async () => {
2176
- if (sendFeatureFlags) {
2177
- // If we are sending feature flags, we evaluate them locally if the user prefers it, otherwise we fall back to remote evaluation
2178
- const sendFeatureFlagsOptions = typeof sendFeatureFlags === 'object' ? sendFeatureFlags : undefined;
2179
- return await this.getFeatureFlagsForEvent(distinctId, groups, disableGeoip, sendFeatureFlagsOptions);
2180
- }
2181
- if (event === '$feature_flag_called') {
2182
- // 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.
2183
- return {};
2184
- }
2185
- return {};
2186
- }).then(flags => {
2187
- // Derive the relevant flag properties to add
2188
- const additionalProperties = {};
2189
- if (flags) {
2190
- for (const [feature, variant] of Object.entries(flags)) {
2191
- additionalProperties[`$feature/${feature}`] = variant;
2192
- }
2165
+ }).catch(err => {
2166
+ if (err) {
2167
+ console.error(err);
2193
2168
  }
2194
- const activeFlags = Object.keys(flags || {}).filter(flag => flags?.[flag] !== false).sort();
2195
- if (activeFlags.length > 0) {
2196
- additionalProperties['$active_feature_flags'] = activeFlags;
2197
- }
2198
- return additionalProperties;
2199
- }).catch(() => {
2200
- // Something went wrong getting the flag info - we should capture the event anyways
2201
- return {};
2202
- }).then(additionalProperties => {
2203
- // No matter what - capture the event
2204
- _capture({
2205
- ...additionalProperties,
2206
- ...(eventMessage.properties || {}),
2207
- $groups: eventMessage.groups || groups
2208
- });
2209
- });
2210
- this.addPendingPromise(capturePromise);
2169
+ }));
2211
2170
  }
2212
2171
  async captureImmediate(props) {
2213
2172
  if (typeof props === 'string') {
2214
- this.logMsgIfDebug(() => console.warn('Called capture() with a string as the first argument when an object was expected.'));
2173
+ this.logMsgIfDebug(() => console.warn('Called captureImmediate() with a string as the first argument when an object was expected.'));
2215
2174
  }
2216
- const {
2175
+ return this.addPendingPromise(this.prepareEventMessage(props).then(({
2217
2176
  distinctId,
2218
2177
  event,
2219
2178
  properties,
2220
- groups,
2221
- sendFeatureFlags,
2222
- timestamp,
2223
- disableGeoip,
2224
- uuid
2225
- } = props;
2226
- // Run before_send if configured
2227
- const eventMessage = this._runBeforeSend({
2228
- distinctId,
2229
- event,
2230
- properties,
2231
- groups,
2232
- sendFeatureFlags,
2233
- timestamp,
2234
- disableGeoip,
2235
- uuid
2236
- });
2237
- if (!eventMessage) {
2238
- return;
2239
- }
2240
- const _capture = props => {
2241
- return super.captureStatelessImmediate(eventMessage.distinctId, eventMessage.event, props, {
2242
- timestamp: eventMessage.timestamp,
2243
- disableGeoip: eventMessage.disableGeoip,
2244
- uuid: eventMessage.uuid
2179
+ options
2180
+ }) => {
2181
+ return super.captureStatelessImmediate(distinctId, event, properties, {
2182
+ timestamp: options.timestamp,
2183
+ disableGeoip: options.disableGeoip,
2184
+ uuid: options.uuid
2245
2185
  });
2246
- };
2247
- const capturePromise = Promise.resolve().then(async () => {
2248
- if (sendFeatureFlags) {
2249
- // If we are sending feature flags, we evaluate them locally if the user prefers it, otherwise we fall back to remote evaluation
2250
- const sendFeatureFlagsOptions = typeof sendFeatureFlags === 'object' ? sendFeatureFlags : undefined;
2251
- return await this.getFeatureFlagsForEvent(distinctId, groups, disableGeoip, sendFeatureFlagsOptions);
2252
- }
2253
- if (event === '$feature_flag_called') {
2254
- // 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.
2255
- return {};
2256
- }
2257
- return {};
2258
- }).then(flags => {
2259
- // Derive the relevant flag properties to add
2260
- const additionalProperties = {};
2261
- if (flags) {
2262
- for (const [feature, variant] of Object.entries(flags)) {
2263
- additionalProperties[`$feature/${feature}`] = variant;
2264
- }
2186
+ }).catch(err => {
2187
+ if (err) {
2188
+ console.error(err);
2265
2189
  }
2266
- const activeFlags = Object.keys(flags || {}).filter(flag => flags?.[flag] !== false).sort();
2267
- if (activeFlags.length > 0) {
2268
- additionalProperties['$active_feature_flags'] = activeFlags;
2269
- }
2270
- return additionalProperties;
2271
- }).catch(() => {
2272
- // Something went wrong getting the flag info - we should capture the event anyways
2273
- return {};
2274
- }).then(additionalProperties => {
2275
- // No matter what - capture the event
2276
- _capture({
2277
- ...additionalProperties,
2278
- ...(eventMessage.properties || {}),
2279
- $groups: eventMessage.groups || groups
2280
- });
2281
- });
2282
- await capturePromise;
2190
+ }));
2283
2191
  }
2284
2192
  identify({
2285
2193
  distinctId,
@@ -2549,6 +2457,7 @@ class PostHogBackendClient extends core.PostHogCoreStateless {
2549
2457
  }
2550
2458
  async _shutdown(shutdownTimeoutMs) {
2551
2459
  this.featureFlagsPoller?.stopPoller();
2460
+ this.errorTracking.shutdown();
2552
2461
  return super._shutdown(shutdownTimeoutMs);
2553
2462
  }
2554
2463
  async _requestRemoteConfigPayload(flagKey) {
@@ -2567,7 +2476,7 @@ class PostHogBackendClient extends core.PostHogCoreStateless {
2567
2476
  let abortTimeout = null;
2568
2477
  if (this.options.requestTimeout && typeof this.options.requestTimeout === 'number') {
2569
2478
  const controller = new AbortController();
2570
- abortTimeout = safeSetTimeout(() => {
2479
+ abortTimeout = core.safeSetTimeout(() => {
2571
2480
  controller.abort();
2572
2481
  }, this.options.requestTimeout);
2573
2482
  options.signal = controller.signal;
@@ -2676,18 +2585,88 @@ class PostHogBackendClient extends core.PostHogCoreStateless {
2676
2585
  }
2677
2586
  captureException(error, distinctId, additionalProperties) {
2678
2587
  const syntheticException = new Error('PostHog syntheticException');
2679
- ErrorTracking.buildEventMessage(error, {
2588
+ this.addPendingPromise(ErrorTracking.buildEventMessage(error, {
2680
2589
  syntheticException
2681
- }, distinctId, additionalProperties).then(msg => {
2682
- this.capture(msg);
2683
- });
2590
+ }, distinctId, additionalProperties).then(msg => this.capture(msg)));
2684
2591
  }
2685
2592
  async captureExceptionImmediate(error, distinctId, additionalProperties) {
2686
2593
  const syntheticException = new Error('PostHog syntheticException');
2687
- const evtMsg = await ErrorTracking.buildEventMessage(error, {
2594
+ this.addPendingPromise(ErrorTracking.buildEventMessage(error, {
2688
2595
  syntheticException
2689
- }, distinctId, additionalProperties);
2690
- return await this.captureImmediate(evtMsg);
2596
+ }, distinctId, additionalProperties).then(msg => this.captureImmediate(msg)));
2597
+ }
2598
+ async prepareEventMessage(props) {
2599
+ const {
2600
+ distinctId,
2601
+ event,
2602
+ properties,
2603
+ groups,
2604
+ sendFeatureFlags,
2605
+ timestamp,
2606
+ disableGeoip,
2607
+ uuid
2608
+ } = props;
2609
+ // Run before_send if configured
2610
+ const eventMessage = this._runBeforeSend({
2611
+ distinctId,
2612
+ event,
2613
+ properties,
2614
+ groups,
2615
+ sendFeatureFlags,
2616
+ timestamp,
2617
+ disableGeoip,
2618
+ uuid
2619
+ });
2620
+ if (!eventMessage) {
2621
+ return Promise.reject(null);
2622
+ }
2623
+ // :TRICKY: If we flush, or need to shut down, to not lose events we want this promise to resolve before we flush
2624
+ const eventProperties = await Promise.resolve().then(async () => {
2625
+ if (sendFeatureFlags) {
2626
+ // If we are sending feature flags, we evaluate them locally if the user prefers it, otherwise we fall back to remote evaluation
2627
+ const sendFeatureFlagsOptions = typeof sendFeatureFlags === 'object' ? sendFeatureFlags : undefined;
2628
+ return await this.getFeatureFlagsForEvent(distinctId, groups, disableGeoip, sendFeatureFlagsOptions);
2629
+ }
2630
+ if (event === '$feature_flag_called') {
2631
+ // 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.
2632
+ return {};
2633
+ }
2634
+ return {};
2635
+ }).then(flags => {
2636
+ // Derive the relevant flag properties to add
2637
+ const additionalProperties = {};
2638
+ if (flags) {
2639
+ for (const [feature, variant] of Object.entries(flags)) {
2640
+ additionalProperties[`$feature/${feature}`] = variant;
2641
+ }
2642
+ }
2643
+ const activeFlags = Object.keys(flags || {}).filter(flag => flags?.[flag] !== false).sort();
2644
+ if (activeFlags.length > 0) {
2645
+ additionalProperties['$active_feature_flags'] = activeFlags;
2646
+ }
2647
+ return additionalProperties;
2648
+ }).catch(() => {
2649
+ // Something went wrong getting the flag info - we should capture the event anyways
2650
+ return {};
2651
+ }).then(additionalProperties => {
2652
+ // No matter what - capture the event
2653
+ const props = {
2654
+ ...additionalProperties,
2655
+ ...(eventMessage.properties || {}),
2656
+ $groups: eventMessage.groups || groups
2657
+ };
2658
+ return props;
2659
+ });
2660
+ return {
2661
+ distinctId: eventMessage.distinctId,
2662
+ event: eventMessage.event,
2663
+ properties: eventProperties,
2664
+ options: {
2665
+ timestamp: eventMessage.timestamp,
2666
+ disableGeoip: eventMessage.disableGeoip,
2667
+ uuid: eventMessage.uuid
2668
+ }
2669
+ };
2691
2670
  }
2692
2671
  _runBeforeSend(eventMessage) {
2693
2672
  const beforeSend = this.options.before_send;