posthog-node 5.0.0 → 5.1.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 +8 -0
- package/lib/edge/index.cjs +52 -147
- package/lib/edge/index.cjs.map +1 -1
- package/lib/edge/index.mjs +52 -147
- package/lib/edge/index.mjs.map +1 -1
- package/lib/index.d.ts +14 -14
- package/lib/node/index.cjs +52 -147
- package/lib/node/index.cjs.map +1 -1
- package/lib/node/index.mjs +52 -147
- package/lib/node/index.mjs.map +1 -1
- package/package.json +1 -1
package/lib/edge/index.mjs
CHANGED
|
@@ -913,7 +913,7 @@ function setupExpressErrorHandler(_posthog, app) {
|
|
|
913
913
|
});
|
|
914
914
|
}
|
|
915
915
|
|
|
916
|
-
var version = "5.
|
|
916
|
+
var version = "5.1.1";
|
|
917
917
|
|
|
918
918
|
var PostHogPersistedProperty;
|
|
919
919
|
(function (PostHogPersistedProperty) {
|
|
@@ -937,11 +937,11 @@ var PostHogPersistedProperty;
|
|
|
937
937
|
PostHogPersistedProperty["InstalledAppBuild"] = "installed_app_build";
|
|
938
938
|
PostHogPersistedProperty["InstalledAppVersion"] = "installed_app_version";
|
|
939
939
|
PostHogPersistedProperty["SessionReplay"] = "session_replay";
|
|
940
|
-
PostHogPersistedProperty["DecideEndpointWasHit"] = "decide_endpoint_was_hit";
|
|
941
940
|
PostHogPersistedProperty["SurveyLastSeenDate"] = "survey_last_seen_date";
|
|
942
941
|
PostHogPersistedProperty["SurveysSeen"] = "surveys_seen";
|
|
943
942
|
PostHogPersistedProperty["Surveys"] = "surveys";
|
|
944
943
|
PostHogPersistedProperty["RemoteConfig"] = "remote_config";
|
|
944
|
+
PostHogPersistedProperty["FlagsEndpointWasHit"] = "flags_endpoint_was_hit";
|
|
945
945
|
})(PostHogPersistedProperty || (PostHogPersistedProperty = {}));
|
|
946
946
|
// Any key prefixed with `attr__` can be added
|
|
947
947
|
var Compression;
|
|
@@ -1009,27 +1009,27 @@ var ActionStepStringMatching;
|
|
|
1009
1009
|
ActionStepStringMatching["Regex"] = "regex";
|
|
1010
1010
|
})(ActionStepStringMatching || (ActionStepStringMatching = {}));
|
|
1011
1011
|
|
|
1012
|
-
const
|
|
1013
|
-
if ('flags' in
|
|
1014
|
-
// Convert
|
|
1015
|
-
const featureFlags = getFlagValuesFromFlags(
|
|
1016
|
-
const featureFlagPayloads = getPayloadsFromFlags(
|
|
1012
|
+
const normalizeFlagsResponse = (flagsResponse) => {
|
|
1013
|
+
if ('flags' in flagsResponse) {
|
|
1014
|
+
// Convert v2 format to v1 format
|
|
1015
|
+
const featureFlags = getFlagValuesFromFlags(flagsResponse.flags);
|
|
1016
|
+
const featureFlagPayloads = getPayloadsFromFlags(flagsResponse.flags);
|
|
1017
1017
|
return {
|
|
1018
|
-
...
|
|
1018
|
+
...flagsResponse,
|
|
1019
1019
|
featureFlags,
|
|
1020
1020
|
featureFlagPayloads,
|
|
1021
1021
|
};
|
|
1022
1022
|
}
|
|
1023
1023
|
else {
|
|
1024
|
-
// Convert
|
|
1025
|
-
const featureFlags =
|
|
1026
|
-
const featureFlagPayloads = Object.fromEntries(Object.entries(
|
|
1024
|
+
// Convert v1 format to v2 format
|
|
1025
|
+
const featureFlags = flagsResponse.featureFlags ?? {};
|
|
1026
|
+
const featureFlagPayloads = Object.fromEntries(Object.entries(flagsResponse.featureFlagPayloads || {}).map(([k, v]) => [k, parsePayload(v)]));
|
|
1027
1027
|
const flags = Object.fromEntries(Object.entries(featureFlags).map(([key, value]) => [
|
|
1028
1028
|
key,
|
|
1029
1029
|
getFlagDetailFromFlagAndPayload(key, value, featureFlagPayloads[key]),
|
|
1030
1030
|
]));
|
|
1031
1031
|
return {
|
|
1032
|
-
...
|
|
1032
|
+
...flagsResponse,
|
|
1033
1033
|
featureFlags,
|
|
1034
1034
|
featureFlagPayloads,
|
|
1035
1035
|
flags,
|
|
@@ -1092,90 +1092,6 @@ const parsePayload = (response) => {
|
|
|
1092
1092
|
}
|
|
1093
1093
|
};
|
|
1094
1094
|
|
|
1095
|
-
// Rollout constants
|
|
1096
|
-
const NEW_FLAGS_ROLLOUT_PERCENTAGE = 1;
|
|
1097
|
-
// The fnv1a hashes of the tokens that are explicitly excluded from the rollout
|
|
1098
|
-
// see https://github.com/PostHog/posthog-js-lite/blob/main/posthog-core/src/utils.ts#L84
|
|
1099
|
-
// are hashed API tokens from our top 10 for each category supported by this SDK.
|
|
1100
|
-
const NEW_FLAGS_EXCLUDED_HASHES = new Set([
|
|
1101
|
-
// Node
|
|
1102
|
-
'61be3dd8',
|
|
1103
|
-
'96f6df5f',
|
|
1104
|
-
'8cfdba9b',
|
|
1105
|
-
'bf027177',
|
|
1106
|
-
'e59430a8',
|
|
1107
|
-
'7fa5500b',
|
|
1108
|
-
'569798e9',
|
|
1109
|
-
'04809ff7',
|
|
1110
|
-
'0ebc61a5',
|
|
1111
|
-
'32de7f98',
|
|
1112
|
-
'3beeb69a',
|
|
1113
|
-
'12d34ad9',
|
|
1114
|
-
'733853ec',
|
|
1115
|
-
'0645bb64',
|
|
1116
|
-
'5dcbee21',
|
|
1117
|
-
'b1f95fa3',
|
|
1118
|
-
'2189e408',
|
|
1119
|
-
'82b460c2',
|
|
1120
|
-
'3a8cc979',
|
|
1121
|
-
'29ef8843',
|
|
1122
|
-
'2cdbf767',
|
|
1123
|
-
'38084b54',
|
|
1124
|
-
// React Native
|
|
1125
|
-
'50f9f8de',
|
|
1126
|
-
'41d0df91',
|
|
1127
|
-
'5c236689',
|
|
1128
|
-
'c11aedd3',
|
|
1129
|
-
'ada46672',
|
|
1130
|
-
'f4331ee1',
|
|
1131
|
-
'42fed62a',
|
|
1132
|
-
'c957462c',
|
|
1133
|
-
'd62f705a',
|
|
1134
|
-
// Web (lots of teams per org, hence lots of API tokens)
|
|
1135
|
-
'e0162666',
|
|
1136
|
-
'01b3e5cf',
|
|
1137
|
-
'441cef7f',
|
|
1138
|
-
'bb9cafee',
|
|
1139
|
-
'8f348eb0',
|
|
1140
|
-
'b2553f3a',
|
|
1141
|
-
'97469d7d',
|
|
1142
|
-
'39f21a76',
|
|
1143
|
-
'03706dcc',
|
|
1144
|
-
'27d50569',
|
|
1145
|
-
'307584a7',
|
|
1146
|
-
'6433e92e',
|
|
1147
|
-
'150c7fbb',
|
|
1148
|
-
'49f57f22',
|
|
1149
|
-
'3772f65b',
|
|
1150
|
-
'01eb8256',
|
|
1151
|
-
'3c9e9234',
|
|
1152
|
-
'f853c7f7',
|
|
1153
|
-
'c0ac4b67',
|
|
1154
|
-
'cd609d40',
|
|
1155
|
-
'10ca9b1a',
|
|
1156
|
-
'8a87f11b',
|
|
1157
|
-
'8e8e5216',
|
|
1158
|
-
'1f6b63b3',
|
|
1159
|
-
'db7943dd',
|
|
1160
|
-
'79b7164c',
|
|
1161
|
-
'07f78e33',
|
|
1162
|
-
'2d21b6fd',
|
|
1163
|
-
'952db5ee',
|
|
1164
|
-
'a7d3b43f',
|
|
1165
|
-
'1924dd9c',
|
|
1166
|
-
'84e1b8f6',
|
|
1167
|
-
'dff631b6',
|
|
1168
|
-
'c5aa8a79',
|
|
1169
|
-
'fa133a95',
|
|
1170
|
-
'498a4508',
|
|
1171
|
-
'24748755',
|
|
1172
|
-
'98f3d658',
|
|
1173
|
-
'21bbda67',
|
|
1174
|
-
'7dbfed69',
|
|
1175
|
-
'be3ec24c',
|
|
1176
|
-
'fc80b8e2',
|
|
1177
|
-
'75cc0998',
|
|
1178
|
-
]);
|
|
1179
1095
|
const STRING_FORMAT = 'utf8';
|
|
1180
1096
|
function assert(truthyValue, message) {
|
|
1181
1097
|
if (!truthyValue || typeof truthyValue !== 'string' || isEmpty(truthyValue)) {
|
|
@@ -1222,30 +1138,6 @@ function safeSetTimeout(fn, timeout) {
|
|
|
1222
1138
|
t?.unref && t?.unref();
|
|
1223
1139
|
return t;
|
|
1224
1140
|
}
|
|
1225
|
-
// FNV-1a hash function
|
|
1226
|
-
// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
|
|
1227
|
-
// I know, I know, I'm rolling my own hash function, but I didn't want to take on
|
|
1228
|
-
// a crypto dependency and this is just temporary anyway
|
|
1229
|
-
function fnv1a(str) {
|
|
1230
|
-
let hash = 0x811c9dc5; // FNV offset basis
|
|
1231
|
-
for (let i = 0; i < str.length; i++) {
|
|
1232
|
-
hash ^= str.charCodeAt(i);
|
|
1233
|
-
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
|
|
1234
|
-
}
|
|
1235
|
-
// Convert to hex string, padding to 8 chars
|
|
1236
|
-
return (hash >>> 0).toString(16).padStart(8, '0');
|
|
1237
|
-
}
|
|
1238
|
-
function isTokenInRollout(token, percentage = 0, excludedHashes) {
|
|
1239
|
-
const tokenHash = fnv1a(token);
|
|
1240
|
-
// Check excluded hashes (we're explicitly including these tokens from the rollout)
|
|
1241
|
-
if (excludedHashes?.has(tokenHash)) {
|
|
1242
|
-
return false;
|
|
1243
|
-
}
|
|
1244
|
-
// Convert hash to int and divide by max value to get number between 0-1
|
|
1245
|
-
const hashInt = parseInt(tokenHash, 16);
|
|
1246
|
-
const hashFloat = hashInt / 0xffffffff;
|
|
1247
|
-
return hashFloat < percentage;
|
|
1248
|
-
}
|
|
1249
1141
|
function allSettled(promises) {
|
|
1250
1142
|
return Promise.all(promises.map((p) => (p ?? Promise.resolve()).then((value) => ({ status: 'fulfilled', value }), (reason) => ({ status: 'rejected', reason }))));
|
|
1251
1143
|
}
|
|
@@ -1570,13 +1462,9 @@ class PostHogCoreStateless {
|
|
|
1570
1462
|
/***
|
|
1571
1463
|
*** FEATURE FLAGS
|
|
1572
1464
|
***/
|
|
1573
|
-
async
|
|
1465
|
+
async getFlags(distinctId, groups = {}, personProperties = {}, groupProperties = {}, extraPayload = {}) {
|
|
1574
1466
|
await this._initPromise;
|
|
1575
|
-
|
|
1576
|
-
// This is a temporary measure to ensure that we can still use the old flags API
|
|
1577
|
-
// while we migrate to the new flags API
|
|
1578
|
-
const useFlags = isTokenInRollout(this.apiKey, NEW_FLAGS_ROLLOUT_PERCENTAGE, NEW_FLAGS_EXCLUDED_HASHES);
|
|
1579
|
-
const url = useFlags ? `${this.host}/flags/?v=2` : `${this.host}/decide/?v=4`;
|
|
1467
|
+
const url = `${this.host}/flags/?v=2&config=true`;
|
|
1580
1468
|
const fetchOptions = {
|
|
1581
1469
|
method: 'POST',
|
|
1582
1470
|
headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
|
|
@@ -1589,11 +1477,11 @@ class PostHogCoreStateless {
|
|
|
1589
1477
|
...extraPayload,
|
|
1590
1478
|
}),
|
|
1591
1479
|
};
|
|
1592
|
-
this.logMsgIfDebug(() => console.log('PostHog Debug', '
|
|
1593
|
-
// Don't retry /
|
|
1480
|
+
this.logMsgIfDebug(() => console.log('PostHog Debug', 'Flags URL', url));
|
|
1481
|
+
// Don't retry /flags API calls
|
|
1594
1482
|
return this.fetchWithRetry(url, fetchOptions, { retryCount: 0 }, this.featureFlagsRequestTimeoutMs)
|
|
1595
1483
|
.then((response) => response.json())
|
|
1596
|
-
.then((response) =>
|
|
1484
|
+
.then((response) => normalizeFlagsResponse(response))
|
|
1597
1485
|
.catch((error) => {
|
|
1598
1486
|
this._events.emit('error', error);
|
|
1599
1487
|
return undefined;
|
|
@@ -1622,15 +1510,15 @@ class PostHogCoreStateless {
|
|
|
1622
1510
|
}
|
|
1623
1511
|
async getFeatureFlagDetailStateless(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
|
|
1624
1512
|
await this._initPromise;
|
|
1625
|
-
const
|
|
1626
|
-
if (
|
|
1513
|
+
const flagsResponse = await this.getFeatureFlagDetailsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, [key]);
|
|
1514
|
+
if (flagsResponse === undefined) {
|
|
1627
1515
|
return undefined;
|
|
1628
1516
|
}
|
|
1629
|
-
const featureFlags =
|
|
1517
|
+
const featureFlags = flagsResponse.flags;
|
|
1630
1518
|
const flagDetail = featureFlags[key];
|
|
1631
1519
|
return {
|
|
1632
1520
|
response: flagDetail,
|
|
1633
|
-
requestId:
|
|
1521
|
+
requestId: flagsResponse.requestId,
|
|
1634
1522
|
};
|
|
1635
1523
|
}
|
|
1636
1524
|
async getFeatureFlagPayloadStateless(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
|
|
@@ -1680,26 +1568,26 @@ class PostHogCoreStateless {
|
|
|
1680
1568
|
if (flagKeysToEvaluate) {
|
|
1681
1569
|
extraPayload['flag_keys_to_evaluate'] = flagKeysToEvaluate;
|
|
1682
1570
|
}
|
|
1683
|
-
const
|
|
1684
|
-
if (
|
|
1571
|
+
const flagsResponse = await this.getFlags(distinctId, groups, personProperties, groupProperties, extraPayload);
|
|
1572
|
+
if (flagsResponse === undefined) {
|
|
1685
1573
|
// We probably errored out, so return undefined
|
|
1686
1574
|
return undefined;
|
|
1687
1575
|
}
|
|
1688
|
-
// if there's an error on the
|
|
1689
|
-
if (
|
|
1576
|
+
// if there's an error on the flagsResponse, log a console error, but don't throw an error
|
|
1577
|
+
if (flagsResponse.errorsWhileComputingFlags) {
|
|
1690
1578
|
console.error('[FEATURE FLAGS] Error while computing feature flags, some flags may be missing or incorrect. Learn more at https://posthog.com/docs/feature-flags/best-practices');
|
|
1691
1579
|
}
|
|
1692
1580
|
// Add check for quota limitation on feature flags
|
|
1693
|
-
if (
|
|
1581
|
+
if (flagsResponse.quotaLimited?.includes(QuotaLimitedFeature.FeatureFlags)) {
|
|
1694
1582
|
console.warn('[FEATURE FLAGS] Feature flags quota limit exceeded - feature flags unavailable. Learn more about billing limits at https://posthog.com/docs/billing/limits-alerts');
|
|
1695
1583
|
return {
|
|
1696
1584
|
flags: {},
|
|
1697
1585
|
featureFlags: {},
|
|
1698
1586
|
featureFlagPayloads: {},
|
|
1699
|
-
requestId:
|
|
1587
|
+
requestId: flagsResponse?.requestId,
|
|
1700
1588
|
};
|
|
1701
1589
|
}
|
|
1702
|
-
return
|
|
1590
|
+
return flagsResponse;
|
|
1703
1591
|
}
|
|
1704
1592
|
/***
|
|
1705
1593
|
*** SURVEYS
|
|
@@ -2303,7 +2191,7 @@ class FeatureFlagsPoller {
|
|
|
2303
2191
|
await this.loadFeatureFlags();
|
|
2304
2192
|
const response = {};
|
|
2305
2193
|
const payloads = {};
|
|
2306
|
-
let
|
|
2194
|
+
let fallbackToFlags = this.featureFlags.length == 0;
|
|
2307
2195
|
await Promise.all(this.featureFlags.map(async flag => {
|
|
2308
2196
|
try {
|
|
2309
2197
|
const matchValue = await this.computeFlagLocally(flag, distinctId, groups, personProperties, groupProperties);
|
|
@@ -2316,13 +2204,13 @@ class FeatureFlagsPoller {
|
|
|
2316
2204
|
if (e instanceof InconclusiveMatchError) ; else if (e instanceof Error) {
|
|
2317
2205
|
this.onError?.(new Error(`Error computing flag locally: ${flag.key}: ${e}`));
|
|
2318
2206
|
}
|
|
2319
|
-
|
|
2207
|
+
fallbackToFlags = true;
|
|
2320
2208
|
}
|
|
2321
2209
|
}));
|
|
2322
2210
|
return {
|
|
2323
2211
|
response,
|
|
2324
2212
|
payloads,
|
|
2325
|
-
|
|
2213
|
+
fallbackToFlags
|
|
2326
2214
|
};
|
|
2327
2215
|
}
|
|
2328
2216
|
async computeFlagLocally(flag, distinctId, groups = {}, personProperties = {}, groupProperties = {}) {
|
|
@@ -3254,7 +3142,24 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
3254
3142
|
return response;
|
|
3255
3143
|
}
|
|
3256
3144
|
async getRemoteConfigPayload(flagKey) {
|
|
3257
|
-
|
|
3145
|
+
const response = await this.featureFlagsPoller?._requestRemoteConfigPayload(flagKey);
|
|
3146
|
+
if (!response) {
|
|
3147
|
+
return undefined;
|
|
3148
|
+
}
|
|
3149
|
+
const parsed = await response.json();
|
|
3150
|
+
// The payload from the endpoint is stored as a JSON encoded string. So when we return
|
|
3151
|
+
// it, it's effectively double encoded. As far as we know, we should never get single-encoded
|
|
3152
|
+
// JSON, but we'll be defensive here just in case.
|
|
3153
|
+
if (typeof parsed === 'string') {
|
|
3154
|
+
try {
|
|
3155
|
+
// If the parsed value is a string, try parsing it again to handle double-encoded JSON
|
|
3156
|
+
return JSON.parse(parsed);
|
|
3157
|
+
} catch (e) {
|
|
3158
|
+
// If second parse fails, return the string as is
|
|
3159
|
+
return parsed;
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
return parsed;
|
|
3258
3163
|
}
|
|
3259
3164
|
async isFeatureEnabled(key, distinctId, options) {
|
|
3260
3165
|
const feat = await this.getFeatureFlag(key, distinctId, options);
|
|
@@ -3287,13 +3192,13 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
3287
3192
|
const localEvaluationResult = await this.featureFlagsPoller?.getAllFlagsAndPayloads(distinctId, groups, personProperties, groupProperties);
|
|
3288
3193
|
let featureFlags = {};
|
|
3289
3194
|
let featureFlagPayloads = {};
|
|
3290
|
-
let
|
|
3195
|
+
let fallbackToFlags = true;
|
|
3291
3196
|
if (localEvaluationResult) {
|
|
3292
3197
|
featureFlags = localEvaluationResult.response;
|
|
3293
3198
|
featureFlagPayloads = localEvaluationResult.payloads;
|
|
3294
|
-
|
|
3199
|
+
fallbackToFlags = localEvaluationResult.fallbackToFlags;
|
|
3295
3200
|
}
|
|
3296
|
-
if (
|
|
3201
|
+
if (fallbackToFlags && !onlyEvaluateLocally) {
|
|
3297
3202
|
const remoteEvaluationResult = await super.getFeatureFlagsAndPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip);
|
|
3298
3203
|
featureFlags = {
|
|
3299
3204
|
...featureFlags,
|