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 +9 -0
- package/lib/index.cjs.js +328 -77
- package/lib/index.cjs.js.map +1 -1
- package/lib/index.d.ts +51 -2
- package/lib/index.esm.js +328 -77
- package/lib/index.esm.js.map +1 -1
- package/lib/posthog-core/src/index.d.ts +17 -0
- package/lib/posthog-core/src/utils.d.ts +2 -1
- package/lib/posthog-node/src/posthog-node.d.ts +8 -1
- package/lib/posthog-node/src/types.d.ts +26 -0
- package/lib/posthog-node/test/test-utils.d.ts +1 -0
- package/package.json +1 -1
- package/src/posthog-node.ts +102 -2
- package/src/types.ts +26 -0
- package/test/posthog-node.spec.ts +21 -23
- package/test/test-utils.ts +5 -0
package/lib/index.esm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { posix, dirname, sep } from 'path';
|
|
2
2
|
|
|
3
|
-
var version = "4.
|
|
3
|
+
var version = "4.16.0";
|
|
4
4
|
|
|
5
5
|
var PostHogPersistedProperty;
|
|
6
6
|
(function (PostHogPersistedProperty) {
|
|
@@ -256,6 +256,7 @@ const NEW_FLAGS_EXCLUDED_HASHES = new Set([
|
|
|
256
256
|
'fc80b8e2',
|
|
257
257
|
'75cc0998',
|
|
258
258
|
]);
|
|
259
|
+
const STRING_FORMAT = 'utf8';
|
|
259
260
|
function assert(truthyValue, message) {
|
|
260
261
|
if (!truthyValue || typeof truthyValue !== 'string' || isEmpty(truthyValue)) {
|
|
261
262
|
throw new Error(message);
|
|
@@ -1194,11 +1195,21 @@ const uuidv7 = () => uuidv7obj().toString();
|
|
|
1194
1195
|
const uuidv7obj = () => (defaultGenerator || (defaultGenerator = new V7Generator())).generate();
|
|
1195
1196
|
|
|
1196
1197
|
class PostHogFetchHttpError extends Error {
|
|
1197
|
-
constructor(response) {
|
|
1198
|
-
super('HTTP error while fetching PostHog: ' + response.status);
|
|
1198
|
+
constructor(response, reqByteLength) {
|
|
1199
|
+
super('HTTP error while fetching PostHog: status=' + response.status + ', reqByteLength=' + reqByteLength);
|
|
1199
1200
|
this.response = response;
|
|
1201
|
+
this.reqByteLength = reqByteLength;
|
|
1200
1202
|
this.name = 'PostHogFetchHttpError';
|
|
1201
1203
|
}
|
|
1204
|
+
get status() {
|
|
1205
|
+
return this.response.status;
|
|
1206
|
+
}
|
|
1207
|
+
get text() {
|
|
1208
|
+
return this.response.text();
|
|
1209
|
+
}
|
|
1210
|
+
get json() {
|
|
1211
|
+
return this.response.json();
|
|
1212
|
+
}
|
|
1202
1213
|
}
|
|
1203
1214
|
class PostHogFetchNetworkError extends Error {
|
|
1204
1215
|
constructor(error) {
|
|
@@ -1210,9 +1221,26 @@ class PostHogFetchNetworkError extends Error {
|
|
|
1210
1221
|
this.name = 'PostHogFetchNetworkError';
|
|
1211
1222
|
}
|
|
1212
1223
|
}
|
|
1224
|
+
async function logFlushError(err) {
|
|
1225
|
+
if (err instanceof PostHogFetchHttpError) {
|
|
1226
|
+
let text = '';
|
|
1227
|
+
try {
|
|
1228
|
+
text = await err.text;
|
|
1229
|
+
}
|
|
1230
|
+
catch { }
|
|
1231
|
+
console.error(`Error while flushing PostHog: message=${err.message}, response body=${text}`, err);
|
|
1232
|
+
}
|
|
1233
|
+
else {
|
|
1234
|
+
console.error('Error while flushing PostHog', err);
|
|
1235
|
+
}
|
|
1236
|
+
return Promise.resolve();
|
|
1237
|
+
}
|
|
1213
1238
|
function isPostHogFetchError(err) {
|
|
1214
1239
|
return typeof err === 'object' && (err instanceof PostHogFetchHttpError || err instanceof PostHogFetchNetworkError);
|
|
1215
1240
|
}
|
|
1241
|
+
function isPostHogFetchContentTooLargeError(err) {
|
|
1242
|
+
return typeof err === 'object' && err instanceof PostHogFetchHttpError && err.status === 413;
|
|
1243
|
+
}
|
|
1216
1244
|
var QuotaLimitedFeature;
|
|
1217
1245
|
(function (QuotaLimitedFeature) {
|
|
1218
1246
|
QuotaLimitedFeature["FeatureFlags"] = "feature_flags";
|
|
@@ -1221,6 +1249,7 @@ var QuotaLimitedFeature;
|
|
|
1221
1249
|
class PostHogCoreStateless {
|
|
1222
1250
|
constructor(apiKey, options) {
|
|
1223
1251
|
this.flushPromise = null;
|
|
1252
|
+
this.shutdownPromise = null;
|
|
1224
1253
|
this.pendingPromises = {};
|
|
1225
1254
|
// internal
|
|
1226
1255
|
this._events = new SimpleEventEmitter();
|
|
@@ -1343,12 +1372,26 @@ class PostHogCoreStateless {
|
|
|
1343
1372
|
this.enqueue('identify', payload, options);
|
|
1344
1373
|
});
|
|
1345
1374
|
}
|
|
1375
|
+
async identifyStatelessImmediate(distinctId, properties, options) {
|
|
1376
|
+
const payload = {
|
|
1377
|
+
...this.buildPayload({
|
|
1378
|
+
distinct_id: distinctId,
|
|
1379
|
+
event: '$identify',
|
|
1380
|
+
properties,
|
|
1381
|
+
}),
|
|
1382
|
+
};
|
|
1383
|
+
await this.sendImmediate('identify', payload, options);
|
|
1384
|
+
}
|
|
1346
1385
|
captureStateless(distinctId, event, properties, options) {
|
|
1347
1386
|
this.wrap(() => {
|
|
1348
1387
|
const payload = this.buildPayload({ distinct_id: distinctId, event, properties });
|
|
1349
1388
|
this.enqueue('capture', payload, options);
|
|
1350
1389
|
});
|
|
1351
1390
|
}
|
|
1391
|
+
async captureStatelessImmediate(distinctId, event, properties, options) {
|
|
1392
|
+
const payload = this.buildPayload({ distinct_id: distinctId, event, properties });
|
|
1393
|
+
await this.sendImmediate('capture', payload, options);
|
|
1394
|
+
}
|
|
1352
1395
|
aliasStateless(alias, distinctId, properties, options) {
|
|
1353
1396
|
this.wrap(() => {
|
|
1354
1397
|
const payload = this.buildPayload({
|
|
@@ -1363,6 +1406,18 @@ class PostHogCoreStateless {
|
|
|
1363
1406
|
this.enqueue('alias', payload, options);
|
|
1364
1407
|
});
|
|
1365
1408
|
}
|
|
1409
|
+
async aliasStatelessImmediate(alias, distinctId, properties, options) {
|
|
1410
|
+
const payload = this.buildPayload({
|
|
1411
|
+
event: '$create_alias',
|
|
1412
|
+
distinct_id: distinctId,
|
|
1413
|
+
properties: {
|
|
1414
|
+
...(properties || {}),
|
|
1415
|
+
distinct_id: distinctId,
|
|
1416
|
+
alias,
|
|
1417
|
+
},
|
|
1418
|
+
});
|
|
1419
|
+
await this.sendImmediate('alias', payload, options);
|
|
1420
|
+
}
|
|
1366
1421
|
/***
|
|
1367
1422
|
*** GROUPS
|
|
1368
1423
|
***/
|
|
@@ -1606,25 +1661,7 @@ class PostHogCoreStateless {
|
|
|
1606
1661
|
this._events.emit(type, `Library is disabled. Not sending event. To re-enable, call posthog.optIn()`);
|
|
1607
1662
|
return;
|
|
1608
1663
|
}
|
|
1609
|
-
const message =
|
|
1610
|
-
..._message,
|
|
1611
|
-
type: type,
|
|
1612
|
-
library: this.getLibraryId(),
|
|
1613
|
-
library_version: this.getLibraryVersion(),
|
|
1614
|
-
timestamp: options?.timestamp ? options?.timestamp : currentISOTime(),
|
|
1615
|
-
uuid: options?.uuid ? options.uuid : uuidv7(),
|
|
1616
|
-
};
|
|
1617
|
-
const addGeoipDisableProperty = options?.disableGeoip ?? this.disableGeoip;
|
|
1618
|
-
if (addGeoipDisableProperty) {
|
|
1619
|
-
if (!message.properties) {
|
|
1620
|
-
message.properties = {};
|
|
1621
|
-
}
|
|
1622
|
-
message['properties']['$geoip_disable'] = true;
|
|
1623
|
-
}
|
|
1624
|
-
if (message.distinctId) {
|
|
1625
|
-
message.distinct_id = message.distinctId;
|
|
1626
|
-
delete message.distinctId;
|
|
1627
|
-
}
|
|
1664
|
+
const message = this.prepareMessage(type, _message, options);
|
|
1628
1665
|
const queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
|
|
1629
1666
|
if (queue.length >= this.maxQueueSize) {
|
|
1630
1667
|
queue.shift();
|
|
@@ -1642,6 +1679,73 @@ class PostHogCoreStateless {
|
|
|
1642
1679
|
}
|
|
1643
1680
|
});
|
|
1644
1681
|
}
|
|
1682
|
+
async sendImmediate(type, _message, options) {
|
|
1683
|
+
if (this.disabled) {
|
|
1684
|
+
this.logMsgIfDebug(() => console.warn('[PostHog] The client is disabled'));
|
|
1685
|
+
return;
|
|
1686
|
+
}
|
|
1687
|
+
if (!this._isInitialized) {
|
|
1688
|
+
await this._initPromise;
|
|
1689
|
+
}
|
|
1690
|
+
if (this.optedOut) {
|
|
1691
|
+
this._events.emit(type, `Library is disabled. Not sending event. To re-enable, call posthog.optIn()`);
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
const data = {
|
|
1695
|
+
api_key: this.apiKey,
|
|
1696
|
+
batch: [this.prepareMessage(type, _message, options)],
|
|
1697
|
+
sent_at: currentISOTime(),
|
|
1698
|
+
};
|
|
1699
|
+
if (this.historicalMigration) {
|
|
1700
|
+
data.historical_migration = true;
|
|
1701
|
+
}
|
|
1702
|
+
const payload = JSON.stringify(data);
|
|
1703
|
+
const url = this.captureMode === 'form'
|
|
1704
|
+
? `${this.host}/e/?ip=1&_=${currentTimestamp()}&v=${this.getLibraryVersion()}`
|
|
1705
|
+
: `${this.host}/batch/`;
|
|
1706
|
+
const fetchOptions = this.captureMode === 'form'
|
|
1707
|
+
? {
|
|
1708
|
+
method: 'POST',
|
|
1709
|
+
mode: 'no-cors',
|
|
1710
|
+
credentials: 'omit',
|
|
1711
|
+
headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
1712
|
+
body: `data=${encodeURIComponent(LZString.compressToBase64(payload))}&compression=lz64`,
|
|
1713
|
+
}
|
|
1714
|
+
: {
|
|
1715
|
+
method: 'POST',
|
|
1716
|
+
headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
|
|
1717
|
+
body: payload,
|
|
1718
|
+
};
|
|
1719
|
+
try {
|
|
1720
|
+
await this.fetchWithRetry(url, fetchOptions);
|
|
1721
|
+
}
|
|
1722
|
+
catch (err) {
|
|
1723
|
+
this._events.emit('error', err);
|
|
1724
|
+
throw err;
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
prepareMessage(type, _message, options) {
|
|
1728
|
+
const message = {
|
|
1729
|
+
..._message,
|
|
1730
|
+
type: type,
|
|
1731
|
+
library: this.getLibraryId(),
|
|
1732
|
+
library_version: this.getLibraryVersion(),
|
|
1733
|
+
timestamp: options?.timestamp ? options?.timestamp : currentISOTime(),
|
|
1734
|
+
uuid: options?.uuid ? options.uuid : uuidv7(),
|
|
1735
|
+
};
|
|
1736
|
+
const addGeoipDisableProperty = options?.disableGeoip ?? this.disableGeoip;
|
|
1737
|
+
if (addGeoipDisableProperty) {
|
|
1738
|
+
if (!message.properties) {
|
|
1739
|
+
message.properties = {};
|
|
1740
|
+
}
|
|
1741
|
+
message['properties']['$geoip_disable'] = true;
|
|
1742
|
+
}
|
|
1743
|
+
if (message.distinctId) {
|
|
1744
|
+
message.distinct_id = message.distinctId;
|
|
1745
|
+
delete message.distinctId;
|
|
1746
|
+
}
|
|
1747
|
+
return message;
|
|
1748
|
+
}
|
|
1645
1749
|
clearFlushTimer() {
|
|
1646
1750
|
if (this._flushTimer) {
|
|
1647
1751
|
clearTimeout(this._flushTimer);
|
|
@@ -1653,16 +1757,26 @@ class PostHogCoreStateless {
|
|
|
1653
1757
|
* Avoids unnecessary promise errors
|
|
1654
1758
|
*/
|
|
1655
1759
|
flushBackground() {
|
|
1656
|
-
void this.flush().catch(() => {
|
|
1760
|
+
void this.flush().catch(async (err) => {
|
|
1761
|
+
await logFlushError(err);
|
|
1762
|
+
});
|
|
1657
1763
|
}
|
|
1658
1764
|
async flush() {
|
|
1659
|
-
|
|
1660
|
-
|
|
1765
|
+
// Wait for the current flush operation to finish (regardless of success or failure), then try to flush again.
|
|
1766
|
+
// Use allSettled instead of finally to be defensive around flush throwing errors immediately rather than rejecting.
|
|
1767
|
+
const nextFlushPromise = Promise.allSettled([this.flushPromise]).then(() => {
|
|
1768
|
+
return this._flush();
|
|
1769
|
+
});
|
|
1770
|
+
this.flushPromise = nextFlushPromise;
|
|
1771
|
+
void this.addPendingPromise(nextFlushPromise);
|
|
1772
|
+
Promise.allSettled([nextFlushPromise]).then(() => {
|
|
1773
|
+
// If there are no others waiting to flush, clear the promise.
|
|
1774
|
+
// We don't strictly need to do this, but it could make debugging easier
|
|
1775
|
+
if (this.flushPromise === nextFlushPromise) {
|
|
1661
1776
|
this.flushPromise = null;
|
|
1662
|
-
}
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
return this.flushPromise;
|
|
1777
|
+
}
|
|
1778
|
+
});
|
|
1779
|
+
return nextFlushPromise;
|
|
1666
1780
|
}
|
|
1667
1781
|
getCustomHeaders() {
|
|
1668
1782
|
// Don't set the user agent if we're not on a browser. The latest spec allows
|
|
@@ -1679,56 +1793,80 @@ class PostHogCoreStateless {
|
|
|
1679
1793
|
async _flush() {
|
|
1680
1794
|
this.clearFlushTimer();
|
|
1681
1795
|
await this._initPromise;
|
|
1682
|
-
|
|
1796
|
+
let queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
|
|
1683
1797
|
if (!queue.length) {
|
|
1684
1798
|
return [];
|
|
1685
1799
|
}
|
|
1686
|
-
const
|
|
1687
|
-
const
|
|
1688
|
-
|
|
1689
|
-
const
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
const fetchOptions = this.captureMode === 'form'
|
|
1705
|
-
? {
|
|
1706
|
-
method: 'POST',
|
|
1707
|
-
mode: 'no-cors',
|
|
1708
|
-
credentials: 'omit',
|
|
1709
|
-
headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
1710
|
-
body: `data=${encodeURIComponent(LZString.compressToBase64(payload))}&compression=lz64`,
|
|
1800
|
+
const sentMessages = [];
|
|
1801
|
+
const originalQueueLength = queue.length;
|
|
1802
|
+
while (queue.length > 0 && sentMessages.length < originalQueueLength) {
|
|
1803
|
+
const batchItems = queue.slice(0, this.maxBatchSize);
|
|
1804
|
+
const batchMessages = batchItems.map((item) => item.message);
|
|
1805
|
+
const persistQueueChange = () => {
|
|
1806
|
+
const refreshedQueue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
|
|
1807
|
+
const newQueue = refreshedQueue.slice(batchItems.length);
|
|
1808
|
+
this.setPersistedProperty(PostHogPersistedProperty.Queue, newQueue);
|
|
1809
|
+
queue = newQueue;
|
|
1810
|
+
};
|
|
1811
|
+
const data = {
|
|
1812
|
+
api_key: this.apiKey,
|
|
1813
|
+
batch: batchMessages,
|
|
1814
|
+
sent_at: currentISOTime(),
|
|
1815
|
+
};
|
|
1816
|
+
if (this.historicalMigration) {
|
|
1817
|
+
data.historical_migration = true;
|
|
1711
1818
|
}
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1819
|
+
const payload = JSON.stringify(data);
|
|
1820
|
+
const url = this.captureMode === 'form'
|
|
1821
|
+
? `${this.host}/e/?ip=1&_=${currentTimestamp()}&v=${this.getLibraryVersion()}`
|
|
1822
|
+
: `${this.host}/batch/`;
|
|
1823
|
+
const fetchOptions = this.captureMode === 'form'
|
|
1824
|
+
? {
|
|
1825
|
+
method: 'POST',
|
|
1826
|
+
mode: 'no-cors',
|
|
1827
|
+
credentials: 'omit',
|
|
1828
|
+
headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
1829
|
+
body: `data=${encodeURIComponent(LZString.compressToBase64(payload))}&compression=lz64`,
|
|
1830
|
+
}
|
|
1831
|
+
: {
|
|
1832
|
+
method: 'POST',
|
|
1833
|
+
headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
|
|
1834
|
+
body: payload,
|
|
1835
|
+
};
|
|
1836
|
+
const retryOptions = {
|
|
1837
|
+
retryCheck: (err) => {
|
|
1838
|
+
// don't automatically retry on 413 errors, we want to reduce the batch size first
|
|
1839
|
+
if (isPostHogFetchContentTooLargeError(err)) {
|
|
1840
|
+
return false;
|
|
1841
|
+
}
|
|
1842
|
+
// otherwise, retry on network errors
|
|
1843
|
+
return isPostHogFetchError(err);
|
|
1844
|
+
},
|
|
1716
1845
|
};
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
}
|
|
1720
|
-
catch (err) {
|
|
1721
|
-
// depending on the error type, eg a malformed JSON or broken queue, it'll always return an error
|
|
1722
|
-
// 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
|
|
1723
|
-
if (!(err instanceof PostHogFetchNetworkError)) {
|
|
1724
|
-
persistQueueChange();
|
|
1846
|
+
try {
|
|
1847
|
+
await this.fetchWithRetry(url, fetchOptions, retryOptions);
|
|
1725
1848
|
}
|
|
1726
|
-
|
|
1727
|
-
|
|
1849
|
+
catch (err) {
|
|
1850
|
+
if (isPostHogFetchContentTooLargeError(err) && batchMessages.length > 1) {
|
|
1851
|
+
// if we get a 413 error, we want to reduce the batch size and try again
|
|
1852
|
+
this.maxBatchSize = Math.max(1, Math.floor(batchMessages.length / 2));
|
|
1853
|
+
this.logMsgIfDebug(() => console.warn(`Received 413 when sending batch of size ${batchMessages.length}, reducing batch size to ${this.maxBatchSize}`));
|
|
1854
|
+
// do not persist the queue change, we want to retry the same batch
|
|
1855
|
+
continue;
|
|
1856
|
+
}
|
|
1857
|
+
// depending on the error type, eg a malformed JSON or broken queue, it'll always return an error
|
|
1858
|
+
// 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
|
|
1859
|
+
if (!(err instanceof PostHogFetchNetworkError)) {
|
|
1860
|
+
persistQueueChange();
|
|
1861
|
+
}
|
|
1862
|
+
this._events.emit('error', err);
|
|
1863
|
+
throw err;
|
|
1864
|
+
}
|
|
1865
|
+
persistQueueChange();
|
|
1866
|
+
sentMessages.push(...batchMessages);
|
|
1728
1867
|
}
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
return messages;
|
|
1868
|
+
this._events.emit('flush', sentMessages);
|
|
1869
|
+
return sentMessages;
|
|
1732
1870
|
}
|
|
1733
1871
|
async fetchWithRetry(url, options, retryOptions, requestTimeout) {
|
|
1734
1872
|
var _a;
|
|
@@ -1737,6 +1875,8 @@ class PostHogCoreStateless {
|
|
|
1737
1875
|
setTimeout(() => ctrl.abort(), ms);
|
|
1738
1876
|
return ctrl.signal;
|
|
1739
1877
|
});
|
|
1878
|
+
const body = options.body ? options.body : '';
|
|
1879
|
+
const reqByteLength = Buffer.byteLength(body, STRING_FORMAT);
|
|
1740
1880
|
return await retriable(async () => {
|
|
1741
1881
|
let res = null;
|
|
1742
1882
|
try {
|
|
@@ -1754,12 +1894,12 @@ class PostHogCoreStateless {
|
|
|
1754
1894
|
// https://developer.mozilla.org/en-US/docs/Web/API/Request/mode#no-cors
|
|
1755
1895
|
const isNoCors = options.mode === 'no-cors';
|
|
1756
1896
|
if (!isNoCors && (res.status < 200 || res.status >= 400)) {
|
|
1757
|
-
throw new PostHogFetchHttpError(res);
|
|
1897
|
+
throw new PostHogFetchHttpError(res, reqByteLength);
|
|
1758
1898
|
}
|
|
1759
1899
|
return res;
|
|
1760
1900
|
}, { ...this._retryOptions, ...retryOptions });
|
|
1761
1901
|
}
|
|
1762
|
-
async
|
|
1902
|
+
async _shutdown(shutdownTimeoutMs = 30000) {
|
|
1763
1903
|
// A little tricky - we want to have a max shutdown time and enforce it, even if that means we have some
|
|
1764
1904
|
// dangling promises. We'll keep track of the timeout and resolve/reject based on that.
|
|
1765
1905
|
await this._initPromise;
|
|
@@ -1786,7 +1926,7 @@ class PostHogCoreStateless {
|
|
|
1786
1926
|
if (!isPostHogFetchError(e)) {
|
|
1787
1927
|
throw e;
|
|
1788
1928
|
}
|
|
1789
|
-
|
|
1929
|
+
await logFlushError(e);
|
|
1790
1930
|
}
|
|
1791
1931
|
};
|
|
1792
1932
|
return Promise.race([
|
|
@@ -1800,6 +1940,22 @@ class PostHogCoreStateless {
|
|
|
1800
1940
|
doShutdown(),
|
|
1801
1941
|
]);
|
|
1802
1942
|
}
|
|
1943
|
+
/**
|
|
1944
|
+
* Call shutdown() once before the node process exits, so ensure that all events have been sent and all promises
|
|
1945
|
+
* have resolved. Do not use this function if you intend to keep using this PostHog instance after calling it.
|
|
1946
|
+
* @param shutdownTimeoutMs
|
|
1947
|
+
*/
|
|
1948
|
+
async shutdown(shutdownTimeoutMs = 30000) {
|
|
1949
|
+
if (this.shutdownPromise) {
|
|
1950
|
+
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'));
|
|
1951
|
+
}
|
|
1952
|
+
else {
|
|
1953
|
+
this.shutdownPromise = this._shutdown(shutdownTimeoutMs).finally(() => {
|
|
1954
|
+
this.shutdownPromise = null;
|
|
1955
|
+
});
|
|
1956
|
+
}
|
|
1957
|
+
return this.shutdownPromise;
|
|
1958
|
+
}
|
|
1803
1959
|
}
|
|
1804
1960
|
|
|
1805
1961
|
class PostHogMemoryStorage {
|
|
@@ -3667,6 +3823,79 @@ class PostHog extends PostHogCoreStateless {
|
|
|
3667
3823
|
});
|
|
3668
3824
|
this.addPendingPromise(capturePromise);
|
|
3669
3825
|
}
|
|
3826
|
+
async captureImmediate(props) {
|
|
3827
|
+
if (typeof props === 'string') {
|
|
3828
|
+
this.logMsgIfDebug(() => console.warn('Called capture() with a string as the first argument when an object was expected.'));
|
|
3829
|
+
}
|
|
3830
|
+
const {
|
|
3831
|
+
distinctId,
|
|
3832
|
+
event,
|
|
3833
|
+
properties,
|
|
3834
|
+
groups,
|
|
3835
|
+
sendFeatureFlags,
|
|
3836
|
+
timestamp,
|
|
3837
|
+
disableGeoip,
|
|
3838
|
+
uuid
|
|
3839
|
+
} = props;
|
|
3840
|
+
const _capture = props => {
|
|
3841
|
+
return super.captureStatelessImmediate(distinctId, event, props, {
|
|
3842
|
+
timestamp,
|
|
3843
|
+
disableGeoip,
|
|
3844
|
+
uuid
|
|
3845
|
+
});
|
|
3846
|
+
};
|
|
3847
|
+
const _getFlags = async (distinctId, groups, disableGeoip) => {
|
|
3848
|
+
return (await super.getFeatureFlagsStateless(distinctId, groups, undefined, undefined, disableGeoip)).flags;
|
|
3849
|
+
};
|
|
3850
|
+
const capturePromise = Promise.resolve().then(async () => {
|
|
3851
|
+
if (sendFeatureFlags) {
|
|
3852
|
+
// If we are sending feature flags, we need to make sure we have the latest flags
|
|
3853
|
+
// return await super.getFeatureFlagsStateless(distinctId, groups, undefined, undefined, disableGeoip)
|
|
3854
|
+
return await _getFlags(distinctId, groups, disableGeoip);
|
|
3855
|
+
}
|
|
3856
|
+
if (event === '$feature_flag_called') {
|
|
3857
|
+
// 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.
|
|
3858
|
+
return {};
|
|
3859
|
+
}
|
|
3860
|
+
if ((this.featureFlagsPoller?.featureFlags?.length || 0) > 0) {
|
|
3861
|
+
// Otherwise we may as well check for the flags locally and include them if they are already loaded
|
|
3862
|
+
const groupsWithStringValues = {};
|
|
3863
|
+
for (const [key, value] of Object.entries(groups || {})) {
|
|
3864
|
+
groupsWithStringValues[key] = String(value);
|
|
3865
|
+
}
|
|
3866
|
+
return await this.getAllFlags(distinctId, {
|
|
3867
|
+
groups: groupsWithStringValues,
|
|
3868
|
+
disableGeoip,
|
|
3869
|
+
onlyEvaluateLocally: true
|
|
3870
|
+
});
|
|
3871
|
+
}
|
|
3872
|
+
return {};
|
|
3873
|
+
}).then(flags => {
|
|
3874
|
+
// Derive the relevant flag properties to add
|
|
3875
|
+
const additionalProperties = {};
|
|
3876
|
+
if (flags) {
|
|
3877
|
+
for (const [feature, variant] of Object.entries(flags)) {
|
|
3878
|
+
additionalProperties[`$feature/${feature}`] = variant;
|
|
3879
|
+
}
|
|
3880
|
+
}
|
|
3881
|
+
const activeFlags = Object.keys(flags || {}).filter(flag => flags?.[flag] !== false).sort();
|
|
3882
|
+
if (activeFlags.length > 0) {
|
|
3883
|
+
additionalProperties['$active_feature_flags'] = activeFlags;
|
|
3884
|
+
}
|
|
3885
|
+
return additionalProperties;
|
|
3886
|
+
}).catch(() => {
|
|
3887
|
+
// Something went wrong getting the flag info - we should capture the event anyways
|
|
3888
|
+
return {};
|
|
3889
|
+
}).then(additionalProperties => {
|
|
3890
|
+
// No matter what - capture the event
|
|
3891
|
+
_capture({
|
|
3892
|
+
...additionalProperties,
|
|
3893
|
+
...properties,
|
|
3894
|
+
$groups: groups
|
|
3895
|
+
});
|
|
3896
|
+
});
|
|
3897
|
+
await capturePromise;
|
|
3898
|
+
}
|
|
3670
3899
|
identify({
|
|
3671
3900
|
distinctId,
|
|
3672
3901
|
properties,
|
|
@@ -3685,11 +3914,33 @@ class PostHog extends PostHogCoreStateless {
|
|
|
3685
3914
|
disableGeoip
|
|
3686
3915
|
});
|
|
3687
3916
|
}
|
|
3917
|
+
async identifyImmediate({
|
|
3918
|
+
distinctId,
|
|
3919
|
+
properties,
|
|
3920
|
+
disableGeoip
|
|
3921
|
+
}) {
|
|
3922
|
+
// promote $set and $set_once to top level
|
|
3923
|
+
const userPropsOnce = properties?.$set_once;
|
|
3924
|
+
delete properties?.$set_once;
|
|
3925
|
+
// if no $set is provided we assume all properties are $set
|
|
3926
|
+
const userProps = properties?.$set || properties;
|
|
3927
|
+
await super.identifyStatelessImmediate(distinctId, {
|
|
3928
|
+
$set: userProps,
|
|
3929
|
+
$set_once: userPropsOnce
|
|
3930
|
+
}, {
|
|
3931
|
+
disableGeoip
|
|
3932
|
+
});
|
|
3933
|
+
}
|
|
3688
3934
|
alias(data) {
|
|
3689
3935
|
super.aliasStateless(data.alias, data.distinctId, undefined, {
|
|
3690
3936
|
disableGeoip: data.disableGeoip
|
|
3691
3937
|
});
|
|
3692
3938
|
}
|
|
3939
|
+
async aliasImmediate(data) {
|
|
3940
|
+
await super.aliasStatelessImmediate(data.alias, data.distinctId, undefined, {
|
|
3941
|
+
disableGeoip: data.disableGeoip
|
|
3942
|
+
});
|
|
3943
|
+
}
|
|
3693
3944
|
isLocalEvaluationReady() {
|
|
3694
3945
|
return this.featureFlagsPoller?.isLocalEvaluationReady() ?? false;
|
|
3695
3946
|
}
|
|
@@ -3896,9 +4147,9 @@ class PostHog extends PostHogCoreStateless {
|
|
|
3896
4147
|
async reloadFeatureFlags() {
|
|
3897
4148
|
await this.featureFlagsPoller?.loadFeatureFlags(true);
|
|
3898
4149
|
}
|
|
3899
|
-
async
|
|
4150
|
+
async _shutdown(shutdownTimeoutMs) {
|
|
3900
4151
|
this.featureFlagsPoller?.stopPoller();
|
|
3901
|
-
return super.
|
|
4152
|
+
return super._shutdown(shutdownTimeoutMs);
|
|
3902
4153
|
}
|
|
3903
4154
|
addLocalPersonAndGroupProperties(distinctId, groups, personProperties, groupProperties) {
|
|
3904
4155
|
const allPersonProperties = {
|