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.cjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
738
|
-
const
|
|
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(
|
|
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.
|
|
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
|
-
|
|
2337
|
+
this.addPendingPromise(this.prepareEventMessage(props).then(({
|
|
2144
2338
|
distinctId,
|
|
2145
2339
|
event,
|
|
2146
2340
|
properties,
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
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
|
-
|
|
2175
|
-
|
|
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
|
-
|
|
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
|
|
2399
|
+
this.logMsgIfDebug(() => console.warn('Called captureImmediate() with a string as the first argument when an object was expected.'));
|
|
2215
2400
|
}
|
|
2216
|
-
|
|
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
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
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
|
-
|
|
2248
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
2545
|
-
*
|
|
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
|
-
|
|
3279
|
+
this.addPendingPromise(ErrorTracking.buildEventMessage(error, {
|
|
2688
3280
|
syntheticException
|
|
2689
|
-
}, distinctId, additionalProperties);
|
|
2690
|
-
|
|
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;
|