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