posthog-node 4.17.1 → 4.18.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,3 +1,14 @@
1
+ # Next
2
+
3
+ ## Added
4
+
5
+ 1. rotate session id if expired after 24 hours
6
+
7
+ # 4.17.2 - 2025-05-22
8
+
9
+ 1. chore: improve event prop types
10
+ 2. fix: no throw in sendImmediate
11
+
1
12
  # 4.17.1 - 2025-05-02
2
13
 
3
14
  1. fix: fix imports for old node.js version
@@ -589,6 +589,47 @@ function addUnhandledRejectionListener(captureFn) {
589
589
  });
590
590
  }
591
591
 
592
+ // Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
593
+ // Licensed under the MIT License
594
+ let parsedStackResults;
595
+ let lastKeysCount;
596
+ let cachedFilenameChunkIds;
597
+ function getFilenameToChunkIdMap(stackParser) {
598
+ const chunkIdMap = globalThis._posthogChunkIds;
599
+ if (!chunkIdMap) {
600
+ console.error('No chunk id map found');
601
+ return {};
602
+ }
603
+ const chunkIdKeys = Object.keys(chunkIdMap);
604
+ if (cachedFilenameChunkIds && chunkIdKeys.length === lastKeysCount) {
605
+ return cachedFilenameChunkIds;
606
+ }
607
+ lastKeysCount = chunkIdKeys.length;
608
+ cachedFilenameChunkIds = chunkIdKeys.reduce((acc, stackKey) => {
609
+ if (!parsedStackResults) {
610
+ parsedStackResults = {};
611
+ }
612
+ const result = parsedStackResults[stackKey];
613
+ if (result) {
614
+ acc[result[0]] = result[1];
615
+ } else {
616
+ const parsedStack = stackParser(stackKey);
617
+ for (let i = parsedStack.length - 1; i >= 0; i--) {
618
+ const stackFrame = parsedStack[i];
619
+ const filename = stackFrame?.filename;
620
+ const chunkId = chunkIdMap[stackKey];
621
+ if (filename && chunkId) {
622
+ acc[filename] = chunkId;
623
+ parsedStackResults[stackKey] = [filename, chunkId];
624
+ break;
625
+ }
626
+ }
627
+ }
628
+ return acc;
629
+ }, {});
630
+ return cachedFilenameChunkIds;
631
+ }
632
+
592
633
  // Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
593
634
  // Licensed under the MIT License
594
635
  function isEvent(candidate) {
@@ -822,7 +863,16 @@ async function exceptionFromError(stackParser, frameModifiers, error) {
822
863
  * Extracts stack frames from the error.stack string
823
864
  */
824
865
  function parseStackFrames(stackParser, error) {
825
- return stackParser(error.stack || '', 1);
866
+ return applyChunkIds(stackParser(error.stack || '', 1), stackParser);
867
+ }
868
+ function applyChunkIds(frames, parser) {
869
+ const filenameChunkIdMap = getFilenameToChunkIdMap(parser);
870
+ frames.forEach(frame => {
871
+ if (frame.filename) {
872
+ frame.chunk_id = filenameChunkIdMap[frame.filename];
873
+ }
874
+ });
875
+ return frames;
826
876
  }
827
877
 
828
878
  const SHUTDOWN_TIMEOUT = 2000;
@@ -885,7 +935,7 @@ function setupExpressErrorHandler(_posthog, app) {
885
935
  });
886
936
  }
887
937
 
888
- var version = "4.17.1";
938
+ var version = "4.18.0";
889
939
 
890
940
  var PostHogPersistedProperty;
891
941
  (function (PostHogPersistedProperty) {
@@ -902,6 +952,7 @@ var PostHogPersistedProperty;
902
952
  PostHogPersistedProperty["Queue"] = "queue";
903
953
  PostHogPersistedProperty["OptedOut"] = "opted_out";
904
954
  PostHogPersistedProperty["SessionId"] = "session_id";
955
+ PostHogPersistedProperty["SessionStartTimestamp"] = "session_start_timestamp";
905
956
  PostHogPersistedProperty["SessionLastTimestamp"] = "session_timestamp";
906
957
  PostHogPersistedProperty["PersonProperties"] = "person_properties";
907
958
  PostHogPersistedProperty["GroupProperties"] = "group_properties";
@@ -1216,6 +1267,9 @@ function isTokenInRollout(token, percentage = 0, excludedHashes) {
1216
1267
  const hashInt = parseInt(tokenHash, 16);
1217
1268
  const hashFloat = hashInt / 0xffffffff;
1218
1269
  return hashFloat < percentage;
1270
+ }
1271
+ function allSettled(promises) {
1272
+ return Promise.all(promises.map((p) => (p ?? Promise.resolve()).then((value) => ({ status: 'fulfilled', value }), (reason) => ({ status: 'rejected', reason }))));
1219
1273
  }
1220
1274
 
1221
1275
  // Copyright (c) 2013 Pieroxy <pieroxy@pieroxy.net>
@@ -1960,6 +2014,7 @@ class PostHogCoreStateless {
1960
2014
  ...extraPayload,
1961
2015
  }),
1962
2016
  };
2017
+ this.logMsgIfDebug(() => console.log('PostHog Debug', 'Decide URL', url));
1963
2018
  // Don't retry /decide API calls
1964
2019
  return this.fetchWithRetry(url, fetchOptions, { retryCount: 0 }, this.featureFlagsRequestTimeoutMs)
1965
2020
  .then((response) => response.json())
@@ -2077,7 +2132,7 @@ class PostHogCoreStateless {
2077
2132
  async getSurveysStateless() {
2078
2133
  await this._initPromise;
2079
2134
  if (this.disableSurveys === true) {
2080
- this.logMsgIfDebug(() => console.log('Loading surveys is disabled.'));
2135
+ this.logMsgIfDebug(() => console.log('PostHog Debug', 'Loading surveys is disabled.'));
2081
2136
  return [];
2082
2137
  }
2083
2138
  const url = `${this.host}/api/surveys/?token=${this.apiKey}`;
@@ -2200,7 +2255,6 @@ class PostHogCoreStateless {
2200
2255
  }
2201
2256
  catch (err) {
2202
2257
  this._events.emit('error', err);
2203
- throw err;
2204
2258
  }
2205
2259
  }
2206
2260
  prepareMessage(type, _message, options) {
@@ -2240,15 +2294,38 @@ class PostHogCoreStateless {
2240
2294
  await logFlushError(err);
2241
2295
  });
2242
2296
  }
2297
+ /**
2298
+ * Flushes the queue
2299
+ *
2300
+ * This function will return a promise that will resolve when the flush is complete,
2301
+ * or reject if there was an error (for example if the server or network is down).
2302
+ *
2303
+ * If there is already a flush in progress, this function will wait for that flush to complete.
2304
+ *
2305
+ * It's recommended to do error handling in the callback of the promise.
2306
+ *
2307
+ * @example
2308
+ * posthog.flush().then(() => {
2309
+ * console.log('Flush complete')
2310
+ * }).catch((err) => {
2311
+ * console.error('Flush failed', err)
2312
+ * })
2313
+ *
2314
+ *
2315
+ * @throws PostHogFetchHttpError
2316
+ * @throws PostHogFetchNetworkError
2317
+ * @throws Error
2318
+ */
2243
2319
  async flush() {
2244
2320
  // Wait for the current flush operation to finish (regardless of success or failure), then try to flush again.
2245
2321
  // Use allSettled instead of finally to be defensive around flush throwing errors immediately rather than rejecting.
2246
- const nextFlushPromise = Promise.allSettled([this.flushPromise]).then(() => {
2322
+ // Use a custom allSettled implementation to avoid issues with patching Promise on RN
2323
+ const nextFlushPromise = allSettled([this.flushPromise]).then(() => {
2247
2324
  return this._flush();
2248
2325
  });
2249
2326
  this.flushPromise = nextFlushPromise;
2250
2327
  void this.addPendingPromise(nextFlushPromise);
2251
- Promise.allSettled([nextFlushPromise]).then(() => {
2328
+ allSettled([nextFlushPromise]).then(() => {
2252
2329
  // If there are no others waiting to flush, clear the promise.
2253
2330
  // We don't strictly need to do this, but it could make debugging easier
2254
2331
  if (this.flushPromise === nextFlushPromise) {
@@ -2274,7 +2351,7 @@ class PostHogCoreStateless {
2274
2351
  await this._initPromise;
2275
2352
  let queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
2276
2353
  if (!queue.length) {
2277
- return [];
2354
+ return;
2278
2355
  }
2279
2356
  const sentMessages = [];
2280
2357
  const originalQueueLength = queue.length;
@@ -2345,7 +2422,6 @@ class PostHogCoreStateless {
2345
2422
  sentMessages.push(...batchMessages);
2346
2423
  }
2347
2424
  this._events.emit('flush', sentMessages);
2348
- return sentMessages;
2349
2425
  }
2350
2426
  async fetchWithRetry(url, options, retryOptions, requestTimeout) {
2351
2427
  var _a;
@@ -2355,7 +2431,14 @@ class PostHogCoreStateless {
2355
2431
  return ctrl.signal;
2356
2432
  });
2357
2433
  const body = options.body ? options.body : '';
2358
- const reqByteLength = Buffer.byteLength(body, STRING_FORMAT);
2434
+ let reqByteLength = -1;
2435
+ try {
2436
+ reqByteLength = Buffer.byteLength(body, STRING_FORMAT);
2437
+ }
2438
+ catch {
2439
+ const encoded = new TextEncoder().encode(body);
2440
+ reqByteLength = encoded.length;
2441
+ }
2359
2442
  return await retriable(async () => {
2360
2443
  let res = null;
2361
2444
  try {
@@ -2901,12 +2984,12 @@ class FeatureFlagsPoller {
2901
2984
  case 200:
2902
2985
  {
2903
2986
  // Process successful response
2904
- const responseJson = await res.json();
2987
+ const responseJson = (await res.json()) ?? {};
2905
2988
  if (!('flags' in responseJson)) {
2906
2989
  this.onError?.(new Error(`Invalid response when getting feature flags: ${JSON.stringify(responseJson)}`));
2907
2990
  return;
2908
2991
  }
2909
- this.featureFlags = responseJson.flags || [];
2992
+ this.featureFlags = responseJson.flags ?? [];
2910
2993
  this.featureFlagsByKey = this.featureFlags.reduce((acc, curr) => (acc[curr.key] = curr, acc), {});
2911
2994
  this.groupTypeMapping = responseJson.group_type_mapping || {};
2912
2995
  this.cohorts = responseJson.cohorts || {};