posthog-js-lite 3.5.1 → 3.6.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,12 @@
1
1
  # Next
2
2
 
3
+ # 3.6.0 – 2025-06-05
4
+
5
+ ## Added
6
+
7
+ 1. chore: improve event prop types
8
+ 2. rotate session id if expired after 24 hours
9
+
3
10
  # 3.5.1 – 2025-05-06
4
11
 
5
12
  ## Fixed
package/lib/index.cjs CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var version = "3.5.1";
5
+ var version = "3.6.0";
6
6
 
7
7
  var PostHogPersistedProperty;
8
8
  (function (PostHogPersistedProperty) {
@@ -19,6 +19,7 @@ var PostHogPersistedProperty;
19
19
  PostHogPersistedProperty["Queue"] = "queue";
20
20
  PostHogPersistedProperty["OptedOut"] = "opted_out";
21
21
  PostHogPersistedProperty["SessionId"] = "session_id";
22
+ PostHogPersistedProperty["SessionStartTimestamp"] = "session_start_timestamp";
22
23
  PostHogPersistedProperty["SessionLastTimestamp"] = "session_timestamp";
23
24
  PostHogPersistedProperty["PersonProperties"] = "person_properties";
24
25
  PostHogPersistedProperty["GroupProperties"] = "group_properties";
@@ -371,6 +372,9 @@ function isTokenInRollout(token, percentage = 0, excludedHashes) {
371
372
  const hashInt = parseInt(tokenHash, 16);
372
373
  const hashFloat = hashInt / 0xffffffff;
373
374
  return hashFloat < percentage;
375
+ }
376
+ function allSettled(promises) {
377
+ return Promise.all(promises.map((p) => (p ?? Promise.resolve()).then((value) => ({ status: 'fulfilled', value }), (reason) => ({ status: 'rejected', reason }))));
374
378
  }
375
379
 
376
380
  // Copyright (c) 2013 Pieroxy <pieroxy@pieroxy.net>
@@ -1261,6 +1265,7 @@ class PostHogFetchNetworkError extends Error {
1261
1265
  this.name = 'PostHogFetchNetworkError';
1262
1266
  }
1263
1267
  }
1268
+ const maybeAdd = (key, value) => value !== undefined ? { [key]: value } : {};
1264
1269
  async function logFlushError(err) {
1265
1270
  if (err instanceof PostHogFetchHttpError) {
1266
1271
  let text = '';
@@ -1521,6 +1526,7 @@ class PostHogCoreStateless {
1521
1526
  ...extraPayload,
1522
1527
  }),
1523
1528
  };
1529
+ this.logMsgIfDebug(() => console.log('PostHog Debug', 'Decide URL', url));
1524
1530
  // Don't retry /decide API calls
1525
1531
  return this.fetchWithRetry(url, fetchOptions, { retryCount: 0 }, this.featureFlagsRequestTimeoutMs)
1526
1532
  .then((response) => response.json())
@@ -1638,7 +1644,7 @@ class PostHogCoreStateless {
1638
1644
  async getSurveysStateless() {
1639
1645
  await this._initPromise;
1640
1646
  if (this.disableSurveys === true) {
1641
- this.logMsgIfDebug(() => console.log('Loading surveys is disabled.'));
1647
+ this.logMsgIfDebug(() => console.log('PostHog Debug', 'Loading surveys is disabled.'));
1642
1648
  return [];
1643
1649
  }
1644
1650
  const url = `${this.host}/api/surveys/?token=${this.apiKey}`;
@@ -1761,7 +1767,6 @@ class PostHogCoreStateless {
1761
1767
  }
1762
1768
  catch (err) {
1763
1769
  this._events.emit('error', err);
1764
- throw err;
1765
1770
  }
1766
1771
  }
1767
1772
  prepareMessage(type, _message, options) {
@@ -1801,15 +1806,38 @@ class PostHogCoreStateless {
1801
1806
  await logFlushError(err);
1802
1807
  });
1803
1808
  }
1809
+ /**
1810
+ * Flushes the queue
1811
+ *
1812
+ * This function will return a promise that will resolve when the flush is complete,
1813
+ * or reject if there was an error (for example if the server or network is down).
1814
+ *
1815
+ * If there is already a flush in progress, this function will wait for that flush to complete.
1816
+ *
1817
+ * It's recommended to do error handling in the callback of the promise.
1818
+ *
1819
+ * @example
1820
+ * posthog.flush().then(() => {
1821
+ * console.log('Flush complete')
1822
+ * }).catch((err) => {
1823
+ * console.error('Flush failed', err)
1824
+ * })
1825
+ *
1826
+ *
1827
+ * @throws PostHogFetchHttpError
1828
+ * @throws PostHogFetchNetworkError
1829
+ * @throws Error
1830
+ */
1804
1831
  async flush() {
1805
1832
  // Wait for the current flush operation to finish (regardless of success or failure), then try to flush again.
1806
1833
  // Use allSettled instead of finally to be defensive around flush throwing errors immediately rather than rejecting.
1807
- const nextFlushPromise = Promise.allSettled([this.flushPromise]).then(() => {
1834
+ // Use a custom allSettled implementation to avoid issues with patching Promise on RN
1835
+ const nextFlushPromise = allSettled([this.flushPromise]).then(() => {
1808
1836
  return this._flush();
1809
1837
  });
1810
1838
  this.flushPromise = nextFlushPromise;
1811
1839
  void this.addPendingPromise(nextFlushPromise);
1812
- Promise.allSettled([nextFlushPromise]).then(() => {
1840
+ allSettled([nextFlushPromise]).then(() => {
1813
1841
  // If there are no others waiting to flush, clear the promise.
1814
1842
  // We don't strictly need to do this, but it could make debugging easier
1815
1843
  if (this.flushPromise === nextFlushPromise) {
@@ -1835,7 +1863,7 @@ class PostHogCoreStateless {
1835
1863
  await this._initPromise;
1836
1864
  let queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
1837
1865
  if (!queue.length) {
1838
- return [];
1866
+ return;
1839
1867
  }
1840
1868
  const sentMessages = [];
1841
1869
  const originalQueueLength = queue.length;
@@ -1906,7 +1934,6 @@ class PostHogCoreStateless {
1906
1934
  sentMessages.push(...batchMessages);
1907
1935
  }
1908
1936
  this._events.emit('flush', sentMessages);
1909
- return sentMessages;
1910
1937
  }
1911
1938
  async fetchWithRetry(url, options, retryOptions, requestTimeout) {
1912
1939
  var _a;
@@ -1916,7 +1943,14 @@ class PostHogCoreStateless {
1916
1943
  return ctrl.signal;
1917
1944
  });
1918
1945
  const body = options.body ? options.body : '';
1919
- const reqByteLength = Buffer.byteLength(body, STRING_FORMAT);
1946
+ let reqByteLength = -1;
1947
+ try {
1948
+ reqByteLength = Buffer.byteLength(body, STRING_FORMAT);
1949
+ }
1950
+ catch {
1951
+ const encoded = new TextEncoder().encode(body);
1952
+ reqByteLength = encoded.length;
1953
+ }
1920
1954
  return await retriable(async () => {
1921
1955
  let res = null;
1922
1956
  try {
@@ -2005,6 +2039,7 @@ class PostHogCore extends PostHogCoreStateless {
2005
2039
  const featureFlagsRequestTimeoutMs = options?.featureFlagsRequestTimeoutMs ?? 10000; // 10 seconds
2006
2040
  super(apiKey, { ...options, disableGeoip: disableGeoipOption, featureFlagsRequestTimeoutMs });
2007
2041
  this.flagCallReported = {};
2042
+ this._sessionMaxLengthSeconds = 24 * 60 * 60; // 24 hours
2008
2043
  this.sessionProps = {};
2009
2044
  this.sendFeatureFlagEvent = options?.sendFeatureFlagEvent ?? true;
2010
2045
  this._sessionExpirationTimeSeconds = options?.sessionExpirationTimeSeconds ?? 1800; // 30 minutes
@@ -2078,7 +2113,7 @@ class PostHogCore extends PostHogCoreStateless {
2078
2113
  }
2079
2114
  }
2080
2115
  return {
2081
- $active_feature_flags: featureFlags ? Object.keys(featureFlags) : undefined,
2116
+ ...maybeAdd('$active_feature_flags', featureFlags ? Object.keys(featureFlags) : undefined),
2082
2117
  ...featureVariantProperties,
2083
2118
  ...super.getCommonEventProperties(),
2084
2119
  };
@@ -2100,18 +2135,26 @@ class PostHogCore extends PostHogCoreStateless {
2100
2135
  return '';
2101
2136
  }
2102
2137
  let sessionId = this.getPersistedProperty(PostHogPersistedProperty.SessionId);
2103
- const sessionTimestamp = this.getPersistedProperty(PostHogPersistedProperty.SessionLastTimestamp) || 0;
2104
- if (!sessionId || Date.now() - sessionTimestamp > this._sessionExpirationTimeSeconds * 1000) {
2138
+ const sessionLastTimestamp = this.getPersistedProperty(PostHogPersistedProperty.SessionLastTimestamp) || 0;
2139
+ const sessionStartTimestamp = this.getPersistedProperty(PostHogPersistedProperty.SessionStartTimestamp) || 0;
2140
+ const now = Date.now();
2141
+ const sessionLastDif = now - sessionLastTimestamp;
2142
+ const sessionStartDif = now - sessionStartTimestamp;
2143
+ if (!sessionId ||
2144
+ sessionLastDif > this._sessionExpirationTimeSeconds * 1000 ||
2145
+ sessionStartDif > this._sessionMaxLengthSeconds * 1000) {
2105
2146
  sessionId = uuidv7();
2106
2147
  this.setPersistedProperty(PostHogPersistedProperty.SessionId, sessionId);
2148
+ this.setPersistedProperty(PostHogPersistedProperty.SessionStartTimestamp, now);
2107
2149
  }
2108
- this.setPersistedProperty(PostHogPersistedProperty.SessionLastTimestamp, Date.now());
2150
+ this.setPersistedProperty(PostHogPersistedProperty.SessionLastTimestamp, now);
2109
2151
  return sessionId;
2110
2152
  }
2111
2153
  resetSessionId() {
2112
2154
  this.wrap(() => {
2113
2155
  this.setPersistedProperty(PostHogPersistedProperty.SessionId, null);
2114
2156
  this.setPersistedProperty(PostHogPersistedProperty.SessionLastTimestamp, null);
2157
+ this.setPersistedProperty(PostHogPersistedProperty.SessionStartTimestamp, null);
2115
2158
  });
2116
2159
  }
2117
2160
  /**
@@ -2163,8 +2206,8 @@ class PostHogCore extends PostHogCoreStateless {
2163
2206
  const userProps = properties?.$set || properties;
2164
2207
  const allProperties = this.enrichProperties({
2165
2208
  $anon_distinct_id: this.getAnonymousId(),
2166
- $set: userProps,
2167
- $set_once: userPropsOnce,
2209
+ ...maybeAdd('$set', userProps),
2210
+ ...maybeAdd('$set_once', userPropsOnce),
2168
2211
  });
2169
2212
  if (distinctId !== previousDistinctId) {
2170
2213
  // We keep the AnonymousId to be used by decide calls and identify to link the previousId
@@ -2312,14 +2355,16 @@ class PostHogCore extends PostHogCoreStateless {
2312
2355
  }
2313
2356
  return this._decideAsync(sendAnonDistinctId);
2314
2357
  }
2315
- cacheSessionReplay(response) {
2358
+ cacheSessionReplay(source, response) {
2316
2359
  const sessionReplay = response?.sessionRecording;
2317
2360
  if (sessionReplay) {
2318
2361
  this.setPersistedProperty(PostHogPersistedProperty.SessionReplay, sessionReplay);
2319
- this.logMsgIfDebug(() => console.log('PostHog Debug', 'Session replay config: ', JSON.stringify(sessionReplay)));
2362
+ this.logMsgIfDebug(() => console.log('PostHog Debug', `Session replay config from ${source}: `, JSON.stringify(sessionReplay)));
2320
2363
  }
2321
- else {
2322
- this.logMsgIfDebug(() => console.info('PostHog Debug', 'Session replay config disabled.'));
2364
+ else if (typeof sessionReplay === 'boolean' && sessionReplay === false) {
2365
+ // if session replay is disabled, we don't need to cache it
2366
+ // we need to check for this because the response might be undefined (/flags does not return sessionRecording yet)
2367
+ this.logMsgIfDebug(() => console.info('PostHog Debug', `Session replay config from ${source} disabled.`));
2323
2368
  this.setPersistedProperty(PostHogPersistedProperty.SessionReplay, null);
2324
2369
  }
2325
2370
  }
@@ -2333,25 +2378,30 @@ class PostHogCore extends PostHogCoreStateless {
2333
2378
  const remoteConfigWithoutSurveys = { ...response };
2334
2379
  delete remoteConfigWithoutSurveys.surveys;
2335
2380
  this.logMsgIfDebug(() => console.log('PostHog Debug', 'Fetched remote config: ', JSON.stringify(remoteConfigWithoutSurveys)));
2336
- const surveys = response.surveys;
2337
- let hasSurveys = true;
2338
- if (!Array.isArray(surveys)) {
2339
- // If surveys is not an array, it means there are no surveys (its a boolean instead)
2340
- this.logMsgIfDebug(() => console.log('PostHog Debug', 'There are no surveys.'));
2341
- hasSurveys = false;
2342
- }
2343
- else {
2344
- this.logMsgIfDebug(() => console.log('PostHog Debug', 'Surveys fetched from remote config: ', JSON.stringify(surveys)));
2345
- }
2346
- if (this.disableSurveys === false && hasSurveys) {
2347
- this.setPersistedProperty(PostHogPersistedProperty.Surveys, surveys);
2381
+ if (this.disableSurveys === false) {
2382
+ const surveys = response.surveys;
2383
+ let hasSurveys = true;
2384
+ if (!Array.isArray(surveys)) {
2385
+ // If surveys is not an array, it means there are no surveys (its a boolean instead)
2386
+ this.logMsgIfDebug(() => console.log('PostHog Debug', 'There are no surveys.'));
2387
+ hasSurveys = false;
2388
+ }
2389
+ else {
2390
+ this.logMsgIfDebug(() => console.log('PostHog Debug', 'Surveys fetched from remote config: ', JSON.stringify(surveys)));
2391
+ }
2392
+ if (hasSurveys) {
2393
+ this.setPersistedProperty(PostHogPersistedProperty.Surveys, surveys);
2394
+ }
2395
+ else {
2396
+ this.setPersistedProperty(PostHogPersistedProperty.Surveys, null);
2397
+ }
2348
2398
  }
2349
2399
  else {
2350
2400
  this.setPersistedProperty(PostHogPersistedProperty.Surveys, null);
2351
2401
  }
2352
2402
  // we cache the surveys in its own storage key
2353
2403
  this.setPersistedProperty(PostHogPersistedProperty.RemoteConfig, remoteConfigWithoutSurveys);
2354
- this.cacheSessionReplay(response);
2404
+ this.cacheSessionReplay('remote config', response);
2355
2405
  // we only dont load flags if the remote config has no feature flags
2356
2406
  if (response.hasFeatureFlags === false) {
2357
2407
  // resetting flags to empty object
@@ -2408,7 +2458,7 @@ class PostHogCore extends PostHogCoreStateless {
2408
2458
  this.setKnownFeatureFlagDetails(newFeatureFlagDetails);
2409
2459
  // Mark that we hit the /decide endpoint so we can capture this in the $feature_flag_called event
2410
2460
  this.setPersistedProperty(PostHogPersistedProperty.DecideEndpointWasHit, true);
2411
- this.cacheSessionReplay(res);
2461
+ this.cacheSessionReplay('decide/flags', res);
2412
2462
  }
2413
2463
  return res;
2414
2464
  })
@@ -2494,14 +2544,14 @@ class PostHogCore extends PostHogCoreStateless {
2494
2544
  this.capture('$feature_flag_called', {
2495
2545
  $feature_flag: key,
2496
2546
  $feature_flag_response: response,
2497
- $feature_flag_id: featureFlag?.metadata?.id,
2498
- $feature_flag_version: featureFlag?.metadata?.version,
2499
- $feature_flag_reason: featureFlag?.reason?.description ?? featureFlag?.reason?.code,
2500
- $feature_flag_bootstrapped_response: bootstrappedResponse,
2501
- $feature_flag_bootstrapped_payload: bootstrappedPayload,
2547
+ ...maybeAdd('$feature_flag_id', featureFlag?.metadata?.id),
2548
+ ...maybeAdd('$feature_flag_version', featureFlag?.metadata?.version),
2549
+ ...maybeAdd('$feature_flag_reason', featureFlag?.reason?.description ?? featureFlag?.reason?.code),
2550
+ ...maybeAdd('$feature_flag_bootstrapped_response', bootstrappedResponse),
2551
+ ...maybeAdd('$feature_flag_bootstrapped_payload', bootstrappedPayload),
2502
2552
  // If we haven't yet received a response from the /decide endpoint, we must have used the bootstrapped value
2503
2553
  $used_bootstrap_value: !this.getPersistedProperty(PostHogPersistedProperty.DecideEndpointWasHit),
2504
- $feature_flag_request_id: details.requestId,
2554
+ ...maybeAdd('$feature_flag_request_id', details.requestId),
2505
2555
  });
2506
2556
  }
2507
2557
  // If we have flags we either return the value (true or string) or false
@@ -2575,7 +2625,7 @@ class PostHogCore extends PostHogCoreStateless {
2575
2625
  .catch((e) => {
2576
2626
  cb?.(e, undefined);
2577
2627
  if (!cb) {
2578
- this.logMsgIfDebug(() => console.log('[PostHog] Error reloading feature flags', e));
2628
+ this.logMsgIfDebug(() => console.log('PostHog Debug', 'Error reloading feature flags', e));
2579
2629
  }
2580
2630
  });
2581
2631
  }