posthog-node 5.8.0 → 5.8.2
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/dist/edge/index.cjs +834 -170
- package/dist/edge/index.cjs.map +1 -1
- package/dist/edge/index.mjs +835 -171
- package/dist/edge/index.mjs.map +1 -1
- package/dist/index.d.ts +701 -4
- package/dist/node/index.cjs +834 -170
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.mjs +835 -171
- package/dist/node/index.mjs.map +1 -1
- package/package.json +5 -3
package/dist/edge/index.cjs
CHANGED
|
@@ -315,7 +315,7 @@ function makeUncaughtExceptionHandler(captureFn, onFatalFn) {
|
|
|
315
315
|
});
|
|
316
316
|
if (!calledFatalError && processWouldExit) {
|
|
317
317
|
calledFatalError = true;
|
|
318
|
-
onFatalFn();
|
|
318
|
+
onFatalFn(error);
|
|
319
319
|
}
|
|
320
320
|
}, {
|
|
321
321
|
_posthogErrorHandler: true
|
|
@@ -326,7 +326,7 @@ function addUncaughtExceptionListener(captureFn, onFatalFn) {
|
|
|
326
326
|
}
|
|
327
327
|
function addUnhandledRejectionListener(captureFn) {
|
|
328
328
|
global.process.on('unhandledRejection', reason => {
|
|
329
|
-
captureFn(reason, {
|
|
329
|
+
return captureFn(reason, {
|
|
330
330
|
mechanism: {
|
|
331
331
|
type: 'onunhandledrejection',
|
|
332
332
|
handled: false
|
|
@@ -343,8 +343,7 @@ let cachedFilenameChunkIds;
|
|
|
343
343
|
function getFilenameToChunkIdMap(stackParser) {
|
|
344
344
|
const chunkIdMap = globalThis._posthogChunkIds;
|
|
345
345
|
if (!chunkIdMap) {
|
|
346
|
-
|
|
347
|
-
return {};
|
|
346
|
+
return null;
|
|
348
347
|
}
|
|
349
348
|
const chunkIdKeys = Object.keys(chunkIdMap);
|
|
350
349
|
if (cachedFilenameChunkIds && chunkIdKeys.length === lastKeysCount) {
|
|
@@ -615,7 +614,7 @@ function parseStackFrames(stackParser, error) {
|
|
|
615
614
|
function applyChunkIds(frames, parser) {
|
|
616
615
|
const filenameChunkIdMap = getFilenameToChunkIdMap(parser);
|
|
617
616
|
frames.forEach(frame => {
|
|
618
|
-
if (frame.filename) {
|
|
617
|
+
if (frame.filename && filenameChunkIdMap) {
|
|
619
618
|
frame.chunk_id = filenameChunkIdMap[frame.filename];
|
|
620
619
|
}
|
|
621
620
|
});
|
|
@@ -644,6 +643,12 @@ function clampToRange(value, min, max, logger, fallbackValue) {
|
|
|
644
643
|
}
|
|
645
644
|
|
|
646
645
|
class BucketedRateLimiter {
|
|
646
|
+
stop() {
|
|
647
|
+
if (this._removeInterval) {
|
|
648
|
+
clearInterval(this._removeInterval);
|
|
649
|
+
this._removeInterval = void 0;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
647
652
|
constructor(_options){
|
|
648
653
|
this._options = _options;
|
|
649
654
|
this._buckets = {};
|
|
@@ -675,7 +680,7 @@ class BucketedRateLimiter {
|
|
|
675
680
|
this._bucketSize = clampToRange(this._options.bucketSize, 0, 100, this._options._logger);
|
|
676
681
|
this._refillRate = clampToRange(this._options.refillRate, 0, this._bucketSize, this._options._logger);
|
|
677
682
|
this._refillInterval = clampToRange(this._options.refillInterval, 0, 86400000, this._options._logger);
|
|
678
|
-
setInterval(()=>{
|
|
683
|
+
this._removeInterval = setInterval(()=>{
|
|
679
684
|
this._refillBuckets();
|
|
680
685
|
}, this._refillInterval);
|
|
681
686
|
}
|
|
@@ -689,6 +694,22 @@ function safeSetTimeout(fn, timeout) {
|
|
|
689
694
|
|
|
690
695
|
const SHUTDOWN_TIMEOUT = 2000;
|
|
691
696
|
class ErrorTracking {
|
|
697
|
+
constructor(client, options, _logger) {
|
|
698
|
+
this.client = client;
|
|
699
|
+
this._exceptionAutocaptureEnabled = options.enableExceptionAutocapture || false;
|
|
700
|
+
this._logger = _logger;
|
|
701
|
+
// by default captures ten exceptions before rate limiting by exception type
|
|
702
|
+
// refills at a rate of one token / 10 second period
|
|
703
|
+
// e.g. will capture 1 exception rate limited exception every 10 seconds until burst ends
|
|
704
|
+
this._rateLimiter = new BucketedRateLimiter({
|
|
705
|
+
refillRate: 1,
|
|
706
|
+
bucketSize: 10,
|
|
707
|
+
refillInterval: 10000,
|
|
708
|
+
// ten seconds in milliseconds
|
|
709
|
+
_logger: this._logger
|
|
710
|
+
});
|
|
711
|
+
this.startAutocaptureIfEnabled();
|
|
712
|
+
}
|
|
692
713
|
static async buildEventMessage(error, hint, distinctId, additionalProperties) {
|
|
693
714
|
const properties = {
|
|
694
715
|
...additionalProperties
|
|
@@ -708,31 +729,16 @@ class ErrorTracking {
|
|
|
708
729
|
}
|
|
709
730
|
};
|
|
710
731
|
}
|
|
711
|
-
constructor(client, options, _logger) {
|
|
712
|
-
this.client = client;
|
|
713
|
-
this._exceptionAutocaptureEnabled = options.enableExceptionAutocapture || false;
|
|
714
|
-
this._logger = _logger;
|
|
715
|
-
// by default captures ten exceptions before rate limiting by exception type
|
|
716
|
-
// refills at a rate of one token / 10 second period
|
|
717
|
-
// e.g. will capture 1 exception rate limited exception every 10 seconds until burst ends
|
|
718
|
-
this._rateLimiter = new BucketedRateLimiter({
|
|
719
|
-
refillRate: 1,
|
|
720
|
-
bucketSize: 10,
|
|
721
|
-
refillInterval: 10000,
|
|
722
|
-
// ten seconds in milliseconds
|
|
723
|
-
_logger: this._logger
|
|
724
|
-
});
|
|
725
|
-
this.startAutocaptureIfEnabled();
|
|
726
|
-
}
|
|
727
732
|
startAutocaptureIfEnabled() {
|
|
728
733
|
if (this.isEnabled()) {
|
|
729
734
|
addUncaughtExceptionListener(this.onException.bind(this), this.onFatalError.bind(this));
|
|
730
735
|
addUnhandledRejectionListener(this.onException.bind(this));
|
|
731
736
|
}
|
|
732
737
|
}
|
|
733
|
-
onException(exception, hint) {
|
|
734
|
-
|
|
735
|
-
const
|
|
738
|
+
async onException(exception, hint) {
|
|
739
|
+
this.client.addPendingPromise((async () => {
|
|
740
|
+
const eventMessage = await ErrorTracking.buildEventMessage(exception, hint);
|
|
741
|
+
const exceptionProperties = eventMessage.properties;
|
|
736
742
|
const exceptionType = exceptionProperties?.$exception_list[0].type ?? 'Exception';
|
|
737
743
|
const isRateLimited = this._rateLimiter.consumeRateLimit(exceptionType);
|
|
738
744
|
if (isRateLimited) {
|
|
@@ -741,15 +747,20 @@ class ErrorTracking {
|
|
|
741
747
|
});
|
|
742
748
|
return;
|
|
743
749
|
}
|
|
744
|
-
this.client.capture(
|
|
745
|
-
});
|
|
750
|
+
return this.client.capture(eventMessage);
|
|
751
|
+
})());
|
|
746
752
|
}
|
|
747
|
-
async onFatalError() {
|
|
753
|
+
async onFatalError(exception) {
|
|
754
|
+
console.error(exception);
|
|
748
755
|
await this.client.shutdown(SHUTDOWN_TIMEOUT);
|
|
756
|
+
process.exit(1);
|
|
749
757
|
}
|
|
750
758
|
isEnabled() {
|
|
751
759
|
return !this.client.isDisabled && this._exceptionAutocaptureEnabled;
|
|
752
760
|
}
|
|
761
|
+
shutdown() {
|
|
762
|
+
this._rateLimiter.stop();
|
|
763
|
+
}
|
|
753
764
|
}
|
|
754
765
|
|
|
755
766
|
function setupExpressErrorHandler(_posthog, app) {
|
|
@@ -769,7 +780,7 @@ function setupExpressErrorHandler(_posthog, app) {
|
|
|
769
780
|
});
|
|
770
781
|
}
|
|
771
782
|
|
|
772
|
-
var version = "5.8.
|
|
783
|
+
var version = "5.8.2";
|
|
773
784
|
|
|
774
785
|
/**
|
|
775
786
|
* A lazy value that is only computed when needed. Inspired by C#'s Lazy<T> class.
|
|
@@ -1670,6 +1681,35 @@ const THIRTY_SECONDS = 30 * 1000;
|
|
|
1670
1681
|
const MAX_CACHE_SIZE = 50 * 1000;
|
|
1671
1682
|
// The actual exported Nodejs API.
|
|
1672
1683
|
class PostHogBackendClient extends core.PostHogCoreStateless {
|
|
1684
|
+
/**
|
|
1685
|
+
* Initialize a new PostHog client instance.
|
|
1686
|
+
*
|
|
1687
|
+
* @example
|
|
1688
|
+
* ```ts
|
|
1689
|
+
* // Basic initialization
|
|
1690
|
+
* const client = new PostHogBackendClient(
|
|
1691
|
+
* 'your-api-key',
|
|
1692
|
+
* { host: 'https://app.posthog.com' }
|
|
1693
|
+
* )
|
|
1694
|
+
* ```
|
|
1695
|
+
*
|
|
1696
|
+
* @example
|
|
1697
|
+
* ```ts
|
|
1698
|
+
* // With personal API key
|
|
1699
|
+
* const client = new PostHogBackendClient(
|
|
1700
|
+
* 'your-api-key',
|
|
1701
|
+
* {
|
|
1702
|
+
* host: 'https://app.posthog.com',
|
|
1703
|
+
* personalApiKey: 'your-personal-api-key'
|
|
1704
|
+
* }
|
|
1705
|
+
* )
|
|
1706
|
+
* ```
|
|
1707
|
+
*
|
|
1708
|
+
* {@label Initialization}
|
|
1709
|
+
*
|
|
1710
|
+
* @param apiKey - Your PostHog project API key
|
|
1711
|
+
* @param options - Configuration options for the client
|
|
1712
|
+
*/
|
|
1673
1713
|
constructor(apiKey, options = {}) {
|
|
1674
1714
|
super(apiKey, options);
|
|
1675
1715
|
this._memoryStorage = new PostHogMemoryStorage();
|
|
@@ -1705,176 +1745,302 @@ class PostHogBackendClient extends core.PostHogCoreStateless {
|
|
|
1705
1745
|
this.distinctIdHasSentFlagCalls = {};
|
|
1706
1746
|
this.maxCacheSize = options.maxCacheSize || MAX_CACHE_SIZE;
|
|
1707
1747
|
}
|
|
1748
|
+
/**
|
|
1749
|
+
* Get a persisted property value from memory storage.
|
|
1750
|
+
*
|
|
1751
|
+
* @example
|
|
1752
|
+
* ```ts
|
|
1753
|
+
* // Get user ID
|
|
1754
|
+
* const userId = client.getPersistedProperty('userId')
|
|
1755
|
+
* ```
|
|
1756
|
+
*
|
|
1757
|
+
* @example
|
|
1758
|
+
* ```ts
|
|
1759
|
+
* // Get session ID
|
|
1760
|
+
* const sessionId = client.getPersistedProperty('sessionId')
|
|
1761
|
+
* ```
|
|
1762
|
+
*
|
|
1763
|
+
* {@label Initialization}
|
|
1764
|
+
*
|
|
1765
|
+
* @param key - The property key to retrieve
|
|
1766
|
+
* @returns The stored property value or undefined if not found
|
|
1767
|
+
*/
|
|
1708
1768
|
getPersistedProperty(key) {
|
|
1709
1769
|
return this._memoryStorage.getProperty(key);
|
|
1710
1770
|
}
|
|
1771
|
+
/**
|
|
1772
|
+
* Set a persisted property value in memory storage.
|
|
1773
|
+
*
|
|
1774
|
+
* @example
|
|
1775
|
+
* ```ts
|
|
1776
|
+
* // Set user ID
|
|
1777
|
+
* client.setPersistedProperty('userId', 'user_123')
|
|
1778
|
+
* ```
|
|
1779
|
+
*
|
|
1780
|
+
* @example
|
|
1781
|
+
* ```ts
|
|
1782
|
+
* // Set session ID
|
|
1783
|
+
* client.setPersistedProperty('sessionId', 'session_456')
|
|
1784
|
+
* ```
|
|
1785
|
+
*
|
|
1786
|
+
* {@label Initialization}
|
|
1787
|
+
*
|
|
1788
|
+
* @param key - The property key to set
|
|
1789
|
+
* @param value - The value to store (null to remove)
|
|
1790
|
+
*/
|
|
1711
1791
|
setPersistedProperty(key, value) {
|
|
1712
1792
|
return this._memoryStorage.setProperty(key, value);
|
|
1713
1793
|
}
|
|
1794
|
+
/**
|
|
1795
|
+
* Make an HTTP request using the configured fetch function or default fetch.
|
|
1796
|
+
*
|
|
1797
|
+
* @example
|
|
1798
|
+
* ```ts
|
|
1799
|
+
* // POST request
|
|
1800
|
+
* const response = await client.fetch('/api/endpoint', {
|
|
1801
|
+
* method: 'POST',
|
|
1802
|
+
* headers: { 'Content-Type': 'application/json' },
|
|
1803
|
+
* body: JSON.stringify(data)
|
|
1804
|
+
* })
|
|
1805
|
+
* ```
|
|
1806
|
+
*
|
|
1807
|
+
* @internal
|
|
1808
|
+
*
|
|
1809
|
+
* {@label Initialization}
|
|
1810
|
+
*
|
|
1811
|
+
* @param url - The URL to fetch
|
|
1812
|
+
* @param options - Fetch options
|
|
1813
|
+
* @returns Promise resolving to the fetch response
|
|
1814
|
+
*/
|
|
1714
1815
|
fetch(url, options) {
|
|
1715
1816
|
return this.options.fetch ? this.options.fetch(url, options) : fetch(url, options);
|
|
1716
1817
|
}
|
|
1818
|
+
/**
|
|
1819
|
+
* Get the library version from package.json.
|
|
1820
|
+
*
|
|
1821
|
+
* @example
|
|
1822
|
+
* ```ts
|
|
1823
|
+
* // Get version
|
|
1824
|
+
* const version = client.getLibraryVersion()
|
|
1825
|
+
* console.log(`Using PostHog SDK version: ${version}`)
|
|
1826
|
+
* ```
|
|
1827
|
+
*
|
|
1828
|
+
* {@label Initialization}
|
|
1829
|
+
*
|
|
1830
|
+
* @returns The current library version string
|
|
1831
|
+
*/
|
|
1717
1832
|
getLibraryVersion() {
|
|
1718
1833
|
return version;
|
|
1719
1834
|
}
|
|
1835
|
+
/**
|
|
1836
|
+
* Get the custom user agent string for this client.
|
|
1837
|
+
*
|
|
1838
|
+
* @example
|
|
1839
|
+
* ```ts
|
|
1840
|
+
* // Get user agent
|
|
1841
|
+
* const userAgent = client.getCustomUserAgent()
|
|
1842
|
+
* // Returns: "posthog-node/5.7.0"
|
|
1843
|
+
* ```
|
|
1844
|
+
*
|
|
1845
|
+
* {@label Identification}
|
|
1846
|
+
*
|
|
1847
|
+
* @returns The formatted user agent string
|
|
1848
|
+
*/
|
|
1720
1849
|
getCustomUserAgent() {
|
|
1721
1850
|
return `${this.getLibraryId()}/${this.getLibraryVersion()}`;
|
|
1722
1851
|
}
|
|
1852
|
+
/**
|
|
1853
|
+
* Enable the PostHog client (opt-in).
|
|
1854
|
+
*
|
|
1855
|
+
* @example
|
|
1856
|
+
* ```ts
|
|
1857
|
+
* // Enable client
|
|
1858
|
+
* await client.enable()
|
|
1859
|
+
* // Client is now enabled and will capture events
|
|
1860
|
+
* ```
|
|
1861
|
+
*
|
|
1862
|
+
* {@label Privacy}
|
|
1863
|
+
*
|
|
1864
|
+
* @returns Promise that resolves when the client is enabled
|
|
1865
|
+
*/
|
|
1723
1866
|
enable() {
|
|
1724
1867
|
return super.optIn();
|
|
1725
1868
|
}
|
|
1869
|
+
/**
|
|
1870
|
+
* Disable the PostHog client (opt-out).
|
|
1871
|
+
*
|
|
1872
|
+
* @example
|
|
1873
|
+
* ```ts
|
|
1874
|
+
* // Disable client
|
|
1875
|
+
* await client.disable()
|
|
1876
|
+
* // Client is now disabled and will not capture events
|
|
1877
|
+
* ```
|
|
1878
|
+
*
|
|
1879
|
+
* {@label Privacy}
|
|
1880
|
+
*
|
|
1881
|
+
* @returns Promise that resolves when the client is disabled
|
|
1882
|
+
*/
|
|
1726
1883
|
disable() {
|
|
1727
1884
|
return super.optOut();
|
|
1728
1885
|
}
|
|
1886
|
+
/**
|
|
1887
|
+
* Enable or disable debug logging.
|
|
1888
|
+
*
|
|
1889
|
+
* @example
|
|
1890
|
+
* ```ts
|
|
1891
|
+
* // Enable debug logging
|
|
1892
|
+
* client.debug(true)
|
|
1893
|
+
* ```
|
|
1894
|
+
*
|
|
1895
|
+
* @example
|
|
1896
|
+
* ```ts
|
|
1897
|
+
* // Disable debug logging
|
|
1898
|
+
* client.debug(false)
|
|
1899
|
+
* ```
|
|
1900
|
+
*
|
|
1901
|
+
* {@label Initialization}
|
|
1902
|
+
*
|
|
1903
|
+
* @param enabled - Whether to enable debug logging
|
|
1904
|
+
*/
|
|
1729
1905
|
debug(enabled = true) {
|
|
1730
1906
|
super.debug(enabled);
|
|
1731
1907
|
this.featureFlagsPoller?.debug(enabled);
|
|
1732
1908
|
}
|
|
1909
|
+
/**
|
|
1910
|
+
* Capture an event manually.
|
|
1911
|
+
*
|
|
1912
|
+
* @example
|
|
1913
|
+
* ```ts
|
|
1914
|
+
* // Basic capture
|
|
1915
|
+
* client.capture({
|
|
1916
|
+
* distinctId: 'user_123',
|
|
1917
|
+
* event: 'button_clicked',
|
|
1918
|
+
* properties: { button_color: 'red' }
|
|
1919
|
+
* })
|
|
1920
|
+
* ```
|
|
1921
|
+
*
|
|
1922
|
+
* {@label Capture}
|
|
1923
|
+
*
|
|
1924
|
+
* @param props - The event properties
|
|
1925
|
+
* @returns void
|
|
1926
|
+
*/
|
|
1733
1927
|
capture(props) {
|
|
1734
1928
|
if (typeof props === 'string') {
|
|
1735
1929
|
this.logMsgIfDebug(() => console.warn('Called capture() with a string as the first argument when an object was expected.'));
|
|
1736
1930
|
}
|
|
1737
|
-
|
|
1931
|
+
this.addPendingPromise(this.prepareEventMessage(props).then(({
|
|
1738
1932
|
distinctId,
|
|
1739
1933
|
event,
|
|
1740
1934
|
properties,
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
// Run before_send if configured
|
|
1748
|
-
const eventMessage = this._runBeforeSend({
|
|
1749
|
-
distinctId,
|
|
1750
|
-
event,
|
|
1751
|
-
properties,
|
|
1752
|
-
groups,
|
|
1753
|
-
sendFeatureFlags,
|
|
1754
|
-
timestamp,
|
|
1755
|
-
disableGeoip,
|
|
1756
|
-
uuid
|
|
1757
|
-
});
|
|
1758
|
-
if (!eventMessage) {
|
|
1759
|
-
return;
|
|
1760
|
-
}
|
|
1761
|
-
const _capture = props => {
|
|
1762
|
-
super.captureStateless(eventMessage.distinctId, eventMessage.event, props, {
|
|
1763
|
-
timestamp: eventMessage.timestamp,
|
|
1764
|
-
disableGeoip: eventMessage.disableGeoip,
|
|
1765
|
-
uuid: eventMessage.uuid
|
|
1935
|
+
options
|
|
1936
|
+
}) => {
|
|
1937
|
+
return super.captureStateless(distinctId, event, properties, {
|
|
1938
|
+
timestamp: options.timestamp,
|
|
1939
|
+
disableGeoip: options.disableGeoip,
|
|
1940
|
+
uuid: options.uuid
|
|
1766
1941
|
});
|
|
1767
|
-
}
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
if (sendFeatureFlags) {
|
|
1771
|
-
// If we are sending feature flags, we evaluate them locally if the user prefers it, otherwise we fall back to remote evaluation
|
|
1772
|
-
const sendFeatureFlagsOptions = typeof sendFeatureFlags === 'object' ? sendFeatureFlags : undefined;
|
|
1773
|
-
return await this.getFeatureFlagsForEvent(distinctId, groups, disableGeoip, sendFeatureFlagsOptions);
|
|
1774
|
-
}
|
|
1775
|
-
if (event === '$feature_flag_called') {
|
|
1776
|
-
// 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.
|
|
1777
|
-
return {};
|
|
1778
|
-
}
|
|
1779
|
-
return {};
|
|
1780
|
-
}).then(flags => {
|
|
1781
|
-
// Derive the relevant flag properties to add
|
|
1782
|
-
const additionalProperties = {};
|
|
1783
|
-
if (flags) {
|
|
1784
|
-
for (const [feature, variant] of Object.entries(flags)) {
|
|
1785
|
-
additionalProperties[`$feature/${feature}`] = variant;
|
|
1786
|
-
}
|
|
1787
|
-
}
|
|
1788
|
-
const activeFlags = Object.keys(flags || {}).filter(flag => flags?.[flag] !== false).sort();
|
|
1789
|
-
if (activeFlags.length > 0) {
|
|
1790
|
-
additionalProperties['$active_feature_flags'] = activeFlags;
|
|
1942
|
+
}).catch(err => {
|
|
1943
|
+
if (err) {
|
|
1944
|
+
console.error(err);
|
|
1791
1945
|
}
|
|
1792
|
-
|
|
1793
|
-
}).catch(() => {
|
|
1794
|
-
// Something went wrong getting the flag info - we should capture the event anyways
|
|
1795
|
-
return {};
|
|
1796
|
-
}).then(additionalProperties => {
|
|
1797
|
-
// No matter what - capture the event
|
|
1798
|
-
_capture({
|
|
1799
|
-
...additionalProperties,
|
|
1800
|
-
...(eventMessage.properties || {}),
|
|
1801
|
-
$groups: eventMessage.groups || groups
|
|
1802
|
-
});
|
|
1803
|
-
});
|
|
1804
|
-
this.addPendingPromise(capturePromise);
|
|
1946
|
+
}));
|
|
1805
1947
|
}
|
|
1948
|
+
/**
|
|
1949
|
+
* Capture an event immediately (synchronously).
|
|
1950
|
+
*
|
|
1951
|
+
* @example
|
|
1952
|
+
* ```ts
|
|
1953
|
+
* // Basic immediate capture
|
|
1954
|
+
* await client.captureImmediate({
|
|
1955
|
+
* distinctId: 'user_123',
|
|
1956
|
+
* event: 'button_clicked',
|
|
1957
|
+
* properties: { button_color: 'red' }
|
|
1958
|
+
* })
|
|
1959
|
+
* ```
|
|
1960
|
+
*
|
|
1961
|
+
* @example
|
|
1962
|
+
* ```ts
|
|
1963
|
+
* // With feature flags
|
|
1964
|
+
* await client.captureImmediate({
|
|
1965
|
+
* distinctId: 'user_123',
|
|
1966
|
+
* event: 'user_action',
|
|
1967
|
+
* sendFeatureFlags: true
|
|
1968
|
+
* })
|
|
1969
|
+
* ```
|
|
1970
|
+
*
|
|
1971
|
+
* @example
|
|
1972
|
+
* ```ts
|
|
1973
|
+
* // With custom feature flags options
|
|
1974
|
+
* await client.captureImmediate({
|
|
1975
|
+
* distinctId: 'user_123',
|
|
1976
|
+
* event: 'user_action',
|
|
1977
|
+
* sendFeatureFlags: {
|
|
1978
|
+
* onlyEvaluateLocally: true,
|
|
1979
|
+
* personProperties: { plan: 'premium' },
|
|
1980
|
+
* groupProperties: { org: { tier: 'enterprise' } }
|
|
1981
|
+
* flagKeys: ['flag1', 'flag2']
|
|
1982
|
+
* }
|
|
1983
|
+
* })
|
|
1984
|
+
* ```
|
|
1985
|
+
*
|
|
1986
|
+
* {@label Capture}
|
|
1987
|
+
*
|
|
1988
|
+
* @param props - The event properties
|
|
1989
|
+
* @returns Promise that resolves when the event is captured
|
|
1990
|
+
*/
|
|
1806
1991
|
async captureImmediate(props) {
|
|
1807
1992
|
if (typeof props === 'string') {
|
|
1808
|
-
this.logMsgIfDebug(() => console.warn('Called
|
|
1993
|
+
this.logMsgIfDebug(() => console.warn('Called captureImmediate() with a string as the first argument when an object was expected.'));
|
|
1809
1994
|
}
|
|
1810
|
-
|
|
1811
|
-
distinctId,
|
|
1812
|
-
event,
|
|
1813
|
-
properties,
|
|
1814
|
-
groups,
|
|
1815
|
-
sendFeatureFlags,
|
|
1816
|
-
timestamp,
|
|
1817
|
-
disableGeoip,
|
|
1818
|
-
uuid
|
|
1819
|
-
} = props;
|
|
1820
|
-
// Run before_send if configured
|
|
1821
|
-
const eventMessage = this._runBeforeSend({
|
|
1995
|
+
return this.addPendingPromise(this.prepareEventMessage(props).then(({
|
|
1822
1996
|
distinctId,
|
|
1823
1997
|
event,
|
|
1824
1998
|
properties,
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
if (!eventMessage) {
|
|
1832
|
-
return;
|
|
1833
|
-
}
|
|
1834
|
-
const _capture = props => {
|
|
1835
|
-
return super.captureStatelessImmediate(eventMessage.distinctId, eventMessage.event, props, {
|
|
1836
|
-
timestamp: eventMessage.timestamp,
|
|
1837
|
-
disableGeoip: eventMessage.disableGeoip,
|
|
1838
|
-
uuid: eventMessage.uuid
|
|
1999
|
+
options
|
|
2000
|
+
}) => {
|
|
2001
|
+
return super.captureStatelessImmediate(distinctId, event, properties, {
|
|
2002
|
+
timestamp: options.timestamp,
|
|
2003
|
+
disableGeoip: options.disableGeoip,
|
|
2004
|
+
uuid: options.uuid
|
|
1839
2005
|
});
|
|
1840
|
-
}
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
// If we are sending feature flags, we evaluate them locally if the user prefers it, otherwise we fall back to remote evaluation
|
|
1844
|
-
const sendFeatureFlagsOptions = typeof sendFeatureFlags === 'object' ? sendFeatureFlags : undefined;
|
|
1845
|
-
return await this.getFeatureFlagsForEvent(distinctId, groups, disableGeoip, sendFeatureFlagsOptions);
|
|
1846
|
-
}
|
|
1847
|
-
if (event === '$feature_flag_called') {
|
|
1848
|
-
// 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.
|
|
1849
|
-
return {};
|
|
2006
|
+
}).catch(err => {
|
|
2007
|
+
if (err) {
|
|
2008
|
+
console.error(err);
|
|
1850
2009
|
}
|
|
1851
|
-
|
|
1852
|
-
}).then(flags => {
|
|
1853
|
-
// Derive the relevant flag properties to add
|
|
1854
|
-
const additionalProperties = {};
|
|
1855
|
-
if (flags) {
|
|
1856
|
-
for (const [feature, variant] of Object.entries(flags)) {
|
|
1857
|
-
additionalProperties[`$feature/${feature}`] = variant;
|
|
1858
|
-
}
|
|
1859
|
-
}
|
|
1860
|
-
const activeFlags = Object.keys(flags || {}).filter(flag => flags?.[flag] !== false).sort();
|
|
1861
|
-
if (activeFlags.length > 0) {
|
|
1862
|
-
additionalProperties['$active_feature_flags'] = activeFlags;
|
|
1863
|
-
}
|
|
1864
|
-
return additionalProperties;
|
|
1865
|
-
}).catch(() => {
|
|
1866
|
-
// Something went wrong getting the flag info - we should capture the event anyways
|
|
1867
|
-
return {};
|
|
1868
|
-
}).then(additionalProperties => {
|
|
1869
|
-
// No matter what - capture the event
|
|
1870
|
-
_capture({
|
|
1871
|
-
...additionalProperties,
|
|
1872
|
-
...(eventMessage.properties || {}),
|
|
1873
|
-
$groups: eventMessage.groups || groups
|
|
1874
|
-
});
|
|
1875
|
-
});
|
|
1876
|
-
await capturePromise;
|
|
2010
|
+
}));
|
|
1877
2011
|
}
|
|
2012
|
+
/**
|
|
2013
|
+
* Identify a user and set their properties.
|
|
2014
|
+
*
|
|
2015
|
+
* @example
|
|
2016
|
+
* ```ts
|
|
2017
|
+
* // Basic identify with properties
|
|
2018
|
+
* client.identify({
|
|
2019
|
+
* distinctId: 'user_123',
|
|
2020
|
+
* properties: {
|
|
2021
|
+
* name: 'John Doe',
|
|
2022
|
+
* email: 'john@example.com',
|
|
2023
|
+
* plan: 'premium'
|
|
2024
|
+
* }
|
|
2025
|
+
* })
|
|
2026
|
+
* ```
|
|
2027
|
+
*
|
|
2028
|
+
* @example
|
|
2029
|
+
* ```ts
|
|
2030
|
+
* // Using $set and $set_once
|
|
2031
|
+
* client.identify({
|
|
2032
|
+
* distinctId: 'user_123',
|
|
2033
|
+
* properties: {
|
|
2034
|
+
* $set: { name: 'John Doe', email: 'john@example.com' },
|
|
2035
|
+
* $set_once: { first_login: new Date().toISOString() }
|
|
2036
|
+
* }
|
|
2037
|
+
* })
|
|
2038
|
+
* ```
|
|
2039
|
+
*
|
|
2040
|
+
* {@label Identification}
|
|
2041
|
+
*
|
|
2042
|
+
* @param data - The identify data containing distinctId and properties
|
|
2043
|
+
*/
|
|
1878
2044
|
identify({
|
|
1879
2045
|
distinctId,
|
|
1880
2046
|
properties,
|
|
@@ -1893,6 +2059,26 @@ class PostHogBackendClient extends core.PostHogCoreStateless {
|
|
|
1893
2059
|
disableGeoip
|
|
1894
2060
|
});
|
|
1895
2061
|
}
|
|
2062
|
+
/**
|
|
2063
|
+
* Identify a user and set their properties immediately (synchronously).
|
|
2064
|
+
*
|
|
2065
|
+
* @example
|
|
2066
|
+
* ```ts
|
|
2067
|
+
* // Basic immediate identify
|
|
2068
|
+
* await client.identifyImmediate({
|
|
2069
|
+
* distinctId: 'user_123',
|
|
2070
|
+
* properties: {
|
|
2071
|
+
* name: 'John Doe',
|
|
2072
|
+
* email: 'john@example.com'
|
|
2073
|
+
* }
|
|
2074
|
+
* })
|
|
2075
|
+
* ```
|
|
2076
|
+
*
|
|
2077
|
+
* {@label Identification}
|
|
2078
|
+
*
|
|
2079
|
+
* @param data - The identify data containing distinctId and properties
|
|
2080
|
+
* @returns Promise that resolves when the identify is processed
|
|
2081
|
+
*/
|
|
1896
2082
|
async identifyImmediate({
|
|
1897
2083
|
distinctId,
|
|
1898
2084
|
properties,
|
|
@@ -1910,19 +2096,96 @@ class PostHogBackendClient extends core.PostHogCoreStateless {
|
|
|
1910
2096
|
disableGeoip
|
|
1911
2097
|
});
|
|
1912
2098
|
}
|
|
2099
|
+
/**
|
|
2100
|
+
* Create an alias to link two distinct IDs together.
|
|
2101
|
+
*
|
|
2102
|
+
* @example
|
|
2103
|
+
* ```ts
|
|
2104
|
+
* // Link an anonymous user to an identified user
|
|
2105
|
+
* client.alias({
|
|
2106
|
+
* distinctId: 'anonymous_123',
|
|
2107
|
+
* alias: 'user_456'
|
|
2108
|
+
* })
|
|
2109
|
+
* ```
|
|
2110
|
+
*
|
|
2111
|
+
* {@label Identification}
|
|
2112
|
+
*
|
|
2113
|
+
* @param data - The alias data containing distinctId and alias
|
|
2114
|
+
*/
|
|
1913
2115
|
alias(data) {
|
|
1914
2116
|
super.aliasStateless(data.alias, data.distinctId, undefined, {
|
|
1915
2117
|
disableGeoip: data.disableGeoip
|
|
1916
2118
|
});
|
|
1917
2119
|
}
|
|
2120
|
+
/**
|
|
2121
|
+
* Create an alias to link two distinct IDs together immediately (synchronously).
|
|
2122
|
+
*
|
|
2123
|
+
* @example
|
|
2124
|
+
* ```ts
|
|
2125
|
+
* // Link an anonymous user to an identified user immediately
|
|
2126
|
+
* await client.aliasImmediate({
|
|
2127
|
+
* distinctId: 'anonymous_123',
|
|
2128
|
+
* alias: 'user_456'
|
|
2129
|
+
* })
|
|
2130
|
+
* ```
|
|
2131
|
+
*
|
|
2132
|
+
* {@label Identification}
|
|
2133
|
+
*
|
|
2134
|
+
* @param data - The alias data containing distinctId and alias
|
|
2135
|
+
* @returns Promise that resolves when the alias is processed
|
|
2136
|
+
*/
|
|
1918
2137
|
async aliasImmediate(data) {
|
|
1919
2138
|
await super.aliasStatelessImmediate(data.alias, data.distinctId, undefined, {
|
|
1920
2139
|
disableGeoip: data.disableGeoip
|
|
1921
2140
|
});
|
|
1922
2141
|
}
|
|
2142
|
+
/**
|
|
2143
|
+
* Check if local evaluation of feature flags is ready.
|
|
2144
|
+
*
|
|
2145
|
+
* @example
|
|
2146
|
+
* ```ts
|
|
2147
|
+
* // Check if ready
|
|
2148
|
+
* if (client.isLocalEvaluationReady()) {
|
|
2149
|
+
* // Local evaluation is ready, can evaluate flags locally
|
|
2150
|
+
* const flag = await client.getFeatureFlag('flag-key', 'user_123')
|
|
2151
|
+
* } else {
|
|
2152
|
+
* // Local evaluation not ready, will use remote evaluation
|
|
2153
|
+
* const flag = await client.getFeatureFlag('flag-key', 'user_123')
|
|
2154
|
+
* }
|
|
2155
|
+
* ```
|
|
2156
|
+
*
|
|
2157
|
+
* {@label Feature flags}
|
|
2158
|
+
*
|
|
2159
|
+
* @returns true if local evaluation is ready, false otherwise
|
|
2160
|
+
*/
|
|
1923
2161
|
isLocalEvaluationReady() {
|
|
1924
2162
|
return this.featureFlagsPoller?.isLocalEvaluationReady() ?? false;
|
|
1925
2163
|
}
|
|
2164
|
+
/**
|
|
2165
|
+
* Wait for local evaluation of feature flags to be ready.
|
|
2166
|
+
*
|
|
2167
|
+
* @example
|
|
2168
|
+
* ```ts
|
|
2169
|
+
* // Wait for local evaluation
|
|
2170
|
+
* const isReady = await client.waitForLocalEvaluationReady()
|
|
2171
|
+
* if (isReady) {
|
|
2172
|
+
* console.log('Local evaluation is ready')
|
|
2173
|
+
* } else {
|
|
2174
|
+
* console.log('Local evaluation timed out')
|
|
2175
|
+
* }
|
|
2176
|
+
* ```
|
|
2177
|
+
*
|
|
2178
|
+
* @example
|
|
2179
|
+
* ```ts
|
|
2180
|
+
* // Wait with custom timeout
|
|
2181
|
+
* const isReady = await client.waitForLocalEvaluationReady(10000) // 10 seconds
|
|
2182
|
+
* ```
|
|
2183
|
+
*
|
|
2184
|
+
* {@label Feature flags}
|
|
2185
|
+
*
|
|
2186
|
+
* @param timeoutMs - Timeout in milliseconds (default: 30000)
|
|
2187
|
+
* @returns Promise that resolves to true if ready, false if timed out
|
|
2188
|
+
*/
|
|
1926
2189
|
async waitForLocalEvaluationReady(timeoutMs = THIRTY_SECONDS) {
|
|
1927
2190
|
if (this.isLocalEvaluationReady()) {
|
|
1928
2191
|
return true;
|
|
@@ -1942,6 +2205,47 @@ class PostHogBackendClient extends core.PostHogCoreStateless {
|
|
|
1942
2205
|
});
|
|
1943
2206
|
});
|
|
1944
2207
|
}
|
|
2208
|
+
/**
|
|
2209
|
+
* Get the value of a feature flag for a specific user.
|
|
2210
|
+
*
|
|
2211
|
+
* @example
|
|
2212
|
+
* ```ts
|
|
2213
|
+
* // Basic feature flag check
|
|
2214
|
+
* const flagValue = await client.getFeatureFlag('new-feature', 'user_123')
|
|
2215
|
+
* if (flagValue === 'variant-a') {
|
|
2216
|
+
* // Show variant A
|
|
2217
|
+
* } else if (flagValue === 'variant-b') {
|
|
2218
|
+
* // Show variant B
|
|
2219
|
+
* } else {
|
|
2220
|
+
* // Flag is disabled or not found
|
|
2221
|
+
* }
|
|
2222
|
+
* ```
|
|
2223
|
+
*
|
|
2224
|
+
* @example
|
|
2225
|
+
* ```ts
|
|
2226
|
+
* // With groups and properties
|
|
2227
|
+
* const flagValue = await client.getFeatureFlag('org-feature', 'user_123', {
|
|
2228
|
+
* groups: { organization: 'acme-corp' },
|
|
2229
|
+
* personProperties: { plan: 'enterprise' },
|
|
2230
|
+
* groupProperties: { organization: { tier: 'premium' } }
|
|
2231
|
+
* })
|
|
2232
|
+
* ```
|
|
2233
|
+
*
|
|
2234
|
+
* @example
|
|
2235
|
+
* ```ts
|
|
2236
|
+
* // Only evaluate locally
|
|
2237
|
+
* const flagValue = await client.getFeatureFlag('local-flag', 'user_123', {
|
|
2238
|
+
* onlyEvaluateLocally: true
|
|
2239
|
+
* })
|
|
2240
|
+
* ```
|
|
2241
|
+
*
|
|
2242
|
+
* {@label Feature flags}
|
|
2243
|
+
*
|
|
2244
|
+
* @param key - The feature flag key
|
|
2245
|
+
* @param distinctId - The user's distinct ID
|
|
2246
|
+
* @param options - Optional configuration for flag evaluation
|
|
2247
|
+
* @returns Promise that resolves to the flag value or undefined
|
|
2248
|
+
*/
|
|
1945
2249
|
async getFeatureFlag(key, distinctId, options) {
|
|
1946
2250
|
const {
|
|
1947
2251
|
groups,
|
|
@@ -1961,7 +2265,7 @@ class PostHogBackendClient extends core.PostHogCoreStateless {
|
|
|
1961
2265
|
onlyEvaluateLocally = false;
|
|
1962
2266
|
}
|
|
1963
2267
|
if (sendFeatureFlagEvents == undefined) {
|
|
1964
|
-
sendFeatureFlagEvents = true;
|
|
2268
|
+
sendFeatureFlagEvents = this.options.sendFeatureFlagEvent ?? true;
|
|
1965
2269
|
}
|
|
1966
2270
|
let response = await this.featureFlagsPoller?.getFeatureFlag(key, distinctId, groups, personProperties, groupProperties);
|
|
1967
2271
|
const flagWasLocallyEvaluated = response !== undefined;
|
|
@@ -2005,6 +2309,41 @@ class PostHogBackendClient extends core.PostHogCoreStateless {
|
|
|
2005
2309
|
}
|
|
2006
2310
|
return response;
|
|
2007
2311
|
}
|
|
2312
|
+
/**
|
|
2313
|
+
* Get the payload for a feature flag.
|
|
2314
|
+
*
|
|
2315
|
+
* @example
|
|
2316
|
+
* ```ts
|
|
2317
|
+
* // Get payload for a feature flag
|
|
2318
|
+
* const payload = await client.getFeatureFlagPayload('flag-key', 'user_123')
|
|
2319
|
+
* if (payload) {
|
|
2320
|
+
* console.log('Flag payload:', payload)
|
|
2321
|
+
* }
|
|
2322
|
+
* ```
|
|
2323
|
+
*
|
|
2324
|
+
* @example
|
|
2325
|
+
* ```ts
|
|
2326
|
+
* // Get payload with specific match value
|
|
2327
|
+
* const payload = await client.getFeatureFlagPayload('flag-key', 'user_123', 'variant-a')
|
|
2328
|
+
* ```
|
|
2329
|
+
*
|
|
2330
|
+
* @example
|
|
2331
|
+
* ```ts
|
|
2332
|
+
* // With groups and properties
|
|
2333
|
+
* const payload = await client.getFeatureFlagPayload('org-flag', 'user_123', undefined, {
|
|
2334
|
+
* groups: { organization: 'acme-corp' },
|
|
2335
|
+
* personProperties: { plan: 'enterprise' }
|
|
2336
|
+
* })
|
|
2337
|
+
* ```
|
|
2338
|
+
*
|
|
2339
|
+
* {@label Feature flags}
|
|
2340
|
+
*
|
|
2341
|
+
* @param key - The feature flag key
|
|
2342
|
+
* @param distinctId - The user's distinct ID
|
|
2343
|
+
* @param matchValue - Optional match value to get payload for
|
|
2344
|
+
* @param options - Optional configuration for flag evaluation
|
|
2345
|
+
* @returns Promise that resolves to the flag payload or undefined
|
|
2346
|
+
*/
|
|
2008
2347
|
async getFeatureFlagPayload(key, distinctId, matchValue, options) {
|
|
2009
2348
|
const {
|
|
2010
2349
|
groups,
|
|
@@ -2012,7 +2351,6 @@ class PostHogBackendClient extends core.PostHogCoreStateless {
|
|
|
2012
2351
|
} = options || {};
|
|
2013
2352
|
let {
|
|
2014
2353
|
onlyEvaluateLocally,
|
|
2015
|
-
sendFeatureFlagEvents,
|
|
2016
2354
|
personProperties,
|
|
2017
2355
|
groupProperties
|
|
2018
2356
|
} = options || {};
|
|
@@ -2037,15 +2375,30 @@ class PostHogBackendClient extends core.PostHogCoreStateless {
|
|
|
2037
2375
|
if (onlyEvaluateLocally == undefined) {
|
|
2038
2376
|
onlyEvaluateLocally = false;
|
|
2039
2377
|
}
|
|
2040
|
-
if (sendFeatureFlagEvents == undefined) {
|
|
2041
|
-
sendFeatureFlagEvents = true;
|
|
2042
|
-
}
|
|
2043
2378
|
const payloadWasLocallyEvaluated = response !== undefined;
|
|
2044
2379
|
if (!payloadWasLocallyEvaluated && !onlyEvaluateLocally) {
|
|
2045
2380
|
response = await super.getFeatureFlagPayloadStateless(key, distinctId, groups, personProperties, groupProperties, disableGeoip);
|
|
2046
2381
|
}
|
|
2047
2382
|
return response;
|
|
2048
2383
|
}
|
|
2384
|
+
/**
|
|
2385
|
+
* Get the remote config payload for a feature flag.
|
|
2386
|
+
*
|
|
2387
|
+
* @example
|
|
2388
|
+
* ```ts
|
|
2389
|
+
* // Get remote config payload
|
|
2390
|
+
* const payload = await client.getRemoteConfigPayload('flag-key')
|
|
2391
|
+
* if (payload) {
|
|
2392
|
+
* console.log('Remote config payload:', payload)
|
|
2393
|
+
* }
|
|
2394
|
+
* ```
|
|
2395
|
+
*
|
|
2396
|
+
* {@label Feature flags}
|
|
2397
|
+
*
|
|
2398
|
+
* @param flagKey - The feature flag key
|
|
2399
|
+
* @returns Promise that resolves to the remote config payload or undefined
|
|
2400
|
+
* @throws Error if personal API key is not provided
|
|
2401
|
+
*/
|
|
2049
2402
|
async getRemoteConfigPayload(flagKey) {
|
|
2050
2403
|
if (!this.options.personalApiKey) {
|
|
2051
2404
|
throw new Error('Personal API key is required for remote config payload decryption');
|
|
@@ -2069,6 +2422,38 @@ class PostHogBackendClient extends core.PostHogCoreStateless {
|
|
|
2069
2422
|
}
|
|
2070
2423
|
return parsed;
|
|
2071
2424
|
}
|
|
2425
|
+
/**
|
|
2426
|
+
* Check if a feature flag is enabled for a specific user.
|
|
2427
|
+
*
|
|
2428
|
+
* @example
|
|
2429
|
+
* ```ts
|
|
2430
|
+
* // Basic feature flag check
|
|
2431
|
+
* const isEnabled = await client.isFeatureEnabled('new-feature', 'user_123')
|
|
2432
|
+
* if (isEnabled) {
|
|
2433
|
+
* // Feature is enabled
|
|
2434
|
+
* console.log('New feature is active')
|
|
2435
|
+
* } else {
|
|
2436
|
+
* // Feature is disabled
|
|
2437
|
+
* console.log('New feature is not active')
|
|
2438
|
+
* }
|
|
2439
|
+
* ```
|
|
2440
|
+
*
|
|
2441
|
+
* @example
|
|
2442
|
+
* ```ts
|
|
2443
|
+
* // With groups and properties
|
|
2444
|
+
* const isEnabled = await client.isFeatureEnabled('org-feature', 'user_123', {
|
|
2445
|
+
* groups: { organization: 'acme-corp' },
|
|
2446
|
+
* personProperties: { plan: 'enterprise' }
|
|
2447
|
+
* })
|
|
2448
|
+
* ```
|
|
2449
|
+
*
|
|
2450
|
+
* {@label Feature flags}
|
|
2451
|
+
*
|
|
2452
|
+
* @param key - The feature flag key
|
|
2453
|
+
* @param distinctId - The user's distinct ID
|
|
2454
|
+
* @param options - Optional configuration for flag evaluation
|
|
2455
|
+
* @returns Promise that resolves to true if enabled, false if disabled, undefined if not found
|
|
2456
|
+
*/
|
|
2072
2457
|
async isFeatureEnabled(key, distinctId, options) {
|
|
2073
2458
|
const feat = await this.getFeatureFlag(key, distinctId, options);
|
|
2074
2459
|
if (feat === undefined) {
|
|
@@ -2076,10 +2461,77 @@ class PostHogBackendClient extends core.PostHogCoreStateless {
|
|
|
2076
2461
|
}
|
|
2077
2462
|
return !!feat || false;
|
|
2078
2463
|
}
|
|
2464
|
+
/**
|
|
2465
|
+
* Get all feature flag values for a specific user.
|
|
2466
|
+
*
|
|
2467
|
+
* @example
|
|
2468
|
+
* ```ts
|
|
2469
|
+
* // Get all flags for a user
|
|
2470
|
+
* const allFlags = await client.getAllFlags('user_123')
|
|
2471
|
+
* console.log('User flags:', allFlags)
|
|
2472
|
+
* // Output: { 'flag-1': 'variant-a', 'flag-2': false, 'flag-3': 'variant-b' }
|
|
2473
|
+
* ```
|
|
2474
|
+
*
|
|
2475
|
+
* @example
|
|
2476
|
+
* ```ts
|
|
2477
|
+
* // With specific flag keys
|
|
2478
|
+
* const specificFlags = await client.getAllFlags('user_123', {
|
|
2479
|
+
* flagKeys: ['flag-1', 'flag-2']
|
|
2480
|
+
* })
|
|
2481
|
+
* ```
|
|
2482
|
+
*
|
|
2483
|
+
* @example
|
|
2484
|
+
* ```ts
|
|
2485
|
+
* // With groups and properties
|
|
2486
|
+
* const orgFlags = await client.getAllFlags('user_123', {
|
|
2487
|
+
* groups: { organization: 'acme-corp' },
|
|
2488
|
+
* personProperties: { plan: 'enterprise' }
|
|
2489
|
+
* })
|
|
2490
|
+
* ```
|
|
2491
|
+
*
|
|
2492
|
+
* {@label Feature flags}
|
|
2493
|
+
*
|
|
2494
|
+
* @param distinctId - The user's distinct ID
|
|
2495
|
+
* @param options - Optional configuration for flag evaluation
|
|
2496
|
+
* @returns Promise that resolves to a record of flag keys and their values
|
|
2497
|
+
*/
|
|
2079
2498
|
async getAllFlags(distinctId, options) {
|
|
2080
2499
|
const response = await this.getAllFlagsAndPayloads(distinctId, options);
|
|
2081
2500
|
return response.featureFlags || {};
|
|
2082
2501
|
}
|
|
2502
|
+
/**
|
|
2503
|
+
* Get all feature flag values and payloads for a specific user.
|
|
2504
|
+
*
|
|
2505
|
+
* @example
|
|
2506
|
+
* ```ts
|
|
2507
|
+
* // Get all flags and payloads for a user
|
|
2508
|
+
* const result = await client.getAllFlagsAndPayloads('user_123')
|
|
2509
|
+
* console.log('Flags:', result.featureFlags)
|
|
2510
|
+
* console.log('Payloads:', result.featureFlagPayloads)
|
|
2511
|
+
* ```
|
|
2512
|
+
*
|
|
2513
|
+
* @example
|
|
2514
|
+
* ```ts
|
|
2515
|
+
* // With specific flag keys
|
|
2516
|
+
* const result = await client.getAllFlagsAndPayloads('user_123', {
|
|
2517
|
+
* flagKeys: ['flag-1', 'flag-2']
|
|
2518
|
+
* })
|
|
2519
|
+
* ```
|
|
2520
|
+
*
|
|
2521
|
+
* @example
|
|
2522
|
+
* ```ts
|
|
2523
|
+
* // Only evaluate locally
|
|
2524
|
+
* const result = await client.getAllFlagsAndPayloads('user_123', {
|
|
2525
|
+
* onlyEvaluateLocally: true
|
|
2526
|
+
* })
|
|
2527
|
+
* ```
|
|
2528
|
+
*
|
|
2529
|
+
* {@label Feature flags}
|
|
2530
|
+
*
|
|
2531
|
+
* @param distinctId - The user's distinct ID
|
|
2532
|
+
* @param options - Optional configuration for flag evaluation
|
|
2533
|
+
* @returns Promise that resolves to flags and payloads
|
|
2534
|
+
*/
|
|
2083
2535
|
async getAllFlagsAndPayloads(distinctId, options) {
|
|
2084
2536
|
const {
|
|
2085
2537
|
groups,
|
|
@@ -2123,6 +2575,41 @@ class PostHogBackendClient extends core.PostHogCoreStateless {
|
|
|
2123
2575
|
featureFlagPayloads
|
|
2124
2576
|
};
|
|
2125
2577
|
}
|
|
2578
|
+
/**
|
|
2579
|
+
* Create or update a group and its properties.
|
|
2580
|
+
*
|
|
2581
|
+
* @example
|
|
2582
|
+
* ```ts
|
|
2583
|
+
* // Create a company group
|
|
2584
|
+
* client.groupIdentify({
|
|
2585
|
+
* groupType: 'company',
|
|
2586
|
+
* groupKey: 'acme-corp',
|
|
2587
|
+
* properties: {
|
|
2588
|
+
* name: 'Acme Corporation',
|
|
2589
|
+
* industry: 'Technology',
|
|
2590
|
+
* employee_count: 500
|
|
2591
|
+
* },
|
|
2592
|
+
* distinctId: 'user_123'
|
|
2593
|
+
* })
|
|
2594
|
+
* ```
|
|
2595
|
+
*
|
|
2596
|
+
* @example
|
|
2597
|
+
* ```ts
|
|
2598
|
+
* // Update organization properties
|
|
2599
|
+
* client.groupIdentify({
|
|
2600
|
+
* groupType: 'organization',
|
|
2601
|
+
* groupKey: 'org-456',
|
|
2602
|
+
* properties: {
|
|
2603
|
+
* plan: 'enterprise',
|
|
2604
|
+
* region: 'US-West'
|
|
2605
|
+
* }
|
|
2606
|
+
* })
|
|
2607
|
+
* ```
|
|
2608
|
+
*
|
|
2609
|
+
* {@label Identification}
|
|
2610
|
+
*
|
|
2611
|
+
* @param data - The group identify data
|
|
2612
|
+
*/
|
|
2126
2613
|
groupIdentify({
|
|
2127
2614
|
groupType,
|
|
2128
2615
|
groupKey,
|
|
@@ -2135,14 +2622,52 @@ class PostHogBackendClient extends core.PostHogCoreStateless {
|
|
|
2135
2622
|
}, distinctId);
|
|
2136
2623
|
}
|
|
2137
2624
|
/**
|
|
2138
|
-
*
|
|
2139
|
-
*
|
|
2625
|
+
* Reload feature flag definitions from the server for local evaluation.
|
|
2626
|
+
*
|
|
2627
|
+
* @example
|
|
2628
|
+
* ```ts
|
|
2629
|
+
* // Force reload of feature flags
|
|
2630
|
+
* await client.reloadFeatureFlags()
|
|
2631
|
+
* console.log('Feature flags reloaded')
|
|
2632
|
+
* ```
|
|
2633
|
+
*
|
|
2634
|
+
* @example
|
|
2635
|
+
* ```ts
|
|
2636
|
+
* // Reload before checking a specific flag
|
|
2637
|
+
* await client.reloadFeatureFlags()
|
|
2638
|
+
* const flag = await client.getFeatureFlag('flag-key', 'user_123')
|
|
2639
|
+
* ```
|
|
2640
|
+
*
|
|
2641
|
+
* {@label Feature flags}
|
|
2642
|
+
*
|
|
2643
|
+
* @returns Promise that resolves when flags are reloaded
|
|
2140
2644
|
*/
|
|
2141
2645
|
async reloadFeatureFlags() {
|
|
2142
2646
|
await this.featureFlagsPoller?.loadFeatureFlags(true);
|
|
2143
2647
|
}
|
|
2648
|
+
/**
|
|
2649
|
+
* Shutdown the PostHog client gracefully.
|
|
2650
|
+
*
|
|
2651
|
+
* @example
|
|
2652
|
+
* ```ts
|
|
2653
|
+
* // Shutdown with default timeout
|
|
2654
|
+
* await client._shutdown()
|
|
2655
|
+
* ```
|
|
2656
|
+
*
|
|
2657
|
+
* @example
|
|
2658
|
+
* ```ts
|
|
2659
|
+
* // Shutdown with custom timeout
|
|
2660
|
+
* await client._shutdown(5000) // 5 seconds
|
|
2661
|
+
* ```
|
|
2662
|
+
*
|
|
2663
|
+
* {@label Shutdown}
|
|
2664
|
+
*
|
|
2665
|
+
* @param shutdownTimeoutMs - Timeout in milliseconds for shutdown
|
|
2666
|
+
* @returns Promise that resolves when shutdown is complete
|
|
2667
|
+
*/
|
|
2144
2668
|
async _shutdown(shutdownTimeoutMs) {
|
|
2145
2669
|
this.featureFlagsPoller?.stopPoller();
|
|
2670
|
+
this.errorTracking.shutdown();
|
|
2146
2671
|
return super._shutdown(shutdownTimeoutMs);
|
|
2147
2672
|
}
|
|
2148
2673
|
async _requestRemoteConfigPayload(flagKey) {
|
|
@@ -2161,7 +2686,7 @@ class PostHogBackendClient extends core.PostHogCoreStateless {
|
|
|
2161
2686
|
let abortTimeout = null;
|
|
2162
2687
|
if (this.options.requestTimeout && typeof this.options.requestTimeout === 'number') {
|
|
2163
2688
|
const controller = new AbortController();
|
|
2164
|
-
abortTimeout = safeSetTimeout(() => {
|
|
2689
|
+
abortTimeout = core.safeSetTimeout(() => {
|
|
2165
2690
|
controller.abort();
|
|
2166
2691
|
}, this.options.requestTimeout);
|
|
2167
2692
|
options.signal = controller.signal;
|
|
@@ -2268,20 +2793,159 @@ class PostHogBackendClient extends core.PostHogCoreStateless {
|
|
|
2268
2793
|
allGroupProperties
|
|
2269
2794
|
};
|
|
2270
2795
|
}
|
|
2796
|
+
/**
|
|
2797
|
+
* Capture an error exception as an event.
|
|
2798
|
+
*
|
|
2799
|
+
* @example
|
|
2800
|
+
* ```ts
|
|
2801
|
+
* // Capture an error with user ID
|
|
2802
|
+
* try {
|
|
2803
|
+
* // Some risky operation
|
|
2804
|
+
* riskyOperation()
|
|
2805
|
+
* } catch (error) {
|
|
2806
|
+
* client.captureException(error, 'user_123')
|
|
2807
|
+
* }
|
|
2808
|
+
* ```
|
|
2809
|
+
*
|
|
2810
|
+
* @example
|
|
2811
|
+
* ```ts
|
|
2812
|
+
* // Capture with additional properties
|
|
2813
|
+
* try {
|
|
2814
|
+
* apiCall()
|
|
2815
|
+
* } catch (error) {
|
|
2816
|
+
* client.captureException(error, 'user_123', {
|
|
2817
|
+
* endpoint: '/api/users',
|
|
2818
|
+
* method: 'POST',
|
|
2819
|
+
* status_code: 500
|
|
2820
|
+
* })
|
|
2821
|
+
* }
|
|
2822
|
+
* ```
|
|
2823
|
+
*
|
|
2824
|
+
* {@label Error tracking}
|
|
2825
|
+
*
|
|
2826
|
+
* @param error - The error to capture
|
|
2827
|
+
* @param distinctId - Optional user distinct ID
|
|
2828
|
+
* @param additionalProperties - Optional additional properties to include
|
|
2829
|
+
*/
|
|
2271
2830
|
captureException(error, distinctId, additionalProperties) {
|
|
2272
2831
|
const syntheticException = new Error('PostHog syntheticException');
|
|
2273
|
-
ErrorTracking.buildEventMessage(error, {
|
|
2832
|
+
this.addPendingPromise(ErrorTracking.buildEventMessage(error, {
|
|
2274
2833
|
syntheticException
|
|
2275
|
-
}, distinctId, additionalProperties).then(msg =>
|
|
2276
|
-
this.capture(msg);
|
|
2277
|
-
});
|
|
2834
|
+
}, distinctId, additionalProperties).then(msg => this.capture(msg)));
|
|
2278
2835
|
}
|
|
2836
|
+
/**
|
|
2837
|
+
* Capture an error exception as an event immediately (synchronously).
|
|
2838
|
+
*
|
|
2839
|
+
* @example
|
|
2840
|
+
* ```ts
|
|
2841
|
+
* // Capture an error immediately with user ID
|
|
2842
|
+
* try {
|
|
2843
|
+
* // Some risky operation
|
|
2844
|
+
* riskyOperation()
|
|
2845
|
+
* } catch (error) {
|
|
2846
|
+
* await client.captureExceptionImmediate(error, 'user_123')
|
|
2847
|
+
* }
|
|
2848
|
+
* ```
|
|
2849
|
+
*
|
|
2850
|
+
* @example
|
|
2851
|
+
* ```ts
|
|
2852
|
+
* // Capture with additional properties
|
|
2853
|
+
* try {
|
|
2854
|
+
* apiCall()
|
|
2855
|
+
* } catch (error) {
|
|
2856
|
+
* await client.captureExceptionImmediate(error, 'user_123', {
|
|
2857
|
+
* endpoint: '/api/users',
|
|
2858
|
+
* method: 'POST',
|
|
2859
|
+
* status_code: 500
|
|
2860
|
+
* })
|
|
2861
|
+
* }
|
|
2862
|
+
* ```
|
|
2863
|
+
*
|
|
2864
|
+
* {@label Error tracking}
|
|
2865
|
+
*
|
|
2866
|
+
* @param error - The error to capture
|
|
2867
|
+
* @param distinctId - Optional user distinct ID
|
|
2868
|
+
* @param additionalProperties - Optional additional properties to include
|
|
2869
|
+
* @returns Promise that resolves when the error is captured
|
|
2870
|
+
*/
|
|
2279
2871
|
async captureExceptionImmediate(error, distinctId, additionalProperties) {
|
|
2280
2872
|
const syntheticException = new Error('PostHog syntheticException');
|
|
2281
|
-
|
|
2873
|
+
this.addPendingPromise(ErrorTracking.buildEventMessage(error, {
|
|
2282
2874
|
syntheticException
|
|
2283
|
-
}, distinctId, additionalProperties);
|
|
2284
|
-
|
|
2875
|
+
}, distinctId, additionalProperties).then(msg => this.captureImmediate(msg)));
|
|
2876
|
+
}
|
|
2877
|
+
async prepareEventMessage(props) {
|
|
2878
|
+
const {
|
|
2879
|
+
distinctId,
|
|
2880
|
+
event,
|
|
2881
|
+
properties,
|
|
2882
|
+
groups,
|
|
2883
|
+
sendFeatureFlags,
|
|
2884
|
+
timestamp,
|
|
2885
|
+
disableGeoip,
|
|
2886
|
+
uuid
|
|
2887
|
+
} = props;
|
|
2888
|
+
// Run before_send if configured
|
|
2889
|
+
const eventMessage = this._runBeforeSend({
|
|
2890
|
+
distinctId,
|
|
2891
|
+
event,
|
|
2892
|
+
properties,
|
|
2893
|
+
groups,
|
|
2894
|
+
sendFeatureFlags,
|
|
2895
|
+
timestamp,
|
|
2896
|
+
disableGeoip,
|
|
2897
|
+
uuid
|
|
2898
|
+
});
|
|
2899
|
+
if (!eventMessage) {
|
|
2900
|
+
return Promise.reject(null);
|
|
2901
|
+
}
|
|
2902
|
+
// :TRICKY: If we flush, or need to shut down, to not lose events we want this promise to resolve before we flush
|
|
2903
|
+
const eventProperties = await Promise.resolve().then(async () => {
|
|
2904
|
+
if (sendFeatureFlags) {
|
|
2905
|
+
// If we are sending feature flags, we evaluate them locally if the user prefers it, otherwise we fall back to remote evaluation
|
|
2906
|
+
const sendFeatureFlagsOptions = typeof sendFeatureFlags === 'object' ? sendFeatureFlags : undefined;
|
|
2907
|
+
return await this.getFeatureFlagsForEvent(distinctId, groups, disableGeoip, sendFeatureFlagsOptions);
|
|
2908
|
+
}
|
|
2909
|
+
if (event === '$feature_flag_called') {
|
|
2910
|
+
// 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.
|
|
2911
|
+
return {};
|
|
2912
|
+
}
|
|
2913
|
+
return {};
|
|
2914
|
+
}).then(flags => {
|
|
2915
|
+
// Derive the relevant flag properties to add
|
|
2916
|
+
const additionalProperties = {};
|
|
2917
|
+
if (flags) {
|
|
2918
|
+
for (const [feature, variant] of Object.entries(flags)) {
|
|
2919
|
+
additionalProperties[`$feature/${feature}`] = variant;
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
const activeFlags = Object.keys(flags || {}).filter(flag => flags?.[flag] !== false).sort();
|
|
2923
|
+
if (activeFlags.length > 0) {
|
|
2924
|
+
additionalProperties['$active_feature_flags'] = activeFlags;
|
|
2925
|
+
}
|
|
2926
|
+
return additionalProperties;
|
|
2927
|
+
}).catch(() => {
|
|
2928
|
+
// Something went wrong getting the flag info - we should capture the event anyways
|
|
2929
|
+
return {};
|
|
2930
|
+
}).then(additionalProperties => {
|
|
2931
|
+
// No matter what - capture the event
|
|
2932
|
+
const props = {
|
|
2933
|
+
...additionalProperties,
|
|
2934
|
+
...(eventMessage.properties || {}),
|
|
2935
|
+
$groups: eventMessage.groups || groups
|
|
2936
|
+
};
|
|
2937
|
+
return props;
|
|
2938
|
+
});
|
|
2939
|
+
return {
|
|
2940
|
+
distinctId: eventMessage.distinctId,
|
|
2941
|
+
event: eventMessage.event,
|
|
2942
|
+
properties: eventProperties,
|
|
2943
|
+
options: {
|
|
2944
|
+
timestamp: eventMessage.timestamp,
|
|
2945
|
+
disableGeoip: eventMessage.disableGeoip,
|
|
2946
|
+
uuid: eventMessage.uuid
|
|
2947
|
+
}
|
|
2948
|
+
};
|
|
2285
2949
|
}
|
|
2286
2950
|
_runBeforeSend(eventMessage) {
|
|
2287
2951
|
const beforeSend = this.options.before_send;
|