var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __values = (this && this.__values) || function(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
import { entries, extend } from './utils';
import { Compression, } from './types';
import { PERSISTENCE_EARLY_ACCESS_FEATURES, PERSISTENCE_FEATURE_FLAG_DETAILS, ENABLED_FEATURE_FLAGS, STORED_GROUP_PROPERTIES_KEY, STORED_PERSON_PROPERTIES_KEY, FLAG_CALL_REPORTED, } from './constants';
import { isArray, isUndefined } from './utils/type-utils';
import { createLogger } from './utils/logger';
var logger = createLogger('[FeatureFlags]');
var PERSISTENCE_ACTIVE_FEATURE_FLAGS = '$active_feature_flags';
var PERSISTENCE_OVERRIDE_FEATURE_FLAGS = '$override_feature_flags';
var PERSISTENCE_FEATURE_FLAG_PAYLOADS = '$feature_flag_payloads';
var PERSISTENCE_OVERRIDE_FEATURE_FLAG_PAYLOADS = '$override_feature_flag_payloads';
var PERSISTENCE_FEATURE_FLAG_REQUEST_ID = '$feature_flag_request_id';
export var filterActiveFeatureFlags = function (featureFlags) {
var e_1, _a;
var activeFeatureFlags = {};
try {
for (var _b = __values(entries(featureFlags || {})), _c = _b.next(); !_c.done; _c = _b.next()) {
var _d = __read(_c.value, 2), key = _d[0], value = _d[1];
if (value) {
activeFeatureFlags[key] = value;
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_1) throw e_1.error; }
}
return activeFeatureFlags;
};
export var parseFeatureFlagDecideResponse = function (response, persistence, currentFlags, currentFlagPayloads, currentFlagDetails) {
var _a, _b, _c;
if (currentFlags === void 0) { currentFlags = {}; }
if (currentFlagPayloads === void 0) { currentFlagPayloads = {}; }
if (currentFlagDetails === void 0) { currentFlagDetails = {}; }
var normalizedResponse = normalizeDecideResponse(response);
var flagDetails = normalizedResponse.flags;
var featureFlags = normalizedResponse.featureFlags;
var flagPayloads = normalizedResponse.featureFlagPayloads;
if (!featureFlags) {
return; // <-- This early return means we don't update anything, which is good.
}
var requestId = response['requestId'];
// using the v1 api
if (isArray(featureFlags)) {
logger.warn('v1 of the feature flags endpoint is deprecated. Please use the latest version.');
var $enabled_feature_flags = {};
if (featureFlags) {
for (var i = 0; i < featureFlags.length; i++) {
$enabled_feature_flags[featureFlags[i]] = true;
}
}
persistence &&
persistence.register((_a = {},
_a[PERSISTENCE_ACTIVE_FEATURE_FLAGS] = featureFlags,
_a[ENABLED_FEATURE_FLAGS] = $enabled_feature_flags,
_a));
return;
}
// using the v2+ api
var newFeatureFlags = featureFlags;
var newFeatureFlagPayloads = flagPayloads;
var newFeatureFlagDetails = flagDetails;
if (response.errorsWhileComputingFlags) {
// if not all flags were computed, we upsert flags instead of replacing them
newFeatureFlags = __assign(__assign({}, currentFlags), newFeatureFlags);
newFeatureFlagPayloads = __assign(__assign({}, currentFlagPayloads), newFeatureFlagPayloads);
newFeatureFlagDetails = __assign(__assign({}, currentFlagDetails), newFeatureFlagDetails);
}
persistence &&
persistence.register(__assign((_b = {}, _b[PERSISTENCE_ACTIVE_FEATURE_FLAGS] = Object.keys(filterActiveFeatureFlags(newFeatureFlags)), _b[ENABLED_FEATURE_FLAGS] = newFeatureFlags || {}, _b[PERSISTENCE_FEATURE_FLAG_PAYLOADS] = newFeatureFlagPayloads || {}, _b[PERSISTENCE_FEATURE_FLAG_DETAILS] = newFeatureFlagDetails || {}, _b), (requestId ? (_c = {}, _c[PERSISTENCE_FEATURE_FLAG_REQUEST_ID] = requestId, _c) : {})));
};
var normalizeDecideResponse = function (response) {
var flagDetails = response['flags'];
if (flagDetails) {
// This is a v=4 request.
// Map of flag keys to flag values: Record<string, string | boolean>
response.featureFlags = Object.fromEntries(Object.keys(flagDetails).map(function (flag) { var _a; return [flag, (_a = flagDetails[flag].variant) !== null && _a !== void 0 ? _a : flagDetails[flag].enabled]; }));
// Map of flag keys to flag payloads: Record<string, JsonType>
response.featureFlagPayloads = Object.fromEntries(Object.keys(flagDetails)
.filter(function (flag) { return flagDetails[flag].enabled; })
.filter(function (flag) { var _a; return (_a = flagDetails[flag].metadata) === null || _a === void 0 ? void 0 : _a.payload; })
.map(function (flag) { var _a; return [flag, (_a = flagDetails[flag].metadata) === null || _a === void 0 ? void 0 : _a.payload]; }));
}
else {
logger.warn('Using an older version of the feature flags endpoint. Please upgrade your PostHog server to the latest version');
}
return response;
};
export var QuotaLimitedResource;
(function (QuotaLimitedResource) {
QuotaLimitedResource["FeatureFlags"] = "feature_flags";
QuotaLimitedResource["Recordings"] = "recordings";
})(QuotaLimitedResource || (QuotaLimitedResource = {}));
var PostHogFeatureFlags = /** @class */ (function () {
function PostHogFeatureFlags(instance) {
this.instance = instance;
this._override_warning = false;
this._hasLoadedFlags = false;
this._requestInFlight = false;
this._reloadingDisabled = false;
this._additionalReloadRequested = false;
this._decideCalled = false;
this._flagsLoadedFromRemote = false;
this.featureFlagEventHandlers = [];
}
PostHogFeatureFlags.prototype.decide = function () {
if (this.instance.config.__preview_remote_config) {
// If remote config is enabled we don't call decide and we mark it as called so that we don't simulate it
this._decideCalled = true;
return;
}
// TRICKY: We want to disable flags if we don't have a queued reload, and one of the settings exist for disabling on first load
var disableFlags = !this._reloadDebouncer &&
(this.instance.config.advanced_disable_feature_flags ||
this.instance.config.advanced_disable_feature_flags_on_first_load);
this._callDecideEndpoint({
disableFlags: disableFlags,
});
};
Object.defineProperty(PostHogFeatureFlags.prototype, "hasLoadedFlags", {
get: function () {
return this._hasLoadedFlags;
},
enumerable: false,
configurable: true
});
PostHogFeatureFlags.prototype.getFlags = function () {
return Object.keys(this.getFlagVariants());
};
PostHogFeatureFlags.prototype.getFlagsWithDetails = function () {
var e_2, _a;
var _b, _c;
var flagDetails = this.instance.get_property(PERSISTENCE_FEATURE_FLAG_DETAILS);
var overridenFlags = this.instance.get_property(PERSISTENCE_OVERRIDE_FEATURE_FLAGS);
var overriddenPayloads = this.instance.get_property(PERSISTENCE_OVERRIDE_FEATURE_FLAG_PAYLOADS);
if (!overriddenPayloads && !overridenFlags) {
return flagDetails || {};
}
var finalDetails = extend({}, flagDetails || {});
var overriddenKeys = __spreadArray([], __read(new Set(__spreadArray(__spreadArray([], __read(Object.keys(overriddenPayloads || {})), false), __read(Object.keys(overridenFlags || {})), false))), false);
try {
for (var overriddenKeys_1 = __values(overriddenKeys), overriddenKeys_1_1 = overriddenKeys_1.next(); !overriddenKeys_1_1.done; overriddenKeys_1_1 = overriddenKeys_1.next()) {
var key = overriddenKeys_1_1.value;
var originalDetail = finalDetails[key];
var overrideFlagValue = overridenFlags === null || overridenFlags === void 0 ? void 0 : overridenFlags[key];
var finalEnabled = isUndefined(overrideFlagValue)
? ((_b = originalDetail === null || originalDetail === void 0 ? void 0 : originalDetail.enabled) !== null && _b !== void 0 ? _b : false)
: !!overrideFlagValue;
var overrideVariant = isUndefined(overrideFlagValue)
? originalDetail.variant
: typeof overrideFlagValue === 'string'
? overrideFlagValue
: undefined;
var overridePayload = overriddenPayloads === null || overriddenPayloads === void 0 ? void 0 : overriddenPayloads[key];
var overridenDetail = __assign(__assign({}, originalDetail), { enabled: finalEnabled,
// If the flag is not enabled, the variant should be undefined, even if the original has a variant value.
variant: finalEnabled ? (overrideVariant !== null && overrideVariant !== void 0 ? overrideVariant : originalDetail === null || originalDetail === void 0 ? void 0 : originalDetail.variant) : undefined });
// Keep track of the original enabled and variant values so we can send them in the $feature_flag_called event.
// This will be helpful for debugging and for understanding the impact of overrides.
if (finalEnabled !== (originalDetail === null || originalDetail === void 0 ? void 0 : originalDetail.enabled)) {
overridenDetail.original_enabled = originalDetail === null || originalDetail === void 0 ? void 0 : originalDetail.enabled;
}
if (overrideVariant !== (originalDetail === null || originalDetail === void 0 ? void 0 : originalDetail.variant)) {
overridenDetail.original_variant = originalDetail === null || originalDetail === void 0 ? void 0 : originalDetail.variant;
}
if (overridePayload) {
overridenDetail.metadata = __assign(__assign({}, originalDetail === null || originalDetail === void 0 ? void 0 : originalDetail.metadata), { payload: overridePayload, original_payload: (_c = originalDetail === null || originalDetail === void 0 ? void 0 : originalDetail.metadata) === null || _c === void 0 ? void 0 : _c.payload });
}
finalDetails[key] = overridenDetail;
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (overriddenKeys_1_1 && !overriddenKeys_1_1.done && (_a = overriddenKeys_1.return)) _a.call(overriddenKeys_1);
}
finally { if (e_2) throw e_2.error; }
}
if (!this._override_warning) {
logger.warn(' Overriding feature flag details!', {
flagDetails: flagDetails,
overriddenPayloads: overriddenPayloads,
finalDetails: finalDetails,
});
this._override_warning = true;
}
return finalDetails;
};
PostHogFeatureFlags.prototype.getFlagVariants = function () {
var enabledFlags = this.instance.get_property(ENABLED_FEATURE_FLAGS);
var overriddenFlags = this.instance.get_property(PERSISTENCE_OVERRIDE_FEATURE_FLAGS);
if (!overriddenFlags) {
return enabledFlags || {};
}
var finalFlags = extend({}, enabledFlags);
var overriddenKeys = Object.keys(overriddenFlags);
for (var i = 0; i < overriddenKeys.length; i++) {
finalFlags[overriddenKeys[i]] = overriddenFlags[overriddenKeys[i]];
}
if (!this._override_warning) {
logger.warn(' Overriding feature flags!', {
enabledFlags: enabledFlags,
overriddenFlags: overriddenFlags,
finalFlags: finalFlags,
});
this._override_warning = true;
}
return finalFlags;
};
PostHogFeatureFlags.prototype.getFlagPayloads = function () {
var flagPayloads = this.instance.get_property(PERSISTENCE_FEATURE_FLAG_PAYLOADS);
var overriddenPayloads = this.instance.get_property(PERSISTENCE_OVERRIDE_FEATURE_FLAG_PAYLOADS);
if (!overriddenPayloads) {
return flagPayloads || {};
}
var finalPayloads = extend({}, flagPayloads || {});
var overriddenKeys = Object.keys(overriddenPayloads);
for (var i = 0; i < overriddenKeys.length; i++) {
finalPayloads[overriddenKeys[i]] = overriddenPayloads[overriddenKeys[i]];
}
if (!this._override_warning) {
logger.warn(' Overriding feature flag payloads!', {
flagPayloads: flagPayloads,
overriddenPayloads: overriddenPayloads,
finalPayloads: finalPayloads,
});
this._override_warning = true;
}
return finalPayloads;
};
/**
* Reloads feature flags asynchronously.
*
* Constraints:
*
* 1. Avoid parallel requests
* 2. Delay a few milliseconds after each reloadFeatureFlags call to batch subsequent changes together
*/
PostHogFeatureFlags.prototype.reloadFeatureFlags = function () {
var _this = this;
if (this._reloadingDisabled || this.instance.config.advanced_disable_feature_flags) {
// If reloading has been explicitly disabled then we don't want to do anything
// Or if feature flags are disabled
return;
}
if (this._reloadDebouncer) {
// If we're already in a debounce then we don't want to do anything
return;
}
// Debounce multiple calls on the same tick
this._reloadDebouncer = setTimeout(function () {
_this._callDecideEndpoint();
}, 5);
};
PostHogFeatureFlags.prototype.clearDebouncer = function () {
clearTimeout(this._reloadDebouncer);
this._reloadDebouncer = undefined;
};
PostHogFeatureFlags.prototype.ensureFlagsLoaded = function () {
if (this._hasLoadedFlags || this._requestInFlight || this._reloadDebouncer) {
// If we are or have already loaded the flags then we don't want to do anything
return;
}
this.reloadFeatureFlags();
};
PostHogFeatureFlags.prototype.setAnonymousDistinctId = function (anon_distinct_id) {
this.$anon_distinct_id = anon_distinct_id;
};
PostHogFeatureFlags.prototype.setReloadingPaused = function (isPaused) {
this._reloadingDisabled = isPaused;
};
/**
* NOTE: This is used both for flags and remote config. Once the RemoteConfig is fully released this will essentially only
* be for flags and can eventually be replaced with the new flags endpoint
*/
PostHogFeatureFlags.prototype._callDecideEndpoint = function (options) {
var _this = this;
var _a;
// Ensure we don't have double queued decide requests
this.clearDebouncer();
if (this.instance.config.advanced_disable_decide) {
// The way this is documented is essentially used to refuse to ever call the decide endpoint.
return;
}
if (this._requestInFlight) {
this._additionalReloadRequested = true;
return;
}
var token = this.instance.config.token;
var data = {
token: token,
distinct_id: this.instance.get_distinct_id(),
groups: this.instance.getGroups(),
$anon_distinct_id: this.$anon_distinct_id,
person_properties: __assign(__assign({}, (((_a = this.instance.persistence) === null || _a === void 0 ? void 0 : _a.get_initial_props()) || {})), (this.instance.get_property(STORED_PERSON_PROPERTIES_KEY) || {})),
group_properties: this.instance.get_property(STORED_GROUP_PROPERTIES_KEY),
};
if ((options === null || options === void 0 ? void 0 : options.disableFlags) || this.instance.config.advanced_disable_feature_flags) {
data.disable_flags = true;
}
this._requestInFlight = true;
this.instance._send_request({
method: 'POST',
url: this.instance.requestRouter.endpointFor('api', '/decide/?v=4'),
data: data,
compression: this.instance.config.disable_compression ? undefined : Compression.Base64,
timeout: this.instance.config.feature_flag_request_timeout_ms,
callback: function (response) {
var _a, _b, _c;
var errorsLoading = true;
if (response.statusCode === 200) {
// successful request
// reset anon_distinct_id after at least a single request with it
// makes it through
if (!_this._additionalReloadRequested) {
_this.$anon_distinct_id = undefined;
}
errorsLoading = false;
}
_this._requestInFlight = false;
if (!_this._decideCalled) {
_this._decideCalled = true;
_this.instance._onRemoteConfig((_a = response.json) !== null && _a !== void 0 ? _a : {});
}
if (data.disable_flags && !_this._additionalReloadRequested) {
// If flags are disabled then there is no need to call decide again (flags are the only thing that may change)
// UNLESS, an additional reload is requested.
return;
}
_this._flagsLoadedFromRemote = !errorsLoading;
if (response.json && ((_b = response.json.quotaLimited) === null || _b === void 0 ? void 0 : _b.includes(QuotaLimitedResource.FeatureFlags))) {
// log a warning and then early return
logger.warn('You have hit your feature flags quota limit, and will not be able to load feature flags until the quota is reset. Please visit https://posthog.com/docs/billing/limits-alerts to learn more.');
return;
}
_this.receivedFeatureFlags((_c = response.json) !== null && _c !== void 0 ? _c : {}, errorsLoading);
if (_this._additionalReloadRequested) {
_this._additionalReloadRequested = false;
_this._callDecideEndpoint();
}
},
});
};
/*
* Get feature flag's value for user.
*
* ### Usage:
*
* if(posthog.getFeatureFlag('my-flag') === 'some-variant') { // do something }
*
* @param {Object|String} key Key of the feature flag.
* @param {Object|String} options (optional) If {send_event: false}, we won't send an $feature_flag_called event to PostHog.
*/
PostHogFeatureFlags.prototype.getFeatureFlag = function (key, options) {
var _a;
var _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
if (options === void 0) { options = {}; }
if (!this._hasLoadedFlags && !(this.getFlags() && this.getFlags().length > 0)) {
logger.warn('getFeatureFlag for key "' + key + '" failed. Feature flags didn\'t load in time.');
return undefined;
}
var flagValue = this.getFlagVariants()[key];
var flagReportValue = "".concat(flagValue);
var requestId = this.instance.get_property(PERSISTENCE_FEATURE_FLAG_REQUEST_ID) || undefined;
var flagCallReported = this.instance.get_property(FLAG_CALL_REPORTED) || {};
if (options.send_event || !('send_event' in options)) {
if (!(key in flagCallReported) || !flagCallReported[key].includes(flagReportValue)) {
if (isArray(flagCallReported[key])) {
flagCallReported[key].push(flagReportValue);
}
else {
flagCallReported[key] = [flagReportValue];
}
(_b = this.instance.persistence) === null || _b === void 0 ? void 0 : _b.register((_a = {}, _a[FLAG_CALL_REPORTED] = flagCallReported, _a));
var flagDetails = this.getFeatureFlagDetails(key);
var properties = {
$feature_flag: key,
$feature_flag_response: flagValue,
$feature_flag_payload: this.getFeatureFlagPayload(key) || null,
$feature_flag_request_id: requestId,
$feature_flag_bootstrapped_response: ((_d = (_c = this.instance.config.bootstrap) === null || _c === void 0 ? void 0 : _c.featureFlags) === null || _d === void 0 ? void 0 : _d[key]) || null,
$feature_flag_bootstrapped_payload: ((_f = (_e = this.instance.config.bootstrap) === null || _e === void 0 ? void 0 : _e.featureFlagPayloads) === null || _f === void 0 ? void 0 : _f[key]) || null,
// If we haven't yet received a response from the /decide endpoint, we must have used the bootstrapped value
$used_bootstrap_value: !this._flagsLoadedFromRemote,
};
if (!isUndefined((_g = flagDetails === null || flagDetails === void 0 ? void 0 : flagDetails.metadata) === null || _g === void 0 ? void 0 : _g.version)) {
properties.$feature_flag_version = flagDetails.metadata.version;
}
var reason = (_j = (_h = flagDetails === null || flagDetails === void 0 ? void 0 : flagDetails.reason) === null || _h === void 0 ? void 0 : _h.description) !== null && _j !== void 0 ? _j : (_k = flagDetails === null || flagDetails === void 0 ? void 0 : flagDetails.reason) === null || _k === void 0 ? void 0 : _k.code;
if (reason) {
properties.$feature_flag_reason = reason;
}
if ((_l = flagDetails === null || flagDetails === void 0 ? void 0 : flagDetails.metadata) === null || _l === void 0 ? void 0 : _l.id) {
properties.$feature_flag_id = flagDetails.metadata.id;
}
// It's possible that flag values were overridden by calling overrideFeatureFlags.
// We want to capture the original values in case someone forgets they were using overrides
// and is wondering why their app is acting weird.
if (!isUndefined(flagDetails === null || flagDetails === void 0 ? void 0 : flagDetails.original_variant) || !isUndefined(flagDetails === null || flagDetails === void 0 ? void 0 : flagDetails.original_enabled)) {
properties.$feature_flag_original_response = !isUndefined(flagDetails.original_variant)
? flagDetails.original_variant
: flagDetails.original_enabled;
}
if ((_m = flagDetails === null || flagDetails === void 0 ? void 0 : flagDetails.metadata) === null || _m === void 0 ? void 0 : _m.original_payload) {
properties.$feature_flag_original_payload = (_o = flagDetails === null || flagDetails === void 0 ? void 0 : flagDetails.metadata) === null || _o === void 0 ? void 0 : _o.original_payload;
}
this.instance.capture('$feature_flag_called', properties);
}
}
return flagValue;
};
/*
* Retrieves the details for a feature flag.
*
* ### Usage:
*
* const details = getFeatureFlagDetails("my-flag")
* console.log(details.metadata.version)
* console.log(details.reason)
*
* @param {String} key Key of the feature flag.
*/
PostHogFeatureFlags.prototype.getFeatureFlagDetails = function (key) {
var details = this.getFlagsWithDetails();
return details[key];
};
PostHogFeatureFlags.prototype.getFeatureFlagPayload = function (key) {
var payloads = this.getFlagPayloads();
return payloads[key];
};
/*
* Fetches the payload for a remote config feature flag. This method will bypass any cached values and fetch the latest
* value from the PostHog API.
*
* Note: Because the posthog-js SDK is primarily used with public project API keys, encrypted remote config payloads will
* be redacted, never decrypted in the response.
*
* ### Usage:
*
* getRemoteConfigPayload("home-page-welcome-message", (payload) => console.log(`Fetched remote config: ${payload}`))
*
* @param {String} key Key of the feature flag.
* @param {Function} [callback] The callback function will be called once the remote config feature flag payload has been fetched.
*/
PostHogFeatureFlags.prototype.getRemoteConfigPayload = function (key, callback) {
var token = this.instance.config.token;
this.instance._send_request({
method: 'POST',
url: this.instance.requestRouter.endpointFor('api', '/decide/?v=4'),
data: {
distinct_id: this.instance.get_distinct_id(),
token: token,
},
compression: this.instance.config.disable_compression ? undefined : Compression.Base64,
timeout: this.instance.config.feature_flag_request_timeout_ms,
callback: function (response) {
var _a;
var flagPayloads = (_a = response.json) === null || _a === void 0 ? void 0 : _a['featureFlagPayloads'];
callback((flagPayloads === null || flagPayloads === void 0 ? void 0 : flagPayloads[key]) || undefined);
},
});
};
/*
* See if feature flag is enabled for user.
*
* ### Usage:
*
* if(posthog.isFeatureEnabled('beta-feature')) { // do something }
*
* @param {Object|String} key Key of the feature flag.
* @param {Object|String} options (optional) If {send_event: false}, we won't send an $feature_flag_call event to PostHog.
*/
PostHogFeatureFlags.prototype.isFeatureEnabled = function (key, options) {
if (options === void 0) { options = {}; }
if (!this._hasLoadedFlags && !(this.getFlags() && this.getFlags().length > 0)) {
logger.warn('isFeatureEnabled for key "' + key + '" failed. Feature flags didn\'t load in time.');
return undefined;
}
return !!this.getFeatureFlag(key, options);
};
PostHogFeatureFlags.prototype.addFeatureFlagsHandler = function (handler) {
this.featureFlagEventHandlers.push(handler);
};
PostHogFeatureFlags.prototype.removeFeatureFlagsHandler = function (handler) {
this.featureFlagEventHandlers = this.featureFlagEventHandlers.filter(function (h) { return h !== handler; });
};
PostHogFeatureFlags.prototype.receivedFeatureFlags = function (response, errorsLoading) {
if (!this.instance.persistence) {
return;
}
this._hasLoadedFlags = true;
var currentFlags = this.getFlagVariants();
var currentFlagPayloads = this.getFlagPayloads();
var currentFlagDetails = this.getFlagsWithDetails();
parseFeatureFlagDecideResponse(response, this.instance.persistence, currentFlags, currentFlagPayloads, currentFlagDetails);
this._fireFeatureFlagsCallbacks(errorsLoading);
};
/**
* @deprecated Use overrideFeatureFlags instead. This will be removed in a future version.
*/
PostHogFeatureFlags.prototype.override = function (flags, suppressWarning) {
if (suppressWarning === void 0) { suppressWarning = false; }
logger.warn('override is deprecated. Please use overrideFeatureFlags instead.');
this.overrideFeatureFlags({
flags: flags,
suppressWarning: suppressWarning,
});
};
/**
* Override feature flags on the client-side. Useful for setting non-persistent feature flags,
* or for testing/debugging feature flags in the PostHog app.
*
* ### Usage:
*
* - posthog.featureFlags.overrideFeatureFlags(false) // clear all overrides
* - posthog.featureFlags.overrideFeatureFlags(['beta-feature']) // enable flags
* - posthog.featureFlags.overrideFeatureFlags({'beta-feature': 'variant'}) // set variants
* - posthog.featureFlags.overrideFeatureFlags({ // set both flags and payloads
* flags: {'beta-feature': 'variant'},
* payloads: { 'beta-feature': { someData: true } }
* })
* - posthog.featureFlags.overrideFeatureFlags({ // only override payloads
* payloads: { 'beta-feature': { someData: true } }
* })
*/
PostHogFeatureFlags.prototype.overrideFeatureFlags = function (overrideOptions) {
var _a, _b, _c;
var _d;
if (!this.instance.__loaded || !this.instance.persistence) {
return logger.uninitializedWarning('posthog.featureFlags.overrideFeatureFlags');
}
// Clear all overrides if false, lets you do something like posthog.featureFlags.overrideFeatureFlags(false)
if (overrideOptions === false) {
this.instance.persistence.unregister(PERSISTENCE_OVERRIDE_FEATURE_FLAGS);
this.instance.persistence.unregister(PERSISTENCE_OVERRIDE_FEATURE_FLAG_PAYLOADS);
this._fireFeatureFlagsCallbacks();
return;
}
if (overrideOptions &&
typeof overrideOptions === 'object' &&
('flags' in overrideOptions || 'payloads' in overrideOptions)) {
var options = overrideOptions;
this._override_warning = Boolean((_d = options.suppressWarning) !== null && _d !== void 0 ? _d : false);
// Handle flags if provided, lets you do something like posthog.featureFlags.overrideFeatureFlags({flags: ['beta-feature']})
if ('flags' in options) {
if (options.flags === false) {
this.instance.persistence.unregister(PERSISTENCE_OVERRIDE_FEATURE_FLAGS);
}
else if (options.flags) {
if (isArray(options.flags)) {
var flagsObj = {};
for (var i = 0; i < options.flags.length; i++) {
flagsObj[options.flags[i]] = true;
}
this.instance.persistence.register((_a = {}, _a[PERSISTENCE_OVERRIDE_FEATURE_FLAGS] = flagsObj, _a));
}
else {
this.instance.persistence.register((_b = {}, _b[PERSISTENCE_OVERRIDE_FEATURE_FLAGS] = options.flags, _b));
}
}
}
// Handle payloads independently, lets you do something like posthog.featureFlags.overrideFeatureFlags({payloads: { 'beta-feature': { someData: true } }})
if ('payloads' in options) {
if (options.payloads === false) {
this.instance.persistence.unregister(PERSISTENCE_OVERRIDE_FEATURE_FLAG_PAYLOADS);
}
else if (options.payloads) {
this.instance.persistence.register((_c = {},
_c[PERSISTENCE_OVERRIDE_FEATURE_FLAG_PAYLOADS] = options.payloads,
_c));
}
}
this._fireFeatureFlagsCallbacks();
return;
}
this._fireFeatureFlagsCallbacks();
};
/*
* Register an event listener that runs when feature flags become available or when they change.
* If there are flags, the listener is called immediately in addition to being called on future changes.
*
* ### Usage:
*
* posthog.onFeatureFlags(function(featureFlags, featureFlagsVariants, { errorsLoading }) { // do something })
*
* @param {Function} [callback] The callback function will be called once the feature flags are ready or when they are updated.
* It'll return a list of feature flags enabled for the user, the variants,
* and also a context object indicating whether we succeeded to fetch the flags or not.
* @returns {Function} A function that can be called to unsubscribe the listener. Used by useEffect when the component unmounts.
*/
PostHogFeatureFlags.prototype.onFeatureFlags = function (callback) {
var _this = this;
this.addFeatureFlagsHandler(callback);
if (this._hasLoadedFlags) {
var _a = this._prepareFeatureFlagsForCallbacks(), flags = _a.flags, flagVariants = _a.flagVariants;
callback(flags, flagVariants);
}
return function () { return _this.removeFeatureFlagsHandler(callback); };
};
PostHogFeatureFlags.prototype.updateEarlyAccessFeatureEnrollment = function (key, isEnrolled) {
var _a, _b, _c;
var _d;
var existing_early_access_features = this.instance.get_property(PERSISTENCE_EARLY_ACCESS_FEATURES) || [];
var feature = existing_early_access_features.find(function (f) { return f.flagKey === key; });
var enrollmentPersonProp = (_a = {},
_a["$feature_enrollment/".concat(key)] = isEnrolled,
_a);
var properties = {
$feature_flag: key,
$feature_enrollment: isEnrolled,
$set: enrollmentPersonProp,
};
if (feature) {
properties['$early_access_feature_name'] = feature.name;
}
this.instance.capture('$feature_enrollment_update', properties);
this.setPersonPropertiesForFlags(enrollmentPersonProp, false);
var newFlags = __assign(__assign({}, this.getFlagVariants()), (_b = {}, _b[key] = isEnrolled, _b));
(_d = this.instance.persistence) === null || _d === void 0 ? void 0 : _d.register((_c = {},
_c[PERSISTENCE_ACTIVE_FEATURE_FLAGS] = Object.keys(filterActiveFeatureFlags(newFlags)),
_c[ENABLED_FEATURE_FLAGS] = newFlags,
_c));
this._fireFeatureFlagsCallbacks();
};
PostHogFeatureFlags.prototype.getEarlyAccessFeatures = function (callback, force_reload, stages) {
var _this = this;
if (force_reload === void 0) { force_reload = false; }
var existing_early_access_features = this.instance.get_property(PERSISTENCE_EARLY_ACCESS_FEATURES);
var stageParams = stages ? "&".concat(stages.map(function (s) { return "stage=".concat(s); }).join('&')) : '';
if (!existing_early_access_features || force_reload) {
this.instance._send_request({
url: this.instance.requestRouter.endpointFor('api', "/api/early_access_features/?token=".concat(this.instance.config.token).concat(stageParams)),
method: 'GET',
callback: function (response) {
var _a;
var _b;
if (!response.json) {
return;
}
var earlyAccessFeatures = response.json.earlyAccessFeatures;
(_b = _this.instance.persistence) === null || _b === void 0 ? void 0 : _b.register((_a = {}, _a[PERSISTENCE_EARLY_ACCESS_FEATURES] = earlyAccessFeatures, _a));
return callback(earlyAccessFeatures);
},
});
}
else {
return callback(existing_early_access_features);
}
};
PostHogFeatureFlags.prototype._prepareFeatureFlagsForCallbacks = function () {
var flags = this.getFlags();
var flagVariants = this.getFlagVariants();
// Return truthy
var truthyFlags = flags.filter(function (flag) { return flagVariants[flag]; });
var truthyFlagVariants = Object.keys(flagVariants)
.filter(function (variantKey) { return flagVariants[variantKey]; })
.reduce(function (res, key) {
res[key] = flagVariants[key];
return res;
}, {});
return {
flags: truthyFlags,
flagVariants: truthyFlagVariants,
};
};
PostHogFeatureFlags.prototype._fireFeatureFlagsCallbacks = function (errorsLoading) {
var _a = this._prepareFeatureFlagsForCallbacks(), flags = _a.flags, flagVariants = _a.flagVariants;
this.featureFlagEventHandlers.forEach(function (handler) { return handler(flags, flagVariants, { errorsLoading: errorsLoading }); });
};
/**
* Set override person properties for feature flags.
* This is used when dealing with new persons / where you don't want to wait for ingestion
* to update user properties.
*/
PostHogFeatureFlags.prototype.setPersonPropertiesForFlags = function (properties, reloadFeatureFlags) {
var _a;
if (reloadFeatureFlags === void 0) { reloadFeatureFlags = true; }
// Get persisted person properties
var existingProperties = this.instance.get_property(STORED_PERSON_PROPERTIES_KEY) || {};
this.instance.register((_a = {},
_a[STORED_PERSON_PROPERTIES_KEY] = __assign(__assign({}, existingProperties), properties),
_a));
if (reloadFeatureFlags) {
this.instance.reloadFeatureFlags();
}
};
PostHogFeatureFlags.prototype.resetPersonPropertiesForFlags = function () {
this.instance.unregister(STORED_PERSON_PROPERTIES_KEY);
};
/**
* Set override group properties for feature flags.
* This is used when dealing with new groups / where you don't want to wait for ingestion
* to update properties.
* Takes in an object, the key of which is the group type.
* For example:
* setGroupPropertiesForFlags({'organization': { name: 'CYZ', employees: '11' } })
*/
PostHogFeatureFlags.prototype.setGroupPropertiesForFlags = function (properties, reloadFeatureFlags) {
var _a;
if (reloadFeatureFlags === void 0) { reloadFeatureFlags = true; }
// Get persisted group properties
var existingProperties = this.instance.get_property(STORED_GROUP_PROPERTIES_KEY) || {};
if (Object.keys(existingProperties).length !== 0) {
Object.keys(existingProperties).forEach(function (groupType) {
existingProperties[groupType] = __assign(__assign({}, existingProperties[groupType]), properties[groupType]);
delete properties[groupType];
});
}
this.instance.register((_a = {},
_a[STORED_GROUP_PROPERTIES_KEY] = __assign(__assign({}, existingProperties), properties),
_a));
if (reloadFeatureFlags) {
this.instance.reloadFeatureFlags();
}
};
PostHogFeatureFlags.prototype.resetGroupPropertiesForFlags = function (group_type) {
var _a, _b;
if (group_type) {
var existingProperties = this.instance.get_property(STORED_GROUP_PROPERTIES_KEY) || {};
this.instance.register((_a = {},
_a[STORED_GROUP_PROPERTIES_KEY] = __assign(__assign({}, existingProperties), (_b = {}, _b[group_type] = {}, _b)),
_a));
}
else {
this.instance.unregister(STORED_GROUP_PROPERTIES_KEY);
}
};
return PostHogFeatureFlags;
}());
export { PostHogFeatureFlags };
//# sourceMappingURL=posthog-featureflags.js.map