posthog-node 4.13.0 → 4.15.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 +249 -26
- package/lib/index.cjs.js.map +1 -1
- package/lib/index.d.ts +52 -0
- package/lib/index.esm.js +249 -26
- package/lib/index.esm.js.map +1 -1
- package/lib/posthog-core/src/index.d.ts +20 -7
- package/lib/posthog-core/src/utils.d.ts +1 -0
- package/lib/posthog-node/src/posthog-node.d.ts +7 -0
- package/lib/posthog-node/src/types.d.ts +26 -0
- package/package.json +1 -1
- package/src/posthog-node.ts +100 -0
- package/src/types.ts +26 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
# 4.15.0 - 2025-04-30
|
|
2
|
+
|
|
3
|
+
1. chore: add immediate-mode
|
|
4
|
+
2. chore: better error logging when flushing events
|
|
5
|
+
|
|
6
|
+
# 4.14.0 - 2025-04-24
|
|
7
|
+
|
|
8
|
+
1. feat: Add super properties as a concept to the Node SDK
|
|
9
|
+
|
|
1
10
|
# 4.13.0 - 2025-04-21
|
|
2
11
|
|
|
3
12
|
1. feat: Add method to wait for local evaluation feature flag definitions to be loaded
|
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.
|
|
25
|
+
var version = "4.15.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);
|
|
@@ -327,7 +328,7 @@ function safeSetTimeout(fn, timeout) {
|
|
|
327
328
|
return t;
|
|
328
329
|
}
|
|
329
330
|
function getFetch() {
|
|
330
|
-
return typeof fetch !== 'undefined' ? fetch : typeof
|
|
331
|
+
return typeof fetch !== 'undefined' ? fetch : typeof globalThis.fetch !== 'undefined' ? globalThis.fetch : undefined;
|
|
331
332
|
}
|
|
332
333
|
// FNV-1a hash function
|
|
333
334
|
// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
|
|
@@ -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,6 +1243,20 @@ 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
|
}
|
|
@@ -1365,12 +1390,26 @@ class PostHogCoreStateless {
|
|
|
1365
1390
|
this.enqueue('identify', payload, options);
|
|
1366
1391
|
});
|
|
1367
1392
|
}
|
|
1393
|
+
async identifyStatelessImmediate(distinctId, properties, options) {
|
|
1394
|
+
const payload = {
|
|
1395
|
+
...this.buildPayload({
|
|
1396
|
+
distinct_id: distinctId,
|
|
1397
|
+
event: '$identify',
|
|
1398
|
+
properties,
|
|
1399
|
+
}),
|
|
1400
|
+
};
|
|
1401
|
+
await this.sendImmediate('identify', payload, options);
|
|
1402
|
+
}
|
|
1368
1403
|
captureStateless(distinctId, event, properties, options) {
|
|
1369
1404
|
this.wrap(() => {
|
|
1370
1405
|
const payload = this.buildPayload({ distinct_id: distinctId, event, properties });
|
|
1371
1406
|
this.enqueue('capture', payload, options);
|
|
1372
1407
|
});
|
|
1373
1408
|
}
|
|
1409
|
+
async captureStatelessImmediate(distinctId, event, properties, options) {
|
|
1410
|
+
const payload = this.buildPayload({ distinct_id: distinctId, event, properties });
|
|
1411
|
+
await this.sendImmediate('capture', payload, options);
|
|
1412
|
+
}
|
|
1374
1413
|
aliasStateless(alias, distinctId, properties, options) {
|
|
1375
1414
|
this.wrap(() => {
|
|
1376
1415
|
const payload = this.buildPayload({
|
|
@@ -1385,6 +1424,18 @@ class PostHogCoreStateless {
|
|
|
1385
1424
|
this.enqueue('alias', payload, options);
|
|
1386
1425
|
});
|
|
1387
1426
|
}
|
|
1427
|
+
async aliasStatelessImmediate(alias, distinctId, properties, options) {
|
|
1428
|
+
const payload = this.buildPayload({
|
|
1429
|
+
event: '$create_alias',
|
|
1430
|
+
distinct_id: distinctId,
|
|
1431
|
+
properties: {
|
|
1432
|
+
...(properties || {}),
|
|
1433
|
+
distinct_id: distinctId,
|
|
1434
|
+
alias,
|
|
1435
|
+
},
|
|
1436
|
+
});
|
|
1437
|
+
await this.sendImmediate('alias', payload, options);
|
|
1438
|
+
}
|
|
1388
1439
|
/***
|
|
1389
1440
|
*** GROUPS
|
|
1390
1441
|
***/
|
|
@@ -1595,6 +1646,30 @@ class PostHogCoreStateless {
|
|
|
1595
1646
|
}
|
|
1596
1647
|
return newSurveys ?? [];
|
|
1597
1648
|
}
|
|
1649
|
+
get props() {
|
|
1650
|
+
if (!this._props) {
|
|
1651
|
+
this._props = this.getPersistedProperty(PostHogPersistedProperty.Props);
|
|
1652
|
+
}
|
|
1653
|
+
return this._props || {};
|
|
1654
|
+
}
|
|
1655
|
+
set props(val) {
|
|
1656
|
+
this._props = val;
|
|
1657
|
+
}
|
|
1658
|
+
async register(properties) {
|
|
1659
|
+
this.wrap(() => {
|
|
1660
|
+
this.props = {
|
|
1661
|
+
...this.props,
|
|
1662
|
+
...properties,
|
|
1663
|
+
};
|
|
1664
|
+
this.setPersistedProperty(PostHogPersistedProperty.Props, this.props);
|
|
1665
|
+
});
|
|
1666
|
+
}
|
|
1667
|
+
async unregister(property) {
|
|
1668
|
+
this.wrap(() => {
|
|
1669
|
+
delete this.props[property];
|
|
1670
|
+
this.setPersistedProperty(PostHogPersistedProperty.Props, this.props);
|
|
1671
|
+
});
|
|
1672
|
+
}
|
|
1598
1673
|
/***
|
|
1599
1674
|
*** QUEUEING AND FLUSHING
|
|
1600
1675
|
***/
|
|
@@ -1604,25 +1679,7 @@ class PostHogCoreStateless {
|
|
|
1604
1679
|
this._events.emit(type, `Library is disabled. Not sending event. To re-enable, call posthog.optIn()`);
|
|
1605
1680
|
return;
|
|
1606
1681
|
}
|
|
1607
|
-
const message =
|
|
1608
|
-
..._message,
|
|
1609
|
-
type: type,
|
|
1610
|
-
library: this.getLibraryId(),
|
|
1611
|
-
library_version: this.getLibraryVersion(),
|
|
1612
|
-
timestamp: options?.timestamp ? options?.timestamp : currentISOTime(),
|
|
1613
|
-
uuid: options?.uuid ? options.uuid : uuidv7(),
|
|
1614
|
-
};
|
|
1615
|
-
const addGeoipDisableProperty = options?.disableGeoip ?? this.disableGeoip;
|
|
1616
|
-
if (addGeoipDisableProperty) {
|
|
1617
|
-
if (!message.properties) {
|
|
1618
|
-
message.properties = {};
|
|
1619
|
-
}
|
|
1620
|
-
message['properties']['$geoip_disable'] = true;
|
|
1621
|
-
}
|
|
1622
|
-
if (message.distinctId) {
|
|
1623
|
-
message.distinct_id = message.distinctId;
|
|
1624
|
-
delete message.distinctId;
|
|
1625
|
-
}
|
|
1682
|
+
const message = this.prepareMessage(type, _message, options);
|
|
1626
1683
|
const queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
|
|
1627
1684
|
if (queue.length >= this.maxQueueSize) {
|
|
1628
1685
|
queue.shift();
|
|
@@ -1640,6 +1697,73 @@ class PostHogCoreStateless {
|
|
|
1640
1697
|
}
|
|
1641
1698
|
});
|
|
1642
1699
|
}
|
|
1700
|
+
async sendImmediate(type, _message, options) {
|
|
1701
|
+
if (this.disabled) {
|
|
1702
|
+
this.logMsgIfDebug(() => console.warn('[PostHog] The client is disabled'));
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
if (!this._isInitialized) {
|
|
1706
|
+
await this._initPromise;
|
|
1707
|
+
}
|
|
1708
|
+
if (this.optedOut) {
|
|
1709
|
+
this._events.emit(type, `Library is disabled. Not sending event. To re-enable, call posthog.optIn()`);
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
const data = {
|
|
1713
|
+
api_key: this.apiKey,
|
|
1714
|
+
batch: [this.prepareMessage(type, _message, options)],
|
|
1715
|
+
sent_at: currentISOTime(),
|
|
1716
|
+
};
|
|
1717
|
+
if (this.historicalMigration) {
|
|
1718
|
+
data.historical_migration = true;
|
|
1719
|
+
}
|
|
1720
|
+
const payload = JSON.stringify(data);
|
|
1721
|
+
const url = this.captureMode === 'form'
|
|
1722
|
+
? `${this.host}/e/?ip=1&_=${currentTimestamp()}&v=${this.getLibraryVersion()}`
|
|
1723
|
+
: `${this.host}/batch/`;
|
|
1724
|
+
const fetchOptions = this.captureMode === 'form'
|
|
1725
|
+
? {
|
|
1726
|
+
method: 'POST',
|
|
1727
|
+
mode: 'no-cors',
|
|
1728
|
+
credentials: 'omit',
|
|
1729
|
+
headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
1730
|
+
body: `data=${encodeURIComponent(LZString.compressToBase64(payload))}&compression=lz64`,
|
|
1731
|
+
}
|
|
1732
|
+
: {
|
|
1733
|
+
method: 'POST',
|
|
1734
|
+
headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
|
|
1735
|
+
body: payload,
|
|
1736
|
+
};
|
|
1737
|
+
try {
|
|
1738
|
+
await this.fetchWithRetry(url, fetchOptions);
|
|
1739
|
+
}
|
|
1740
|
+
catch (err) {
|
|
1741
|
+
this._events.emit('error', err);
|
|
1742
|
+
throw err;
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
prepareMessage(type, _message, options) {
|
|
1746
|
+
const message = {
|
|
1747
|
+
..._message,
|
|
1748
|
+
type: type,
|
|
1749
|
+
library: this.getLibraryId(),
|
|
1750
|
+
library_version: this.getLibraryVersion(),
|
|
1751
|
+
timestamp: options?.timestamp ? options?.timestamp : currentISOTime(),
|
|
1752
|
+
uuid: options?.uuid ? options.uuid : uuidv7(),
|
|
1753
|
+
};
|
|
1754
|
+
const addGeoipDisableProperty = options?.disableGeoip ?? this.disableGeoip;
|
|
1755
|
+
if (addGeoipDisableProperty) {
|
|
1756
|
+
if (!message.properties) {
|
|
1757
|
+
message.properties = {};
|
|
1758
|
+
}
|
|
1759
|
+
message['properties']['$geoip_disable'] = true;
|
|
1760
|
+
}
|
|
1761
|
+
if (message.distinctId) {
|
|
1762
|
+
message.distinct_id = message.distinctId;
|
|
1763
|
+
delete message.distinctId;
|
|
1764
|
+
}
|
|
1765
|
+
return message;
|
|
1766
|
+
}
|
|
1643
1767
|
clearFlushTimer() {
|
|
1644
1768
|
if (this._flushTimer) {
|
|
1645
1769
|
clearTimeout(this._flushTimer);
|
|
@@ -1651,7 +1775,9 @@ class PostHogCoreStateless {
|
|
|
1651
1775
|
* Avoids unnecessary promise errors
|
|
1652
1776
|
*/
|
|
1653
1777
|
flushBackground() {
|
|
1654
|
-
void this.flush().catch(() => {
|
|
1778
|
+
void this.flush().catch(async (err) => {
|
|
1779
|
+
await logFlushError(err);
|
|
1780
|
+
});
|
|
1655
1781
|
}
|
|
1656
1782
|
async flush() {
|
|
1657
1783
|
if (!this.flushPromise) {
|
|
@@ -1735,6 +1861,8 @@ class PostHogCoreStateless {
|
|
|
1735
1861
|
setTimeout(() => ctrl.abort(), ms);
|
|
1736
1862
|
return ctrl.signal;
|
|
1737
1863
|
});
|
|
1864
|
+
const body = options.body ? options.body : '';
|
|
1865
|
+
const reqByteLength = Buffer.byteLength(body, STRING_FORMAT);
|
|
1738
1866
|
return await retriable(async () => {
|
|
1739
1867
|
let res = null;
|
|
1740
1868
|
try {
|
|
@@ -1752,7 +1880,7 @@ class PostHogCoreStateless {
|
|
|
1752
1880
|
// https://developer.mozilla.org/en-US/docs/Web/API/Request/mode#no-cors
|
|
1753
1881
|
const isNoCors = options.mode === 'no-cors';
|
|
1754
1882
|
if (!isNoCors && (res.status < 200 || res.status >= 400)) {
|
|
1755
|
-
throw new PostHogFetchHttpError(res);
|
|
1883
|
+
throw new PostHogFetchHttpError(res, reqByteLength);
|
|
1756
1884
|
}
|
|
1757
1885
|
return res;
|
|
1758
1886
|
}, { ...this._retryOptions, ...retryOptions });
|
|
@@ -1784,7 +1912,7 @@ class PostHogCoreStateless {
|
|
|
1784
1912
|
if (!isPostHogFetchError(e)) {
|
|
1785
1913
|
throw e;
|
|
1786
1914
|
}
|
|
1787
|
-
|
|
1915
|
+
await logFlushError(e);
|
|
1788
1916
|
}
|
|
1789
1917
|
};
|
|
1790
1918
|
return Promise.race([
|
|
@@ -3665,6 +3793,79 @@ class PostHog extends PostHogCoreStateless {
|
|
|
3665
3793
|
});
|
|
3666
3794
|
this.addPendingPromise(capturePromise);
|
|
3667
3795
|
}
|
|
3796
|
+
async captureImmediate(props) {
|
|
3797
|
+
if (typeof props === 'string') {
|
|
3798
|
+
this.logMsgIfDebug(() => console.warn('Called capture() with a string as the first argument when an object was expected.'));
|
|
3799
|
+
}
|
|
3800
|
+
const {
|
|
3801
|
+
distinctId,
|
|
3802
|
+
event,
|
|
3803
|
+
properties,
|
|
3804
|
+
groups,
|
|
3805
|
+
sendFeatureFlags,
|
|
3806
|
+
timestamp,
|
|
3807
|
+
disableGeoip,
|
|
3808
|
+
uuid
|
|
3809
|
+
} = props;
|
|
3810
|
+
const _capture = props => {
|
|
3811
|
+
return super.captureStatelessImmediate(distinctId, event, props, {
|
|
3812
|
+
timestamp,
|
|
3813
|
+
disableGeoip,
|
|
3814
|
+
uuid
|
|
3815
|
+
});
|
|
3816
|
+
};
|
|
3817
|
+
const _getFlags = async (distinctId, groups, disableGeoip) => {
|
|
3818
|
+
return (await super.getFeatureFlagsStateless(distinctId, groups, undefined, undefined, disableGeoip)).flags;
|
|
3819
|
+
};
|
|
3820
|
+
const capturePromise = Promise.resolve().then(async () => {
|
|
3821
|
+
if (sendFeatureFlags) {
|
|
3822
|
+
// If we are sending feature flags, we need to make sure we have the latest flags
|
|
3823
|
+
// return await super.getFeatureFlagsStateless(distinctId, groups, undefined, undefined, disableGeoip)
|
|
3824
|
+
return await _getFlags(distinctId, groups, disableGeoip);
|
|
3825
|
+
}
|
|
3826
|
+
if (event === '$feature_flag_called') {
|
|
3827
|
+
// 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.
|
|
3828
|
+
return {};
|
|
3829
|
+
}
|
|
3830
|
+
if ((this.featureFlagsPoller?.featureFlags?.length || 0) > 0) {
|
|
3831
|
+
// Otherwise we may as well check for the flags locally and include them if they are already loaded
|
|
3832
|
+
const groupsWithStringValues = {};
|
|
3833
|
+
for (const [key, value] of Object.entries(groups || {})) {
|
|
3834
|
+
groupsWithStringValues[key] = String(value);
|
|
3835
|
+
}
|
|
3836
|
+
return await this.getAllFlags(distinctId, {
|
|
3837
|
+
groups: groupsWithStringValues,
|
|
3838
|
+
disableGeoip,
|
|
3839
|
+
onlyEvaluateLocally: true
|
|
3840
|
+
});
|
|
3841
|
+
}
|
|
3842
|
+
return {};
|
|
3843
|
+
}).then(flags => {
|
|
3844
|
+
// Derive the relevant flag properties to add
|
|
3845
|
+
const additionalProperties = {};
|
|
3846
|
+
if (flags) {
|
|
3847
|
+
for (const [feature, variant] of Object.entries(flags)) {
|
|
3848
|
+
additionalProperties[`$feature/${feature}`] = variant;
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
const activeFlags = Object.keys(flags || {}).filter(flag => flags?.[flag] !== false).sort();
|
|
3852
|
+
if (activeFlags.length > 0) {
|
|
3853
|
+
additionalProperties['$active_feature_flags'] = activeFlags;
|
|
3854
|
+
}
|
|
3855
|
+
return additionalProperties;
|
|
3856
|
+
}).catch(() => {
|
|
3857
|
+
// Something went wrong getting the flag info - we should capture the event anyways
|
|
3858
|
+
return {};
|
|
3859
|
+
}).then(additionalProperties => {
|
|
3860
|
+
// No matter what - capture the event
|
|
3861
|
+
_capture({
|
|
3862
|
+
...additionalProperties,
|
|
3863
|
+
...properties,
|
|
3864
|
+
$groups: groups
|
|
3865
|
+
});
|
|
3866
|
+
});
|
|
3867
|
+
await capturePromise;
|
|
3868
|
+
}
|
|
3668
3869
|
identify({
|
|
3669
3870
|
distinctId,
|
|
3670
3871
|
properties,
|
|
@@ -3683,11 +3884,33 @@ class PostHog extends PostHogCoreStateless {
|
|
|
3683
3884
|
disableGeoip
|
|
3684
3885
|
});
|
|
3685
3886
|
}
|
|
3887
|
+
async identifyImmediate({
|
|
3888
|
+
distinctId,
|
|
3889
|
+
properties,
|
|
3890
|
+
disableGeoip
|
|
3891
|
+
}) {
|
|
3892
|
+
// promote $set and $set_once to top level
|
|
3893
|
+
const userPropsOnce = properties?.$set_once;
|
|
3894
|
+
delete properties?.$set_once;
|
|
3895
|
+
// if no $set is provided we assume all properties are $set
|
|
3896
|
+
const userProps = properties?.$set || properties;
|
|
3897
|
+
await super.identifyStatelessImmediate(distinctId, {
|
|
3898
|
+
$set: userProps,
|
|
3899
|
+
$set_once: userPropsOnce
|
|
3900
|
+
}, {
|
|
3901
|
+
disableGeoip
|
|
3902
|
+
});
|
|
3903
|
+
}
|
|
3686
3904
|
alias(data) {
|
|
3687
3905
|
super.aliasStateless(data.alias, data.distinctId, undefined, {
|
|
3688
3906
|
disableGeoip: data.disableGeoip
|
|
3689
3907
|
});
|
|
3690
3908
|
}
|
|
3909
|
+
async aliasImmediate(data) {
|
|
3910
|
+
await super.aliasStatelessImmediate(data.alias, data.distinctId, undefined, {
|
|
3911
|
+
disableGeoip: data.disableGeoip
|
|
3912
|
+
});
|
|
3913
|
+
}
|
|
3691
3914
|
isLocalEvaluationReady() {
|
|
3692
3915
|
return this.featureFlagsPoller?.isLocalEvaluationReady() ?? false;
|
|
3693
3916
|
}
|