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