posthog-node 5.8.0 → 5.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/edge/index.cjs +834 -170
- package/dist/edge/index.cjs.map +1 -1
- package/dist/edge/index.mjs +835 -171
- package/dist/edge/index.mjs.map +1 -1
- package/dist/index.d.ts +701 -4
- package/dist/node/index.cjs +834 -170
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.mjs +835 -171
- package/dist/node/index.mjs.map +1 -1
- package/package.json +5 -3
package/dist/node/index.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
736
|
-
const
|
|
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(
|
|
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.
|
|
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
|
-
|
|
2335
|
+
this.addPendingPromise(this.prepareEventMessage(props).then(({
|
|
2142
2336
|
distinctId,
|
|
2143
2337
|
event,
|
|
2144
2338
|
properties,
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
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
|
-
|
|
2173
|
-
|
|
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
|
-
|
|
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
|
|
2397
|
+
this.logMsgIfDebug(() => console.warn('Called captureImmediate() with a string as the first argument when an object was expected.'));
|
|
2213
2398
|
}
|
|
2214
|
-
|
|
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
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
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
|
-
|
|
2246
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
2543
|
-
*
|
|
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
|
-
|
|
3277
|
+
this.addPendingPromise(ErrorTracking.buildEventMessage(error, {
|
|
2686
3278
|
syntheticException
|
|
2687
|
-
}, distinctId, additionalProperties);
|
|
2688
|
-
|
|
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;
|