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.
@@ -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
- console.error('No chunk id map found');
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
- return ErrorTracking.buildEventMessage(exception, hint).then(msg => {
735
- const exceptionProperties = msg.properties;
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(msg);
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.0";
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
- const {
1931
+ this.addPendingPromise(this.prepareEventMessage(props).then(({
1738
1932
  distinctId,
1739
1933
  event,
1740
1934
  properties,
1741
- groups,
1742
- sendFeatureFlags,
1743
- timestamp,
1744
- disableGeoip,
1745
- uuid
1746
- } = props;
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
- // :TRICKY: If we flush, or need to shut down, to not lose events we want this promise to resolve before we flush
1769
- const capturePromise = Promise.resolve().then(async () => {
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
- return additionalProperties;
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 capture() with a string as the first argument when an object was expected.'));
1993
+ this.logMsgIfDebug(() => console.warn('Called captureImmediate() with a string as the first argument when an object was expected.'));
1809
1994
  }
1810
- const {
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
- groups,
1826
- sendFeatureFlags,
1827
- timestamp,
1828
- disableGeoip,
1829
- uuid
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
- const capturePromise = Promise.resolve().then(async () => {
1842
- if (sendFeatureFlags) {
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
- return {};
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
- * Reloads the feature flag definitions from the server for local evaluation.
2139
- * This is useful to call if you want to ensure that the feature flags are up to date before calling getFeatureFlag.
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
- const evtMsg = await ErrorTracking.buildEventMessage(error, {
2873
+ this.addPendingPromise(ErrorTracking.buildEventMessage(error, {
2282
2874
  syntheticException
2283
- }, distinctId, additionalProperties);
2284
- return await this.captureImmediate(evtMsg);
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;