posthog-node 2.0.2 → 2.2.0

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 CHANGED
@@ -1,3 +1,14 @@
1
+ # 2.2.0 - 2022-11-18
2
+
3
+ 1. Add support for variant overrides for feature flag local evaluation.
4
+ 2. Add support for date operators in feature flag local evaluation.
5
+
6
+ # 2.1.0 - 2022-09-08
7
+
8
+ 1. Swaps `unidici` for `axios` in order to support older versions of Node
9
+ 2. The `fetch` implementation can be overridden as an option for those who wish to use an alternative implementation
10
+ 3. Fixes the minimum Node version to >=14.17.0
11
+
1
12
  # 2.0.2 - 2022-08-23
2
13
 
3
14
  1. Removes references to `cli.js`
@@ -13,7 +24,6 @@ Breaking changes:
13
24
  4. The `callback` parameter passed as an optional last argument to most of the methods is no longer supported
14
25
  5. The CLI is no longer supported
15
26
 
16
-
17
27
  What's new:
18
28
 
19
29
  1. You can now evaluate feature flags locally (i.e. without sending a request to your PostHog servers) by setting a personal API key, and passing in groups and person properties to `isFeatureEnabled` and `getFeatureFlag` calls.
package/lib/index.cjs.js CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var undici = require('undici');
6
5
  var crypto = require('crypto');
6
+ var axios = require('axios');
7
7
 
8
8
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
9
9
 
10
- var undici__default = /*#__PURE__*/_interopDefaultLegacy(undici);
10
+ var axios__default = /*#__PURE__*/_interopDefaultLegacy(axios);
11
11
 
12
12
  /******************************************************************************
13
13
  Copyright (c) Microsoft Corporation.
@@ -51,6 +51,18 @@ var __assign = function () {
51
51
  };
52
52
  return __assign.apply(this, arguments);
53
53
  };
54
+ function __rest(s, e) {
55
+ var t = {};
56
+ for (var p in s)
57
+ if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
58
+ t[p] = s[p];
59
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
60
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
61
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
62
+ t[p[i]] = s[p[i]];
63
+ }
64
+ return t;
65
+ }
54
66
  function __awaiter(thisArg, _arguments, P, generator) {
55
67
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
56
68
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -151,7 +163,7 @@ function __spreadArray(to, from, pack) {
151
163
  return to.concat(ar || Array.prototype.slice.call(from));
152
164
  }
153
165
 
154
- var version = "2.0.2";
166
+ var version = "2.2.0";
155
167
 
156
168
  var PostHogPersistedProperty;
157
169
  (function (PostHogPersistedProperty) {
@@ -748,6 +760,26 @@ var PostHogCore = /** @class */ (function () {
748
760
  }
749
761
  return __assign({ $lib: this.getLibraryId(), $lib_version: this.getLibraryVersion(), $active_feature_flags: featureFlags ? Object.keys(featureFlags) : undefined }, featureVariantProperties);
750
762
  };
763
+ PostHogCore.prototype.setupBootstrap = function (options) {
764
+ var _a, _b, _c, _d;
765
+ if ((_a = options === null || options === void 0 ? void 0 : options.bootstrap) === null || _a === void 0 ? void 0 : _a.distinctId) {
766
+ if ((_b = options === null || options === void 0 ? void 0 : options.bootstrap) === null || _b === void 0 ? void 0 : _b.isIdentifiedId) {
767
+ this.setPersistedProperty(PostHogPersistedProperty.DistinctId, options.bootstrap.distinctId);
768
+ }
769
+ else {
770
+ this.setPersistedProperty(PostHogPersistedProperty.AnonymousId, options.bootstrap.distinctId);
771
+ }
772
+ }
773
+ if ((_c = options === null || options === void 0 ? void 0 : options.bootstrap) === null || _c === void 0 ? void 0 : _c.featureFlags) {
774
+ var activeFlags = Object.keys(((_d = options.bootstrap) === null || _d === void 0 ? void 0 : _d.featureFlags) || {})
775
+ .filter(function (flag) { var _a, _b; return !!((_b = (_a = options.bootstrap) === null || _a === void 0 ? void 0 : _a.featureFlags) === null || _b === void 0 ? void 0 : _b[flag]); })
776
+ .reduce(function (res, key) {
777
+ var _a, _b;
778
+ return ((res[key] = ((_b = (_a = options.bootstrap) === null || _a === void 0 ? void 0 : _a.featureFlags) === null || _b === void 0 ? void 0 : _b[key]) || false), res);
779
+ }, {});
780
+ this.setKnownFeatureFlags(activeFlags);
781
+ }
782
+ };
751
783
  Object.defineProperty(PostHogCore.prototype, "props", {
752
784
  // NOTE: Props are lazy loaded from localstorage hence the complex getter setter logic
753
785
  get: function () {
@@ -991,8 +1023,7 @@ var PostHogCore = /** @class */ (function () {
991
1023
  .then(function (r) { return r.json(); })
992
1024
  .then(function (res) {
993
1025
  if (res.featureFlags) {
994
- _this.setPersistedProperty(PostHogPersistedProperty.FeatureFlags, res.featureFlags);
995
- _this._events.emit('featureflags', res.featureFlags);
1026
+ _this.setKnownFeatureFlags(res.featureFlags);
996
1027
  }
997
1028
  return res;
998
1029
  })
@@ -1003,6 +1034,10 @@ var PostHogCore = /** @class */ (function () {
1003
1034
  });
1004
1035
  });
1005
1036
  };
1037
+ PostHogCore.prototype.setKnownFeatureFlags = function (featureFlags) {
1038
+ this.setPersistedProperty(PostHogPersistedProperty.FeatureFlags, featureFlags);
1039
+ this._events.emit('featureflags', featureFlags);
1040
+ };
1006
1041
  PostHogCore.prototype.getFeatureFlag = function (key) {
1007
1042
  var featureFlags = this.getFeatureFlags();
1008
1043
  if (!featureFlags) {
@@ -1230,6 +1265,42 @@ var PostHogMemoryStorage = /** @class */ (function () {
1230
1265
  return PostHogMemoryStorage;
1231
1266
  }());
1232
1267
 
1268
+ // So that alternative implementations can be used if desired
1269
+
1270
+ var fetch = function (url, options) {
1271
+ return __awaiter(void 0, void 0, void 0, function () {
1272
+ var res;
1273
+ return __generator(this, function (_a) {
1274
+ switch (_a.label) {
1275
+ case 0:
1276
+ return [4
1277
+ /*yield*/
1278
+ , axios__default["default"].request({
1279
+ url: url,
1280
+ headers: options.headers,
1281
+ method: options.method.toLowerCase(),
1282
+ data: options.body,
1283
+ signal: options.signal
1284
+ })];
1285
+
1286
+ case 1:
1287
+ res = _a.sent();
1288
+ return [2
1289
+ /*return*/
1290
+ , {
1291
+ status: res.status,
1292
+ text: function () {
1293
+ return res.data;
1294
+ },
1295
+ json: function () {
1296
+ return res.data;
1297
+ }
1298
+ }];
1299
+ }
1300
+ });
1301
+ });
1302
+ };
1303
+
1233
1304
  var LONG_SCALE = 0xfffffffffffffff;
1234
1305
 
1235
1306
  var ClientError =
@@ -1278,7 +1349,9 @@ function () {
1278
1349
  personalApiKey = _a.personalApiKey,
1279
1350
  projectApiKey = _a.projectApiKey,
1280
1351
  timeout = _a.timeout,
1281
- host = _a.host;
1352
+ host = _a.host,
1353
+ options = __rest(_a, ["pollingInterval", "personalApiKey", "projectApiKey", "timeout", "host"]);
1354
+
1282
1355
  this.pollingInterval = pollingInterval;
1283
1356
  this.personalApiKey = personalApiKey;
1284
1357
  this.featureFlags = [];
@@ -1287,7 +1360,9 @@ function () {
1287
1360
  this.timeout = timeout;
1288
1361
  this.projectApiKey = projectApiKey;
1289
1362
  this.host = host;
1290
- this.poller = undefined;
1363
+ this.poller = undefined; // NOTE: as any is required here as the AbortSignal typing is slightly misaligned but works just fine
1364
+
1365
+ this.fetch = options.fetch || fetch;
1291
1366
  void this.loadFeatureFlags();
1292
1367
  }
1293
1368
 
@@ -1453,16 +1528,44 @@ function () {
1453
1528
  };
1454
1529
 
1455
1530
  FeatureFlagsPoller.prototype.matchFeatureFlagProperties = function (flag, distinctId, properties) {
1456
- var _this = this;
1531
+ var _a;
1457
1532
 
1458
1533
  var flagFilters = flag.filters || {};
1459
1534
  var flagConditions = flagFilters.groups || [];
1460
1535
  var isInconclusive = false;
1461
- var result = undefined;
1462
- flagConditions.forEach(function (condition) {
1536
+ var result = undefined; // # Stable sort conditions with variant overrides to the top. This ensures that if overrides are present, they are
1537
+ // # evaluated first, and the variant override is applied to the first matching condition.
1538
+
1539
+ var sortedFlagConditions = __spreadArray([], flagConditions, true).sort(function (conditionA, conditionB) {
1540
+ var AHasVariantOverride = !!conditionA.variant;
1541
+ var BHasVariantOverride = !!conditionB.variant;
1542
+
1543
+ if (AHasVariantOverride && BHasVariantOverride) {
1544
+ return 0;
1545
+ } else if (AHasVariantOverride) {
1546
+ return -1;
1547
+ } else if (BHasVariantOverride) {
1548
+ return 1;
1549
+ } else {
1550
+ return 0;
1551
+ }
1552
+ });
1553
+
1554
+ var _loop_1 = function (condition) {
1463
1555
  try {
1464
- if (_this.isConditionMatch(flag, distinctId, condition, properties)) {
1465
- result = _this.getMatchingVariant(flag, distinctId) || true;
1556
+ if (this_1.isConditionMatch(flag, distinctId, condition, properties)) {
1557
+ var variantOverride_1 = condition.variant;
1558
+ var flagVariants = ((_a = flagFilters.multivariate) === null || _a === void 0 ? void 0 : _a.variants) || [];
1559
+
1560
+ if (variantOverride_1 && flagVariants.some(function (variant) {
1561
+ return variant.key === variantOverride_1;
1562
+ })) {
1563
+ result = variantOverride_1;
1564
+ } else {
1565
+ result = this_1.getMatchingVariant(flag, distinctId) || true;
1566
+ }
1567
+
1568
+ return "break";
1466
1569
  }
1467
1570
  } catch (e) {
1468
1571
  if (e instanceof InconclusiveMatchError) {
@@ -1471,7 +1574,17 @@ function () {
1471
1574
  throw e;
1472
1575
  }
1473
1576
  }
1474
- });
1577
+ };
1578
+
1579
+ var this_1 = this;
1580
+
1581
+ for (var _i = 0, sortedFlagConditions_1 = sortedFlagConditions; _i < sortedFlagConditions_1.length; _i++) {
1582
+ var condition = sortedFlagConditions_1[_i];
1583
+
1584
+ var state_1 = _loop_1(condition);
1585
+
1586
+ if (state_1 === "break") break;
1587
+ }
1475
1588
 
1476
1589
  if (result !== undefined) {
1477
1590
  return result;
@@ -1599,13 +1712,13 @@ function () {
1599
1712
  case 2:
1600
1713
  res = _a.sent();
1601
1714
 
1602
- if (res && res.statusCode === 401) {
1715
+ if (res && res.status === 401) {
1603
1716
  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");
1604
1717
  }
1605
1718
 
1606
1719
  return [4
1607
1720
  /*yield*/
1608
- , res.body.json()];
1721
+ , res.json()];
1609
1722
 
1610
1723
  case 3:
1611
1724
  responseJson = _a.sent();
@@ -1644,48 +1757,57 @@ function () {
1644
1757
 
1645
1758
  FeatureFlagsPoller.prototype._requestFeatureFlagDefinitions = function () {
1646
1759
  return __awaiter(this, void 0, void 0, function () {
1647
- var url, headers, options, res, err_2;
1760
+ var url, options, abortTimeout, controller_1, err_2;
1648
1761
  return __generator(this, function (_a) {
1649
1762
  switch (_a.label) {
1650
1763
  case 0:
1651
1764
  url = "".concat(this.host, "/api/feature_flag/local_evaluation?token=").concat(this.projectApiKey);
1652
- headers = {
1653
- 'Content-Type': 'application/json',
1654
- Authorization: "Bearer ".concat(this.personalApiKey),
1655
- 'user-agent': "posthog-node/".concat(version)
1656
- };
1657
1765
  options = {
1658
1766
  method: 'GET',
1659
- headers: headers
1767
+ headers: {
1768
+ 'Content-Type': 'application/json',
1769
+ Authorization: "Bearer ".concat(this.personalApiKey),
1770
+ 'user-agent': "posthog-node/".concat(version)
1771
+ }
1660
1772
  };
1773
+ abortTimeout = null;
1661
1774
 
1662
1775
  if (this.timeout && typeof this.timeout === 'number') {
1663
- options.bodyTimeout = this.timeout;
1776
+ controller_1 = new AbortController();
1777
+ abortTimeout = safeSetTimeout(function () {
1778
+ controller_1.abort();
1779
+ }, this.timeout);
1780
+ options.signal = controller_1.signal;
1664
1781
  }
1665
1782
 
1666
1783
  _a.label = 1;
1667
1784
 
1668
1785
  case 1:
1669
- _a.trys.push([1, 3,, 4]);
1786
+ _a.trys.push([1, 3, 4, 5]);
1670
1787
 
1671
1788
  return [4
1672
1789
  /*yield*/
1673
- , undici.request(url, options)];
1790
+ , this.fetch(url, options)];
1674
1791
 
1675
1792
  case 2:
1676
- res = _a.sent();
1677
- return [3
1678
- /*break*/
1679
- , 4];
1793
+ return [2
1794
+ /*return*/
1795
+ , _a.sent()];
1680
1796
 
1681
1797
  case 3:
1682
1798
  err_2 = _a.sent();
1683
1799
  throw new Error("Request failed with error: ".concat(err_2));
1684
1800
 
1685
1801
  case 4:
1802
+ clearTimeout(abortTimeout);
1803
+ return [7
1804
+ /*endfinally*/
1805
+ ];
1806
+
1807
+ case 5:
1686
1808
  return [2
1687
1809
  /*return*/
1688
- , res];
1810
+ ];
1689
1811
  }
1690
1812
  });
1691
1813
  });
@@ -1759,6 +1881,17 @@ function matchProperty(property, propertyValues) {
1759
1881
  case 'lte':
1760
1882
  return typeof overrideValue == typeof value && overrideValue <= value;
1761
1883
 
1884
+ case 'is_date_after':
1885
+ case 'is_date_before':
1886
+ var parsedDate = convertToDateTime(value);
1887
+ var overrideDate = convertToDateTime(overrideValue);
1888
+
1889
+ if (operator === 'is_date_before') {
1890
+ return overrideDate < parsedDate;
1891
+ }
1892
+
1893
+ return overrideDate > parsedDate;
1894
+
1762
1895
  default:
1763
1896
  console.error("Unknown operator: ".concat(operator));
1764
1897
  return false;
@@ -1774,6 +1907,22 @@ function isValidRegex(regex) {
1774
1907
  }
1775
1908
  }
1776
1909
 
1910
+ function convertToDateTime(value) {
1911
+ if (value instanceof Date) {
1912
+ return value;
1913
+ } else if (typeof value === 'string' || typeof value === 'number') {
1914
+ var date = new Date(value);
1915
+
1916
+ if (!isNaN(date.valueOf())) {
1917
+ return date;
1918
+ }
1919
+
1920
+ throw new InconclusiveMatchError("".concat(value, " is in an invalid date format"));
1921
+ } else {
1922
+ throw new InconclusiveMatchError("The date provided ".concat(value, " must be a string, number, or date object"));
1923
+ }
1924
+ }
1925
+
1777
1926
  var THIRTY_SECONDS = 30 * 1000;
1778
1927
  var MAX_CACHE_SIZE = 50 * 1000;
1779
1928
 
@@ -1795,6 +1944,7 @@ function (_super) {
1795
1944
  options.sendFeatureFlagEvent = false; // Let `posthog-node` handle this on its own, since we're dealing with multiple distinctIDs
1796
1945
 
1797
1946
  _this = _super.call(this, apiKey, options) || this;
1947
+ _this.options = options;
1798
1948
  _this._memoryStorage = new PostHogMemoryStorage();
1799
1949
  return _this;
1800
1950
  }
@@ -1813,7 +1963,7 @@ function (_super) {
1813
1963
  };
1814
1964
 
1815
1965
  PostHogClient.prototype.fetch = function (url, options) {
1816
- return undici__default["default"].fetch(url, options);
1966
+ return this.options.fetch ? this.options.fetch(url, options) : fetch(url, options);
1817
1967
  };
1818
1968
 
1819
1969
  PostHogClient.prototype.getLibraryId = function () {
@@ -1848,7 +1998,8 @@ function () {
1848
1998
  personalApiKey: options.personalApiKey,
1849
1999
  projectApiKey: apiKey,
1850
2000
  timeout: options.requestTimeout,
1851
- host: this._sharedClient.host
2001
+ host: this._sharedClient.host,
2002
+ fetch: options.fetch
1852
2003
  });
1853
2004
  }
1854
2005