posthog-node 4.2.0 → 4.2.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/CHANGELOG.md +4 -0
- package/CONTRIBUTING.md +14 -0
- package/lib/index.cjs.js +70 -252
- package/lib/index.cjs.js.map +1 -1
- package/lib/index.d.ts +21 -17
- package/lib/index.esm.js +70 -252
- package/lib/index.esm.js.map +1 -1
- package/lib/posthog-core/src/index.d.ts +3 -2
- package/lib/posthog-core/src/types.d.ts +16 -13
- package/lib/posthog-node/src/extensions/sentry-integration.d.ts +2 -2
- package/lib/posthog-node/src/feature-flags.d.ts +2 -1
- package/lib/posthog-node/src/fetch.d.ts +1 -1
- package/lib/posthog-node/src/posthog-node.d.ts +1 -1
- package/lib/posthog-node/src/types.d.ts +5 -5
- package/package.json +1 -1
- package/src/feature-flags.ts +14 -14
- package/src/posthog-node.ts +0 -1
package/lib/index.cjs.js
CHANGED
|
@@ -4,7 +4,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
4
4
|
|
|
5
5
|
var rusha = require('rusha');
|
|
6
6
|
|
|
7
|
-
var version = "4.2.
|
|
7
|
+
var version = "4.2.1";
|
|
8
8
|
|
|
9
9
|
var PostHogPersistedProperty;
|
|
10
10
|
(function (PostHogPersistedProperty) {
|
|
@@ -22,6 +22,7 @@ var PostHogPersistedProperty;
|
|
|
22
22
|
PostHogPersistedProperty["GroupProperties"] = "group_properties";
|
|
23
23
|
PostHogPersistedProperty["InstalledAppBuild"] = "installed_app_build";
|
|
24
24
|
PostHogPersistedProperty["InstalledAppVersion"] = "installed_app_version";
|
|
25
|
+
PostHogPersistedProperty["SessionReplay"] = "session_replay";
|
|
25
26
|
})(PostHogPersistedProperty || (PostHogPersistedProperty = {}));
|
|
26
27
|
|
|
27
28
|
function assert(truthyValue, message) {
|
|
@@ -966,7 +967,7 @@ class PostHogCoreStateless {
|
|
|
966
967
|
this.maxBatchSize = Math.max(this.flushAt, options?.maxBatchSize ?? 100);
|
|
967
968
|
this.maxQueueSize = Math.max(this.flushAt, options?.maxQueueSize ?? 1000);
|
|
968
969
|
this.flushInterval = options?.flushInterval ?? 10000;
|
|
969
|
-
this.captureMode = options?.captureMode || '
|
|
970
|
+
this.captureMode = options?.captureMode || 'json';
|
|
970
971
|
// If enable is explicitly set to false we override the optout
|
|
971
972
|
this.defaultOptIn = options?.defaultOptIn ?? true;
|
|
972
973
|
this._retryOptions = {
|
|
@@ -983,11 +984,14 @@ class PostHogCoreStateless {
|
|
|
983
984
|
this._initPromise = Promise.resolve();
|
|
984
985
|
this._isInitialized = true;
|
|
985
986
|
}
|
|
987
|
+
logMsgIfDebug(fn) {
|
|
988
|
+
if (this.isDebug) {
|
|
989
|
+
fn();
|
|
990
|
+
}
|
|
991
|
+
}
|
|
986
992
|
wrap(fn) {
|
|
987
993
|
if (this.disabled) {
|
|
988
|
-
|
|
989
|
-
console.warn('[PostHog] The client is disabled');
|
|
990
|
-
}
|
|
994
|
+
this.logMsgIfDebug(() => console.warn('[PostHog] The client is disabled'));
|
|
991
995
|
return;
|
|
992
996
|
}
|
|
993
997
|
if (this._isInitialized) {
|
|
@@ -1227,7 +1231,7 @@ class PostHogCoreStateless {
|
|
|
1227
1231
|
const queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
|
|
1228
1232
|
if (queue.length >= this.maxQueueSize) {
|
|
1229
1233
|
queue.shift();
|
|
1230
|
-
console.info('Queue is full, the oldest event is dropped.');
|
|
1234
|
+
this.logMsgIfDebug(() => console.info('Queue is full, the oldest event is dropped.'));
|
|
1231
1235
|
}
|
|
1232
1236
|
queue.push({ message });
|
|
1233
1237
|
this.setPersistedProperty(PostHogPersistedProperty.Queue, queue);
|
|
@@ -1384,7 +1388,7 @@ class PostHogCoreStateless {
|
|
|
1384
1388
|
if (!isPostHogFetchError(e)) {
|
|
1385
1389
|
throw e;
|
|
1386
1390
|
}
|
|
1387
|
-
console.error('Error while shutting down PostHog', e);
|
|
1391
|
+
this.logMsgIfDebug(() => console.error('Error while shutting down PostHog', e));
|
|
1388
1392
|
}
|
|
1389
1393
|
}
|
|
1390
1394
|
}
|
|
@@ -1409,14 +1413,13 @@ class PostHogMemoryStorage {
|
|
|
1409
1413
|
* This is currently solved by using the global fetch if available instead.
|
|
1410
1414
|
* See https://github.com/PostHog/posthog-js-lite/issues/127 for more info
|
|
1411
1415
|
*/
|
|
1412
|
-
let _fetch =
|
|
1416
|
+
let _fetch =
|
|
1417
|
+
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
|
|
1413
1418
|
// @ts-ignore
|
|
1414
1419
|
typeof fetch !== 'undefined' ? fetch : typeof global.fetch !== 'undefined' ? global.fetch : undefined;
|
|
1415
|
-
|
|
1416
1420
|
if (!_fetch) {
|
|
1417
1421
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
1418
1422
|
const axios = require('axios');
|
|
1419
|
-
|
|
1420
1423
|
_fetch = async (url, options) => {
|
|
1421
1424
|
const res = await axios.request({
|
|
1422
1425
|
url,
|
|
@@ -1433,14 +1436,13 @@ if (!_fetch) {
|
|
|
1433
1436
|
json: async () => res.data
|
|
1434
1437
|
};
|
|
1435
1438
|
};
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
+
}
|
|
1440
|
+
// NOTE: We have to export this as default, even though we prefer named exports as we are relying on detecting "fetch" in the global scope
|
|
1439
1441
|
var fetch$1 = _fetch;
|
|
1440
1442
|
|
|
1443
|
+
// eslint-disable-next-line
|
|
1441
1444
|
const LONG_SCALE = 0xfffffffffffffff;
|
|
1442
1445
|
const NULL_VALUES_ALLOWED_OPERATORS = ['is_not'];
|
|
1443
|
-
|
|
1444
1446
|
class ClientError extends Error {
|
|
1445
1447
|
constructor(message) {
|
|
1446
1448
|
super();
|
|
@@ -1449,22 +1451,18 @@ class ClientError extends Error {
|
|
|
1449
1451
|
this.message = message;
|
|
1450
1452
|
Object.setPrototypeOf(this, ClientError.prototype);
|
|
1451
1453
|
}
|
|
1452
|
-
|
|
1453
1454
|
}
|
|
1454
|
-
|
|
1455
1455
|
class InconclusiveMatchError extends Error {
|
|
1456
1456
|
constructor(message) {
|
|
1457
1457
|
super(message);
|
|
1458
1458
|
this.name = this.constructor.name;
|
|
1459
|
-
Error.captureStackTrace(this, this.constructor);
|
|
1459
|
+
Error.captureStackTrace(this, this.constructor);
|
|
1460
|
+
// instanceof doesn't work in ES3 or ES5
|
|
1460
1461
|
// https://www.dannyguo.com/blog/how-to-fix-instanceof-not-working-for-custom-errors-in-typescript/
|
|
1461
1462
|
// this is the workaround
|
|
1462
|
-
|
|
1463
1463
|
Object.setPrototypeOf(this, InconclusiveMatchError.prototype);
|
|
1464
1464
|
}
|
|
1465
|
-
|
|
1466
1465
|
}
|
|
1467
|
-
|
|
1468
1466
|
class FeatureFlagsPoller {
|
|
1469
1467
|
constructor({
|
|
1470
1468
|
pollingInterval,
|
|
@@ -1486,81 +1484,69 @@ class FeatureFlagsPoller {
|
|
|
1486
1484
|
this.timeout = timeout;
|
|
1487
1485
|
this.projectApiKey = projectApiKey;
|
|
1488
1486
|
this.host = host;
|
|
1489
|
-
this.poller = undefined;
|
|
1490
|
-
|
|
1487
|
+
this.poller = undefined;
|
|
1488
|
+
// NOTE: as any is required here as the AbortSignal typing is slightly misaligned but works just fine
|
|
1491
1489
|
this.fetch = options.fetch || fetch$1;
|
|
1492
1490
|
this.onError = options.onError;
|
|
1493
1491
|
this.customHeaders = customHeaders;
|
|
1494
1492
|
void this.loadFeatureFlags();
|
|
1495
1493
|
}
|
|
1496
|
-
|
|
1497
1494
|
debug(enabled = true) {
|
|
1498
1495
|
this.debugMode = enabled;
|
|
1499
1496
|
}
|
|
1500
|
-
|
|
1497
|
+
logMsgIfDebug(fn) {
|
|
1498
|
+
if (this.debugMode) {
|
|
1499
|
+
fn();
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1501
1502
|
async getFeatureFlag(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}) {
|
|
1502
1503
|
await this.loadFeatureFlags();
|
|
1503
1504
|
let response = undefined;
|
|
1504
1505
|
let featureFlag = undefined;
|
|
1505
|
-
|
|
1506
1506
|
if (!this.loadedSuccessfullyOnce) {
|
|
1507
1507
|
return response;
|
|
1508
1508
|
}
|
|
1509
|
-
|
|
1510
1509
|
for (const flag of this.featureFlags) {
|
|
1511
1510
|
if (key === flag.key) {
|
|
1512
1511
|
featureFlag = flag;
|
|
1513
1512
|
break;
|
|
1514
1513
|
}
|
|
1515
1514
|
}
|
|
1516
|
-
|
|
1517
1515
|
if (featureFlag !== undefined) {
|
|
1518
1516
|
try {
|
|
1519
1517
|
response = this.computeFlagLocally(featureFlag, distinctId, groups, personProperties, groupProperties);
|
|
1520
|
-
|
|
1521
|
-
if (this.debugMode) {
|
|
1522
|
-
console.debug(`Successfully computed flag locally: ${key} -> ${response}`);
|
|
1523
|
-
}
|
|
1518
|
+
this.logMsgIfDebug(() => console.debug(`Successfully computed flag locally: ${key} -> ${response}`));
|
|
1524
1519
|
} catch (e) {
|
|
1525
1520
|
if (e instanceof InconclusiveMatchError) {
|
|
1526
|
-
|
|
1527
|
-
console.debug(`InconclusiveMatchError when computing flag locally: ${key}: ${e}`);
|
|
1528
|
-
}
|
|
1521
|
+
this.logMsgIfDebug(() => console.debug(`InconclusiveMatchError when computing flag locally: ${key}: ${e}`));
|
|
1529
1522
|
} else if (e instanceof Error) {
|
|
1530
1523
|
this.onError?.(new Error(`Error computing flag locally: ${key}: ${e}`));
|
|
1531
1524
|
}
|
|
1532
1525
|
}
|
|
1533
1526
|
}
|
|
1534
|
-
|
|
1535
1527
|
return response;
|
|
1536
1528
|
}
|
|
1537
|
-
|
|
1538
1529
|
async computeFeatureFlagPayloadLocally(key, matchValue) {
|
|
1539
1530
|
await this.loadFeatureFlags();
|
|
1540
1531
|
let response = undefined;
|
|
1541
|
-
|
|
1542
1532
|
if (!this.loadedSuccessfullyOnce) {
|
|
1543
1533
|
return undefined;
|
|
1544
1534
|
}
|
|
1545
|
-
|
|
1546
1535
|
if (typeof matchValue == 'boolean') {
|
|
1547
1536
|
response = this.featureFlagsByKey?.[key]?.filters?.payloads?.[matchValue.toString()];
|
|
1548
1537
|
} else if (typeof matchValue == 'string') {
|
|
1549
1538
|
response = this.featureFlagsByKey?.[key]?.filters?.payloads?.[matchValue];
|
|
1550
|
-
}
|
|
1551
|
-
|
|
1552
|
-
|
|
1539
|
+
}
|
|
1540
|
+
// Undefined means a loading or missing data issue. Null means evaluation happened and there was no match
|
|
1553
1541
|
if (response === undefined || response === null) {
|
|
1554
1542
|
return null;
|
|
1555
1543
|
}
|
|
1556
|
-
|
|
1557
1544
|
try {
|
|
1558
1545
|
return JSON.parse(response);
|
|
1559
1546
|
} catch {
|
|
1560
1547
|
return response;
|
|
1561
1548
|
}
|
|
1562
1549
|
}
|
|
1563
|
-
|
|
1564
1550
|
async getAllFlagsAndPayloads(distinctId, groups = {}, personProperties = {}, groupProperties = {}) {
|
|
1565
1551
|
await this.loadFeatureFlags();
|
|
1566
1552
|
const response = {};
|
|
@@ -1571,7 +1557,6 @@ class FeatureFlagsPoller {
|
|
|
1571
1557
|
const matchValue = this.computeFlagLocally(flag, distinctId, groups, personProperties, groupProperties);
|
|
1572
1558
|
response[flag.key] = matchValue;
|
|
1573
1559
|
const matchPayload = await this.computeFeatureFlagPayloadLocally(flag.key, matchValue);
|
|
1574
|
-
|
|
1575
1560
|
if (matchPayload) {
|
|
1576
1561
|
payloads[flag.key] = matchPayload;
|
|
1577
1562
|
}
|
|
@@ -1579,7 +1564,6 @@ class FeatureFlagsPoller {
|
|
|
1579
1564
|
if (e instanceof InconclusiveMatchError) ; else if (e instanceof Error) {
|
|
1580
1565
|
this.onError?.(new Error(`Error computing flag locally: ${flag.key}: ${e}`));
|
|
1581
1566
|
}
|
|
1582
|
-
|
|
1583
1567
|
fallbackToDecide = true;
|
|
1584
1568
|
}
|
|
1585
1569
|
});
|
|
@@ -1589,56 +1573,41 @@ class FeatureFlagsPoller {
|
|
|
1589
1573
|
fallbackToDecide
|
|
1590
1574
|
};
|
|
1591
1575
|
}
|
|
1592
|
-
|
|
1593
1576
|
computeFlagLocally(flag, distinctId, groups = {}, personProperties = {}, groupProperties = {}) {
|
|
1594
1577
|
if (flag.ensure_experience_continuity) {
|
|
1595
1578
|
throw new InconclusiveMatchError('Flag has experience continuity enabled');
|
|
1596
1579
|
}
|
|
1597
|
-
|
|
1598
1580
|
if (!flag.active) {
|
|
1599
1581
|
return false;
|
|
1600
1582
|
}
|
|
1601
|
-
|
|
1602
1583
|
const flagFilters = flag.filters || {};
|
|
1603
1584
|
const aggregation_group_type_index = flagFilters.aggregation_group_type_index;
|
|
1604
|
-
|
|
1605
1585
|
if (aggregation_group_type_index != undefined) {
|
|
1606
1586
|
const groupName = this.groupTypeMapping[String(aggregation_group_type_index)];
|
|
1607
|
-
|
|
1608
1587
|
if (!groupName) {
|
|
1609
|
-
|
|
1610
|
-
console.warn(`[FEATURE FLAGS] Unknown group type index ${aggregation_group_type_index} for feature flag ${flag.key}`);
|
|
1611
|
-
}
|
|
1612
|
-
|
|
1588
|
+
this.logMsgIfDebug(() => console.warn(`[FEATURE FLAGS] Unknown group type index ${aggregation_group_type_index} for feature flag ${flag.key}`));
|
|
1613
1589
|
throw new InconclusiveMatchError('Flag has unknown group type index');
|
|
1614
1590
|
}
|
|
1615
|
-
|
|
1616
1591
|
if (!(groupName in groups)) {
|
|
1617
|
-
|
|
1618
|
-
console.warn(`[FEATURE FLAGS] Can't compute group feature flag: ${flag.key} without group names passed in`);
|
|
1619
|
-
}
|
|
1620
|
-
|
|
1592
|
+
this.logMsgIfDebug(() => console.warn(`[FEATURE FLAGS] Can't compute group feature flag: ${flag.key} without group names passed in`));
|
|
1621
1593
|
return false;
|
|
1622
1594
|
}
|
|
1623
|
-
|
|
1624
1595
|
const focusedGroupProperties = groupProperties[groupName];
|
|
1625
1596
|
return this.matchFeatureFlagProperties(flag, groups[groupName], focusedGroupProperties);
|
|
1626
1597
|
} else {
|
|
1627
1598
|
return this.matchFeatureFlagProperties(flag, distinctId, personProperties);
|
|
1628
1599
|
}
|
|
1629
1600
|
}
|
|
1630
|
-
|
|
1631
1601
|
matchFeatureFlagProperties(flag, distinctId, properties) {
|
|
1632
1602
|
const flagFilters = flag.filters || {};
|
|
1633
1603
|
const flagConditions = flagFilters.groups || [];
|
|
1634
1604
|
let isInconclusive = false;
|
|
1635
|
-
let result = undefined;
|
|
1605
|
+
let result = undefined;
|
|
1606
|
+
// # Stable sort conditions with variant overrides to the top. This ensures that if overrides are present, they are
|
|
1636
1607
|
// # evaluated first, and the variant override is applied to the first matching condition.
|
|
1637
|
-
|
|
1638
1608
|
const sortedFlagConditions = [...flagConditions].sort((conditionA, conditionB) => {
|
|
1639
1609
|
const AHasVariantOverride = !!conditionA.variant;
|
|
1640
1610
|
const BHasVariantOverride = !!conditionB.variant;
|
|
1641
|
-
|
|
1642
1611
|
if (AHasVariantOverride && BHasVariantOverride) {
|
|
1643
1612
|
return 0;
|
|
1644
1613
|
} else if (AHasVariantOverride) {
|
|
@@ -1649,19 +1618,16 @@ class FeatureFlagsPoller {
|
|
|
1649
1618
|
return 0;
|
|
1650
1619
|
}
|
|
1651
1620
|
});
|
|
1652
|
-
|
|
1653
1621
|
for (const condition of sortedFlagConditions) {
|
|
1654
1622
|
try {
|
|
1655
1623
|
if (this.isConditionMatch(flag, distinctId, condition, properties)) {
|
|
1656
1624
|
const variantOverride = condition.variant;
|
|
1657
1625
|
const flagVariants = flagFilters.multivariate?.variants || [];
|
|
1658
|
-
|
|
1659
1626
|
if (variantOverride && flagVariants.some(variant => variant.key === variantOverride)) {
|
|
1660
1627
|
result = variantOverride;
|
|
1661
1628
|
} else {
|
|
1662
1629
|
result = this.getMatchingVariant(flag, distinctId) || true;
|
|
1663
1630
|
}
|
|
1664
|
-
|
|
1665
1631
|
break;
|
|
1666
1632
|
}
|
|
1667
1633
|
} catch (e) {
|
|
@@ -1672,68 +1638,51 @@ class FeatureFlagsPoller {
|
|
|
1672
1638
|
}
|
|
1673
1639
|
}
|
|
1674
1640
|
}
|
|
1675
|
-
|
|
1676
1641
|
if (result !== undefined) {
|
|
1677
1642
|
return result;
|
|
1678
1643
|
} else if (isInconclusive) {
|
|
1679
1644
|
throw new InconclusiveMatchError("Can't determine if feature flag is enabled or not with given properties");
|
|
1680
|
-
}
|
|
1681
|
-
|
|
1682
|
-
|
|
1645
|
+
}
|
|
1646
|
+
// We can only return False when all conditions are False
|
|
1683
1647
|
return false;
|
|
1684
1648
|
}
|
|
1685
|
-
|
|
1686
1649
|
isConditionMatch(flag, distinctId, condition, properties) {
|
|
1687
1650
|
const rolloutPercentage = condition.rollout_percentage;
|
|
1688
|
-
|
|
1689
1651
|
const warnFunction = msg => {
|
|
1690
|
-
|
|
1691
|
-
console.warn(msg);
|
|
1692
|
-
}
|
|
1652
|
+
this.logMsgIfDebug(() => console.warn(msg));
|
|
1693
1653
|
};
|
|
1694
|
-
|
|
1695
1654
|
if ((condition.properties || []).length > 0) {
|
|
1696
1655
|
for (const prop of condition.properties) {
|
|
1697
1656
|
const propertyType = prop.type;
|
|
1698
1657
|
let matches = false;
|
|
1699
|
-
|
|
1700
1658
|
if (propertyType === 'cohort') {
|
|
1701
1659
|
matches = matchCohort(prop, properties, this.cohorts, this.debugMode);
|
|
1702
1660
|
} else {
|
|
1703
1661
|
matches = matchProperty(prop, properties, warnFunction);
|
|
1704
1662
|
}
|
|
1705
|
-
|
|
1706
1663
|
if (!matches) {
|
|
1707
1664
|
return false;
|
|
1708
1665
|
}
|
|
1709
1666
|
}
|
|
1710
|
-
|
|
1711
1667
|
if (rolloutPercentage == undefined) {
|
|
1712
1668
|
return true;
|
|
1713
1669
|
}
|
|
1714
1670
|
}
|
|
1715
|
-
|
|
1716
1671
|
if (rolloutPercentage != undefined && _hash(flag.key, distinctId) > rolloutPercentage / 100.0) {
|
|
1717
1672
|
return false;
|
|
1718
1673
|
}
|
|
1719
|
-
|
|
1720
1674
|
return true;
|
|
1721
1675
|
}
|
|
1722
|
-
|
|
1723
1676
|
getMatchingVariant(flag, distinctId) {
|
|
1724
1677
|
const hashValue = _hash(flag.key, distinctId, 'variant');
|
|
1725
|
-
|
|
1726
1678
|
const matchingVariant = this.variantLookupTable(flag).find(variant => {
|
|
1727
1679
|
return hashValue >= variant.valueMin && hashValue < variant.valueMax;
|
|
1728
1680
|
});
|
|
1729
|
-
|
|
1730
1681
|
if (matchingVariant) {
|
|
1731
1682
|
return matchingVariant.key;
|
|
1732
1683
|
}
|
|
1733
|
-
|
|
1734
1684
|
return undefined;
|
|
1735
1685
|
}
|
|
1736
|
-
|
|
1737
1686
|
variantLookupTable(flag) {
|
|
1738
1687
|
const lookupTable = [];
|
|
1739
1688
|
let valueMin = 0;
|
|
@@ -1751,40 +1700,31 @@ class FeatureFlagsPoller {
|
|
|
1751
1700
|
});
|
|
1752
1701
|
return lookupTable;
|
|
1753
1702
|
}
|
|
1754
|
-
|
|
1755
1703
|
async loadFeatureFlags(forceReload = false) {
|
|
1756
1704
|
if (!this.loadedSuccessfullyOnce || forceReload) {
|
|
1757
1705
|
await this._loadFeatureFlags();
|
|
1758
1706
|
}
|
|
1759
1707
|
}
|
|
1760
|
-
|
|
1761
1708
|
async _loadFeatureFlags() {
|
|
1762
1709
|
if (this.poller) {
|
|
1763
1710
|
clearTimeout(this.poller);
|
|
1764
1711
|
this.poller = undefined;
|
|
1765
1712
|
}
|
|
1766
|
-
|
|
1767
1713
|
this.poller = setTimeout(() => this._loadFeatureFlags(), this.pollingInterval);
|
|
1768
|
-
|
|
1769
1714
|
try {
|
|
1770
1715
|
const res = await this._requestFeatureFlagDefinitions();
|
|
1771
|
-
|
|
1772
1716
|
if (res && res.status === 401) {
|
|
1773
1717
|
throw new ClientError(`Your personalApiKey is invalid. Are you sure you're not using your Project API key? More information: https://posthog.com/docs/api/overview`);
|
|
1774
1718
|
}
|
|
1775
|
-
|
|
1776
1719
|
if (res && res.status !== 200) {
|
|
1777
1720
|
// something else went wrong, or the server is down.
|
|
1778
1721
|
// In this case, don't override existing flags
|
|
1779
1722
|
return;
|
|
1780
1723
|
}
|
|
1781
|
-
|
|
1782
1724
|
const responseJson = await res.json();
|
|
1783
|
-
|
|
1784
1725
|
if (!('flags' in responseJson)) {
|
|
1785
1726
|
this.onError?.(new Error(`Invalid response when getting feature flags: ${JSON.stringify(responseJson)}`));
|
|
1786
1727
|
}
|
|
1787
|
-
|
|
1788
1728
|
this.featureFlags = responseJson.flags || [];
|
|
1789
1729
|
this.featureFlagsByKey = this.featureFlags.reduce((acc, curr) => (acc[curr.key] = curr, acc), {});
|
|
1790
1730
|
this.groupTypeMapping = responseJson.group_type_mapping || {};
|
|
@@ -1798,18 +1738,17 @@ class FeatureFlagsPoller {
|
|
|
1798
1738
|
}
|
|
1799
1739
|
}
|
|
1800
1740
|
}
|
|
1801
|
-
|
|
1802
1741
|
async _requestFeatureFlagDefinitions() {
|
|
1803
1742
|
const url = `${this.host}/api/feature_flag/local_evaluation?token=${this.projectApiKey}&send_cohorts`;
|
|
1804
1743
|
const options = {
|
|
1805
1744
|
method: 'GET',
|
|
1806
|
-
headers: {
|
|
1745
|
+
headers: {
|
|
1746
|
+
...this.customHeaders,
|
|
1807
1747
|
'Content-Type': 'application/json',
|
|
1808
1748
|
Authorization: `Bearer ${this.personalApiKey}`
|
|
1809
1749
|
}
|
|
1810
1750
|
};
|
|
1811
1751
|
let abortTimeout = null;
|
|
1812
|
-
|
|
1813
1752
|
if (this.timeout && typeof this.timeout === 'number') {
|
|
1814
1753
|
const controller = new AbortController();
|
|
1815
1754
|
abortTimeout = safeSetTimeout(() => {
|
|
@@ -1817,62 +1756,50 @@ class FeatureFlagsPoller {
|
|
|
1817
1756
|
}, this.timeout);
|
|
1818
1757
|
options.signal = controller.signal;
|
|
1819
1758
|
}
|
|
1820
|
-
|
|
1821
1759
|
try {
|
|
1822
1760
|
return await this.fetch(url, options);
|
|
1823
1761
|
} finally {
|
|
1824
1762
|
clearTimeout(abortTimeout);
|
|
1825
1763
|
}
|
|
1826
1764
|
}
|
|
1827
|
-
|
|
1828
1765
|
stopPoller() {
|
|
1829
1766
|
clearTimeout(this.poller);
|
|
1830
1767
|
}
|
|
1831
|
-
|
|
1832
|
-
|
|
1768
|
+
}
|
|
1769
|
+
// # This function takes a distinct_id and a feature flag key and returns a float between 0 and 1.
|
|
1833
1770
|
// # Given the same distinct_id and key, it'll always return the same float. These floats are
|
|
1834
1771
|
// # uniformly distributed between 0 and 1, so if we want to show this feature to 20% of traffic
|
|
1835
1772
|
// # we can do _hash(key, distinct_id) < 0.2
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
1773
|
function _hash(key, distinctId, salt = '') {
|
|
1839
1774
|
// rusha is a fast sha1 implementation in pure javascript
|
|
1840
1775
|
const sha1Hash = rusha.createHash();
|
|
1841
1776
|
sha1Hash.update(`${key}.${distinctId}${salt}`);
|
|
1842
1777
|
return parseInt(sha1Hash.digest('hex').slice(0, 15), 16) / LONG_SCALE;
|
|
1843
1778
|
}
|
|
1844
|
-
|
|
1845
1779
|
function matchProperty(property, propertyValues, warnFunction) {
|
|
1846
1780
|
const key = property.key;
|
|
1847
1781
|
const value = property.value;
|
|
1848
1782
|
const operator = property.operator || 'exact';
|
|
1849
|
-
|
|
1850
1783
|
if (!(key in propertyValues)) {
|
|
1851
1784
|
throw new InconclusiveMatchError(`Property ${key} not found in propertyValues`);
|
|
1852
1785
|
} else if (operator === 'is_not_set') {
|
|
1853
1786
|
throw new InconclusiveMatchError(`Operator is_not_set is not supported`);
|
|
1854
1787
|
}
|
|
1855
|
-
|
|
1856
1788
|
const overrideValue = propertyValues[key];
|
|
1857
|
-
|
|
1858
1789
|
if (overrideValue == null && !NULL_VALUES_ALLOWED_OPERATORS.includes(operator)) {
|
|
1859
1790
|
// if the value is null, just fail the feature flag comparison
|
|
1860
1791
|
// this isn't an InconclusiveMatchError because the property value was provided.
|
|
1861
1792
|
if (warnFunction) {
|
|
1862
1793
|
warnFunction(`Property ${key} cannot have a value of null/undefined with the ${operator} operator`);
|
|
1863
1794
|
}
|
|
1864
|
-
|
|
1865
1795
|
return false;
|
|
1866
1796
|
}
|
|
1867
|
-
|
|
1868
1797
|
function computeExactMatch(value, overrideValue) {
|
|
1869
1798
|
if (Array.isArray(value)) {
|
|
1870
1799
|
return value.map(val => String(val).toLowerCase()).includes(String(overrideValue).toLowerCase());
|
|
1871
1800
|
}
|
|
1872
|
-
|
|
1873
1801
|
return String(value).toLowerCase() === String(overrideValue).toLowerCase();
|
|
1874
1802
|
}
|
|
1875
|
-
|
|
1876
1803
|
function compare(lhs, rhs, operator) {
|
|
1877
1804
|
if (operator === 'gt') {
|
|
1878
1805
|
return lhs > rhs;
|
|
@@ -1886,29 +1813,21 @@ function matchProperty(property, propertyValues, warnFunction) {
|
|
|
1886
1813
|
throw new Error(`Invalid operator: ${operator}`);
|
|
1887
1814
|
}
|
|
1888
1815
|
}
|
|
1889
|
-
|
|
1890
1816
|
switch (operator) {
|
|
1891
1817
|
case 'exact':
|
|
1892
1818
|
return computeExactMatch(value, overrideValue);
|
|
1893
|
-
|
|
1894
1819
|
case 'is_not':
|
|
1895
1820
|
return !computeExactMatch(value, overrideValue);
|
|
1896
|
-
|
|
1897
1821
|
case 'is_set':
|
|
1898
1822
|
return key in propertyValues;
|
|
1899
|
-
|
|
1900
1823
|
case 'icontains':
|
|
1901
1824
|
return String(overrideValue).toLowerCase().includes(String(value).toLowerCase());
|
|
1902
|
-
|
|
1903
1825
|
case 'not_icontains':
|
|
1904
1826
|
return !String(overrideValue).toLowerCase().includes(String(value).toLowerCase());
|
|
1905
|
-
|
|
1906
1827
|
case 'regex':
|
|
1907
1828
|
return isValidRegex(String(value)) && String(overrideValue).match(String(value)) !== null;
|
|
1908
|
-
|
|
1909
1829
|
case 'not_regex':
|
|
1910
1830
|
return isValidRegex(String(value)) && String(overrideValue).match(String(value)) === null;
|
|
1911
|
-
|
|
1912
1831
|
case 'gt':
|
|
1913
1832
|
case 'gte':
|
|
1914
1833
|
case 'lt':
|
|
@@ -1917,14 +1836,13 @@ function matchProperty(property, propertyValues, warnFunction) {
|
|
|
1917
1836
|
// :TRICKY: We adjust comparison based on the override value passed in,
|
|
1918
1837
|
// to make sure we handle both numeric and string comparisons appropriately.
|
|
1919
1838
|
let parsedValue = typeof value === 'number' ? value : null;
|
|
1920
|
-
|
|
1921
1839
|
if (typeof value === 'string') {
|
|
1922
1840
|
try {
|
|
1923
1841
|
parsedValue = parseFloat(value);
|
|
1924
|
-
} catch (err) {
|
|
1842
|
+
} catch (err) {
|
|
1843
|
+
// pass
|
|
1925
1844
|
}
|
|
1926
1845
|
}
|
|
1927
|
-
|
|
1928
1846
|
if (parsedValue != null && overrideValue != null) {
|
|
1929
1847
|
// check both null and undefined
|
|
1930
1848
|
if (typeof overrideValue === 'string') {
|
|
@@ -1936,66 +1854,50 @@ function matchProperty(property, propertyValues, warnFunction) {
|
|
|
1936
1854
|
return compare(String(overrideValue), String(value), operator);
|
|
1937
1855
|
}
|
|
1938
1856
|
}
|
|
1939
|
-
|
|
1940
1857
|
case 'is_date_after':
|
|
1941
1858
|
case 'is_date_before':
|
|
1942
1859
|
{
|
|
1943
1860
|
let parsedDate = relativeDateParseForFeatureFlagMatching(String(value));
|
|
1944
|
-
|
|
1945
1861
|
if (parsedDate == null) {
|
|
1946
1862
|
parsedDate = convertToDateTime(value);
|
|
1947
1863
|
}
|
|
1948
|
-
|
|
1949
1864
|
if (parsedDate == null) {
|
|
1950
1865
|
throw new InconclusiveMatchError(`Invalid date: ${value}`);
|
|
1951
1866
|
}
|
|
1952
|
-
|
|
1953
1867
|
const overrideDate = convertToDateTime(overrideValue);
|
|
1954
|
-
|
|
1955
1868
|
if (['is_date_before'].includes(operator)) {
|
|
1956
1869
|
return overrideDate < parsedDate;
|
|
1957
1870
|
}
|
|
1958
|
-
|
|
1959
1871
|
return overrideDate > parsedDate;
|
|
1960
1872
|
}
|
|
1961
|
-
|
|
1962
1873
|
default:
|
|
1963
1874
|
throw new InconclusiveMatchError(`Unknown operator: ${operator}`);
|
|
1964
1875
|
}
|
|
1965
1876
|
}
|
|
1966
|
-
|
|
1967
1877
|
function matchCohort(property, propertyValues, cohortProperties, debugMode = false) {
|
|
1968
1878
|
const cohortId = String(property.value);
|
|
1969
|
-
|
|
1970
1879
|
if (!(cohortId in cohortProperties)) {
|
|
1971
1880
|
throw new InconclusiveMatchError("can't match cohort without a given cohort property value");
|
|
1972
1881
|
}
|
|
1973
|
-
|
|
1974
1882
|
const propertyGroup = cohortProperties[cohortId];
|
|
1975
1883
|
return matchPropertyGroup(propertyGroup, propertyValues, cohortProperties, debugMode);
|
|
1976
1884
|
}
|
|
1977
|
-
|
|
1978
1885
|
function matchPropertyGroup(propertyGroup, propertyValues, cohortProperties, debugMode = false) {
|
|
1979
1886
|
if (!propertyGroup) {
|
|
1980
1887
|
return true;
|
|
1981
1888
|
}
|
|
1982
|
-
|
|
1983
1889
|
const propertyGroupType = propertyGroup.type;
|
|
1984
1890
|
const properties = propertyGroup.values;
|
|
1985
|
-
|
|
1986
1891
|
if (!properties || properties.length === 0) {
|
|
1987
1892
|
// empty groups are no-ops, always match
|
|
1988
1893
|
return true;
|
|
1989
1894
|
}
|
|
1990
|
-
|
|
1991
1895
|
let errorMatchingLocally = false;
|
|
1992
|
-
|
|
1993
1896
|
if ('values' in properties[0]) {
|
|
1994
1897
|
// a nested property group
|
|
1995
1898
|
for (const prop of properties) {
|
|
1996
1899
|
try {
|
|
1997
1900
|
const matches = matchPropertyGroup(prop, propertyValues, cohortProperties, debugMode);
|
|
1998
|
-
|
|
1999
1901
|
if (propertyGroupType === 'AND') {
|
|
2000
1902
|
if (!matches) {
|
|
2001
1903
|
return false;
|
|
@@ -2011,39 +1913,32 @@ function matchPropertyGroup(propertyGroup, propertyValues, cohortProperties, deb
|
|
|
2011
1913
|
if (debugMode) {
|
|
2012
1914
|
console.debug(`Failed to compute property ${prop} locally: ${err}`);
|
|
2013
1915
|
}
|
|
2014
|
-
|
|
2015
1916
|
errorMatchingLocally = true;
|
|
2016
1917
|
} else {
|
|
2017
1918
|
throw err;
|
|
2018
1919
|
}
|
|
2019
1920
|
}
|
|
2020
1921
|
}
|
|
2021
|
-
|
|
2022
1922
|
if (errorMatchingLocally) {
|
|
2023
1923
|
throw new InconclusiveMatchError("Can't match cohort without a given cohort property value");
|
|
2024
|
-
}
|
|
2025
|
-
|
|
2026
|
-
|
|
1924
|
+
}
|
|
1925
|
+
// if we get here, all matched in AND case, or none matched in OR case
|
|
2027
1926
|
return propertyGroupType === 'AND';
|
|
2028
1927
|
} else {
|
|
2029
1928
|
for (const prop of properties) {
|
|
2030
1929
|
try {
|
|
2031
1930
|
let matches;
|
|
2032
|
-
|
|
2033
1931
|
if (prop.type === 'cohort') {
|
|
2034
1932
|
matches = matchCohort(prop, propertyValues, cohortProperties, debugMode);
|
|
2035
1933
|
} else {
|
|
2036
1934
|
matches = matchProperty(prop, propertyValues);
|
|
2037
1935
|
}
|
|
2038
|
-
|
|
2039
1936
|
const negation = prop.negation || false;
|
|
2040
|
-
|
|
2041
1937
|
if (propertyGroupType === 'AND') {
|
|
2042
1938
|
// if negated property, do the inverse
|
|
2043
1939
|
if (!matches && !negation) {
|
|
2044
1940
|
return false;
|
|
2045
1941
|
}
|
|
2046
|
-
|
|
2047
1942
|
if (matches && negation) {
|
|
2048
1943
|
return false;
|
|
2049
1944
|
}
|
|
@@ -2052,7 +1947,6 @@ function matchPropertyGroup(propertyGroup, propertyValues, cohortProperties, deb
|
|
|
2052
1947
|
if (matches && !negation) {
|
|
2053
1948
|
return true;
|
|
2054
1949
|
}
|
|
2055
|
-
|
|
2056
1950
|
if (!matches && negation) {
|
|
2057
1951
|
return true;
|
|
2058
1952
|
}
|
|
@@ -2062,23 +1956,19 @@ function matchPropertyGroup(propertyGroup, propertyValues, cohortProperties, deb
|
|
|
2062
1956
|
if (debugMode) {
|
|
2063
1957
|
console.debug(`Failed to compute property ${prop} locally: ${err}`);
|
|
2064
1958
|
}
|
|
2065
|
-
|
|
2066
1959
|
errorMatchingLocally = true;
|
|
2067
1960
|
} else {
|
|
2068
1961
|
throw err;
|
|
2069
1962
|
}
|
|
2070
1963
|
}
|
|
2071
1964
|
}
|
|
2072
|
-
|
|
2073
1965
|
if (errorMatchingLocally) {
|
|
2074
1966
|
throw new InconclusiveMatchError("can't match cohort without a given cohort property value");
|
|
2075
|
-
}
|
|
2076
|
-
|
|
2077
|
-
|
|
1967
|
+
}
|
|
1968
|
+
// if we get here, all matched in AND case, or none matched in OR case
|
|
2078
1969
|
return propertyGroupType === 'AND';
|
|
2079
1970
|
}
|
|
2080
1971
|
}
|
|
2081
|
-
|
|
2082
1972
|
function isValidRegex(regex) {
|
|
2083
1973
|
try {
|
|
2084
1974
|
new RegExp(regex);
|
|
@@ -2087,42 +1977,33 @@ function isValidRegex(regex) {
|
|
|
2087
1977
|
return false;
|
|
2088
1978
|
}
|
|
2089
1979
|
}
|
|
2090
|
-
|
|
2091
1980
|
function convertToDateTime(value) {
|
|
2092
1981
|
if (value instanceof Date) {
|
|
2093
1982
|
return value;
|
|
2094
1983
|
} else if (typeof value === 'string' || typeof value === 'number') {
|
|
2095
1984
|
const date = new Date(value);
|
|
2096
|
-
|
|
2097
1985
|
if (!isNaN(date.valueOf())) {
|
|
2098
1986
|
return date;
|
|
2099
1987
|
}
|
|
2100
|
-
|
|
2101
1988
|
throw new InconclusiveMatchError(`${value} is in an invalid date format`);
|
|
2102
1989
|
} else {
|
|
2103
1990
|
throw new InconclusiveMatchError(`The date provided ${value} must be a string, number, or date object`);
|
|
2104
1991
|
}
|
|
2105
1992
|
}
|
|
2106
|
-
|
|
2107
1993
|
function relativeDateParseForFeatureFlagMatching(value) {
|
|
2108
1994
|
const regex = /^-?(?<number>[0-9]+)(?<interval>[a-z])$/;
|
|
2109
1995
|
const match = value.match(regex);
|
|
2110
1996
|
const parsedDt = new Date(new Date().toISOString());
|
|
2111
|
-
|
|
2112
1997
|
if (match) {
|
|
2113
1998
|
if (!match.groups) {
|
|
2114
1999
|
return null;
|
|
2115
2000
|
}
|
|
2116
|
-
|
|
2117
2001
|
const number = parseInt(match.groups['number']);
|
|
2118
|
-
|
|
2119
2002
|
if (number >= 10000) {
|
|
2120
2003
|
// Guard against overflow, disallow numbers greater than 10_000
|
|
2121
2004
|
return null;
|
|
2122
2005
|
}
|
|
2123
|
-
|
|
2124
2006
|
const interval = match.groups['interval'];
|
|
2125
|
-
|
|
2126
2007
|
if (interval == 'h') {
|
|
2127
2008
|
parsedDt.setUTCHours(parsedDt.getUTCHours() - number);
|
|
2128
2009
|
} else if (interval == 'd') {
|
|
@@ -2136,7 +2017,6 @@ function relativeDateParseForFeatureFlagMatching(value) {
|
|
|
2136
2017
|
} else {
|
|
2137
2018
|
return null;
|
|
2138
2019
|
}
|
|
2139
|
-
|
|
2140
2020
|
return parsedDt;
|
|
2141
2021
|
} else {
|
|
2142
2022
|
return null;
|
|
@@ -2144,15 +2024,13 @@ function relativeDateParseForFeatureFlagMatching(value) {
|
|
|
2144
2024
|
}
|
|
2145
2025
|
|
|
2146
2026
|
const THIRTY_SECONDS = 30 * 1000;
|
|
2147
|
-
const MAX_CACHE_SIZE = 50 * 1000;
|
|
2148
|
-
|
|
2027
|
+
const MAX_CACHE_SIZE = 50 * 1000;
|
|
2028
|
+
// The actual exported Nodejs API.
|
|
2149
2029
|
class PostHog extends PostHogCoreStateless {
|
|
2150
2030
|
constructor(apiKey, options = {}) {
|
|
2151
|
-
options.captureMode = options?.captureMode || 'json';
|
|
2152
2031
|
super(apiKey, options);
|
|
2153
2032
|
this._memoryStorage = new PostHogMemoryStorage();
|
|
2154
2033
|
this.options = options;
|
|
2155
|
-
|
|
2156
2034
|
if (options.personalApiKey) {
|
|
2157
2035
|
this.featureFlagsPoller = new FeatureFlagsPoller({
|
|
2158
2036
|
pollingInterval: typeof options.featureFlagsPollingInterval === 'number' ? options.featureFlagsPollingInterval : THIRTY_SECONDS,
|
|
@@ -2167,48 +2045,37 @@ class PostHog extends PostHogCoreStateless {
|
|
|
2167
2045
|
customHeaders: this.getCustomHeaders()
|
|
2168
2046
|
});
|
|
2169
2047
|
}
|
|
2170
|
-
|
|
2171
2048
|
this.distinctIdHasSentFlagCalls = {};
|
|
2172
2049
|
this.maxCacheSize = options.maxCacheSize || MAX_CACHE_SIZE;
|
|
2173
2050
|
}
|
|
2174
|
-
|
|
2175
2051
|
getPersistedProperty(key) {
|
|
2176
2052
|
return this._memoryStorage.getProperty(key);
|
|
2177
2053
|
}
|
|
2178
|
-
|
|
2179
2054
|
setPersistedProperty(key, value) {
|
|
2180
2055
|
return this._memoryStorage.setProperty(key, value);
|
|
2181
2056
|
}
|
|
2182
|
-
|
|
2183
2057
|
fetch(url, options) {
|
|
2184
2058
|
return this.options.fetch ? this.options.fetch(url, options) : fetch$1(url, options);
|
|
2185
2059
|
}
|
|
2186
|
-
|
|
2187
2060
|
getLibraryId() {
|
|
2188
2061
|
return 'posthog-node';
|
|
2189
2062
|
}
|
|
2190
|
-
|
|
2191
2063
|
getLibraryVersion() {
|
|
2192
2064
|
return version;
|
|
2193
2065
|
}
|
|
2194
|
-
|
|
2195
2066
|
getCustomUserAgent() {
|
|
2196
2067
|
return `${this.getLibraryId()}/${this.getLibraryVersion()}`;
|
|
2197
2068
|
}
|
|
2198
|
-
|
|
2199
2069
|
enable() {
|
|
2200
2070
|
return super.optIn();
|
|
2201
2071
|
}
|
|
2202
|
-
|
|
2203
2072
|
disable() {
|
|
2204
2073
|
return super.optOut();
|
|
2205
2074
|
}
|
|
2206
|
-
|
|
2207
2075
|
debug(enabled = true) {
|
|
2208
2076
|
super.debug(enabled);
|
|
2209
2077
|
this.featureFlagsPoller?.debug(enabled);
|
|
2210
2078
|
}
|
|
2211
|
-
|
|
2212
2079
|
capture({
|
|
2213
2080
|
distinctId,
|
|
2214
2081
|
event,
|
|
@@ -2226,65 +2093,55 @@ class PostHog extends PostHogCoreStateless {
|
|
|
2226
2093
|
uuid
|
|
2227
2094
|
});
|
|
2228
2095
|
};
|
|
2229
|
-
|
|
2230
2096
|
const _getFlags = (distinctId, groups, disableGeoip) => {
|
|
2231
2097
|
return super.getFeatureFlagsStateless(distinctId, groups, undefined, undefined, disableGeoip);
|
|
2232
|
-
};
|
|
2233
|
-
|
|
2234
|
-
|
|
2098
|
+
};
|
|
2099
|
+
// :TRICKY: If we flush, or need to shut down, to not lose events we want this promise to resolve before we flush
|
|
2235
2100
|
const capturePromise = Promise.resolve().then(async () => {
|
|
2236
2101
|
if (sendFeatureFlags) {
|
|
2237
2102
|
// If we are sending feature flags, we need to make sure we have the latest flags
|
|
2238
2103
|
// return await super.getFeatureFlagsStateless(distinctId, groups, undefined, undefined, disableGeoip)
|
|
2239
2104
|
return await _getFlags(distinctId, groups, disableGeoip);
|
|
2240
2105
|
}
|
|
2241
|
-
|
|
2242
2106
|
if ((this.featureFlagsPoller?.featureFlags?.length || 0) > 0) {
|
|
2243
2107
|
// Otherwise we may as well check for the flags locally and include them if there
|
|
2244
2108
|
const groupsWithStringValues = {};
|
|
2245
|
-
|
|
2246
2109
|
for (const [key, value] of Object.entries(groups || {})) {
|
|
2247
2110
|
groupsWithStringValues[key] = String(value);
|
|
2248
2111
|
}
|
|
2249
|
-
|
|
2250
2112
|
return await this.getAllFlags(distinctId, {
|
|
2251
2113
|
groups: groupsWithStringValues,
|
|
2252
2114
|
disableGeoip,
|
|
2253
2115
|
onlyEvaluateLocally: true
|
|
2254
2116
|
});
|
|
2255
2117
|
}
|
|
2256
|
-
|
|
2257
2118
|
return {};
|
|
2258
2119
|
}).then(flags => {
|
|
2259
2120
|
// Derive the relevant flag properties to add
|
|
2260
2121
|
const additionalProperties = {};
|
|
2261
|
-
|
|
2262
2122
|
if (flags) {
|
|
2263
2123
|
for (const [feature, variant] of Object.entries(flags)) {
|
|
2264
2124
|
additionalProperties[`$feature/${feature}`] = variant;
|
|
2265
2125
|
}
|
|
2266
2126
|
}
|
|
2267
|
-
|
|
2268
2127
|
const activeFlags = Object.keys(flags || {}).filter(flag => flags?.[flag] !== false);
|
|
2269
|
-
|
|
2270
2128
|
if (activeFlags.length > 0) {
|
|
2271
2129
|
additionalProperties['$active_feature_flags'] = activeFlags;
|
|
2272
2130
|
}
|
|
2273
|
-
|
|
2274
2131
|
return additionalProperties;
|
|
2275
2132
|
}).catch(() => {
|
|
2276
2133
|
// Something went wrong getting the flag info - we should capture the event anyways
|
|
2277
2134
|
return {};
|
|
2278
2135
|
}).then(additionalProperties => {
|
|
2279
2136
|
// No matter what - capture the event
|
|
2280
|
-
_capture({
|
|
2137
|
+
_capture({
|
|
2138
|
+
...additionalProperties,
|
|
2281
2139
|
...properties,
|
|
2282
2140
|
$groups: groups
|
|
2283
2141
|
});
|
|
2284
2142
|
});
|
|
2285
2143
|
this.addPendingPromise(capturePromise);
|
|
2286
2144
|
}
|
|
2287
|
-
|
|
2288
2145
|
identify({
|
|
2289
2146
|
distinctId,
|
|
2290
2147
|
properties,
|
|
@@ -2298,13 +2155,11 @@ class PostHog extends PostHogCoreStateless {
|
|
|
2298
2155
|
disableGeoip
|
|
2299
2156
|
});
|
|
2300
2157
|
}
|
|
2301
|
-
|
|
2302
2158
|
alias(data) {
|
|
2303
2159
|
super.aliasStateless(data.alias, data.distinctId, undefined, {
|
|
2304
2160
|
disableGeoip: data.disableGeoip
|
|
2305
2161
|
});
|
|
2306
2162
|
}
|
|
2307
|
-
|
|
2308
2163
|
async getFeatureFlag(key, distinctId, options) {
|
|
2309
2164
|
const {
|
|
2310
2165
|
groups,
|
|
@@ -2318,36 +2173,29 @@ class PostHog extends PostHogCoreStateless {
|
|
|
2318
2173
|
} = options || {};
|
|
2319
2174
|
const adjustedProperties = this.addLocalPersonAndGroupProperties(distinctId, groups, personProperties, groupProperties);
|
|
2320
2175
|
personProperties = adjustedProperties.allPersonProperties;
|
|
2321
|
-
groupProperties = adjustedProperties.allGroupProperties;
|
|
2322
|
-
|
|
2176
|
+
groupProperties = adjustedProperties.allGroupProperties;
|
|
2177
|
+
// set defaults
|
|
2323
2178
|
if (onlyEvaluateLocally == undefined) {
|
|
2324
2179
|
onlyEvaluateLocally = false;
|
|
2325
2180
|
}
|
|
2326
|
-
|
|
2327
2181
|
if (sendFeatureFlagEvents == undefined) {
|
|
2328
2182
|
sendFeatureFlagEvents = true;
|
|
2329
2183
|
}
|
|
2330
|
-
|
|
2331
2184
|
let response = await this.featureFlagsPoller?.getFeatureFlag(key, distinctId, groups, personProperties, groupProperties);
|
|
2332
2185
|
const flagWasLocallyEvaluated = response !== undefined;
|
|
2333
|
-
|
|
2334
2186
|
if (!flagWasLocallyEvaluated && !onlyEvaluateLocally) {
|
|
2335
2187
|
response = await super.getFeatureFlagStateless(key, distinctId, groups, personProperties, groupProperties, disableGeoip);
|
|
2336
2188
|
}
|
|
2337
|
-
|
|
2338
2189
|
const featureFlagReportedKey = `${key}_${response}`;
|
|
2339
|
-
|
|
2340
2190
|
if (sendFeatureFlagEvents && (!(distinctId in this.distinctIdHasSentFlagCalls) || !this.distinctIdHasSentFlagCalls[distinctId].includes(featureFlagReportedKey))) {
|
|
2341
2191
|
if (Object.keys(this.distinctIdHasSentFlagCalls).length >= this.maxCacheSize) {
|
|
2342
2192
|
this.distinctIdHasSentFlagCalls = {};
|
|
2343
2193
|
}
|
|
2344
|
-
|
|
2345
2194
|
if (Array.isArray(this.distinctIdHasSentFlagCalls[distinctId])) {
|
|
2346
2195
|
this.distinctIdHasSentFlagCalls[distinctId].push(featureFlagReportedKey);
|
|
2347
2196
|
} else {
|
|
2348
2197
|
this.distinctIdHasSentFlagCalls[distinctId] = [featureFlagReportedKey];
|
|
2349
2198
|
}
|
|
2350
|
-
|
|
2351
2199
|
this.capture({
|
|
2352
2200
|
distinctId,
|
|
2353
2201
|
event: '$feature_flag_called',
|
|
@@ -2361,10 +2209,8 @@ class PostHog extends PostHogCoreStateless {
|
|
|
2361
2209
|
disableGeoip
|
|
2362
2210
|
});
|
|
2363
2211
|
}
|
|
2364
|
-
|
|
2365
2212
|
return response;
|
|
2366
2213
|
}
|
|
2367
|
-
|
|
2368
2214
|
async getFeatureFlagPayload(key, distinctId, matchValue, options) {
|
|
2369
2215
|
const {
|
|
2370
2216
|
groups,
|
|
@@ -2379,56 +2225,45 @@ class PostHog extends PostHogCoreStateless {
|
|
|
2379
2225
|
const adjustedProperties = this.addLocalPersonAndGroupProperties(distinctId, groups, personProperties, groupProperties);
|
|
2380
2226
|
personProperties = adjustedProperties.allPersonProperties;
|
|
2381
2227
|
groupProperties = adjustedProperties.allGroupProperties;
|
|
2382
|
-
let response = undefined;
|
|
2383
|
-
|
|
2228
|
+
let response = undefined;
|
|
2229
|
+
// Try to get match value locally if not provided
|
|
2384
2230
|
if (!matchValue) {
|
|
2385
|
-
matchValue = await this.getFeatureFlag(key, distinctId, {
|
|
2231
|
+
matchValue = await this.getFeatureFlag(key, distinctId, {
|
|
2232
|
+
...options,
|
|
2386
2233
|
onlyEvaluateLocally: true
|
|
2387
2234
|
});
|
|
2388
2235
|
}
|
|
2389
|
-
|
|
2390
2236
|
if (matchValue) {
|
|
2391
2237
|
response = await this.featureFlagsPoller?.computeFeatureFlagPayloadLocally(key, matchValue);
|
|
2392
|
-
}
|
|
2393
|
-
|
|
2394
|
-
|
|
2238
|
+
}
|
|
2239
|
+
// set defaults
|
|
2395
2240
|
if (onlyEvaluateLocally == undefined) {
|
|
2396
2241
|
onlyEvaluateLocally = false;
|
|
2397
2242
|
}
|
|
2398
|
-
|
|
2399
2243
|
if (sendFeatureFlagEvents == undefined) {
|
|
2400
2244
|
sendFeatureFlagEvents = true;
|
|
2401
|
-
}
|
|
2402
|
-
|
|
2403
|
-
|
|
2245
|
+
}
|
|
2246
|
+
// set defaults
|
|
2404
2247
|
if (onlyEvaluateLocally == undefined) {
|
|
2405
2248
|
onlyEvaluateLocally = false;
|
|
2406
2249
|
}
|
|
2407
|
-
|
|
2408
2250
|
const payloadWasLocallyEvaluated = response !== undefined;
|
|
2409
|
-
|
|
2410
2251
|
if (!payloadWasLocallyEvaluated && !onlyEvaluateLocally) {
|
|
2411
2252
|
response = await super.getFeatureFlagPayloadStateless(key, distinctId, groups, personProperties, groupProperties, disableGeoip);
|
|
2412
2253
|
}
|
|
2413
|
-
|
|
2414
2254
|
return response;
|
|
2415
2255
|
}
|
|
2416
|
-
|
|
2417
2256
|
async isFeatureEnabled(key, distinctId, options) {
|
|
2418
2257
|
const feat = await this.getFeatureFlag(key, distinctId, options);
|
|
2419
|
-
|
|
2420
2258
|
if (feat === undefined) {
|
|
2421
2259
|
return undefined;
|
|
2422
2260
|
}
|
|
2423
|
-
|
|
2424
2261
|
return !!feat || false;
|
|
2425
2262
|
}
|
|
2426
|
-
|
|
2427
2263
|
async getAllFlags(distinctId, options) {
|
|
2428
2264
|
const response = await this.getAllFlagsAndPayloads(distinctId, options);
|
|
2429
2265
|
return response.featureFlags;
|
|
2430
2266
|
}
|
|
2431
|
-
|
|
2432
2267
|
async getAllFlagsAndPayloads(distinctId, options) {
|
|
2433
2268
|
const {
|
|
2434
2269
|
groups,
|
|
@@ -2441,39 +2276,36 @@ class PostHog extends PostHogCoreStateless {
|
|
|
2441
2276
|
} = options || {};
|
|
2442
2277
|
const adjustedProperties = this.addLocalPersonAndGroupProperties(distinctId, groups, personProperties, groupProperties);
|
|
2443
2278
|
personProperties = adjustedProperties.allPersonProperties;
|
|
2444
|
-
groupProperties = adjustedProperties.allGroupProperties;
|
|
2445
|
-
|
|
2279
|
+
groupProperties = adjustedProperties.allGroupProperties;
|
|
2280
|
+
// set defaults
|
|
2446
2281
|
if (onlyEvaluateLocally == undefined) {
|
|
2447
2282
|
onlyEvaluateLocally = false;
|
|
2448
2283
|
}
|
|
2449
|
-
|
|
2450
2284
|
const localEvaluationResult = await this.featureFlagsPoller?.getAllFlagsAndPayloads(distinctId, groups, personProperties, groupProperties);
|
|
2451
2285
|
let featureFlags = {};
|
|
2452
2286
|
let featureFlagPayloads = {};
|
|
2453
2287
|
let fallbackToDecide = true;
|
|
2454
|
-
|
|
2455
2288
|
if (localEvaluationResult) {
|
|
2456
2289
|
featureFlags = localEvaluationResult.response;
|
|
2457
2290
|
featureFlagPayloads = localEvaluationResult.payloads;
|
|
2458
2291
|
fallbackToDecide = localEvaluationResult.fallbackToDecide;
|
|
2459
2292
|
}
|
|
2460
|
-
|
|
2461
2293
|
if (fallbackToDecide && !onlyEvaluateLocally) {
|
|
2462
2294
|
const remoteEvaluationResult = await super.getFeatureFlagsAndPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip);
|
|
2463
|
-
featureFlags = {
|
|
2295
|
+
featureFlags = {
|
|
2296
|
+
...featureFlags,
|
|
2464
2297
|
...(remoteEvaluationResult.flags || {})
|
|
2465
2298
|
};
|
|
2466
|
-
featureFlagPayloads = {
|
|
2299
|
+
featureFlagPayloads = {
|
|
2300
|
+
...featureFlagPayloads,
|
|
2467
2301
|
...(remoteEvaluationResult.payloads || {})
|
|
2468
2302
|
};
|
|
2469
2303
|
}
|
|
2470
|
-
|
|
2471
2304
|
return {
|
|
2472
2305
|
featureFlags,
|
|
2473
2306
|
featureFlagPayloads
|
|
2474
2307
|
};
|
|
2475
2308
|
}
|
|
2476
|
-
|
|
2477
2309
|
groupIdentify({
|
|
2478
2310
|
groupType,
|
|
2479
2311
|
groupKey,
|
|
@@ -2485,23 +2317,19 @@ class PostHog extends PostHogCoreStateless {
|
|
|
2485
2317
|
disableGeoip
|
|
2486
2318
|
}, distinctId);
|
|
2487
2319
|
}
|
|
2488
|
-
|
|
2489
2320
|
async reloadFeatureFlags() {
|
|
2490
2321
|
await this.featureFlagsPoller?.loadFeatureFlags(true);
|
|
2491
2322
|
}
|
|
2492
|
-
|
|
2493
2323
|
async shutdown(shutdownTimeoutMs) {
|
|
2494
2324
|
this.featureFlagsPoller?.stopPoller();
|
|
2495
2325
|
return super.shutdown(shutdownTimeoutMs);
|
|
2496
2326
|
}
|
|
2497
|
-
|
|
2498
2327
|
addLocalPersonAndGroupProperties(distinctId, groups, personProperties, groupProperties) {
|
|
2499
2328
|
const allPersonProperties = {
|
|
2500
2329
|
distinct_id: distinctId,
|
|
2501
2330
|
...(personProperties || {})
|
|
2502
2331
|
};
|
|
2503
2332
|
const allGroupProperties = {};
|
|
2504
|
-
|
|
2505
2333
|
if (groups) {
|
|
2506
2334
|
for (const groupName of Object.keys(groups)) {
|
|
2507
2335
|
allGroupProperties[groupName] = {
|
|
@@ -2510,13 +2338,11 @@ class PostHog extends PostHogCoreStateless {
|
|
|
2510
2338
|
};
|
|
2511
2339
|
}
|
|
2512
2340
|
}
|
|
2513
|
-
|
|
2514
2341
|
return {
|
|
2515
2342
|
allPersonProperties,
|
|
2516
2343
|
allGroupProperties
|
|
2517
2344
|
};
|
|
2518
2345
|
}
|
|
2519
|
-
|
|
2520
2346
|
}
|
|
2521
2347
|
|
|
2522
2348
|
/**
|
|
@@ -2547,26 +2373,21 @@ class PostHogSentryIntegration {
|
|
|
2547
2373
|
this.name = 'posthog-node';
|
|
2548
2374
|
this.posthogHost = posthog.options.host ?? 'https://us.i.posthog.com';
|
|
2549
2375
|
}
|
|
2550
|
-
|
|
2551
2376
|
setupOnce(addGlobalEventProcessor, getCurrentHub) {
|
|
2552
2377
|
addGlobalEventProcessor(event => {
|
|
2553
2378
|
if (event.exception?.values === undefined || event.exception.values.length === 0) {
|
|
2554
2379
|
return event;
|
|
2555
2380
|
}
|
|
2556
|
-
|
|
2557
2381
|
if (!event.tags) {
|
|
2558
2382
|
event.tags = {};
|
|
2559
2383
|
}
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2384
|
+
const sentry = getCurrentHub();
|
|
2385
|
+
// Get the PostHog user ID from a specific tag, which users can set on their Sentry scope as they need.
|
|
2563
2386
|
const userId = event.tags[PostHogSentryIntegration.POSTHOG_ID_TAG];
|
|
2564
|
-
|
|
2565
2387
|
if (userId === undefined) {
|
|
2566
2388
|
// If we can't find a user ID, don't bother linking the event. We won't be able to send anything meaningful to PostHog without it.
|
|
2567
2389
|
return event;
|
|
2568
2390
|
}
|
|
2569
|
-
|
|
2570
2391
|
event.tags['PostHog Person URL'] = new URL(`/person/${userId}`, this.posthogHost).toString();
|
|
2571
2392
|
const properties = {
|
|
2572
2393
|
// PostHog Exception Properties
|
|
@@ -2581,11 +2402,9 @@ class PostHogSentryIntegration {
|
|
|
2581
2402
|
$sentry_tags: event.tags
|
|
2582
2403
|
};
|
|
2583
2404
|
const projectId = sentry.getClient()?.getDsn()?.projectId;
|
|
2584
|
-
|
|
2585
2405
|
if (this.organization !== undefined && projectId !== undefined && event.event_id !== undefined) {
|
|
2586
2406
|
properties.$sentry_url = `${this.prefix ?? 'https://sentry.io/organizations'}/${this.organization}/issues/?project=${projectId}&query=${event.event_id}`;
|
|
2587
2407
|
}
|
|
2588
|
-
|
|
2589
2408
|
this.posthog.capture({
|
|
2590
2409
|
event: '$exception',
|
|
2591
2410
|
distinctId: userId,
|
|
@@ -2594,7 +2413,6 @@ class PostHogSentryIntegration {
|
|
|
2594
2413
|
return event;
|
|
2595
2414
|
});
|
|
2596
2415
|
}
|
|
2597
|
-
|
|
2598
2416
|
}
|
|
2599
2417
|
PostHogSentryIntegration.POSTHOG_ID_TAG = 'posthog_distinct_id';
|
|
2600
2418
|
|