posthog-node 4.14.0 → 4.16.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,12 @@
1
+ # 4.16.0 - 2025-05-01
2
+
3
+ 1. chore: improve flush event
4
+
5
+ # 4.15.0 - 2025-04-30
6
+
7
+ 1. chore: add immediate-mode
8
+ 2. chore: better error logging when flushing events
9
+
1
10
  # 4.14.0 - 2025-04-24
2
11
 
3
12
  1. feat: Add super properties as a concept to the Node SDK
package/lib/index.cjs.js CHANGED
@@ -22,7 +22,7 @@ function _interopNamespace(e) {
22
22
  return Object.freeze(n);
23
23
  }
24
24
 
25
- var version = "4.14.0";
25
+ var version = "4.16.0";
26
26
 
27
27
  var PostHogPersistedProperty;
28
28
  (function (PostHogPersistedProperty) {
@@ -278,6 +278,7 @@ const NEW_FLAGS_EXCLUDED_HASHES = new Set([
278
278
  'fc80b8e2',
279
279
  '75cc0998',
280
280
  ]);
281
+ const STRING_FORMAT = 'utf8';
281
282
  function assert(truthyValue, message) {
282
283
  if (!truthyValue || typeof truthyValue !== 'string' || isEmpty(truthyValue)) {
283
284
  throw new Error(message);
@@ -1216,11 +1217,21 @@ const uuidv7 = () => uuidv7obj().toString();
1216
1217
  const uuidv7obj = () => (defaultGenerator || (defaultGenerator = new V7Generator())).generate();
1217
1218
 
1218
1219
  class PostHogFetchHttpError extends Error {
1219
- constructor(response) {
1220
- super('HTTP error while fetching PostHog: ' + response.status);
1220
+ constructor(response, reqByteLength) {
1221
+ super('HTTP error while fetching PostHog: status=' + response.status + ', reqByteLength=' + reqByteLength);
1221
1222
  this.response = response;
1223
+ this.reqByteLength = reqByteLength;
1222
1224
  this.name = 'PostHogFetchHttpError';
1223
1225
  }
1226
+ get status() {
1227
+ return this.response.status;
1228
+ }
1229
+ get text() {
1230
+ return this.response.text();
1231
+ }
1232
+ get json() {
1233
+ return this.response.json();
1234
+ }
1224
1235
  }
1225
1236
  class PostHogFetchNetworkError extends Error {
1226
1237
  constructor(error) {
@@ -1232,9 +1243,26 @@ class PostHogFetchNetworkError extends Error {
1232
1243
  this.name = 'PostHogFetchNetworkError';
1233
1244
  }
1234
1245
  }
1246
+ async function logFlushError(err) {
1247
+ if (err instanceof PostHogFetchHttpError) {
1248
+ let text = '';
1249
+ try {
1250
+ text = await err.text;
1251
+ }
1252
+ catch { }
1253
+ console.error(`Error while flushing PostHog: message=${err.message}, response body=${text}`, err);
1254
+ }
1255
+ else {
1256
+ console.error('Error while flushing PostHog', err);
1257
+ }
1258
+ return Promise.resolve();
1259
+ }
1235
1260
  function isPostHogFetchError(err) {
1236
1261
  return typeof err === 'object' && (err instanceof PostHogFetchHttpError || err instanceof PostHogFetchNetworkError);
1237
1262
  }
1263
+ function isPostHogFetchContentTooLargeError(err) {
1264
+ return typeof err === 'object' && err instanceof PostHogFetchHttpError && err.status === 413;
1265
+ }
1238
1266
  var QuotaLimitedFeature;
1239
1267
  (function (QuotaLimitedFeature) {
1240
1268
  QuotaLimitedFeature["FeatureFlags"] = "feature_flags";
@@ -1243,6 +1271,7 @@ var QuotaLimitedFeature;
1243
1271
  class PostHogCoreStateless {
1244
1272
  constructor(apiKey, options) {
1245
1273
  this.flushPromise = null;
1274
+ this.shutdownPromise = null;
1246
1275
  this.pendingPromises = {};
1247
1276
  // internal
1248
1277
  this._events = new SimpleEventEmitter();
@@ -1365,12 +1394,26 @@ class PostHogCoreStateless {
1365
1394
  this.enqueue('identify', payload, options);
1366
1395
  });
1367
1396
  }
1397
+ async identifyStatelessImmediate(distinctId, properties, options) {
1398
+ const payload = {
1399
+ ...this.buildPayload({
1400
+ distinct_id: distinctId,
1401
+ event: '$identify',
1402
+ properties,
1403
+ }),
1404
+ };
1405
+ await this.sendImmediate('identify', payload, options);
1406
+ }
1368
1407
  captureStateless(distinctId, event, properties, options) {
1369
1408
  this.wrap(() => {
1370
1409
  const payload = this.buildPayload({ distinct_id: distinctId, event, properties });
1371
1410
  this.enqueue('capture', payload, options);
1372
1411
  });
1373
1412
  }
1413
+ async captureStatelessImmediate(distinctId, event, properties, options) {
1414
+ const payload = this.buildPayload({ distinct_id: distinctId, event, properties });
1415
+ await this.sendImmediate('capture', payload, options);
1416
+ }
1374
1417
  aliasStateless(alias, distinctId, properties, options) {
1375
1418
  this.wrap(() => {
1376
1419
  const payload = this.buildPayload({
@@ -1385,6 +1428,18 @@ class PostHogCoreStateless {
1385
1428
  this.enqueue('alias', payload, options);
1386
1429
  });
1387
1430
  }
1431
+ async aliasStatelessImmediate(alias, distinctId, properties, options) {
1432
+ const payload = this.buildPayload({
1433
+ event: '$create_alias',
1434
+ distinct_id: distinctId,
1435
+ properties: {
1436
+ ...(properties || {}),
1437
+ distinct_id: distinctId,
1438
+ alias,
1439
+ },
1440
+ });
1441
+ await this.sendImmediate('alias', payload, options);
1442
+ }
1388
1443
  /***
1389
1444
  *** GROUPS
1390
1445
  ***/
@@ -1628,25 +1683,7 @@ class PostHogCoreStateless {
1628
1683
  this._events.emit(type, `Library is disabled. Not sending event. To re-enable, call posthog.optIn()`);
1629
1684
  return;
1630
1685
  }
1631
- const message = {
1632
- ..._message,
1633
- type: type,
1634
- library: this.getLibraryId(),
1635
- library_version: this.getLibraryVersion(),
1636
- timestamp: options?.timestamp ? options?.timestamp : currentISOTime(),
1637
- uuid: options?.uuid ? options.uuid : uuidv7(),
1638
- };
1639
- const addGeoipDisableProperty = options?.disableGeoip ?? this.disableGeoip;
1640
- if (addGeoipDisableProperty) {
1641
- if (!message.properties) {
1642
- message.properties = {};
1643
- }
1644
- message['properties']['$geoip_disable'] = true;
1645
- }
1646
- if (message.distinctId) {
1647
- message.distinct_id = message.distinctId;
1648
- delete message.distinctId;
1649
- }
1686
+ const message = this.prepareMessage(type, _message, options);
1650
1687
  const queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
1651
1688
  if (queue.length >= this.maxQueueSize) {
1652
1689
  queue.shift();
@@ -1664,6 +1701,73 @@ class PostHogCoreStateless {
1664
1701
  }
1665
1702
  });
1666
1703
  }
1704
+ async sendImmediate(type, _message, options) {
1705
+ if (this.disabled) {
1706
+ this.logMsgIfDebug(() => console.warn('[PostHog] The client is disabled'));
1707
+ return;
1708
+ }
1709
+ if (!this._isInitialized) {
1710
+ await this._initPromise;
1711
+ }
1712
+ if (this.optedOut) {
1713
+ this._events.emit(type, `Library is disabled. Not sending event. To re-enable, call posthog.optIn()`);
1714
+ return;
1715
+ }
1716
+ const data = {
1717
+ api_key: this.apiKey,
1718
+ batch: [this.prepareMessage(type, _message, options)],
1719
+ sent_at: currentISOTime(),
1720
+ };
1721
+ if (this.historicalMigration) {
1722
+ data.historical_migration = true;
1723
+ }
1724
+ const payload = JSON.stringify(data);
1725
+ const url = this.captureMode === 'form'
1726
+ ? `${this.host}/e/?ip=1&_=${currentTimestamp()}&v=${this.getLibraryVersion()}`
1727
+ : `${this.host}/batch/`;
1728
+ const fetchOptions = this.captureMode === 'form'
1729
+ ? {
1730
+ method: 'POST',
1731
+ mode: 'no-cors',
1732
+ credentials: 'omit',
1733
+ headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/x-www-form-urlencoded' },
1734
+ body: `data=${encodeURIComponent(LZString.compressToBase64(payload))}&compression=lz64`,
1735
+ }
1736
+ : {
1737
+ method: 'POST',
1738
+ headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
1739
+ body: payload,
1740
+ };
1741
+ try {
1742
+ await this.fetchWithRetry(url, fetchOptions);
1743
+ }
1744
+ catch (err) {
1745
+ this._events.emit('error', err);
1746
+ throw err;
1747
+ }
1748
+ }
1749
+ prepareMessage(type, _message, options) {
1750
+ const message = {
1751
+ ..._message,
1752
+ type: type,
1753
+ library: this.getLibraryId(),
1754
+ library_version: this.getLibraryVersion(),
1755
+ timestamp: options?.timestamp ? options?.timestamp : currentISOTime(),
1756
+ uuid: options?.uuid ? options.uuid : uuidv7(),
1757
+ };
1758
+ const addGeoipDisableProperty = options?.disableGeoip ?? this.disableGeoip;
1759
+ if (addGeoipDisableProperty) {
1760
+ if (!message.properties) {
1761
+ message.properties = {};
1762
+ }
1763
+ message['properties']['$geoip_disable'] = true;
1764
+ }
1765
+ if (message.distinctId) {
1766
+ message.distinct_id = message.distinctId;
1767
+ delete message.distinctId;
1768
+ }
1769
+ return message;
1770
+ }
1667
1771
  clearFlushTimer() {
1668
1772
  if (this._flushTimer) {
1669
1773
  clearTimeout(this._flushTimer);
@@ -1675,16 +1779,26 @@ class PostHogCoreStateless {
1675
1779
  * Avoids unnecessary promise errors
1676
1780
  */
1677
1781
  flushBackground() {
1678
- void this.flush().catch(() => { });
1782
+ void this.flush().catch(async (err) => {
1783
+ await logFlushError(err);
1784
+ });
1679
1785
  }
1680
1786
  async flush() {
1681
- if (!this.flushPromise) {
1682
- this.flushPromise = this._flush().finally(() => {
1787
+ // Wait for the current flush operation to finish (regardless of success or failure), then try to flush again.
1788
+ // Use allSettled instead of finally to be defensive around flush throwing errors immediately rather than rejecting.
1789
+ const nextFlushPromise = Promise.allSettled([this.flushPromise]).then(() => {
1790
+ return this._flush();
1791
+ });
1792
+ this.flushPromise = nextFlushPromise;
1793
+ void this.addPendingPromise(nextFlushPromise);
1794
+ Promise.allSettled([nextFlushPromise]).then(() => {
1795
+ // If there are no others waiting to flush, clear the promise.
1796
+ // We don't strictly need to do this, but it could make debugging easier
1797
+ if (this.flushPromise === nextFlushPromise) {
1683
1798
  this.flushPromise = null;
1684
- });
1685
- this.addPendingPromise(this.flushPromise);
1686
- }
1687
- return this.flushPromise;
1799
+ }
1800
+ });
1801
+ return nextFlushPromise;
1688
1802
  }
1689
1803
  getCustomHeaders() {
1690
1804
  // Don't set the user agent if we're not on a browser. The latest spec allows
@@ -1701,56 +1815,80 @@ class PostHogCoreStateless {
1701
1815
  async _flush() {
1702
1816
  this.clearFlushTimer();
1703
1817
  await this._initPromise;
1704
- const queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
1818
+ let queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
1705
1819
  if (!queue.length) {
1706
1820
  return [];
1707
1821
  }
1708
- const items = queue.slice(0, this.maxBatchSize);
1709
- const messages = items.map((item) => item.message);
1710
- const persistQueueChange = () => {
1711
- const refreshedQueue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
1712
- this.setPersistedProperty(PostHogPersistedProperty.Queue, refreshedQueue.slice(items.length));
1713
- };
1714
- const data = {
1715
- api_key: this.apiKey,
1716
- batch: messages,
1717
- sent_at: currentISOTime(),
1718
- };
1719
- if (this.historicalMigration) {
1720
- data.historical_migration = true;
1721
- }
1722
- const payload = JSON.stringify(data);
1723
- const url = this.captureMode === 'form'
1724
- ? `${this.host}/e/?ip=1&_=${currentTimestamp()}&v=${this.getLibraryVersion()}`
1725
- : `${this.host}/batch/`;
1726
- const fetchOptions = this.captureMode === 'form'
1727
- ? {
1728
- method: 'POST',
1729
- mode: 'no-cors',
1730
- credentials: 'omit',
1731
- headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/x-www-form-urlencoded' },
1732
- body: `data=${encodeURIComponent(LZString.compressToBase64(payload))}&compression=lz64`,
1822
+ const sentMessages = [];
1823
+ const originalQueueLength = queue.length;
1824
+ while (queue.length > 0 && sentMessages.length < originalQueueLength) {
1825
+ const batchItems = queue.slice(0, this.maxBatchSize);
1826
+ const batchMessages = batchItems.map((item) => item.message);
1827
+ const persistQueueChange = () => {
1828
+ const refreshedQueue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
1829
+ const newQueue = refreshedQueue.slice(batchItems.length);
1830
+ this.setPersistedProperty(PostHogPersistedProperty.Queue, newQueue);
1831
+ queue = newQueue;
1832
+ };
1833
+ const data = {
1834
+ api_key: this.apiKey,
1835
+ batch: batchMessages,
1836
+ sent_at: currentISOTime(),
1837
+ };
1838
+ if (this.historicalMigration) {
1839
+ data.historical_migration = true;
1733
1840
  }
1734
- : {
1735
- method: 'POST',
1736
- headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
1737
- body: payload,
1841
+ const payload = JSON.stringify(data);
1842
+ const url = this.captureMode === 'form'
1843
+ ? `${this.host}/e/?ip=1&_=${currentTimestamp()}&v=${this.getLibraryVersion()}`
1844
+ : `${this.host}/batch/`;
1845
+ const fetchOptions = this.captureMode === 'form'
1846
+ ? {
1847
+ method: 'POST',
1848
+ mode: 'no-cors',
1849
+ credentials: 'omit',
1850
+ headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/x-www-form-urlencoded' },
1851
+ body: `data=${encodeURIComponent(LZString.compressToBase64(payload))}&compression=lz64`,
1852
+ }
1853
+ : {
1854
+ method: 'POST',
1855
+ headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
1856
+ body: payload,
1857
+ };
1858
+ const retryOptions = {
1859
+ retryCheck: (err) => {
1860
+ // don't automatically retry on 413 errors, we want to reduce the batch size first
1861
+ if (isPostHogFetchContentTooLargeError(err)) {
1862
+ return false;
1863
+ }
1864
+ // otherwise, retry on network errors
1865
+ return isPostHogFetchError(err);
1866
+ },
1738
1867
  };
1739
- try {
1740
- await this.fetchWithRetry(url, fetchOptions);
1741
- }
1742
- catch (err) {
1743
- // depending on the error type, eg a malformed JSON or broken queue, it'll always return an error
1744
- // and this will be an endless loop, in this case, if the error isn't a network issue, we always remove the items from the queue
1745
- if (!(err instanceof PostHogFetchNetworkError)) {
1746
- persistQueueChange();
1868
+ try {
1869
+ await this.fetchWithRetry(url, fetchOptions, retryOptions);
1747
1870
  }
1748
- this._events.emit('error', err);
1749
- throw err;
1871
+ catch (err) {
1872
+ if (isPostHogFetchContentTooLargeError(err) && batchMessages.length > 1) {
1873
+ // if we get a 413 error, we want to reduce the batch size and try again
1874
+ this.maxBatchSize = Math.max(1, Math.floor(batchMessages.length / 2));
1875
+ this.logMsgIfDebug(() => console.warn(`Received 413 when sending batch of size ${batchMessages.length}, reducing batch size to ${this.maxBatchSize}`));
1876
+ // do not persist the queue change, we want to retry the same batch
1877
+ continue;
1878
+ }
1879
+ // depending on the error type, eg a malformed JSON or broken queue, it'll always return an error
1880
+ // and this will be an endless loop, in this case, if the error isn't a network issue, we always remove the items from the queue
1881
+ if (!(err instanceof PostHogFetchNetworkError)) {
1882
+ persistQueueChange();
1883
+ }
1884
+ this._events.emit('error', err);
1885
+ throw err;
1886
+ }
1887
+ persistQueueChange();
1888
+ sentMessages.push(...batchMessages);
1750
1889
  }
1751
- persistQueueChange();
1752
- this._events.emit('flush', messages);
1753
- return messages;
1890
+ this._events.emit('flush', sentMessages);
1891
+ return sentMessages;
1754
1892
  }
1755
1893
  async fetchWithRetry(url, options, retryOptions, requestTimeout) {
1756
1894
  var _a;
@@ -1759,6 +1897,8 @@ class PostHogCoreStateless {
1759
1897
  setTimeout(() => ctrl.abort(), ms);
1760
1898
  return ctrl.signal;
1761
1899
  });
1900
+ const body = options.body ? options.body : '';
1901
+ const reqByteLength = Buffer.byteLength(body, STRING_FORMAT);
1762
1902
  return await retriable(async () => {
1763
1903
  let res = null;
1764
1904
  try {
@@ -1776,12 +1916,12 @@ class PostHogCoreStateless {
1776
1916
  // https://developer.mozilla.org/en-US/docs/Web/API/Request/mode#no-cors
1777
1917
  const isNoCors = options.mode === 'no-cors';
1778
1918
  if (!isNoCors && (res.status < 200 || res.status >= 400)) {
1779
- throw new PostHogFetchHttpError(res);
1919
+ throw new PostHogFetchHttpError(res, reqByteLength);
1780
1920
  }
1781
1921
  return res;
1782
1922
  }, { ...this._retryOptions, ...retryOptions });
1783
1923
  }
1784
- async shutdown(shutdownTimeoutMs = 30000) {
1924
+ async _shutdown(shutdownTimeoutMs = 30000) {
1785
1925
  // A little tricky - we want to have a max shutdown time and enforce it, even if that means we have some
1786
1926
  // dangling promises. We'll keep track of the timeout and resolve/reject based on that.
1787
1927
  await this._initPromise;
@@ -1808,7 +1948,7 @@ class PostHogCoreStateless {
1808
1948
  if (!isPostHogFetchError(e)) {
1809
1949
  throw e;
1810
1950
  }
1811
- this.logMsgIfDebug(() => console.error('Error while shutting down PostHog', e));
1951
+ await logFlushError(e);
1812
1952
  }
1813
1953
  };
1814
1954
  return Promise.race([
@@ -1822,6 +1962,22 @@ class PostHogCoreStateless {
1822
1962
  doShutdown(),
1823
1963
  ]);
1824
1964
  }
1965
+ /**
1966
+ * Call shutdown() once before the node process exits, so ensure that all events have been sent and all promises
1967
+ * have resolved. Do not use this function if you intend to keep using this PostHog instance after calling it.
1968
+ * @param shutdownTimeoutMs
1969
+ */
1970
+ async shutdown(shutdownTimeoutMs = 30000) {
1971
+ if (this.shutdownPromise) {
1972
+ this.logMsgIfDebug(() => console.warn('shutdown() called while already shutting down. shutdown() is meant to be called once before process exit - use flush() for per-request cleanup'));
1973
+ }
1974
+ else {
1975
+ this.shutdownPromise = this._shutdown(shutdownTimeoutMs).finally(() => {
1976
+ this.shutdownPromise = null;
1977
+ });
1978
+ }
1979
+ return this.shutdownPromise;
1980
+ }
1825
1981
  }
1826
1982
 
1827
1983
  class PostHogMemoryStorage {
@@ -3689,6 +3845,79 @@ class PostHog extends PostHogCoreStateless {
3689
3845
  });
3690
3846
  this.addPendingPromise(capturePromise);
3691
3847
  }
3848
+ async captureImmediate(props) {
3849
+ if (typeof props === 'string') {
3850
+ this.logMsgIfDebug(() => console.warn('Called capture() with a string as the first argument when an object was expected.'));
3851
+ }
3852
+ const {
3853
+ distinctId,
3854
+ event,
3855
+ properties,
3856
+ groups,
3857
+ sendFeatureFlags,
3858
+ timestamp,
3859
+ disableGeoip,
3860
+ uuid
3861
+ } = props;
3862
+ const _capture = props => {
3863
+ return super.captureStatelessImmediate(distinctId, event, props, {
3864
+ timestamp,
3865
+ disableGeoip,
3866
+ uuid
3867
+ });
3868
+ };
3869
+ const _getFlags = async (distinctId, groups, disableGeoip) => {
3870
+ return (await super.getFeatureFlagsStateless(distinctId, groups, undefined, undefined, disableGeoip)).flags;
3871
+ };
3872
+ const capturePromise = Promise.resolve().then(async () => {
3873
+ if (sendFeatureFlags) {
3874
+ // If we are sending feature flags, we need to make sure we have the latest flags
3875
+ // return await super.getFeatureFlagsStateless(distinctId, groups, undefined, undefined, disableGeoip)
3876
+ return await _getFlags(distinctId, groups, disableGeoip);
3877
+ }
3878
+ if (event === '$feature_flag_called') {
3879
+ // 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.
3880
+ return {};
3881
+ }
3882
+ if ((this.featureFlagsPoller?.featureFlags?.length || 0) > 0) {
3883
+ // Otherwise we may as well check for the flags locally and include them if they are already loaded
3884
+ const groupsWithStringValues = {};
3885
+ for (const [key, value] of Object.entries(groups || {})) {
3886
+ groupsWithStringValues[key] = String(value);
3887
+ }
3888
+ return await this.getAllFlags(distinctId, {
3889
+ groups: groupsWithStringValues,
3890
+ disableGeoip,
3891
+ onlyEvaluateLocally: true
3892
+ });
3893
+ }
3894
+ return {};
3895
+ }).then(flags => {
3896
+ // Derive the relevant flag properties to add
3897
+ const additionalProperties = {};
3898
+ if (flags) {
3899
+ for (const [feature, variant] of Object.entries(flags)) {
3900
+ additionalProperties[`$feature/${feature}`] = variant;
3901
+ }
3902
+ }
3903
+ const activeFlags = Object.keys(flags || {}).filter(flag => flags?.[flag] !== false).sort();
3904
+ if (activeFlags.length > 0) {
3905
+ additionalProperties['$active_feature_flags'] = activeFlags;
3906
+ }
3907
+ return additionalProperties;
3908
+ }).catch(() => {
3909
+ // Something went wrong getting the flag info - we should capture the event anyways
3910
+ return {};
3911
+ }).then(additionalProperties => {
3912
+ // No matter what - capture the event
3913
+ _capture({
3914
+ ...additionalProperties,
3915
+ ...properties,
3916
+ $groups: groups
3917
+ });
3918
+ });
3919
+ await capturePromise;
3920
+ }
3692
3921
  identify({
3693
3922
  distinctId,
3694
3923
  properties,
@@ -3707,11 +3936,33 @@ class PostHog extends PostHogCoreStateless {
3707
3936
  disableGeoip
3708
3937
  });
3709
3938
  }
3939
+ async identifyImmediate({
3940
+ distinctId,
3941
+ properties,
3942
+ disableGeoip
3943
+ }) {
3944
+ // promote $set and $set_once to top level
3945
+ const userPropsOnce = properties?.$set_once;
3946
+ delete properties?.$set_once;
3947
+ // if no $set is provided we assume all properties are $set
3948
+ const userProps = properties?.$set || properties;
3949
+ await super.identifyStatelessImmediate(distinctId, {
3950
+ $set: userProps,
3951
+ $set_once: userPropsOnce
3952
+ }, {
3953
+ disableGeoip
3954
+ });
3955
+ }
3710
3956
  alias(data) {
3711
3957
  super.aliasStateless(data.alias, data.distinctId, undefined, {
3712
3958
  disableGeoip: data.disableGeoip
3713
3959
  });
3714
3960
  }
3961
+ async aliasImmediate(data) {
3962
+ await super.aliasStatelessImmediate(data.alias, data.distinctId, undefined, {
3963
+ disableGeoip: data.disableGeoip
3964
+ });
3965
+ }
3715
3966
  isLocalEvaluationReady() {
3716
3967
  return this.featureFlagsPoller?.isLocalEvaluationReady() ?? false;
3717
3968
  }
@@ -3918,9 +4169,9 @@ class PostHog extends PostHogCoreStateless {
3918
4169
  async reloadFeatureFlags() {
3919
4170
  await this.featureFlagsPoller?.loadFeatureFlags(true);
3920
4171
  }
3921
- async shutdown(shutdownTimeoutMs) {
4172
+ async _shutdown(shutdownTimeoutMs) {
3922
4173
  this.featureFlagsPoller?.stopPoller();
3923
- return super.shutdown(shutdownTimeoutMs);
4174
+ return super._shutdown(shutdownTimeoutMs);
3924
4175
  }
3925
4176
  addLocalPersonAndGroupProperties(distinctId, groups, personProperties, groupProperties) {
3926
4177
  const allPersonProperties = {