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/edge/index.cjs
CHANGED
|
@@ -315,7 +315,7 @@ function makeUncaughtExceptionHandler(captureFn, onFatalFn) {
|
|
|
315
315
|
});
|
|
316
316
|
if (!calledFatalError && processWouldExit) {
|
|
317
317
|
calledFatalError = true;
|
|
318
|
-
onFatalFn();
|
|
318
|
+
onFatalFn(error);
|
|
319
319
|
}
|
|
320
320
|
}, {
|
|
321
321
|
_posthogErrorHandler: true
|
|
@@ -326,7 +326,7 @@ function addUncaughtExceptionListener(captureFn, onFatalFn) {
|
|
|
326
326
|
}
|
|
327
327
|
function addUnhandledRejectionListener(captureFn) {
|
|
328
328
|
global.process.on('unhandledRejection', reason => {
|
|
329
|
-
captureFn(reason, {
|
|
329
|
+
return captureFn(reason, {
|
|
330
330
|
mechanism: {
|
|
331
331
|
type: 'onunhandledrejection',
|
|
332
332
|
handled: false
|
|
@@ -343,8 +343,7 @@ let cachedFilenameChunkIds;
|
|
|
343
343
|
function getFilenameToChunkIdMap(stackParser) {
|
|
344
344
|
const chunkIdMap = globalThis._posthogChunkIds;
|
|
345
345
|
if (!chunkIdMap) {
|
|
346
|
-
|
|
347
|
-
return {};
|
|
346
|
+
return null;
|
|
348
347
|
}
|
|
349
348
|
const chunkIdKeys = Object.keys(chunkIdMap);
|
|
350
349
|
if (cachedFilenameChunkIds && chunkIdKeys.length === lastKeysCount) {
|
|
@@ -615,15 +614,102 @@ function parseStackFrames(stackParser, error) {
|
|
|
615
614
|
function applyChunkIds(frames, parser) {
|
|
616
615
|
const filenameChunkIdMap = getFilenameToChunkIdMap(parser);
|
|
617
616
|
frames.forEach(frame => {
|
|
618
|
-
if (frame.filename) {
|
|
617
|
+
if (frame.filename && filenameChunkIdMap) {
|
|
619
618
|
frame.chunk_id = filenameChunkIdMap[frame.filename];
|
|
620
619
|
}
|
|
621
620
|
});
|
|
622
621
|
return frames;
|
|
623
622
|
}
|
|
624
623
|
|
|
624
|
+
const ObjProto = Object.prototype;
|
|
625
|
+
const type_utils_toString = ObjProto.toString;
|
|
626
|
+
const isNumber = (x)=>'[object Number]' == type_utils_toString.call(x);
|
|
627
|
+
|
|
628
|
+
function clampToRange(value, min, max, logger, fallbackValue) {
|
|
629
|
+
if (min > max) {
|
|
630
|
+
logger.warn('min cannot be greater than max.');
|
|
631
|
+
min = max;
|
|
632
|
+
}
|
|
633
|
+
if (isNumber(value)) if (value > max) {
|
|
634
|
+
logger.warn(' cannot be greater than max: ' + max + '. Using max value instead.');
|
|
635
|
+
return max;
|
|
636
|
+
} else {
|
|
637
|
+
if (!(value < min)) return value;
|
|
638
|
+
logger.warn(' cannot be less than min: ' + min + '. Using min value instead.');
|
|
639
|
+
return min;
|
|
640
|
+
}
|
|
641
|
+
logger.warn(' must be a number. using max or fallback. max: ' + max + ', fallback: ' + fallbackValue);
|
|
642
|
+
return clampToRange(max, min, max, logger);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
class BucketedRateLimiter {
|
|
646
|
+
stop() {
|
|
647
|
+
if (this._removeInterval) {
|
|
648
|
+
clearInterval(this._removeInterval);
|
|
649
|
+
this._removeInterval = void 0;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
constructor(_options){
|
|
653
|
+
this._options = _options;
|
|
654
|
+
this._buckets = {};
|
|
655
|
+
this._refillBuckets = ()=>{
|
|
656
|
+
Object.keys(this._buckets).forEach((key)=>{
|
|
657
|
+
const newTokens = this._getBucket(key) + this._refillRate;
|
|
658
|
+
if (newTokens >= this._bucketSize) delete this._buckets[key];
|
|
659
|
+
else this._setBucket(key, newTokens);
|
|
660
|
+
});
|
|
661
|
+
};
|
|
662
|
+
this._getBucket = (key)=>this._buckets[String(key)];
|
|
663
|
+
this._setBucket = (key, value)=>{
|
|
664
|
+
this._buckets[String(key)] = value;
|
|
665
|
+
};
|
|
666
|
+
this.consumeRateLimit = (key)=>{
|
|
667
|
+
var _this__getBucket;
|
|
668
|
+
let tokens = null != (_this__getBucket = this._getBucket(key)) ? _this__getBucket : this._bucketSize;
|
|
669
|
+
tokens = Math.max(tokens - 1, 0);
|
|
670
|
+
if (0 === tokens) return true;
|
|
671
|
+
this._setBucket(key, tokens);
|
|
672
|
+
const hasReachedZero = 0 === tokens;
|
|
673
|
+
if (hasReachedZero) {
|
|
674
|
+
var _this__onBucketRateLimited, _this;
|
|
675
|
+
null == (_this__onBucketRateLimited = (_this = this)._onBucketRateLimited) || _this__onBucketRateLimited.call(_this, key);
|
|
676
|
+
}
|
|
677
|
+
return hasReachedZero;
|
|
678
|
+
};
|
|
679
|
+
this._onBucketRateLimited = this._options._onBucketRateLimited;
|
|
680
|
+
this._bucketSize = clampToRange(this._options.bucketSize, 0, 100, this._options._logger);
|
|
681
|
+
this._refillRate = clampToRange(this._options.refillRate, 0, this._bucketSize, this._options._logger);
|
|
682
|
+
this._refillInterval = clampToRange(this._options.refillInterval, 0, 86400000, this._options._logger);
|
|
683
|
+
this._removeInterval = setInterval(()=>{
|
|
684
|
+
this._refillBuckets();
|
|
685
|
+
}, this._refillInterval);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function safeSetTimeout(fn, timeout) {
|
|
690
|
+
const t = setTimeout(fn, timeout);
|
|
691
|
+
(null == t ? void 0 : t.unref) && (null == t || t.unref());
|
|
692
|
+
return t;
|
|
693
|
+
}
|
|
694
|
+
|
|
625
695
|
const SHUTDOWN_TIMEOUT = 2000;
|
|
626
696
|
class ErrorTracking {
|
|
697
|
+
constructor(client, options, _logger) {
|
|
698
|
+
this.client = client;
|
|
699
|
+
this._exceptionAutocaptureEnabled = options.enableExceptionAutocapture || false;
|
|
700
|
+
this._logger = _logger;
|
|
701
|
+
// by default captures ten exceptions before rate limiting by exception type
|
|
702
|
+
// refills at a rate of one token / 10 second period
|
|
703
|
+
// e.g. will capture 1 exception rate limited exception every 10 seconds until burst ends
|
|
704
|
+
this._rateLimiter = new BucketedRateLimiter({
|
|
705
|
+
refillRate: 1,
|
|
706
|
+
bucketSize: 10,
|
|
707
|
+
refillInterval: 10000,
|
|
708
|
+
// ten seconds in milliseconds
|
|
709
|
+
_logger: this._logger
|
|
710
|
+
});
|
|
711
|
+
this.startAutocaptureIfEnabled();
|
|
712
|
+
}
|
|
627
713
|
static async buildEventMessage(error, hint, distinctId, additionalProperties) {
|
|
628
714
|
const properties = {
|
|
629
715
|
...additionalProperties
|
|
@@ -643,28 +729,38 @@ class ErrorTracking {
|
|
|
643
729
|
}
|
|
644
730
|
};
|
|
645
731
|
}
|
|
646
|
-
constructor(client, options) {
|
|
647
|
-
this.client = client;
|
|
648
|
-
this._exceptionAutocaptureEnabled = options.enableExceptionAutocapture || false;
|
|
649
|
-
this.startAutocaptureIfEnabled();
|
|
650
|
-
}
|
|
651
732
|
startAutocaptureIfEnabled() {
|
|
652
733
|
if (this.isEnabled()) {
|
|
653
734
|
addUncaughtExceptionListener(this.onException.bind(this), this.onFatalError.bind(this));
|
|
654
735
|
addUnhandledRejectionListener(this.onException.bind(this));
|
|
655
736
|
}
|
|
656
737
|
}
|
|
657
|
-
onException(exception, hint) {
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
738
|
+
async onException(exception, hint) {
|
|
739
|
+
this.client.addPendingPromise((async () => {
|
|
740
|
+
const eventMessage = await ErrorTracking.buildEventMessage(exception, hint);
|
|
741
|
+
const exceptionProperties = eventMessage.properties;
|
|
742
|
+
const exceptionType = exceptionProperties?.$exception_list[0].type ?? 'Exception';
|
|
743
|
+
const isRateLimited = this._rateLimiter.consumeRateLimit(exceptionType);
|
|
744
|
+
if (isRateLimited) {
|
|
745
|
+
this._logger.info('Skipping exception capture because of client rate limiting.', {
|
|
746
|
+
exception: exceptionType
|
|
747
|
+
});
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
return this.client.capture(eventMessage);
|
|
751
|
+
})());
|
|
661
752
|
}
|
|
662
|
-
async onFatalError() {
|
|
753
|
+
async onFatalError(exception) {
|
|
754
|
+
console.error(exception);
|
|
663
755
|
await this.client.shutdown(SHUTDOWN_TIMEOUT);
|
|
756
|
+
process.exit(1);
|
|
664
757
|
}
|
|
665
758
|
isEnabled() {
|
|
666
759
|
return !this.client.isDisabled && this._exceptionAutocaptureEnabled;
|
|
667
760
|
}
|
|
761
|
+
shutdown() {
|
|
762
|
+
this._rateLimiter.stop();
|
|
763
|
+
}
|
|
668
764
|
}
|
|
669
765
|
|
|
670
766
|
function setupExpressErrorHandler(_posthog, app) {
|
|
@@ -684,7 +780,7 @@ function setupExpressErrorHandler(_posthog, app) {
|
|
|
684
780
|
});
|
|
685
781
|
}
|
|
686
782
|
|
|
687
|
-
var version = "5.
|
|
783
|
+
var version = "5.8.1";
|
|
688
784
|
|
|
689
785
|
/**
|
|
690
786
|
* A lazy value that is only computed when needed. Inspired by C#'s Lazy<T> class.
|
|
@@ -972,7 +1068,70 @@ class FeatureFlagsPoller {
|
|
|
972
1068
|
}
|
|
973
1069
|
return null;
|
|
974
1070
|
}
|
|
975
|
-
async
|
|
1071
|
+
async evaluateFlagDependency(property, distinctId, properties, evaluationCache) {
|
|
1072
|
+
const targetFlagKey = property.key;
|
|
1073
|
+
if (!this.featureFlagsByKey) {
|
|
1074
|
+
throw new InconclusiveMatchError('Feature flags not available for dependency evaluation');
|
|
1075
|
+
}
|
|
1076
|
+
// Check if dependency_chain is present - it should always be provided for flag dependencies
|
|
1077
|
+
if (!('dependency_chain' in property)) {
|
|
1078
|
+
throw new InconclusiveMatchError(`Flag dependency property for '${targetFlagKey}' is missing required 'dependency_chain' field`);
|
|
1079
|
+
}
|
|
1080
|
+
const dependencyChain = property.dependency_chain;
|
|
1081
|
+
// Check for missing or invalid dependency chain (This should never happen, but being defensive)
|
|
1082
|
+
if (!Array.isArray(dependencyChain)) {
|
|
1083
|
+
throw new InconclusiveMatchError(`Flag dependency property for '${targetFlagKey}' has an invalid 'dependency_chain' (expected array, got ${typeof dependencyChain})`);
|
|
1084
|
+
}
|
|
1085
|
+
// Handle circular dependency (empty chain means circular) (This should never happen, but being defensive)
|
|
1086
|
+
if (dependencyChain.length === 0) {
|
|
1087
|
+
throw new InconclusiveMatchError(`Circular dependency detected for flag '${targetFlagKey}' (empty dependency chain)`);
|
|
1088
|
+
}
|
|
1089
|
+
// Evaluate all dependencies in the chain order
|
|
1090
|
+
for (const depFlagKey of dependencyChain) {
|
|
1091
|
+
if (!(depFlagKey in evaluationCache)) {
|
|
1092
|
+
// Need to evaluate this dependency first
|
|
1093
|
+
const depFlag = this.featureFlagsByKey[depFlagKey];
|
|
1094
|
+
if (!depFlag) {
|
|
1095
|
+
// Missing flag dependency - cannot evaluate locally
|
|
1096
|
+
throw new InconclusiveMatchError(`Missing flag dependency '${depFlagKey}' for flag '${targetFlagKey}'`);
|
|
1097
|
+
} else if (!depFlag.active) {
|
|
1098
|
+
// Inactive flag evaluates to false
|
|
1099
|
+
evaluationCache[depFlagKey] = false;
|
|
1100
|
+
} else {
|
|
1101
|
+
// Recursively evaluate the dependency
|
|
1102
|
+
try {
|
|
1103
|
+
const depResult = await this.matchFeatureFlagProperties(depFlag, distinctId, properties, evaluationCache);
|
|
1104
|
+
evaluationCache[depFlagKey] = depResult;
|
|
1105
|
+
} catch (error) {
|
|
1106
|
+
// If we can't evaluate a dependency, store throw InconclusiveMatchError(`Missing flag dependency '${depFlagKey}' for flag '${targetFlagKey}'`)
|
|
1107
|
+
throw new InconclusiveMatchError(`Error evaluating flag dependency '${depFlagKey}' for flag '${targetFlagKey}': ${error}`);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
// Check if dependency evaluation was inconclusive
|
|
1112
|
+
const cachedResult = evaluationCache[depFlagKey];
|
|
1113
|
+
if (cachedResult === null || cachedResult === undefined) {
|
|
1114
|
+
throw new InconclusiveMatchError(`Dependency '${depFlagKey}' could not be evaluated`);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
// The target flag is specified in property.key (This should match the last element in the dependency chain)
|
|
1118
|
+
const targetFlagValue = evaluationCache[targetFlagKey];
|
|
1119
|
+
return this.flagEvaluatesToExpectedValue(property.value, targetFlagValue);
|
|
1120
|
+
}
|
|
1121
|
+
flagEvaluatesToExpectedValue(expectedValue, flagValue) {
|
|
1122
|
+
// If the expected value is a boolean, then return true if the flag evaluated to true (or any string variant)
|
|
1123
|
+
// If the expected value is false, then only return true if the flag evaluated to false.
|
|
1124
|
+
if (typeof expectedValue === 'boolean') {
|
|
1125
|
+
return expectedValue === flagValue || typeof flagValue === 'string' && flagValue !== '' && expectedValue === true;
|
|
1126
|
+
}
|
|
1127
|
+
// If the expected value is a string, then return true if and only if the flag evaluated to the expected value.
|
|
1128
|
+
if (typeof expectedValue === 'string') {
|
|
1129
|
+
return flagValue === expectedValue;
|
|
1130
|
+
}
|
|
1131
|
+
// The `flag_evaluates_to` operator is not supported for numbers and arrays.
|
|
1132
|
+
return false;
|
|
1133
|
+
}
|
|
1134
|
+
async matchFeatureFlagProperties(flag, distinctId, properties, evaluationCache = {}) {
|
|
976
1135
|
const flagFilters = flag.filters || {};
|
|
977
1136
|
const flagConditions = flagFilters.groups || [];
|
|
978
1137
|
let isInconclusive = false;
|
|
@@ -994,7 +1153,7 @@ class FeatureFlagsPoller {
|
|
|
994
1153
|
});
|
|
995
1154
|
for (const condition of sortedFlagConditions) {
|
|
996
1155
|
try {
|
|
997
|
-
if (await this.isConditionMatch(flag, distinctId, condition, properties)) {
|
|
1156
|
+
if (await this.isConditionMatch(flag, distinctId, condition, properties, evaluationCache)) {
|
|
998
1157
|
const variantOverride = condition.variant;
|
|
999
1158
|
const flagVariants = flagFilters.multivariate?.variants || [];
|
|
1000
1159
|
if (variantOverride && flagVariants.some(variant => variant.key === variantOverride)) {
|
|
@@ -1020,7 +1179,7 @@ class FeatureFlagsPoller {
|
|
|
1020
1179
|
// We can only return False when all conditions are False
|
|
1021
1180
|
return false;
|
|
1022
1181
|
}
|
|
1023
|
-
async isConditionMatch(flag, distinctId, condition, properties) {
|
|
1182
|
+
async isConditionMatch(flag, distinctId, condition, properties, evaluationCache = {}) {
|
|
1024
1183
|
const rolloutPercentage = condition.rollout_percentage;
|
|
1025
1184
|
const warnFunction = msg => {
|
|
1026
1185
|
this.logMsgIfDebug(() => console.warn(msg));
|
|
@@ -1032,8 +1191,7 @@ class FeatureFlagsPoller {
|
|
|
1032
1191
|
if (propertyType === 'cohort') {
|
|
1033
1192
|
matches = matchCohort(prop, properties, this.cohorts, this.debugMode);
|
|
1034
1193
|
} else if (propertyType === 'flag') {
|
|
1035
|
-
|
|
1036
|
-
continue;
|
|
1194
|
+
matches = await this.evaluateFlagDependency(prop, distinctId, properties, evaluationCache);
|
|
1037
1195
|
} else {
|
|
1038
1196
|
matches = matchProperty(prop, properties, warnFunction);
|
|
1039
1197
|
}
|
|
@@ -1194,7 +1352,7 @@ class FeatureFlagsPoller {
|
|
|
1194
1352
|
let abortTimeout = null;
|
|
1195
1353
|
if (this.timeout && typeof this.timeout === 'number') {
|
|
1196
1354
|
const controller = new AbortController();
|
|
1197
|
-
abortTimeout =
|
|
1355
|
+
abortTimeout = safeSetTimeout(() => {
|
|
1198
1356
|
controller.abort();
|
|
1199
1357
|
}, this.timeout);
|
|
1200
1358
|
options.signal = controller.signal;
|
|
@@ -1298,6 +1456,10 @@ function matchProperty(property, propertyValues, warnFunction) {
|
|
|
1298
1456
|
case 'is_date_after':
|
|
1299
1457
|
case 'is_date_before':
|
|
1300
1458
|
{
|
|
1459
|
+
// Boolean values should never be used with date operations
|
|
1460
|
+
if (typeof value === 'boolean') {
|
|
1461
|
+
throw new InconclusiveMatchError(`Date operations cannot be performed on boolean values`);
|
|
1462
|
+
}
|
|
1301
1463
|
let parsedDate = relativeDateParseForFeatureFlagMatching(String(value));
|
|
1302
1464
|
if (parsedDate == null) {
|
|
1303
1465
|
parsedDate = convertToDateTime(value);
|
|
@@ -1481,6 +1643,37 @@ class PostHogMemoryStorage {
|
|
|
1481
1643
|
}
|
|
1482
1644
|
}
|
|
1483
1645
|
|
|
1646
|
+
const _createLogger = (prefix, logMsgIfDebug) => {
|
|
1647
|
+
const logger = {
|
|
1648
|
+
_log: (level, ...args) => {
|
|
1649
|
+
logMsgIfDebug(() => {
|
|
1650
|
+
const consoleLog = console[level];
|
|
1651
|
+
consoleLog(prefix, ...args);
|
|
1652
|
+
});
|
|
1653
|
+
},
|
|
1654
|
+
info: (...args) => {
|
|
1655
|
+
logger._log('log', ...args);
|
|
1656
|
+
},
|
|
1657
|
+
warn: (...args) => {
|
|
1658
|
+
logger._log('warn', ...args);
|
|
1659
|
+
},
|
|
1660
|
+
error: (...args) => {
|
|
1661
|
+
logger._log('error', ...args);
|
|
1662
|
+
},
|
|
1663
|
+
critical: (...args) => {
|
|
1664
|
+
// Critical errors are always logged to the console
|
|
1665
|
+
// eslint-disable-next-line no-console
|
|
1666
|
+
console.error(prefix, ...args);
|
|
1667
|
+
},
|
|
1668
|
+
uninitializedWarning: methodName => {
|
|
1669
|
+
logger.error(`You must initialize PostHog before calling ${methodName}`);
|
|
1670
|
+
},
|
|
1671
|
+
createLogger: additionalPrefix => _createLogger(`${prefix} ${additionalPrefix}`, logMsgIfDebug)
|
|
1672
|
+
};
|
|
1673
|
+
return logger;
|
|
1674
|
+
};
|
|
1675
|
+
const createLogger = logMsgIfDebug => _createLogger('[PostHog.js]', logMsgIfDebug);
|
|
1676
|
+
|
|
1484
1677
|
// Standard local evaluation rate limit is 600 per minute (10 per second),
|
|
1485
1678
|
// so the fastest a poller should ever be set is 100ms.
|
|
1486
1679
|
const MINIMUM_POLLING_INTERVAL = 100;
|
|
@@ -1492,6 +1685,7 @@ class PostHogBackendClient extends core.PostHogCoreStateless {
|
|
|
1492
1685
|
super(apiKey, options);
|
|
1493
1686
|
this._memoryStorage = new PostHogMemoryStorage();
|
|
1494
1687
|
this.options = options;
|
|
1688
|
+
this.logger = createLogger(this.logMsgIfDebug.bind(this));
|
|
1495
1689
|
this.options.featureFlagsPollingInterval = typeof options.featureFlagsPollingInterval === 'number' ? Math.max(options.featureFlagsPollingInterval, MINIMUM_POLLING_INTERVAL) : THIRTY_SECONDS;
|
|
1496
1690
|
if (options.personalApiKey) {
|
|
1497
1691
|
if (options.personalApiKey.includes('phc_')) {
|
|
@@ -1518,7 +1712,7 @@ class PostHogBackendClient extends core.PostHogCoreStateless {
|
|
|
1518
1712
|
});
|
|
1519
1713
|
}
|
|
1520
1714
|
}
|
|
1521
|
-
this.errorTracking = new ErrorTracking(this, options);
|
|
1715
|
+
this.errorTracking = new ErrorTracking(this, options, this.logger);
|
|
1522
1716
|
this.distinctIdHasSentFlagCalls = {};
|
|
1523
1717
|
this.maxCacheSize = options.maxCacheSize || MAX_CACHE_SIZE;
|
|
1524
1718
|
}
|
|
@@ -1551,146 +1745,43 @@ class PostHogBackendClient extends core.PostHogCoreStateless {
|
|
|
1551
1745
|
if (typeof props === 'string') {
|
|
1552
1746
|
this.logMsgIfDebug(() => console.warn('Called capture() with a string as the first argument when an object was expected.'));
|
|
1553
1747
|
}
|
|
1554
|
-
|
|
1748
|
+
this.addPendingPromise(this.prepareEventMessage(props).then(({
|
|
1555
1749
|
distinctId,
|
|
1556
1750
|
event,
|
|
1557
1751
|
properties,
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
// Run before_send if configured
|
|
1565
|
-
const eventMessage = this._runBeforeSend({
|
|
1566
|
-
distinctId,
|
|
1567
|
-
event,
|
|
1568
|
-
properties,
|
|
1569
|
-
groups,
|
|
1570
|
-
sendFeatureFlags,
|
|
1571
|
-
timestamp,
|
|
1572
|
-
disableGeoip,
|
|
1573
|
-
uuid
|
|
1574
|
-
});
|
|
1575
|
-
if (!eventMessage) {
|
|
1576
|
-
return;
|
|
1577
|
-
}
|
|
1578
|
-
const _capture = props => {
|
|
1579
|
-
super.captureStateless(eventMessage.distinctId, eventMessage.event, props, {
|
|
1580
|
-
timestamp: eventMessage.timestamp,
|
|
1581
|
-
disableGeoip: eventMessage.disableGeoip,
|
|
1582
|
-
uuid: eventMessage.uuid
|
|
1752
|
+
options
|
|
1753
|
+
}) => {
|
|
1754
|
+
return super.captureStateless(distinctId, event, properties, {
|
|
1755
|
+
timestamp: options.timestamp,
|
|
1756
|
+
disableGeoip: options.disableGeoip,
|
|
1757
|
+
uuid: options.uuid
|
|
1583
1758
|
});
|
|
1584
|
-
}
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
if (sendFeatureFlags) {
|
|
1588
|
-
// If we are sending feature flags, we evaluate them locally if the user prefers it, otherwise we fall back to remote evaluation
|
|
1589
|
-
const sendFeatureFlagsOptions = typeof sendFeatureFlags === 'object' ? sendFeatureFlags : undefined;
|
|
1590
|
-
return await this.getFeatureFlagsForEvent(distinctId, groups, disableGeoip, sendFeatureFlagsOptions);
|
|
1591
|
-
}
|
|
1592
|
-
if (event === '$feature_flag_called') {
|
|
1593
|
-
// 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.
|
|
1594
|
-
return {};
|
|
1595
|
-
}
|
|
1596
|
-
return {};
|
|
1597
|
-
}).then(flags => {
|
|
1598
|
-
// Derive the relevant flag properties to add
|
|
1599
|
-
const additionalProperties = {};
|
|
1600
|
-
if (flags) {
|
|
1601
|
-
for (const [feature, variant] of Object.entries(flags)) {
|
|
1602
|
-
additionalProperties[`$feature/${feature}`] = variant;
|
|
1603
|
-
}
|
|
1759
|
+
}).catch(err => {
|
|
1760
|
+
if (err) {
|
|
1761
|
+
console.error(err);
|
|
1604
1762
|
}
|
|
1605
|
-
|
|
1606
|
-
if (activeFlags.length > 0) {
|
|
1607
|
-
additionalProperties['$active_feature_flags'] = activeFlags;
|
|
1608
|
-
}
|
|
1609
|
-
return additionalProperties;
|
|
1610
|
-
}).catch(() => {
|
|
1611
|
-
// Something went wrong getting the flag info - we should capture the event anyways
|
|
1612
|
-
return {};
|
|
1613
|
-
}).then(additionalProperties => {
|
|
1614
|
-
// No matter what - capture the event
|
|
1615
|
-
_capture({
|
|
1616
|
-
...additionalProperties,
|
|
1617
|
-
...(eventMessage.properties || {}),
|
|
1618
|
-
$groups: eventMessage.groups || groups
|
|
1619
|
-
});
|
|
1620
|
-
});
|
|
1621
|
-
this.addPendingPromise(capturePromise);
|
|
1763
|
+
}));
|
|
1622
1764
|
}
|
|
1623
1765
|
async captureImmediate(props) {
|
|
1624
1766
|
if (typeof props === 'string') {
|
|
1625
|
-
this.logMsgIfDebug(() => console.warn('Called
|
|
1767
|
+
this.logMsgIfDebug(() => console.warn('Called captureImmediate() with a string as the first argument when an object was expected.'));
|
|
1626
1768
|
}
|
|
1627
|
-
|
|
1769
|
+
return this.addPendingPromise(this.prepareEventMessage(props).then(({
|
|
1628
1770
|
distinctId,
|
|
1629
1771
|
event,
|
|
1630
1772
|
properties,
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
// Run before_send if configured
|
|
1638
|
-
const eventMessage = this._runBeforeSend({
|
|
1639
|
-
distinctId,
|
|
1640
|
-
event,
|
|
1641
|
-
properties,
|
|
1642
|
-
groups,
|
|
1643
|
-
sendFeatureFlags,
|
|
1644
|
-
timestamp,
|
|
1645
|
-
disableGeoip,
|
|
1646
|
-
uuid
|
|
1647
|
-
});
|
|
1648
|
-
if (!eventMessage) {
|
|
1649
|
-
return;
|
|
1650
|
-
}
|
|
1651
|
-
const _capture = props => {
|
|
1652
|
-
return super.captureStatelessImmediate(eventMessage.distinctId, eventMessage.event, props, {
|
|
1653
|
-
timestamp: eventMessage.timestamp,
|
|
1654
|
-
disableGeoip: eventMessage.disableGeoip,
|
|
1655
|
-
uuid: eventMessage.uuid
|
|
1773
|
+
options
|
|
1774
|
+
}) => {
|
|
1775
|
+
return super.captureStatelessImmediate(distinctId, event, properties, {
|
|
1776
|
+
timestamp: options.timestamp,
|
|
1777
|
+
disableGeoip: options.disableGeoip,
|
|
1778
|
+
uuid: options.uuid
|
|
1656
1779
|
});
|
|
1657
|
-
}
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
// If we are sending feature flags, we evaluate them locally if the user prefers it, otherwise we fall back to remote evaluation
|
|
1661
|
-
const sendFeatureFlagsOptions = typeof sendFeatureFlags === 'object' ? sendFeatureFlags : undefined;
|
|
1662
|
-
return await this.getFeatureFlagsForEvent(distinctId, groups, disableGeoip, sendFeatureFlagsOptions);
|
|
1663
|
-
}
|
|
1664
|
-
if (event === '$feature_flag_called') {
|
|
1665
|
-
// 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.
|
|
1666
|
-
return {};
|
|
1667
|
-
}
|
|
1668
|
-
return {};
|
|
1669
|
-
}).then(flags => {
|
|
1670
|
-
// Derive the relevant flag properties to add
|
|
1671
|
-
const additionalProperties = {};
|
|
1672
|
-
if (flags) {
|
|
1673
|
-
for (const [feature, variant] of Object.entries(flags)) {
|
|
1674
|
-
additionalProperties[`$feature/${feature}`] = variant;
|
|
1675
|
-
}
|
|
1780
|
+
}).catch(err => {
|
|
1781
|
+
if (err) {
|
|
1782
|
+
console.error(err);
|
|
1676
1783
|
}
|
|
1677
|
-
|
|
1678
|
-
if (activeFlags.length > 0) {
|
|
1679
|
-
additionalProperties['$active_feature_flags'] = activeFlags;
|
|
1680
|
-
}
|
|
1681
|
-
return additionalProperties;
|
|
1682
|
-
}).catch(() => {
|
|
1683
|
-
// Something went wrong getting the flag info - we should capture the event anyways
|
|
1684
|
-
return {};
|
|
1685
|
-
}).then(additionalProperties => {
|
|
1686
|
-
// No matter what - capture the event
|
|
1687
|
-
_capture({
|
|
1688
|
-
...additionalProperties,
|
|
1689
|
-
...(eventMessage.properties || {}),
|
|
1690
|
-
$groups: eventMessage.groups || groups
|
|
1691
|
-
});
|
|
1692
|
-
});
|
|
1693
|
-
await capturePromise;
|
|
1784
|
+
}));
|
|
1694
1785
|
}
|
|
1695
1786
|
identify({
|
|
1696
1787
|
distinctId,
|
|
@@ -1960,6 +2051,7 @@ class PostHogBackendClient extends core.PostHogCoreStateless {
|
|
|
1960
2051
|
}
|
|
1961
2052
|
async _shutdown(shutdownTimeoutMs) {
|
|
1962
2053
|
this.featureFlagsPoller?.stopPoller();
|
|
2054
|
+
this.errorTracking.shutdown();
|
|
1963
2055
|
return super._shutdown(shutdownTimeoutMs);
|
|
1964
2056
|
}
|
|
1965
2057
|
async _requestRemoteConfigPayload(flagKey) {
|
|
@@ -2087,18 +2179,88 @@ class PostHogBackendClient extends core.PostHogCoreStateless {
|
|
|
2087
2179
|
}
|
|
2088
2180
|
captureException(error, distinctId, additionalProperties) {
|
|
2089
2181
|
const syntheticException = new Error('PostHog syntheticException');
|
|
2090
|
-
ErrorTracking.buildEventMessage(error, {
|
|
2182
|
+
this.addPendingPromise(ErrorTracking.buildEventMessage(error, {
|
|
2091
2183
|
syntheticException
|
|
2092
|
-
}, distinctId, additionalProperties).then(msg =>
|
|
2093
|
-
this.capture(msg);
|
|
2094
|
-
});
|
|
2184
|
+
}, distinctId, additionalProperties).then(msg => this.capture(msg)));
|
|
2095
2185
|
}
|
|
2096
2186
|
async captureExceptionImmediate(error, distinctId, additionalProperties) {
|
|
2097
2187
|
const syntheticException = new Error('PostHog syntheticException');
|
|
2098
|
-
|
|
2188
|
+
this.addPendingPromise(ErrorTracking.buildEventMessage(error, {
|
|
2099
2189
|
syntheticException
|
|
2100
|
-
}, distinctId, additionalProperties);
|
|
2101
|
-
|
|
2190
|
+
}, distinctId, additionalProperties).then(msg => this.captureImmediate(msg)));
|
|
2191
|
+
}
|
|
2192
|
+
async prepareEventMessage(props) {
|
|
2193
|
+
const {
|
|
2194
|
+
distinctId,
|
|
2195
|
+
event,
|
|
2196
|
+
properties,
|
|
2197
|
+
groups,
|
|
2198
|
+
sendFeatureFlags,
|
|
2199
|
+
timestamp,
|
|
2200
|
+
disableGeoip,
|
|
2201
|
+
uuid
|
|
2202
|
+
} = props;
|
|
2203
|
+
// Run before_send if configured
|
|
2204
|
+
const eventMessage = this._runBeforeSend({
|
|
2205
|
+
distinctId,
|
|
2206
|
+
event,
|
|
2207
|
+
properties,
|
|
2208
|
+
groups,
|
|
2209
|
+
sendFeatureFlags,
|
|
2210
|
+
timestamp,
|
|
2211
|
+
disableGeoip,
|
|
2212
|
+
uuid
|
|
2213
|
+
});
|
|
2214
|
+
if (!eventMessage) {
|
|
2215
|
+
return Promise.reject(null);
|
|
2216
|
+
}
|
|
2217
|
+
// :TRICKY: If we flush, or need to shut down, to not lose events we want this promise to resolve before we flush
|
|
2218
|
+
const eventProperties = await Promise.resolve().then(async () => {
|
|
2219
|
+
if (sendFeatureFlags) {
|
|
2220
|
+
// If we are sending feature flags, we evaluate them locally if the user prefers it, otherwise we fall back to remote evaluation
|
|
2221
|
+
const sendFeatureFlagsOptions = typeof sendFeatureFlags === 'object' ? sendFeatureFlags : undefined;
|
|
2222
|
+
return await this.getFeatureFlagsForEvent(distinctId, groups, disableGeoip, sendFeatureFlagsOptions);
|
|
2223
|
+
}
|
|
2224
|
+
if (event === '$feature_flag_called') {
|
|
2225
|
+
// 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.
|
|
2226
|
+
return {};
|
|
2227
|
+
}
|
|
2228
|
+
return {};
|
|
2229
|
+
}).then(flags => {
|
|
2230
|
+
// Derive the relevant flag properties to add
|
|
2231
|
+
const additionalProperties = {};
|
|
2232
|
+
if (flags) {
|
|
2233
|
+
for (const [feature, variant] of Object.entries(flags)) {
|
|
2234
|
+
additionalProperties[`$feature/${feature}`] = variant;
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
const activeFlags = Object.keys(flags || {}).filter(flag => flags?.[flag] !== false).sort();
|
|
2238
|
+
if (activeFlags.length > 0) {
|
|
2239
|
+
additionalProperties['$active_feature_flags'] = activeFlags;
|
|
2240
|
+
}
|
|
2241
|
+
return additionalProperties;
|
|
2242
|
+
}).catch(() => {
|
|
2243
|
+
// Something went wrong getting the flag info - we should capture the event anyways
|
|
2244
|
+
return {};
|
|
2245
|
+
}).then(additionalProperties => {
|
|
2246
|
+
// No matter what - capture the event
|
|
2247
|
+
const props = {
|
|
2248
|
+
...additionalProperties,
|
|
2249
|
+
...(eventMessage.properties || {}),
|
|
2250
|
+
$groups: eventMessage.groups || groups
|
|
2251
|
+
};
|
|
2252
|
+
return props;
|
|
2253
|
+
});
|
|
2254
|
+
return {
|
|
2255
|
+
distinctId: eventMessage.distinctId,
|
|
2256
|
+
event: eventMessage.event,
|
|
2257
|
+
properties: eventProperties,
|
|
2258
|
+
options: {
|
|
2259
|
+
timestamp: eventMessage.timestamp,
|
|
2260
|
+
disableGeoip: eventMessage.disableGeoip,
|
|
2261
|
+
uuid: eventMessage.uuid
|
|
2262
|
+
}
|
|
2263
|
+
};
|
|
2102
2264
|
}
|
|
2103
2265
|
_runBeforeSend(eventMessage) {
|
|
2104
2266
|
const beforeSend = this.options.before_send;
|