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