posthog-node 5.7.0 → 5.8.1
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 +318 -156
- package/dist/edge/index.cjs.map +1 -1
- package/dist/edge/index.mjs +319 -157
- package/dist/edge/index.mjs.map +1 -1
- package/dist/index.d.ts +16 -4
- package/dist/node/index.cjs +318 -156
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.mjs +319 -157
- package/dist/node/index.mjs.map +1 -1
- package/package.json +2 -2
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 {
|
|
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,15 +615,102 @@ 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
|
});
|
|
623
622
|
return frames;
|
|
624
623
|
}
|
|
625
624
|
|
|
625
|
+
const ObjProto = Object.prototype;
|
|
626
|
+
const type_utils_toString = ObjProto.toString;
|
|
627
|
+
const isNumber = (x)=>'[object Number]' == type_utils_toString.call(x);
|
|
628
|
+
|
|
629
|
+
function clampToRange(value, min, max, logger, fallbackValue) {
|
|
630
|
+
if (min > max) {
|
|
631
|
+
logger.warn('min cannot be greater than max.');
|
|
632
|
+
min = max;
|
|
633
|
+
}
|
|
634
|
+
if (isNumber(value)) if (value > max) {
|
|
635
|
+
logger.warn(' cannot be greater than max: ' + max + '. Using max value instead.');
|
|
636
|
+
return max;
|
|
637
|
+
} else {
|
|
638
|
+
if (!(value < min)) return value;
|
|
639
|
+
logger.warn(' cannot be less than min: ' + min + '. Using min value instead.');
|
|
640
|
+
return min;
|
|
641
|
+
}
|
|
642
|
+
logger.warn(' must be a number. using max or fallback. max: ' + max + ', fallback: ' + fallbackValue);
|
|
643
|
+
return clampToRange(max, min, max, logger);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
class BucketedRateLimiter {
|
|
647
|
+
stop() {
|
|
648
|
+
if (this._removeInterval) {
|
|
649
|
+
clearInterval(this._removeInterval);
|
|
650
|
+
this._removeInterval = void 0;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
constructor(_options){
|
|
654
|
+
this._options = _options;
|
|
655
|
+
this._buckets = {};
|
|
656
|
+
this._refillBuckets = ()=>{
|
|
657
|
+
Object.keys(this._buckets).forEach((key)=>{
|
|
658
|
+
const newTokens = this._getBucket(key) + this._refillRate;
|
|
659
|
+
if (newTokens >= this._bucketSize) delete this._buckets[key];
|
|
660
|
+
else this._setBucket(key, newTokens);
|
|
661
|
+
});
|
|
662
|
+
};
|
|
663
|
+
this._getBucket = (key)=>this._buckets[String(key)];
|
|
664
|
+
this._setBucket = (key, value)=>{
|
|
665
|
+
this._buckets[String(key)] = value;
|
|
666
|
+
};
|
|
667
|
+
this.consumeRateLimit = (key)=>{
|
|
668
|
+
var _this__getBucket;
|
|
669
|
+
let tokens = null != (_this__getBucket = this._getBucket(key)) ? _this__getBucket : this._bucketSize;
|
|
670
|
+
tokens = Math.max(tokens - 1, 0);
|
|
671
|
+
if (0 === tokens) return true;
|
|
672
|
+
this._setBucket(key, tokens);
|
|
673
|
+
const hasReachedZero = 0 === tokens;
|
|
674
|
+
if (hasReachedZero) {
|
|
675
|
+
var _this__onBucketRateLimited, _this;
|
|
676
|
+
null == (_this__onBucketRateLimited = (_this = this)._onBucketRateLimited) || _this__onBucketRateLimited.call(_this, key);
|
|
677
|
+
}
|
|
678
|
+
return hasReachedZero;
|
|
679
|
+
};
|
|
680
|
+
this._onBucketRateLimited = this._options._onBucketRateLimited;
|
|
681
|
+
this._bucketSize = clampToRange(this._options.bucketSize, 0, 100, this._options._logger);
|
|
682
|
+
this._refillRate = clampToRange(this._options.refillRate, 0, this._bucketSize, this._options._logger);
|
|
683
|
+
this._refillInterval = clampToRange(this._options.refillInterval, 0, 86400000, this._options._logger);
|
|
684
|
+
this._removeInterval = setInterval(()=>{
|
|
685
|
+
this._refillBuckets();
|
|
686
|
+
}, this._refillInterval);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function safeSetTimeout(fn, timeout) {
|
|
691
|
+
const t = setTimeout(fn, timeout);
|
|
692
|
+
(null == t ? void 0 : t.unref) && (null == t || t.unref());
|
|
693
|
+
return t;
|
|
694
|
+
}
|
|
695
|
+
|
|
626
696
|
const SHUTDOWN_TIMEOUT = 2000;
|
|
627
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
|
+
}
|
|
628
714
|
static async buildEventMessage(error, hint, distinctId, additionalProperties) {
|
|
629
715
|
const properties = {
|
|
630
716
|
...additionalProperties
|
|
@@ -644,28 +730,38 @@ class ErrorTracking {
|
|
|
644
730
|
}
|
|
645
731
|
};
|
|
646
732
|
}
|
|
647
|
-
constructor(client, options) {
|
|
648
|
-
this.client = client;
|
|
649
|
-
this._exceptionAutocaptureEnabled = options.enableExceptionAutocapture || false;
|
|
650
|
-
this.startAutocaptureIfEnabled();
|
|
651
|
-
}
|
|
652
733
|
startAutocaptureIfEnabled() {
|
|
653
734
|
if (this.isEnabled()) {
|
|
654
735
|
addUncaughtExceptionListener(this.onException.bind(this), this.onFatalError.bind(this));
|
|
655
736
|
addUnhandledRejectionListener(this.onException.bind(this));
|
|
656
737
|
}
|
|
657
738
|
}
|
|
658
|
-
onException(exception, hint) {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
739
|
+
async onException(exception, hint) {
|
|
740
|
+
this.client.addPendingPromise((async () => {
|
|
741
|
+
const eventMessage = await ErrorTracking.buildEventMessage(exception, hint);
|
|
742
|
+
const exceptionProperties = eventMessage.properties;
|
|
743
|
+
const exceptionType = exceptionProperties?.$exception_list[0].type ?? 'Exception';
|
|
744
|
+
const isRateLimited = this._rateLimiter.consumeRateLimit(exceptionType);
|
|
745
|
+
if (isRateLimited) {
|
|
746
|
+
this._logger.info('Skipping exception capture because of client rate limiting.', {
|
|
747
|
+
exception: exceptionType
|
|
748
|
+
});
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
return this.client.capture(eventMessage);
|
|
752
|
+
})());
|
|
662
753
|
}
|
|
663
|
-
async onFatalError() {
|
|
754
|
+
async onFatalError(exception) {
|
|
755
|
+
console.error(exception);
|
|
664
756
|
await this.client.shutdown(SHUTDOWN_TIMEOUT);
|
|
757
|
+
process.exit(1);
|
|
665
758
|
}
|
|
666
759
|
isEnabled() {
|
|
667
760
|
return !this.client.isDisabled && this._exceptionAutocaptureEnabled;
|
|
668
761
|
}
|
|
762
|
+
shutdown() {
|
|
763
|
+
this._rateLimiter.stop();
|
|
764
|
+
}
|
|
669
765
|
}
|
|
670
766
|
|
|
671
767
|
function setupExpressErrorHandler(_posthog, app) {
|
|
@@ -1088,7 +1184,7 @@ function snipLine(line, colno) {
|
|
|
1088
1184
|
return newLine;
|
|
1089
1185
|
}
|
|
1090
1186
|
|
|
1091
|
-
var version = "5.
|
|
1187
|
+
var version = "5.8.1";
|
|
1092
1188
|
|
|
1093
1189
|
/**
|
|
1094
1190
|
* A lazy value that is only computed when needed. Inspired by C#'s Lazy<T> class.
|
|
@@ -1376,7 +1472,70 @@ class FeatureFlagsPoller {
|
|
|
1376
1472
|
}
|
|
1377
1473
|
return null;
|
|
1378
1474
|
}
|
|
1379
|
-
async
|
|
1475
|
+
async evaluateFlagDependency(property, distinctId, properties, evaluationCache) {
|
|
1476
|
+
const targetFlagKey = property.key;
|
|
1477
|
+
if (!this.featureFlagsByKey) {
|
|
1478
|
+
throw new InconclusiveMatchError('Feature flags not available for dependency evaluation');
|
|
1479
|
+
}
|
|
1480
|
+
// Check if dependency_chain is present - it should always be provided for flag dependencies
|
|
1481
|
+
if (!('dependency_chain' in property)) {
|
|
1482
|
+
throw new InconclusiveMatchError(`Flag dependency property for '${targetFlagKey}' is missing required 'dependency_chain' field`);
|
|
1483
|
+
}
|
|
1484
|
+
const dependencyChain = property.dependency_chain;
|
|
1485
|
+
// Check for missing or invalid dependency chain (This should never happen, but being defensive)
|
|
1486
|
+
if (!Array.isArray(dependencyChain)) {
|
|
1487
|
+
throw new InconclusiveMatchError(`Flag dependency property for '${targetFlagKey}' has an invalid 'dependency_chain' (expected array, got ${typeof dependencyChain})`);
|
|
1488
|
+
}
|
|
1489
|
+
// Handle circular dependency (empty chain means circular) (This should never happen, but being defensive)
|
|
1490
|
+
if (dependencyChain.length === 0) {
|
|
1491
|
+
throw new InconclusiveMatchError(`Circular dependency detected for flag '${targetFlagKey}' (empty dependency chain)`);
|
|
1492
|
+
}
|
|
1493
|
+
// Evaluate all dependencies in the chain order
|
|
1494
|
+
for (const depFlagKey of dependencyChain) {
|
|
1495
|
+
if (!(depFlagKey in evaluationCache)) {
|
|
1496
|
+
// Need to evaluate this dependency first
|
|
1497
|
+
const depFlag = this.featureFlagsByKey[depFlagKey];
|
|
1498
|
+
if (!depFlag) {
|
|
1499
|
+
// Missing flag dependency - cannot evaluate locally
|
|
1500
|
+
throw new InconclusiveMatchError(`Missing flag dependency '${depFlagKey}' for flag '${targetFlagKey}'`);
|
|
1501
|
+
} else if (!depFlag.active) {
|
|
1502
|
+
// Inactive flag evaluates to false
|
|
1503
|
+
evaluationCache[depFlagKey] = false;
|
|
1504
|
+
} else {
|
|
1505
|
+
// Recursively evaluate the dependency
|
|
1506
|
+
try {
|
|
1507
|
+
const depResult = await this.matchFeatureFlagProperties(depFlag, distinctId, properties, evaluationCache);
|
|
1508
|
+
evaluationCache[depFlagKey] = depResult;
|
|
1509
|
+
} catch (error) {
|
|
1510
|
+
// If we can't evaluate a dependency, store throw InconclusiveMatchError(`Missing flag dependency '${depFlagKey}' for flag '${targetFlagKey}'`)
|
|
1511
|
+
throw new InconclusiveMatchError(`Error evaluating flag dependency '${depFlagKey}' for flag '${targetFlagKey}': ${error}`);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
// Check if dependency evaluation was inconclusive
|
|
1516
|
+
const cachedResult = evaluationCache[depFlagKey];
|
|
1517
|
+
if (cachedResult === null || cachedResult === undefined) {
|
|
1518
|
+
throw new InconclusiveMatchError(`Dependency '${depFlagKey}' could not be evaluated`);
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
// The target flag is specified in property.key (This should match the last element in the dependency chain)
|
|
1522
|
+
const targetFlagValue = evaluationCache[targetFlagKey];
|
|
1523
|
+
return this.flagEvaluatesToExpectedValue(property.value, targetFlagValue);
|
|
1524
|
+
}
|
|
1525
|
+
flagEvaluatesToExpectedValue(expectedValue, flagValue) {
|
|
1526
|
+
// If the expected value is a boolean, then return true if the flag evaluated to true (or any string variant)
|
|
1527
|
+
// If the expected value is false, then only return true if the flag evaluated to false.
|
|
1528
|
+
if (typeof expectedValue === 'boolean') {
|
|
1529
|
+
return expectedValue === flagValue || typeof flagValue === 'string' && flagValue !== '' && expectedValue === true;
|
|
1530
|
+
}
|
|
1531
|
+
// If the expected value is a string, then return true if and only if the flag evaluated to the expected value.
|
|
1532
|
+
if (typeof expectedValue === 'string') {
|
|
1533
|
+
return flagValue === expectedValue;
|
|
1534
|
+
}
|
|
1535
|
+
// The `flag_evaluates_to` operator is not supported for numbers and arrays.
|
|
1536
|
+
return false;
|
|
1537
|
+
}
|
|
1538
|
+
async matchFeatureFlagProperties(flag, distinctId, properties, evaluationCache = {}) {
|
|
1380
1539
|
const flagFilters = flag.filters || {};
|
|
1381
1540
|
const flagConditions = flagFilters.groups || [];
|
|
1382
1541
|
let isInconclusive = false;
|
|
@@ -1398,7 +1557,7 @@ class FeatureFlagsPoller {
|
|
|
1398
1557
|
});
|
|
1399
1558
|
for (const condition of sortedFlagConditions) {
|
|
1400
1559
|
try {
|
|
1401
|
-
if (await this.isConditionMatch(flag, distinctId, condition, properties)) {
|
|
1560
|
+
if (await this.isConditionMatch(flag, distinctId, condition, properties, evaluationCache)) {
|
|
1402
1561
|
const variantOverride = condition.variant;
|
|
1403
1562
|
const flagVariants = flagFilters.multivariate?.variants || [];
|
|
1404
1563
|
if (variantOverride && flagVariants.some(variant => variant.key === variantOverride)) {
|
|
@@ -1424,7 +1583,7 @@ class FeatureFlagsPoller {
|
|
|
1424
1583
|
// We can only return False when all conditions are False
|
|
1425
1584
|
return false;
|
|
1426
1585
|
}
|
|
1427
|
-
async isConditionMatch(flag, distinctId, condition, properties) {
|
|
1586
|
+
async isConditionMatch(flag, distinctId, condition, properties, evaluationCache = {}) {
|
|
1428
1587
|
const rolloutPercentage = condition.rollout_percentage;
|
|
1429
1588
|
const warnFunction = msg => {
|
|
1430
1589
|
this.logMsgIfDebug(() => console.warn(msg));
|
|
@@ -1436,8 +1595,7 @@ class FeatureFlagsPoller {
|
|
|
1436
1595
|
if (propertyType === 'cohort') {
|
|
1437
1596
|
matches = matchCohort(prop, properties, this.cohorts, this.debugMode);
|
|
1438
1597
|
} else if (propertyType === 'flag') {
|
|
1439
|
-
|
|
1440
|
-
continue;
|
|
1598
|
+
matches = await this.evaluateFlagDependency(prop, distinctId, properties, evaluationCache);
|
|
1441
1599
|
} else {
|
|
1442
1600
|
matches = matchProperty(prop, properties, warnFunction);
|
|
1443
1601
|
}
|
|
@@ -1702,6 +1860,10 @@ function matchProperty(property, propertyValues, warnFunction) {
|
|
|
1702
1860
|
case 'is_date_after':
|
|
1703
1861
|
case 'is_date_before':
|
|
1704
1862
|
{
|
|
1863
|
+
// Boolean values should never be used with date operations
|
|
1864
|
+
if (typeof value === 'boolean') {
|
|
1865
|
+
throw new InconclusiveMatchError(`Date operations cannot be performed on boolean values`);
|
|
1866
|
+
}
|
|
1705
1867
|
let parsedDate = relativeDateParseForFeatureFlagMatching(String(value));
|
|
1706
1868
|
if (parsedDate == null) {
|
|
1707
1869
|
parsedDate = convertToDateTime(value);
|
|
@@ -1885,6 +2047,37 @@ class PostHogMemoryStorage {
|
|
|
1885
2047
|
}
|
|
1886
2048
|
}
|
|
1887
2049
|
|
|
2050
|
+
const _createLogger = (prefix, logMsgIfDebug) => {
|
|
2051
|
+
const logger = {
|
|
2052
|
+
_log: (level, ...args) => {
|
|
2053
|
+
logMsgIfDebug(() => {
|
|
2054
|
+
const consoleLog = console[level];
|
|
2055
|
+
consoleLog(prefix, ...args);
|
|
2056
|
+
});
|
|
2057
|
+
},
|
|
2058
|
+
info: (...args) => {
|
|
2059
|
+
logger._log('log', ...args);
|
|
2060
|
+
},
|
|
2061
|
+
warn: (...args) => {
|
|
2062
|
+
logger._log('warn', ...args);
|
|
2063
|
+
},
|
|
2064
|
+
error: (...args) => {
|
|
2065
|
+
logger._log('error', ...args);
|
|
2066
|
+
},
|
|
2067
|
+
critical: (...args) => {
|
|
2068
|
+
// Critical errors are always logged to the console
|
|
2069
|
+
// eslint-disable-next-line no-console
|
|
2070
|
+
console.error(prefix, ...args);
|
|
2071
|
+
},
|
|
2072
|
+
uninitializedWarning: methodName => {
|
|
2073
|
+
logger.error(`You must initialize PostHog before calling ${methodName}`);
|
|
2074
|
+
},
|
|
2075
|
+
createLogger: additionalPrefix => _createLogger(`${prefix} ${additionalPrefix}`, logMsgIfDebug)
|
|
2076
|
+
};
|
|
2077
|
+
return logger;
|
|
2078
|
+
};
|
|
2079
|
+
const createLogger = logMsgIfDebug => _createLogger('[PostHog.js]', logMsgIfDebug);
|
|
2080
|
+
|
|
1888
2081
|
// Standard local evaluation rate limit is 600 per minute (10 per second),
|
|
1889
2082
|
// so the fastest a poller should ever be set is 100ms.
|
|
1890
2083
|
const MINIMUM_POLLING_INTERVAL = 100;
|
|
@@ -1896,6 +2089,7 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
1896
2089
|
super(apiKey, options);
|
|
1897
2090
|
this._memoryStorage = new PostHogMemoryStorage();
|
|
1898
2091
|
this.options = options;
|
|
2092
|
+
this.logger = createLogger(this.logMsgIfDebug.bind(this));
|
|
1899
2093
|
this.options.featureFlagsPollingInterval = typeof options.featureFlagsPollingInterval === 'number' ? Math.max(options.featureFlagsPollingInterval, MINIMUM_POLLING_INTERVAL) : THIRTY_SECONDS;
|
|
1900
2094
|
if (options.personalApiKey) {
|
|
1901
2095
|
if (options.personalApiKey.includes('phc_')) {
|
|
@@ -1922,7 +2116,7 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
1922
2116
|
});
|
|
1923
2117
|
}
|
|
1924
2118
|
}
|
|
1925
|
-
this.errorTracking = new ErrorTracking(this, options);
|
|
2119
|
+
this.errorTracking = new ErrorTracking(this, options, this.logger);
|
|
1926
2120
|
this.distinctIdHasSentFlagCalls = {};
|
|
1927
2121
|
this.maxCacheSize = options.maxCacheSize || MAX_CACHE_SIZE;
|
|
1928
2122
|
}
|
|
@@ -1955,146 +2149,43 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
1955
2149
|
if (typeof props === 'string') {
|
|
1956
2150
|
this.logMsgIfDebug(() => console.warn('Called capture() with a string as the first argument when an object was expected.'));
|
|
1957
2151
|
}
|
|
1958
|
-
|
|
2152
|
+
this.addPendingPromise(this.prepareEventMessage(props).then(({
|
|
1959
2153
|
distinctId,
|
|
1960
2154
|
event,
|
|
1961
2155
|
properties,
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
// Run before_send if configured
|
|
1969
|
-
const eventMessage = this._runBeforeSend({
|
|
1970
|
-
distinctId,
|
|
1971
|
-
event,
|
|
1972
|
-
properties,
|
|
1973
|
-
groups,
|
|
1974
|
-
sendFeatureFlags,
|
|
1975
|
-
timestamp,
|
|
1976
|
-
disableGeoip,
|
|
1977
|
-
uuid
|
|
1978
|
-
});
|
|
1979
|
-
if (!eventMessage) {
|
|
1980
|
-
return;
|
|
1981
|
-
}
|
|
1982
|
-
const _capture = props => {
|
|
1983
|
-
super.captureStateless(eventMessage.distinctId, eventMessage.event, props, {
|
|
1984
|
-
timestamp: eventMessage.timestamp,
|
|
1985
|
-
disableGeoip: eventMessage.disableGeoip,
|
|
1986
|
-
uuid: eventMessage.uuid
|
|
2156
|
+
options
|
|
2157
|
+
}) => {
|
|
2158
|
+
return super.captureStateless(distinctId, event, properties, {
|
|
2159
|
+
timestamp: options.timestamp,
|
|
2160
|
+
disableGeoip: options.disableGeoip,
|
|
2161
|
+
uuid: options.uuid
|
|
1987
2162
|
});
|
|
1988
|
-
}
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
if (sendFeatureFlags) {
|
|
1992
|
-
// If we are sending feature flags, we evaluate them locally if the user prefers it, otherwise we fall back to remote evaluation
|
|
1993
|
-
const sendFeatureFlagsOptions = typeof sendFeatureFlags === 'object' ? sendFeatureFlags : undefined;
|
|
1994
|
-
return await this.getFeatureFlagsForEvent(distinctId, groups, disableGeoip, sendFeatureFlagsOptions);
|
|
1995
|
-
}
|
|
1996
|
-
if (event === '$feature_flag_called') {
|
|
1997
|
-
// 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.
|
|
1998
|
-
return {};
|
|
1999
|
-
}
|
|
2000
|
-
return {};
|
|
2001
|
-
}).then(flags => {
|
|
2002
|
-
// Derive the relevant flag properties to add
|
|
2003
|
-
const additionalProperties = {};
|
|
2004
|
-
if (flags) {
|
|
2005
|
-
for (const [feature, variant] of Object.entries(flags)) {
|
|
2006
|
-
additionalProperties[`$feature/${feature}`] = variant;
|
|
2007
|
-
}
|
|
2008
|
-
}
|
|
2009
|
-
const activeFlags = Object.keys(flags || {}).filter(flag => flags?.[flag] !== false).sort();
|
|
2010
|
-
if (activeFlags.length > 0) {
|
|
2011
|
-
additionalProperties['$active_feature_flags'] = activeFlags;
|
|
2163
|
+
}).catch(err => {
|
|
2164
|
+
if (err) {
|
|
2165
|
+
console.error(err);
|
|
2012
2166
|
}
|
|
2013
|
-
|
|
2014
|
-
}).catch(() => {
|
|
2015
|
-
// Something went wrong getting the flag info - we should capture the event anyways
|
|
2016
|
-
return {};
|
|
2017
|
-
}).then(additionalProperties => {
|
|
2018
|
-
// No matter what - capture the event
|
|
2019
|
-
_capture({
|
|
2020
|
-
...additionalProperties,
|
|
2021
|
-
...(eventMessage.properties || {}),
|
|
2022
|
-
$groups: eventMessage.groups || groups
|
|
2023
|
-
});
|
|
2024
|
-
});
|
|
2025
|
-
this.addPendingPromise(capturePromise);
|
|
2167
|
+
}));
|
|
2026
2168
|
}
|
|
2027
2169
|
async captureImmediate(props) {
|
|
2028
2170
|
if (typeof props === 'string') {
|
|
2029
|
-
this.logMsgIfDebug(() => console.warn('Called
|
|
2171
|
+
this.logMsgIfDebug(() => console.warn('Called captureImmediate() with a string as the first argument when an object was expected.'));
|
|
2030
2172
|
}
|
|
2031
|
-
|
|
2173
|
+
return this.addPendingPromise(this.prepareEventMessage(props).then(({
|
|
2032
2174
|
distinctId,
|
|
2033
2175
|
event,
|
|
2034
2176
|
properties,
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
// Run before_send if configured
|
|
2042
|
-
const eventMessage = this._runBeforeSend({
|
|
2043
|
-
distinctId,
|
|
2044
|
-
event,
|
|
2045
|
-
properties,
|
|
2046
|
-
groups,
|
|
2047
|
-
sendFeatureFlags,
|
|
2048
|
-
timestamp,
|
|
2049
|
-
disableGeoip,
|
|
2050
|
-
uuid
|
|
2051
|
-
});
|
|
2052
|
-
if (!eventMessage) {
|
|
2053
|
-
return;
|
|
2054
|
-
}
|
|
2055
|
-
const _capture = props => {
|
|
2056
|
-
return super.captureStatelessImmediate(eventMessage.distinctId, eventMessage.event, props, {
|
|
2057
|
-
timestamp: eventMessage.timestamp,
|
|
2058
|
-
disableGeoip: eventMessage.disableGeoip,
|
|
2059
|
-
uuid: eventMessage.uuid
|
|
2177
|
+
options
|
|
2178
|
+
}) => {
|
|
2179
|
+
return super.captureStatelessImmediate(distinctId, event, properties, {
|
|
2180
|
+
timestamp: options.timestamp,
|
|
2181
|
+
disableGeoip: options.disableGeoip,
|
|
2182
|
+
uuid: options.uuid
|
|
2060
2183
|
});
|
|
2061
|
-
}
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
// If we are sending feature flags, we evaluate them locally if the user prefers it, otherwise we fall back to remote evaluation
|
|
2065
|
-
const sendFeatureFlagsOptions = typeof sendFeatureFlags === 'object' ? sendFeatureFlags : undefined;
|
|
2066
|
-
return await this.getFeatureFlagsForEvent(distinctId, groups, disableGeoip, sendFeatureFlagsOptions);
|
|
2067
|
-
}
|
|
2068
|
-
if (event === '$feature_flag_called') {
|
|
2069
|
-
// 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.
|
|
2070
|
-
return {};
|
|
2071
|
-
}
|
|
2072
|
-
return {};
|
|
2073
|
-
}).then(flags => {
|
|
2074
|
-
// Derive the relevant flag properties to add
|
|
2075
|
-
const additionalProperties = {};
|
|
2076
|
-
if (flags) {
|
|
2077
|
-
for (const [feature, variant] of Object.entries(flags)) {
|
|
2078
|
-
additionalProperties[`$feature/${feature}`] = variant;
|
|
2079
|
-
}
|
|
2184
|
+
}).catch(err => {
|
|
2185
|
+
if (err) {
|
|
2186
|
+
console.error(err);
|
|
2080
2187
|
}
|
|
2081
|
-
|
|
2082
|
-
if (activeFlags.length > 0) {
|
|
2083
|
-
additionalProperties['$active_feature_flags'] = activeFlags;
|
|
2084
|
-
}
|
|
2085
|
-
return additionalProperties;
|
|
2086
|
-
}).catch(() => {
|
|
2087
|
-
// Something went wrong getting the flag info - we should capture the event anyways
|
|
2088
|
-
return {};
|
|
2089
|
-
}).then(additionalProperties => {
|
|
2090
|
-
// No matter what - capture the event
|
|
2091
|
-
_capture({
|
|
2092
|
-
...additionalProperties,
|
|
2093
|
-
...(eventMessage.properties || {}),
|
|
2094
|
-
$groups: eventMessage.groups || groups
|
|
2095
|
-
});
|
|
2096
|
-
});
|
|
2097
|
-
await capturePromise;
|
|
2188
|
+
}));
|
|
2098
2189
|
}
|
|
2099
2190
|
identify({
|
|
2100
2191
|
distinctId,
|
|
@@ -2364,6 +2455,7 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
2364
2455
|
}
|
|
2365
2456
|
async _shutdown(shutdownTimeoutMs) {
|
|
2366
2457
|
this.featureFlagsPoller?.stopPoller();
|
|
2458
|
+
this.errorTracking.shutdown();
|
|
2367
2459
|
return super._shutdown(shutdownTimeoutMs);
|
|
2368
2460
|
}
|
|
2369
2461
|
async _requestRemoteConfigPayload(flagKey) {
|
|
@@ -2382,7 +2474,7 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
2382
2474
|
let abortTimeout = null;
|
|
2383
2475
|
if (this.options.requestTimeout && typeof this.options.requestTimeout === 'number') {
|
|
2384
2476
|
const controller = new AbortController();
|
|
2385
|
-
abortTimeout = safeSetTimeout(() => {
|
|
2477
|
+
abortTimeout = safeSetTimeout$1(() => {
|
|
2386
2478
|
controller.abort();
|
|
2387
2479
|
}, this.options.requestTimeout);
|
|
2388
2480
|
options.signal = controller.signal;
|
|
@@ -2491,18 +2583,88 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
2491
2583
|
}
|
|
2492
2584
|
captureException(error, distinctId, additionalProperties) {
|
|
2493
2585
|
const syntheticException = new Error('PostHog syntheticException');
|
|
2494
|
-
ErrorTracking.buildEventMessage(error, {
|
|
2586
|
+
this.addPendingPromise(ErrorTracking.buildEventMessage(error, {
|
|
2495
2587
|
syntheticException
|
|
2496
|
-
}, distinctId, additionalProperties).then(msg =>
|
|
2497
|
-
this.capture(msg);
|
|
2498
|
-
});
|
|
2588
|
+
}, distinctId, additionalProperties).then(msg => this.capture(msg)));
|
|
2499
2589
|
}
|
|
2500
2590
|
async captureExceptionImmediate(error, distinctId, additionalProperties) {
|
|
2501
2591
|
const syntheticException = new Error('PostHog syntheticException');
|
|
2502
|
-
|
|
2592
|
+
this.addPendingPromise(ErrorTracking.buildEventMessage(error, {
|
|
2503
2593
|
syntheticException
|
|
2504
|
-
}, distinctId, additionalProperties);
|
|
2505
|
-
|
|
2594
|
+
}, distinctId, additionalProperties).then(msg => this.captureImmediate(msg)));
|
|
2595
|
+
}
|
|
2596
|
+
async prepareEventMessage(props) {
|
|
2597
|
+
const {
|
|
2598
|
+
distinctId,
|
|
2599
|
+
event,
|
|
2600
|
+
properties,
|
|
2601
|
+
groups,
|
|
2602
|
+
sendFeatureFlags,
|
|
2603
|
+
timestamp,
|
|
2604
|
+
disableGeoip,
|
|
2605
|
+
uuid
|
|
2606
|
+
} = props;
|
|
2607
|
+
// Run before_send if configured
|
|
2608
|
+
const eventMessage = this._runBeforeSend({
|
|
2609
|
+
distinctId,
|
|
2610
|
+
event,
|
|
2611
|
+
properties,
|
|
2612
|
+
groups,
|
|
2613
|
+
sendFeatureFlags,
|
|
2614
|
+
timestamp,
|
|
2615
|
+
disableGeoip,
|
|
2616
|
+
uuid
|
|
2617
|
+
});
|
|
2618
|
+
if (!eventMessage) {
|
|
2619
|
+
return Promise.reject(null);
|
|
2620
|
+
}
|
|
2621
|
+
// :TRICKY: If we flush, or need to shut down, to not lose events we want this promise to resolve before we flush
|
|
2622
|
+
const eventProperties = await Promise.resolve().then(async () => {
|
|
2623
|
+
if (sendFeatureFlags) {
|
|
2624
|
+
// If we are sending feature flags, we evaluate them locally if the user prefers it, otherwise we fall back to remote evaluation
|
|
2625
|
+
const sendFeatureFlagsOptions = typeof sendFeatureFlags === 'object' ? sendFeatureFlags : undefined;
|
|
2626
|
+
return await this.getFeatureFlagsForEvent(distinctId, groups, disableGeoip, sendFeatureFlagsOptions);
|
|
2627
|
+
}
|
|
2628
|
+
if (event === '$feature_flag_called') {
|
|
2629
|
+
// 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.
|
|
2630
|
+
return {};
|
|
2631
|
+
}
|
|
2632
|
+
return {};
|
|
2633
|
+
}).then(flags => {
|
|
2634
|
+
// Derive the relevant flag properties to add
|
|
2635
|
+
const additionalProperties = {};
|
|
2636
|
+
if (flags) {
|
|
2637
|
+
for (const [feature, variant] of Object.entries(flags)) {
|
|
2638
|
+
additionalProperties[`$feature/${feature}`] = variant;
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
const activeFlags = Object.keys(flags || {}).filter(flag => flags?.[flag] !== false).sort();
|
|
2642
|
+
if (activeFlags.length > 0) {
|
|
2643
|
+
additionalProperties['$active_feature_flags'] = activeFlags;
|
|
2644
|
+
}
|
|
2645
|
+
return additionalProperties;
|
|
2646
|
+
}).catch(() => {
|
|
2647
|
+
// Something went wrong getting the flag info - we should capture the event anyways
|
|
2648
|
+
return {};
|
|
2649
|
+
}).then(additionalProperties => {
|
|
2650
|
+
// No matter what - capture the event
|
|
2651
|
+
const props = {
|
|
2652
|
+
...additionalProperties,
|
|
2653
|
+
...(eventMessage.properties || {}),
|
|
2654
|
+
$groups: eventMessage.groups || groups
|
|
2655
|
+
};
|
|
2656
|
+
return props;
|
|
2657
|
+
});
|
|
2658
|
+
return {
|
|
2659
|
+
distinctId: eventMessage.distinctId,
|
|
2660
|
+
event: eventMessage.event,
|
|
2661
|
+
properties: eventProperties,
|
|
2662
|
+
options: {
|
|
2663
|
+
timestamp: eventMessage.timestamp,
|
|
2664
|
+
disableGeoip: eventMessage.disableGeoip,
|
|
2665
|
+
uuid: eventMessage.uuid
|
|
2666
|
+
}
|
|
2667
|
+
};
|
|
2506
2668
|
}
|
|
2507
2669
|
_runBeforeSend(eventMessage) {
|
|
2508
2670
|
const beforeSend = this.options.before_send;
|