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.
@@ -1,7 +1,7 @@
1
1
  import { dirname, posix, sep } from 'path';
2
2
  import { createReadStream } from 'node:fs';
3
3
  import { createInterface } from 'node:readline';
4
- import { PostHogCoreStateless, getFeatureFlagValue } from '@posthog/core';
4
+ import { PostHogCoreStateless, getFeatureFlagValue, safeSetTimeout as safeSetTimeout$1 } from '@posthog/core';
5
5
 
6
6
  /**
7
7
  * @file Adapted from [posthog-js](https://github.com/PostHog/posthog-js/blob/8157df935a4d0e71d2fefef7127aa85ee51c82d1/src/extensions/sentry-integration.ts) with modifications for the Node SDK.
@@ -316,7 +316,7 @@ function makeUncaughtExceptionHandler(captureFn, onFatalFn) {
316
316
  });
317
317
  if (!calledFatalError && processWouldExit) {
318
318
  calledFatalError = true;
319
- onFatalFn();
319
+ onFatalFn(error);
320
320
  }
321
321
  }, {
322
322
  _posthogErrorHandler: true
@@ -327,7 +327,7 @@ function addUncaughtExceptionListener(captureFn, onFatalFn) {
327
327
  }
328
328
  function addUnhandledRejectionListener(captureFn) {
329
329
  global.process.on('unhandledRejection', reason => {
330
- captureFn(reason, {
330
+ return captureFn(reason, {
331
331
  mechanism: {
332
332
  type: 'onunhandledrejection',
333
333
  handled: false
@@ -344,8 +344,7 @@ let cachedFilenameChunkIds;
344
344
  function getFilenameToChunkIdMap(stackParser) {
345
345
  const chunkIdMap = globalThis._posthogChunkIds;
346
346
  if (!chunkIdMap) {
347
- console.error('No chunk id map found');
348
- return {};
347
+ return null;
349
348
  }
350
349
  const chunkIdKeys = Object.keys(chunkIdMap);
351
350
  if (cachedFilenameChunkIds && chunkIdKeys.length === lastKeysCount) {
@@ -616,7 +615,7 @@ function parseStackFrames(stackParser, error) {
616
615
  function applyChunkIds(frames, parser) {
617
616
  const filenameChunkIdMap = getFilenameToChunkIdMap(parser);
618
617
  frames.forEach(frame => {
619
- if (frame.filename) {
618
+ if (frame.filename && filenameChunkIdMap) {
620
619
  frame.chunk_id = filenameChunkIdMap[frame.filename];
621
620
  }
622
621
  });
@@ -645,6 +644,12 @@ function clampToRange(value, min, max, logger, fallbackValue) {
645
644
  }
646
645
 
647
646
  class BucketedRateLimiter {
647
+ stop() {
648
+ if (this._removeInterval) {
649
+ clearInterval(this._removeInterval);
650
+ this._removeInterval = void 0;
651
+ }
652
+ }
648
653
  constructor(_options){
649
654
  this._options = _options;
650
655
  this._buckets = {};
@@ -676,7 +681,7 @@ class BucketedRateLimiter {
676
681
  this._bucketSize = clampToRange(this._options.bucketSize, 0, 100, this._options._logger);
677
682
  this._refillRate = clampToRange(this._options.refillRate, 0, this._bucketSize, this._options._logger);
678
683
  this._refillInterval = clampToRange(this._options.refillInterval, 0, 86400000, this._options._logger);
679
- setInterval(()=>{
684
+ this._removeInterval = setInterval(()=>{
680
685
  this._refillBuckets();
681
686
  }, this._refillInterval);
682
687
  }
@@ -690,6 +695,22 @@ function safeSetTimeout(fn, timeout) {
690
695
 
691
696
  const SHUTDOWN_TIMEOUT = 2000;
692
697
  class ErrorTracking {
698
+ constructor(client, options, _logger) {
699
+ this.client = client;
700
+ this._exceptionAutocaptureEnabled = options.enableExceptionAutocapture || false;
701
+ this._logger = _logger;
702
+ // by default captures ten exceptions before rate limiting by exception type
703
+ // refills at a rate of one token / 10 second period
704
+ // e.g. will capture 1 exception rate limited exception every 10 seconds until burst ends
705
+ this._rateLimiter = new BucketedRateLimiter({
706
+ refillRate: 1,
707
+ bucketSize: 10,
708
+ refillInterval: 10000,
709
+ // ten seconds in milliseconds
710
+ _logger: this._logger
711
+ });
712
+ this.startAutocaptureIfEnabled();
713
+ }
693
714
  static async buildEventMessage(error, hint, distinctId, additionalProperties) {
694
715
  const properties = {
695
716
  ...additionalProperties
@@ -709,31 +730,16 @@ class ErrorTracking {
709
730
  }
710
731
  };
711
732
  }
712
- constructor(client, options, _logger) {
713
- this.client = client;
714
- this._exceptionAutocaptureEnabled = options.enableExceptionAutocapture || false;
715
- this._logger = _logger;
716
- // by default captures ten exceptions before rate limiting by exception type
717
- // refills at a rate of one token / 10 second period
718
- // e.g. will capture 1 exception rate limited exception every 10 seconds until burst ends
719
- this._rateLimiter = new BucketedRateLimiter({
720
- refillRate: 1,
721
- bucketSize: 10,
722
- refillInterval: 10000,
723
- // ten seconds in milliseconds
724
- _logger: this._logger
725
- });
726
- this.startAutocaptureIfEnabled();
727
- }
728
733
  startAutocaptureIfEnabled() {
729
734
  if (this.isEnabled()) {
730
735
  addUncaughtExceptionListener(this.onException.bind(this), this.onFatalError.bind(this));
731
736
  addUnhandledRejectionListener(this.onException.bind(this));
732
737
  }
733
738
  }
734
- onException(exception, hint) {
735
- return ErrorTracking.buildEventMessage(exception, hint).then(msg => {
736
- const exceptionProperties = msg.properties;
739
+ async onException(exception, hint) {
740
+ this.client.addPendingPromise((async () => {
741
+ const eventMessage = await ErrorTracking.buildEventMessage(exception, hint);
742
+ const exceptionProperties = eventMessage.properties;
737
743
  const exceptionType = exceptionProperties?.$exception_list[0].type ?? 'Exception';
738
744
  const isRateLimited = this._rateLimiter.consumeRateLimit(exceptionType);
739
745
  if (isRateLimited) {
@@ -742,15 +748,20 @@ class ErrorTracking {
742
748
  });
743
749
  return;
744
750
  }
745
- this.client.capture(msg);
746
- });
751
+ return this.client.capture(eventMessage);
752
+ })());
747
753
  }
748
- async onFatalError() {
754
+ async onFatalError(exception) {
755
+ console.error(exception);
749
756
  await this.client.shutdown(SHUTDOWN_TIMEOUT);
757
+ process.exit(1);
750
758
  }
751
759
  isEnabled() {
752
760
  return !this.client.isDisabled && this._exceptionAutocaptureEnabled;
753
761
  }
762
+ shutdown() {
763
+ this._rateLimiter.stop();
764
+ }
754
765
  }
755
766
 
756
767
  function setupExpressErrorHandler(_posthog, app) {
@@ -1173,7 +1184,7 @@ function snipLine(line, colno) {
1173
1184
  return newLine;
1174
1185
  }
1175
1186
 
1176
- var version = "5.8.0";
1187
+ var version = "5.8.2";
1177
1188
 
1178
1189
  /**
1179
1190
  * A lazy value that is only computed when needed. Inspired by C#'s Lazy<T> class.
@@ -2074,6 +2085,35 @@ const THIRTY_SECONDS = 30 * 1000;
2074
2085
  const MAX_CACHE_SIZE = 50 * 1000;
2075
2086
  // The actual exported Nodejs API.
2076
2087
  class PostHogBackendClient extends PostHogCoreStateless {
2088
+ /**
2089
+ * Initialize a new PostHog client instance.
2090
+ *
2091
+ * @example
2092
+ * ```ts
2093
+ * // Basic initialization
2094
+ * const client = new PostHogBackendClient(
2095
+ * 'your-api-key',
2096
+ * { host: 'https://app.posthog.com' }
2097
+ * )
2098
+ * ```
2099
+ *
2100
+ * @example
2101
+ * ```ts
2102
+ * // With personal API key
2103
+ * const client = new PostHogBackendClient(
2104
+ * 'your-api-key',
2105
+ * {
2106
+ * host: 'https://app.posthog.com',
2107
+ * personalApiKey: 'your-personal-api-key'
2108
+ * }
2109
+ * )
2110
+ * ```
2111
+ *
2112
+ * {@label Initialization}
2113
+ *
2114
+ * @param apiKey - Your PostHog project API key
2115
+ * @param options - Configuration options for the client
2116
+ */
2077
2117
  constructor(apiKey, options = {}) {
2078
2118
  super(apiKey, options);
2079
2119
  this._memoryStorage = new PostHogMemoryStorage();
@@ -2109,176 +2149,302 @@ class PostHogBackendClient extends PostHogCoreStateless {
2109
2149
  this.distinctIdHasSentFlagCalls = {};
2110
2150
  this.maxCacheSize = options.maxCacheSize || MAX_CACHE_SIZE;
2111
2151
  }
2152
+ /**
2153
+ * Get a persisted property value from memory storage.
2154
+ *
2155
+ * @example
2156
+ * ```ts
2157
+ * // Get user ID
2158
+ * const userId = client.getPersistedProperty('userId')
2159
+ * ```
2160
+ *
2161
+ * @example
2162
+ * ```ts
2163
+ * // Get session ID
2164
+ * const sessionId = client.getPersistedProperty('sessionId')
2165
+ * ```
2166
+ *
2167
+ * {@label Initialization}
2168
+ *
2169
+ * @param key - The property key to retrieve
2170
+ * @returns The stored property value or undefined if not found
2171
+ */
2112
2172
  getPersistedProperty(key) {
2113
2173
  return this._memoryStorage.getProperty(key);
2114
2174
  }
2175
+ /**
2176
+ * Set a persisted property value in memory storage.
2177
+ *
2178
+ * @example
2179
+ * ```ts
2180
+ * // Set user ID
2181
+ * client.setPersistedProperty('userId', 'user_123')
2182
+ * ```
2183
+ *
2184
+ * @example
2185
+ * ```ts
2186
+ * // Set session ID
2187
+ * client.setPersistedProperty('sessionId', 'session_456')
2188
+ * ```
2189
+ *
2190
+ * {@label Initialization}
2191
+ *
2192
+ * @param key - The property key to set
2193
+ * @param value - The value to store (null to remove)
2194
+ */
2115
2195
  setPersistedProperty(key, value) {
2116
2196
  return this._memoryStorage.setProperty(key, value);
2117
2197
  }
2198
+ /**
2199
+ * Make an HTTP request using the configured fetch function or default fetch.
2200
+ *
2201
+ * @example
2202
+ * ```ts
2203
+ * // POST request
2204
+ * const response = await client.fetch('/api/endpoint', {
2205
+ * method: 'POST',
2206
+ * headers: { 'Content-Type': 'application/json' },
2207
+ * body: JSON.stringify(data)
2208
+ * })
2209
+ * ```
2210
+ *
2211
+ * @internal
2212
+ *
2213
+ * {@label Initialization}
2214
+ *
2215
+ * @param url - The URL to fetch
2216
+ * @param options - Fetch options
2217
+ * @returns Promise resolving to the fetch response
2218
+ */
2118
2219
  fetch(url, options) {
2119
2220
  return this.options.fetch ? this.options.fetch(url, options) : fetch(url, options);
2120
2221
  }
2222
+ /**
2223
+ * Get the library version from package.json.
2224
+ *
2225
+ * @example
2226
+ * ```ts
2227
+ * // Get version
2228
+ * const version = client.getLibraryVersion()
2229
+ * console.log(`Using PostHog SDK version: ${version}`)
2230
+ * ```
2231
+ *
2232
+ * {@label Initialization}
2233
+ *
2234
+ * @returns The current library version string
2235
+ */
2121
2236
  getLibraryVersion() {
2122
2237
  return version;
2123
2238
  }
2239
+ /**
2240
+ * Get the custom user agent string for this client.
2241
+ *
2242
+ * @example
2243
+ * ```ts
2244
+ * // Get user agent
2245
+ * const userAgent = client.getCustomUserAgent()
2246
+ * // Returns: "posthog-node/5.7.0"
2247
+ * ```
2248
+ *
2249
+ * {@label Identification}
2250
+ *
2251
+ * @returns The formatted user agent string
2252
+ */
2124
2253
  getCustomUserAgent() {
2125
2254
  return `${this.getLibraryId()}/${this.getLibraryVersion()}`;
2126
2255
  }
2256
+ /**
2257
+ * Enable the PostHog client (opt-in).
2258
+ *
2259
+ * @example
2260
+ * ```ts
2261
+ * // Enable client
2262
+ * await client.enable()
2263
+ * // Client is now enabled and will capture events
2264
+ * ```
2265
+ *
2266
+ * {@label Privacy}
2267
+ *
2268
+ * @returns Promise that resolves when the client is enabled
2269
+ */
2127
2270
  enable() {
2128
2271
  return super.optIn();
2129
2272
  }
2273
+ /**
2274
+ * Disable the PostHog client (opt-out).
2275
+ *
2276
+ * @example
2277
+ * ```ts
2278
+ * // Disable client
2279
+ * await client.disable()
2280
+ * // Client is now disabled and will not capture events
2281
+ * ```
2282
+ *
2283
+ * {@label Privacy}
2284
+ *
2285
+ * @returns Promise that resolves when the client is disabled
2286
+ */
2130
2287
  disable() {
2131
2288
  return super.optOut();
2132
2289
  }
2290
+ /**
2291
+ * Enable or disable debug logging.
2292
+ *
2293
+ * @example
2294
+ * ```ts
2295
+ * // Enable debug logging
2296
+ * client.debug(true)
2297
+ * ```
2298
+ *
2299
+ * @example
2300
+ * ```ts
2301
+ * // Disable debug logging
2302
+ * client.debug(false)
2303
+ * ```
2304
+ *
2305
+ * {@label Initialization}
2306
+ *
2307
+ * @param enabled - Whether to enable debug logging
2308
+ */
2133
2309
  debug(enabled = true) {
2134
2310
  super.debug(enabled);
2135
2311
  this.featureFlagsPoller?.debug(enabled);
2136
2312
  }
2313
+ /**
2314
+ * Capture an event manually.
2315
+ *
2316
+ * @example
2317
+ * ```ts
2318
+ * // Basic capture
2319
+ * client.capture({
2320
+ * distinctId: 'user_123',
2321
+ * event: 'button_clicked',
2322
+ * properties: { button_color: 'red' }
2323
+ * })
2324
+ * ```
2325
+ *
2326
+ * {@label Capture}
2327
+ *
2328
+ * @param props - The event properties
2329
+ * @returns void
2330
+ */
2137
2331
  capture(props) {
2138
2332
  if (typeof props === 'string') {
2139
2333
  this.logMsgIfDebug(() => console.warn('Called capture() with a string as the first argument when an object was expected.'));
2140
2334
  }
2141
- const {
2335
+ this.addPendingPromise(this.prepareEventMessage(props).then(({
2142
2336
  distinctId,
2143
2337
  event,
2144
2338
  properties,
2145
- groups,
2146
- sendFeatureFlags,
2147
- timestamp,
2148
- disableGeoip,
2149
- uuid
2150
- } = props;
2151
- // Run before_send if configured
2152
- const eventMessage = this._runBeforeSend({
2153
- distinctId,
2154
- event,
2155
- properties,
2156
- groups,
2157
- sendFeatureFlags,
2158
- timestamp,
2159
- disableGeoip,
2160
- uuid
2161
- });
2162
- if (!eventMessage) {
2163
- return;
2164
- }
2165
- const _capture = props => {
2166
- super.captureStateless(eventMessage.distinctId, eventMessage.event, props, {
2167
- timestamp: eventMessage.timestamp,
2168
- disableGeoip: eventMessage.disableGeoip,
2169
- uuid: eventMessage.uuid
2339
+ options
2340
+ }) => {
2341
+ return super.captureStateless(distinctId, event, properties, {
2342
+ timestamp: options.timestamp,
2343
+ disableGeoip: options.disableGeoip,
2344
+ uuid: options.uuid
2170
2345
  });
2171
- };
2172
- // :TRICKY: If we flush, or need to shut down, to not lose events we want this promise to resolve before we flush
2173
- const capturePromise = Promise.resolve().then(async () => {
2174
- if (sendFeatureFlags) {
2175
- // If we are sending feature flags, we evaluate them locally if the user prefers it, otherwise we fall back to remote evaluation
2176
- const sendFeatureFlagsOptions = typeof sendFeatureFlags === 'object' ? sendFeatureFlags : undefined;
2177
- return await this.getFeatureFlagsForEvent(distinctId, groups, disableGeoip, sendFeatureFlagsOptions);
2178
- }
2179
- if (event === '$feature_flag_called') {
2180
- // 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.
2181
- return {};
2182
- }
2183
- return {};
2184
- }).then(flags => {
2185
- // Derive the relevant flag properties to add
2186
- const additionalProperties = {};
2187
- if (flags) {
2188
- for (const [feature, variant] of Object.entries(flags)) {
2189
- additionalProperties[`$feature/${feature}`] = variant;
2190
- }
2191
- }
2192
- const activeFlags = Object.keys(flags || {}).filter(flag => flags?.[flag] !== false).sort();
2193
- if (activeFlags.length > 0) {
2194
- additionalProperties['$active_feature_flags'] = activeFlags;
2346
+ }).catch(err => {
2347
+ if (err) {
2348
+ console.error(err);
2195
2349
  }
2196
- return additionalProperties;
2197
- }).catch(() => {
2198
- // Something went wrong getting the flag info - we should capture the event anyways
2199
- return {};
2200
- }).then(additionalProperties => {
2201
- // No matter what - capture the event
2202
- _capture({
2203
- ...additionalProperties,
2204
- ...(eventMessage.properties || {}),
2205
- $groups: eventMessage.groups || groups
2206
- });
2207
- });
2208
- this.addPendingPromise(capturePromise);
2350
+ }));
2209
2351
  }
2352
+ /**
2353
+ * Capture an event immediately (synchronously).
2354
+ *
2355
+ * @example
2356
+ * ```ts
2357
+ * // Basic immediate capture
2358
+ * await client.captureImmediate({
2359
+ * distinctId: 'user_123',
2360
+ * event: 'button_clicked',
2361
+ * properties: { button_color: 'red' }
2362
+ * })
2363
+ * ```
2364
+ *
2365
+ * @example
2366
+ * ```ts
2367
+ * // With feature flags
2368
+ * await client.captureImmediate({
2369
+ * distinctId: 'user_123',
2370
+ * event: 'user_action',
2371
+ * sendFeatureFlags: true
2372
+ * })
2373
+ * ```
2374
+ *
2375
+ * @example
2376
+ * ```ts
2377
+ * // With custom feature flags options
2378
+ * await client.captureImmediate({
2379
+ * distinctId: 'user_123',
2380
+ * event: 'user_action',
2381
+ * sendFeatureFlags: {
2382
+ * onlyEvaluateLocally: true,
2383
+ * personProperties: { plan: 'premium' },
2384
+ * groupProperties: { org: { tier: 'enterprise' } }
2385
+ * flagKeys: ['flag1', 'flag2']
2386
+ * }
2387
+ * })
2388
+ * ```
2389
+ *
2390
+ * {@label Capture}
2391
+ *
2392
+ * @param props - The event properties
2393
+ * @returns Promise that resolves when the event is captured
2394
+ */
2210
2395
  async captureImmediate(props) {
2211
2396
  if (typeof props === 'string') {
2212
- this.logMsgIfDebug(() => console.warn('Called capture() with a string as the first argument when an object was expected.'));
2397
+ this.logMsgIfDebug(() => console.warn('Called captureImmediate() with a string as the first argument when an object was expected.'));
2213
2398
  }
2214
- const {
2215
- distinctId,
2216
- event,
2217
- properties,
2218
- groups,
2219
- sendFeatureFlags,
2220
- timestamp,
2221
- disableGeoip,
2222
- uuid
2223
- } = props;
2224
- // Run before_send if configured
2225
- const eventMessage = this._runBeforeSend({
2399
+ return this.addPendingPromise(this.prepareEventMessage(props).then(({
2226
2400
  distinctId,
2227
2401
  event,
2228
2402
  properties,
2229
- groups,
2230
- sendFeatureFlags,
2231
- timestamp,
2232
- disableGeoip,
2233
- uuid
2234
- });
2235
- if (!eventMessage) {
2236
- return;
2237
- }
2238
- const _capture = props => {
2239
- return super.captureStatelessImmediate(eventMessage.distinctId, eventMessage.event, props, {
2240
- timestamp: eventMessage.timestamp,
2241
- disableGeoip: eventMessage.disableGeoip,
2242
- uuid: eventMessage.uuid
2403
+ options
2404
+ }) => {
2405
+ return super.captureStatelessImmediate(distinctId, event, properties, {
2406
+ timestamp: options.timestamp,
2407
+ disableGeoip: options.disableGeoip,
2408
+ uuid: options.uuid
2243
2409
  });
2244
- };
2245
- const capturePromise = Promise.resolve().then(async () => {
2246
- if (sendFeatureFlags) {
2247
- // If we are sending feature flags, we evaluate them locally if the user prefers it, otherwise we fall back to remote evaluation
2248
- const sendFeatureFlagsOptions = typeof sendFeatureFlags === 'object' ? sendFeatureFlags : undefined;
2249
- return await this.getFeatureFlagsForEvent(distinctId, groups, disableGeoip, sendFeatureFlagsOptions);
2250
- }
2251
- if (event === '$feature_flag_called') {
2252
- // 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.
2253
- return {};
2410
+ }).catch(err => {
2411
+ if (err) {
2412
+ console.error(err);
2254
2413
  }
2255
- return {};
2256
- }).then(flags => {
2257
- // Derive the relevant flag properties to add
2258
- const additionalProperties = {};
2259
- if (flags) {
2260
- for (const [feature, variant] of Object.entries(flags)) {
2261
- additionalProperties[`$feature/${feature}`] = variant;
2262
- }
2263
- }
2264
- const activeFlags = Object.keys(flags || {}).filter(flag => flags?.[flag] !== false).sort();
2265
- if (activeFlags.length > 0) {
2266
- additionalProperties['$active_feature_flags'] = activeFlags;
2267
- }
2268
- return additionalProperties;
2269
- }).catch(() => {
2270
- // Something went wrong getting the flag info - we should capture the event anyways
2271
- return {};
2272
- }).then(additionalProperties => {
2273
- // No matter what - capture the event
2274
- _capture({
2275
- ...additionalProperties,
2276
- ...(eventMessage.properties || {}),
2277
- $groups: eventMessage.groups || groups
2278
- });
2279
- });
2280
- await capturePromise;
2414
+ }));
2281
2415
  }
2416
+ /**
2417
+ * Identify a user and set their properties.
2418
+ *
2419
+ * @example
2420
+ * ```ts
2421
+ * // Basic identify with properties
2422
+ * client.identify({
2423
+ * distinctId: 'user_123',
2424
+ * properties: {
2425
+ * name: 'John Doe',
2426
+ * email: 'john@example.com',
2427
+ * plan: 'premium'
2428
+ * }
2429
+ * })
2430
+ * ```
2431
+ *
2432
+ * @example
2433
+ * ```ts
2434
+ * // Using $set and $set_once
2435
+ * client.identify({
2436
+ * distinctId: 'user_123',
2437
+ * properties: {
2438
+ * $set: { name: 'John Doe', email: 'john@example.com' },
2439
+ * $set_once: { first_login: new Date().toISOString() }
2440
+ * }
2441
+ * })
2442
+ * ```
2443
+ *
2444
+ * {@label Identification}
2445
+ *
2446
+ * @param data - The identify data containing distinctId and properties
2447
+ */
2282
2448
  identify({
2283
2449
  distinctId,
2284
2450
  properties,
@@ -2297,6 +2463,26 @@ class PostHogBackendClient extends PostHogCoreStateless {
2297
2463
  disableGeoip
2298
2464
  });
2299
2465
  }
2466
+ /**
2467
+ * Identify a user and set their properties immediately (synchronously).
2468
+ *
2469
+ * @example
2470
+ * ```ts
2471
+ * // Basic immediate identify
2472
+ * await client.identifyImmediate({
2473
+ * distinctId: 'user_123',
2474
+ * properties: {
2475
+ * name: 'John Doe',
2476
+ * email: 'john@example.com'
2477
+ * }
2478
+ * })
2479
+ * ```
2480
+ *
2481
+ * {@label Identification}
2482
+ *
2483
+ * @param data - The identify data containing distinctId and properties
2484
+ * @returns Promise that resolves when the identify is processed
2485
+ */
2300
2486
  async identifyImmediate({
2301
2487
  distinctId,
2302
2488
  properties,
@@ -2314,19 +2500,96 @@ class PostHogBackendClient extends PostHogCoreStateless {
2314
2500
  disableGeoip
2315
2501
  });
2316
2502
  }
2503
+ /**
2504
+ * Create an alias to link two distinct IDs together.
2505
+ *
2506
+ * @example
2507
+ * ```ts
2508
+ * // Link an anonymous user to an identified user
2509
+ * client.alias({
2510
+ * distinctId: 'anonymous_123',
2511
+ * alias: 'user_456'
2512
+ * })
2513
+ * ```
2514
+ *
2515
+ * {@label Identification}
2516
+ *
2517
+ * @param data - The alias data containing distinctId and alias
2518
+ */
2317
2519
  alias(data) {
2318
2520
  super.aliasStateless(data.alias, data.distinctId, undefined, {
2319
2521
  disableGeoip: data.disableGeoip
2320
2522
  });
2321
2523
  }
2524
+ /**
2525
+ * Create an alias to link two distinct IDs together immediately (synchronously).
2526
+ *
2527
+ * @example
2528
+ * ```ts
2529
+ * // Link an anonymous user to an identified user immediately
2530
+ * await client.aliasImmediate({
2531
+ * distinctId: 'anonymous_123',
2532
+ * alias: 'user_456'
2533
+ * })
2534
+ * ```
2535
+ *
2536
+ * {@label Identification}
2537
+ *
2538
+ * @param data - The alias data containing distinctId and alias
2539
+ * @returns Promise that resolves when the alias is processed
2540
+ */
2322
2541
  async aliasImmediate(data) {
2323
2542
  await super.aliasStatelessImmediate(data.alias, data.distinctId, undefined, {
2324
2543
  disableGeoip: data.disableGeoip
2325
2544
  });
2326
2545
  }
2546
+ /**
2547
+ * Check if local evaluation of feature flags is ready.
2548
+ *
2549
+ * @example
2550
+ * ```ts
2551
+ * // Check if ready
2552
+ * if (client.isLocalEvaluationReady()) {
2553
+ * // Local evaluation is ready, can evaluate flags locally
2554
+ * const flag = await client.getFeatureFlag('flag-key', 'user_123')
2555
+ * } else {
2556
+ * // Local evaluation not ready, will use remote evaluation
2557
+ * const flag = await client.getFeatureFlag('flag-key', 'user_123')
2558
+ * }
2559
+ * ```
2560
+ *
2561
+ * {@label Feature flags}
2562
+ *
2563
+ * @returns true if local evaluation is ready, false otherwise
2564
+ */
2327
2565
  isLocalEvaluationReady() {
2328
2566
  return this.featureFlagsPoller?.isLocalEvaluationReady() ?? false;
2329
2567
  }
2568
+ /**
2569
+ * Wait for local evaluation of feature flags to be ready.
2570
+ *
2571
+ * @example
2572
+ * ```ts
2573
+ * // Wait for local evaluation
2574
+ * const isReady = await client.waitForLocalEvaluationReady()
2575
+ * if (isReady) {
2576
+ * console.log('Local evaluation is ready')
2577
+ * } else {
2578
+ * console.log('Local evaluation timed out')
2579
+ * }
2580
+ * ```
2581
+ *
2582
+ * @example
2583
+ * ```ts
2584
+ * // Wait with custom timeout
2585
+ * const isReady = await client.waitForLocalEvaluationReady(10000) // 10 seconds
2586
+ * ```
2587
+ *
2588
+ * {@label Feature flags}
2589
+ *
2590
+ * @param timeoutMs - Timeout in milliseconds (default: 30000)
2591
+ * @returns Promise that resolves to true if ready, false if timed out
2592
+ */
2330
2593
  async waitForLocalEvaluationReady(timeoutMs = THIRTY_SECONDS) {
2331
2594
  if (this.isLocalEvaluationReady()) {
2332
2595
  return true;
@@ -2346,6 +2609,47 @@ class PostHogBackendClient extends PostHogCoreStateless {
2346
2609
  });
2347
2610
  });
2348
2611
  }
2612
+ /**
2613
+ * Get the value of a feature flag for a specific user.
2614
+ *
2615
+ * @example
2616
+ * ```ts
2617
+ * // Basic feature flag check
2618
+ * const flagValue = await client.getFeatureFlag('new-feature', 'user_123')
2619
+ * if (flagValue === 'variant-a') {
2620
+ * // Show variant A
2621
+ * } else if (flagValue === 'variant-b') {
2622
+ * // Show variant B
2623
+ * } else {
2624
+ * // Flag is disabled or not found
2625
+ * }
2626
+ * ```
2627
+ *
2628
+ * @example
2629
+ * ```ts
2630
+ * // With groups and properties
2631
+ * const flagValue = await client.getFeatureFlag('org-feature', 'user_123', {
2632
+ * groups: { organization: 'acme-corp' },
2633
+ * personProperties: { plan: 'enterprise' },
2634
+ * groupProperties: { organization: { tier: 'premium' } }
2635
+ * })
2636
+ * ```
2637
+ *
2638
+ * @example
2639
+ * ```ts
2640
+ * // Only evaluate locally
2641
+ * const flagValue = await client.getFeatureFlag('local-flag', 'user_123', {
2642
+ * onlyEvaluateLocally: true
2643
+ * })
2644
+ * ```
2645
+ *
2646
+ * {@label Feature flags}
2647
+ *
2648
+ * @param key - The feature flag key
2649
+ * @param distinctId - The user's distinct ID
2650
+ * @param options - Optional configuration for flag evaluation
2651
+ * @returns Promise that resolves to the flag value or undefined
2652
+ */
2349
2653
  async getFeatureFlag(key, distinctId, options) {
2350
2654
  const {
2351
2655
  groups,
@@ -2365,7 +2669,7 @@ class PostHogBackendClient extends PostHogCoreStateless {
2365
2669
  onlyEvaluateLocally = false;
2366
2670
  }
2367
2671
  if (sendFeatureFlagEvents == undefined) {
2368
- sendFeatureFlagEvents = true;
2672
+ sendFeatureFlagEvents = this.options.sendFeatureFlagEvent ?? true;
2369
2673
  }
2370
2674
  let response = await this.featureFlagsPoller?.getFeatureFlag(key, distinctId, groups, personProperties, groupProperties);
2371
2675
  const flagWasLocallyEvaluated = response !== undefined;
@@ -2409,6 +2713,41 @@ class PostHogBackendClient extends PostHogCoreStateless {
2409
2713
  }
2410
2714
  return response;
2411
2715
  }
2716
+ /**
2717
+ * Get the payload for a feature flag.
2718
+ *
2719
+ * @example
2720
+ * ```ts
2721
+ * // Get payload for a feature flag
2722
+ * const payload = await client.getFeatureFlagPayload('flag-key', 'user_123')
2723
+ * if (payload) {
2724
+ * console.log('Flag payload:', payload)
2725
+ * }
2726
+ * ```
2727
+ *
2728
+ * @example
2729
+ * ```ts
2730
+ * // Get payload with specific match value
2731
+ * const payload = await client.getFeatureFlagPayload('flag-key', 'user_123', 'variant-a')
2732
+ * ```
2733
+ *
2734
+ * @example
2735
+ * ```ts
2736
+ * // With groups and properties
2737
+ * const payload = await client.getFeatureFlagPayload('org-flag', 'user_123', undefined, {
2738
+ * groups: { organization: 'acme-corp' },
2739
+ * personProperties: { plan: 'enterprise' }
2740
+ * })
2741
+ * ```
2742
+ *
2743
+ * {@label Feature flags}
2744
+ *
2745
+ * @param key - The feature flag key
2746
+ * @param distinctId - The user's distinct ID
2747
+ * @param matchValue - Optional match value to get payload for
2748
+ * @param options - Optional configuration for flag evaluation
2749
+ * @returns Promise that resolves to the flag payload or undefined
2750
+ */
2412
2751
  async getFeatureFlagPayload(key, distinctId, matchValue, options) {
2413
2752
  const {
2414
2753
  groups,
@@ -2416,7 +2755,6 @@ class PostHogBackendClient extends PostHogCoreStateless {
2416
2755
  } = options || {};
2417
2756
  let {
2418
2757
  onlyEvaluateLocally,
2419
- sendFeatureFlagEvents,
2420
2758
  personProperties,
2421
2759
  groupProperties
2422
2760
  } = options || {};
@@ -2441,15 +2779,30 @@ class PostHogBackendClient extends PostHogCoreStateless {
2441
2779
  if (onlyEvaluateLocally == undefined) {
2442
2780
  onlyEvaluateLocally = false;
2443
2781
  }
2444
- if (sendFeatureFlagEvents == undefined) {
2445
- sendFeatureFlagEvents = true;
2446
- }
2447
2782
  const payloadWasLocallyEvaluated = response !== undefined;
2448
2783
  if (!payloadWasLocallyEvaluated && !onlyEvaluateLocally) {
2449
2784
  response = await super.getFeatureFlagPayloadStateless(key, distinctId, groups, personProperties, groupProperties, disableGeoip);
2450
2785
  }
2451
2786
  return response;
2452
2787
  }
2788
+ /**
2789
+ * Get the remote config payload for a feature flag.
2790
+ *
2791
+ * @example
2792
+ * ```ts
2793
+ * // Get remote config payload
2794
+ * const payload = await client.getRemoteConfigPayload('flag-key')
2795
+ * if (payload) {
2796
+ * console.log('Remote config payload:', payload)
2797
+ * }
2798
+ * ```
2799
+ *
2800
+ * {@label Feature flags}
2801
+ *
2802
+ * @param flagKey - The feature flag key
2803
+ * @returns Promise that resolves to the remote config payload or undefined
2804
+ * @throws Error if personal API key is not provided
2805
+ */
2453
2806
  async getRemoteConfigPayload(flagKey) {
2454
2807
  if (!this.options.personalApiKey) {
2455
2808
  throw new Error('Personal API key is required for remote config payload decryption');
@@ -2473,6 +2826,38 @@ class PostHogBackendClient extends PostHogCoreStateless {
2473
2826
  }
2474
2827
  return parsed;
2475
2828
  }
2829
+ /**
2830
+ * Check if a feature flag is enabled for a specific user.
2831
+ *
2832
+ * @example
2833
+ * ```ts
2834
+ * // Basic feature flag check
2835
+ * const isEnabled = await client.isFeatureEnabled('new-feature', 'user_123')
2836
+ * if (isEnabled) {
2837
+ * // Feature is enabled
2838
+ * console.log('New feature is active')
2839
+ * } else {
2840
+ * // Feature is disabled
2841
+ * console.log('New feature is not active')
2842
+ * }
2843
+ * ```
2844
+ *
2845
+ * @example
2846
+ * ```ts
2847
+ * // With groups and properties
2848
+ * const isEnabled = await client.isFeatureEnabled('org-feature', 'user_123', {
2849
+ * groups: { organization: 'acme-corp' },
2850
+ * personProperties: { plan: 'enterprise' }
2851
+ * })
2852
+ * ```
2853
+ *
2854
+ * {@label Feature flags}
2855
+ *
2856
+ * @param key - The feature flag key
2857
+ * @param distinctId - The user's distinct ID
2858
+ * @param options - Optional configuration for flag evaluation
2859
+ * @returns Promise that resolves to true if enabled, false if disabled, undefined if not found
2860
+ */
2476
2861
  async isFeatureEnabled(key, distinctId, options) {
2477
2862
  const feat = await this.getFeatureFlag(key, distinctId, options);
2478
2863
  if (feat === undefined) {
@@ -2480,10 +2865,77 @@ class PostHogBackendClient extends PostHogCoreStateless {
2480
2865
  }
2481
2866
  return !!feat || false;
2482
2867
  }
2868
+ /**
2869
+ * Get all feature flag values for a specific user.
2870
+ *
2871
+ * @example
2872
+ * ```ts
2873
+ * // Get all flags for a user
2874
+ * const allFlags = await client.getAllFlags('user_123')
2875
+ * console.log('User flags:', allFlags)
2876
+ * // Output: { 'flag-1': 'variant-a', 'flag-2': false, 'flag-3': 'variant-b' }
2877
+ * ```
2878
+ *
2879
+ * @example
2880
+ * ```ts
2881
+ * // With specific flag keys
2882
+ * const specificFlags = await client.getAllFlags('user_123', {
2883
+ * flagKeys: ['flag-1', 'flag-2']
2884
+ * })
2885
+ * ```
2886
+ *
2887
+ * @example
2888
+ * ```ts
2889
+ * // With groups and properties
2890
+ * const orgFlags = await client.getAllFlags('user_123', {
2891
+ * groups: { organization: 'acme-corp' },
2892
+ * personProperties: { plan: 'enterprise' }
2893
+ * })
2894
+ * ```
2895
+ *
2896
+ * {@label Feature flags}
2897
+ *
2898
+ * @param distinctId - The user's distinct ID
2899
+ * @param options - Optional configuration for flag evaluation
2900
+ * @returns Promise that resolves to a record of flag keys and their values
2901
+ */
2483
2902
  async getAllFlags(distinctId, options) {
2484
2903
  const response = await this.getAllFlagsAndPayloads(distinctId, options);
2485
2904
  return response.featureFlags || {};
2486
2905
  }
2906
+ /**
2907
+ * Get all feature flag values and payloads for a specific user.
2908
+ *
2909
+ * @example
2910
+ * ```ts
2911
+ * // Get all flags and payloads for a user
2912
+ * const result = await client.getAllFlagsAndPayloads('user_123')
2913
+ * console.log('Flags:', result.featureFlags)
2914
+ * console.log('Payloads:', result.featureFlagPayloads)
2915
+ * ```
2916
+ *
2917
+ * @example
2918
+ * ```ts
2919
+ * // With specific flag keys
2920
+ * const result = await client.getAllFlagsAndPayloads('user_123', {
2921
+ * flagKeys: ['flag-1', 'flag-2']
2922
+ * })
2923
+ * ```
2924
+ *
2925
+ * @example
2926
+ * ```ts
2927
+ * // Only evaluate locally
2928
+ * const result = await client.getAllFlagsAndPayloads('user_123', {
2929
+ * onlyEvaluateLocally: true
2930
+ * })
2931
+ * ```
2932
+ *
2933
+ * {@label Feature flags}
2934
+ *
2935
+ * @param distinctId - The user's distinct ID
2936
+ * @param options - Optional configuration for flag evaluation
2937
+ * @returns Promise that resolves to flags and payloads
2938
+ */
2487
2939
  async getAllFlagsAndPayloads(distinctId, options) {
2488
2940
  const {
2489
2941
  groups,
@@ -2527,6 +2979,41 @@ class PostHogBackendClient extends PostHogCoreStateless {
2527
2979
  featureFlagPayloads
2528
2980
  };
2529
2981
  }
2982
+ /**
2983
+ * Create or update a group and its properties.
2984
+ *
2985
+ * @example
2986
+ * ```ts
2987
+ * // Create a company group
2988
+ * client.groupIdentify({
2989
+ * groupType: 'company',
2990
+ * groupKey: 'acme-corp',
2991
+ * properties: {
2992
+ * name: 'Acme Corporation',
2993
+ * industry: 'Technology',
2994
+ * employee_count: 500
2995
+ * },
2996
+ * distinctId: 'user_123'
2997
+ * })
2998
+ * ```
2999
+ *
3000
+ * @example
3001
+ * ```ts
3002
+ * // Update organization properties
3003
+ * client.groupIdentify({
3004
+ * groupType: 'organization',
3005
+ * groupKey: 'org-456',
3006
+ * properties: {
3007
+ * plan: 'enterprise',
3008
+ * region: 'US-West'
3009
+ * }
3010
+ * })
3011
+ * ```
3012
+ *
3013
+ * {@label Identification}
3014
+ *
3015
+ * @param data - The group identify data
3016
+ */
2530
3017
  groupIdentify({
2531
3018
  groupType,
2532
3019
  groupKey,
@@ -2539,14 +3026,52 @@ class PostHogBackendClient extends PostHogCoreStateless {
2539
3026
  }, distinctId);
2540
3027
  }
2541
3028
  /**
2542
- * Reloads the feature flag definitions from the server for local evaluation.
2543
- * This is useful to call if you want to ensure that the feature flags are up to date before calling getFeatureFlag.
3029
+ * Reload feature flag definitions from the server for local evaluation.
3030
+ *
3031
+ * @example
3032
+ * ```ts
3033
+ * // Force reload of feature flags
3034
+ * await client.reloadFeatureFlags()
3035
+ * console.log('Feature flags reloaded')
3036
+ * ```
3037
+ *
3038
+ * @example
3039
+ * ```ts
3040
+ * // Reload before checking a specific flag
3041
+ * await client.reloadFeatureFlags()
3042
+ * const flag = await client.getFeatureFlag('flag-key', 'user_123')
3043
+ * ```
3044
+ *
3045
+ * {@label Feature flags}
3046
+ *
3047
+ * @returns Promise that resolves when flags are reloaded
2544
3048
  */
2545
3049
  async reloadFeatureFlags() {
2546
3050
  await this.featureFlagsPoller?.loadFeatureFlags(true);
2547
3051
  }
3052
+ /**
3053
+ * Shutdown the PostHog client gracefully.
3054
+ *
3055
+ * @example
3056
+ * ```ts
3057
+ * // Shutdown with default timeout
3058
+ * await client._shutdown()
3059
+ * ```
3060
+ *
3061
+ * @example
3062
+ * ```ts
3063
+ * // Shutdown with custom timeout
3064
+ * await client._shutdown(5000) // 5 seconds
3065
+ * ```
3066
+ *
3067
+ * {@label Shutdown}
3068
+ *
3069
+ * @param shutdownTimeoutMs - Timeout in milliseconds for shutdown
3070
+ * @returns Promise that resolves when shutdown is complete
3071
+ */
2548
3072
  async _shutdown(shutdownTimeoutMs) {
2549
3073
  this.featureFlagsPoller?.stopPoller();
3074
+ this.errorTracking.shutdown();
2550
3075
  return super._shutdown(shutdownTimeoutMs);
2551
3076
  }
2552
3077
  async _requestRemoteConfigPayload(flagKey) {
@@ -2565,7 +3090,7 @@ class PostHogBackendClient extends PostHogCoreStateless {
2565
3090
  let abortTimeout = null;
2566
3091
  if (this.options.requestTimeout && typeof this.options.requestTimeout === 'number') {
2567
3092
  const controller = new AbortController();
2568
- abortTimeout = safeSetTimeout(() => {
3093
+ abortTimeout = safeSetTimeout$1(() => {
2569
3094
  controller.abort();
2570
3095
  }, this.options.requestTimeout);
2571
3096
  options.signal = controller.signal;
@@ -2672,20 +3197,159 @@ class PostHogBackendClient extends PostHogCoreStateless {
2672
3197
  allGroupProperties
2673
3198
  };
2674
3199
  }
3200
+ /**
3201
+ * Capture an error exception as an event.
3202
+ *
3203
+ * @example
3204
+ * ```ts
3205
+ * // Capture an error with user ID
3206
+ * try {
3207
+ * // Some risky operation
3208
+ * riskyOperation()
3209
+ * } catch (error) {
3210
+ * client.captureException(error, 'user_123')
3211
+ * }
3212
+ * ```
3213
+ *
3214
+ * @example
3215
+ * ```ts
3216
+ * // Capture with additional properties
3217
+ * try {
3218
+ * apiCall()
3219
+ * } catch (error) {
3220
+ * client.captureException(error, 'user_123', {
3221
+ * endpoint: '/api/users',
3222
+ * method: 'POST',
3223
+ * status_code: 500
3224
+ * })
3225
+ * }
3226
+ * ```
3227
+ *
3228
+ * {@label Error tracking}
3229
+ *
3230
+ * @param error - The error to capture
3231
+ * @param distinctId - Optional user distinct ID
3232
+ * @param additionalProperties - Optional additional properties to include
3233
+ */
2675
3234
  captureException(error, distinctId, additionalProperties) {
2676
3235
  const syntheticException = new Error('PostHog syntheticException');
2677
- ErrorTracking.buildEventMessage(error, {
3236
+ this.addPendingPromise(ErrorTracking.buildEventMessage(error, {
2678
3237
  syntheticException
2679
- }, distinctId, additionalProperties).then(msg => {
2680
- this.capture(msg);
2681
- });
3238
+ }, distinctId, additionalProperties).then(msg => this.capture(msg)));
2682
3239
  }
3240
+ /**
3241
+ * Capture an error exception as an event immediately (synchronously).
3242
+ *
3243
+ * @example
3244
+ * ```ts
3245
+ * // Capture an error immediately with user ID
3246
+ * try {
3247
+ * // Some risky operation
3248
+ * riskyOperation()
3249
+ * } catch (error) {
3250
+ * await client.captureExceptionImmediate(error, 'user_123')
3251
+ * }
3252
+ * ```
3253
+ *
3254
+ * @example
3255
+ * ```ts
3256
+ * // Capture with additional properties
3257
+ * try {
3258
+ * apiCall()
3259
+ * } catch (error) {
3260
+ * await client.captureExceptionImmediate(error, 'user_123', {
3261
+ * endpoint: '/api/users',
3262
+ * method: 'POST',
3263
+ * status_code: 500
3264
+ * })
3265
+ * }
3266
+ * ```
3267
+ *
3268
+ * {@label Error tracking}
3269
+ *
3270
+ * @param error - The error to capture
3271
+ * @param distinctId - Optional user distinct ID
3272
+ * @param additionalProperties - Optional additional properties to include
3273
+ * @returns Promise that resolves when the error is captured
3274
+ */
2683
3275
  async captureExceptionImmediate(error, distinctId, additionalProperties) {
2684
3276
  const syntheticException = new Error('PostHog syntheticException');
2685
- const evtMsg = await ErrorTracking.buildEventMessage(error, {
3277
+ this.addPendingPromise(ErrorTracking.buildEventMessage(error, {
2686
3278
  syntheticException
2687
- }, distinctId, additionalProperties);
2688
- return await this.captureImmediate(evtMsg);
3279
+ }, distinctId, additionalProperties).then(msg => this.captureImmediate(msg)));
3280
+ }
3281
+ async prepareEventMessage(props) {
3282
+ const {
3283
+ distinctId,
3284
+ event,
3285
+ properties,
3286
+ groups,
3287
+ sendFeatureFlags,
3288
+ timestamp,
3289
+ disableGeoip,
3290
+ uuid
3291
+ } = props;
3292
+ // Run before_send if configured
3293
+ const eventMessage = this._runBeforeSend({
3294
+ distinctId,
3295
+ event,
3296
+ properties,
3297
+ groups,
3298
+ sendFeatureFlags,
3299
+ timestamp,
3300
+ disableGeoip,
3301
+ uuid
3302
+ });
3303
+ if (!eventMessage) {
3304
+ return Promise.reject(null);
3305
+ }
3306
+ // :TRICKY: If we flush, or need to shut down, to not lose events we want this promise to resolve before we flush
3307
+ const eventProperties = await Promise.resolve().then(async () => {
3308
+ if (sendFeatureFlags) {
3309
+ // If we are sending feature flags, we evaluate them locally if the user prefers it, otherwise we fall back to remote evaluation
3310
+ const sendFeatureFlagsOptions = typeof sendFeatureFlags === 'object' ? sendFeatureFlags : undefined;
3311
+ return await this.getFeatureFlagsForEvent(distinctId, groups, disableGeoip, sendFeatureFlagsOptions);
3312
+ }
3313
+ if (event === '$feature_flag_called') {
3314
+ // 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.
3315
+ return {};
3316
+ }
3317
+ return {};
3318
+ }).then(flags => {
3319
+ // Derive the relevant flag properties to add
3320
+ const additionalProperties = {};
3321
+ if (flags) {
3322
+ for (const [feature, variant] of Object.entries(flags)) {
3323
+ additionalProperties[`$feature/${feature}`] = variant;
3324
+ }
3325
+ }
3326
+ const activeFlags = Object.keys(flags || {}).filter(flag => flags?.[flag] !== false).sort();
3327
+ if (activeFlags.length > 0) {
3328
+ additionalProperties['$active_feature_flags'] = activeFlags;
3329
+ }
3330
+ return additionalProperties;
3331
+ }).catch(() => {
3332
+ // Something went wrong getting the flag info - we should capture the event anyways
3333
+ return {};
3334
+ }).then(additionalProperties => {
3335
+ // No matter what - capture the event
3336
+ const props = {
3337
+ ...additionalProperties,
3338
+ ...(eventMessage.properties || {}),
3339
+ $groups: eventMessage.groups || groups
3340
+ };
3341
+ return props;
3342
+ });
3343
+ return {
3344
+ distinctId: eventMessage.distinctId,
3345
+ event: eventMessage.event,
3346
+ properties: eventProperties,
3347
+ options: {
3348
+ timestamp: eventMessage.timestamp,
3349
+ disableGeoip: eventMessage.disableGeoip,
3350
+ uuid: eventMessage.uuid
3351
+ }
3352
+ };
2689
3353
  }
2690
3354
  _runBeforeSend(eventMessage) {
2691
3355
  const beforeSend = this.options.before_send;