statsig-js
Version:
Statsig JavaScript client SDK for single user environments.
926 lines (925 loc) • 48.7 kB
JavaScript
"use strict";
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 __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var DynamicConfig_1 = __importDefault(require("./DynamicConfig"));
var Layer_1 = __importDefault(require("./Layer"));
var BootstrapValidator_1 = __importDefault(require("./utils/BootstrapValidator"));
var Constants_1 = require("./utils/Constants");
var Hashing_1 = require("./utils/Hashing");
var StatsigAsyncStorage_1 = __importDefault(require("./utils/StatsigAsyncStorage"));
var StatsigLocalStorage_1 = __importDefault(require("./utils/StatsigLocalStorage"));
var EvaluationReason_1 = require("./utils/EvaluationReason");
var ResponseVerification_1 = require("./utils/ResponseVerification");
var MAX_USER_VALUE_CACHED = 10;
var StatsigStore = /** @class */ (function () {
function StatsigStore(sdkInternal, initializeValues) {
this.overrides = {
gates: {},
configs: {},
layers: {},
};
this.sdkInternal = sdkInternal;
this.userCacheKey = this.sdkInternal.getCurrentUserCacheKey();
this.values = {};
this.userValues = {
feature_gates: {},
dynamic_configs: {},
sticky_experiments: {},
layer_configs: {},
has_updates: false,
time: 0,
evaluation_time: 0,
derived_fields: {},
};
this.stickyDeviceExperiments = {};
this.loaded = false;
this.reason = EvaluationReason_1.EvaluationReason.Uninitialized;
this.userPersistentStorageAdapter = this.sdkInternal
.getOptions()
.getUserPersistentStorage();
this.userPersistentStorageData = { experiments: {} };
if (initializeValues) {
this.bootstrap(initializeValues);
}
else {
this.load();
}
}
StatsigStore.prototype.load = function () {
this.loadFromLocalStorage();
this.partialLoadFromPersistentStorageAdapter();
};
StatsigStore.prototype.loadAsync = function () {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.loadFromAsyncStorage()];
case 1:
_a.sent();
this.partialLoadFromPersistentStorageAdapter();
return [2 /*return*/];
}
});
});
};
StatsigStore.prototype.updateUser = function (isUserPrefetched) {
this.userCacheKey = this.sdkInternal.getCurrentUserCacheKey();
var evaluationTime = this.setUserValueFromCache(isUserPrefetched);
this.partialLoadFromPersistentStorageAdapter();
return evaluationTime;
};
StatsigStore.prototype.getInitializeResponseJson = function () {
return JSON.stringify(this.userValues);
};
StatsigStore.prototype.loadFromAsyncStorage = function () {
return __awaiter(this, void 0, void 0, function () {
var _a, _b;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
_a = this.parseCachedValues;
return [4 /*yield*/, StatsigAsyncStorage_1.default.getItemAsync(Constants_1.INTERNAL_STORE_KEY)];
case 1:
_b = [_c.sent()];
return [4 /*yield*/, StatsigAsyncStorage_1.default.getItemAsync(Constants_1.STICKY_DEVICE_EXPERIMENTS_KEY)];
case 2:
_a.apply(this, _b.concat([_c.sent()]));
// triggered for react native, when async storage is setup. Need to update the cache key
// as the stableID is not available when this is set in the constructor (RN/async storage clients only)
this.userCacheKey = this.sdkInternal.getCurrentUserCacheKey();
this.loaded = true;
return [2 /*return*/];
}
});
});
};
StatsigStore.prototype.bootstrap = function (initializeValues) {
var _a, _b, _c, _d, _f, _g;
var key = this.sdkInternal.getCurrentUserCacheKey();
var user = this.sdkInternal.getCurrentUser();
var stableID = (_c = (_b = (_a = user === null || user === void 0 ? void 0 : user.customIDs) === null || _a === void 0 ? void 0 : _a.stableID) !== null && _b !== void 0 ? _b : this.sdkInternal.getStatsigMetadata().stableID) !== null && _c !== void 0 ? _c : null;
var reason = BootstrapValidator_1.default.getEvaluationReasonForBootstrap(user, initializeValues, stableID);
// clients are going to assume that the SDK is bootstraped after this method runs
// if we fail to parse, we will fall back to defaults, but we dont want to throw
// when clients try to check gates/configs/etc after this point
this.loaded = true;
try {
/* eslint-disable @typescript-eslint/no-explicit-any */
var values = initializeValues;
this.userValues.feature_gates = (_d = values.feature_gates) !== null && _d !== void 0 ? _d : {};
this.userValues.dynamic_configs = (_f = values.dynamic_configs) !== null && _f !== void 0 ? _f : {};
this.userValues.layer_configs = (_g = values.layer_configs) !== null && _g !== void 0 ? _g : {};
this.userValues.evaluation_time = Date.now();
this.userValues.time = Date.now();
this.userValues.hash_used = values.hash_used;
this.values[key.v3] = this.userValues;
this.reason = reason;
this.loadOverrides();
}
catch (_e) {
return;
}
};
StatsigStore.prototype.loadFromLocalStorage = function () {
if (StatsigAsyncStorage_1.default.asyncStorage) {
return;
}
this.parseCachedValues(StatsigLocalStorage_1.default.getItem(Constants_1.INTERNAL_STORE_KEY), StatsigLocalStorage_1.default.getItem(Constants_1.STICKY_DEVICE_EXPERIMENTS_KEY));
this.loaded = true;
};
// Currently only loads experiments, cannot rely on storage adapter for all user values.
StatsigStore.prototype.partialLoadFromPersistentStorageAdapter = function () {
var _a;
if (this.userPersistentStorageAdapter) {
var idType = (_a = this.userPersistentStorageAdapter.userIDType) !== null && _a !== void 0 ? _a : 'userID';
var unitID = this.sdkInternal.getCurrentUserUnitID(idType);
if (unitID) {
try {
this.userPersistentStorageData = JSON.parse(this.userPersistentStorageAdapter.load(unitID + ":" + idType));
}
catch (e) {
console.warn('Failed to load from user persistent storage.', e);
}
this.userValues.sticky_experiments = this.userPersistentStorageData
.experiments;
}
}
};
StatsigStore.prototype.saveStickyExperimentsToPersistentStorageAdapter = function () {
var _a;
if (this.userPersistentStorageAdapter) {
var idType = (_a = this.userPersistentStorageAdapter.userIDType) !== null && _a !== void 0 ? _a : 'userID';
var unitID = this.sdkInternal.getCurrentUserUnitID(idType);
if (unitID) {
var data = __assign(__assign({}, this.userPersistentStorageData), { experiments: this.userValues.sticky_experiments });
try {
this.userPersistentStorageAdapter.save(unitID + ":" + idType, JSON.stringify(data));
}
catch (e) {
console.warn('Failed to save user experiment values to persistent storage.', e);
}
}
}
};
StatsigStore.prototype.isLoaded = function () {
return this.loaded;
};
StatsigStore.prototype.getLastUpdateTime = function (user, stableID) {
var userHash = (0, Hashing_1.djb2HashForObject)(__assign(__assign({}, user), { stableID: stableID }));
if (this.userValues.user_hash == userHash) {
return this.userValues.time;
}
return null;
};
StatsigStore.prototype.getPreviousDerivedFields = function (user, stableID) {
var userHash = (0, Hashing_1.djb2HashForObject)(__assign(__assign({}, user), { stableID: stableID }));
if (this.userValues.user_hash == userHash) {
return this.userValues.derived_fields;
}
return undefined;
};
StatsigStore.prototype.parseCachedValues = function (allValues, deviceExperiments) {
try {
this.values = allValues ? JSON.parse(allValues) : this.values;
this.setUserValueFromCache();
}
catch (e) {
// Cached value corrupted, remove cache
this.removeFromStorage(Constants_1.INTERNAL_STORE_KEY);
}
try {
var deviceExpParsed = deviceExperiments
? JSON.parse(deviceExperiments)
: null;
if (deviceExpParsed) {
this.stickyDeviceExperiments = deviceExpParsed;
}
}
catch (e) {
this.removeFromStorage(Constants_1.STICKY_DEVICE_EXPERIMENTS_KEY);
}
this.loadOverrides();
};
StatsigStore.prototype.getUserValues = function (key) {
var _a, _b;
return (_b = (_a = this.values[key.v3]) !== null && _a !== void 0 ? _a : this.values[key.v2]) !== null && _b !== void 0 ? _b : this.values[key.v1];
};
StatsigStore.prototype.setUserValueFromCache = function (isUserPrefetched) {
var _a;
if (isUserPrefetched === void 0) { isUserPrefetched = false; }
var cachedValues = this.getUserValues(this.userCacheKey);
if (cachedValues == null) {
this.resetUserValues();
this.reason = EvaluationReason_1.EvaluationReason.Uninitialized;
return null;
}
if (cachedValues.stableIDUsed != null &&
cachedValues.stableIDUsed !== this.getStableID()) {
this.sdkInternal
.getErrorBoundary()
.logError('stableIDChanged', new Error("StableID changed from " + cachedValues.stableIDUsed + " to " + this.getStableID()));
}
this.userValues = cachedValues;
this.reason = isUserPrefetched
? EvaluationReason_1.EvaluationReason.Prefetch
: EvaluationReason_1.EvaluationReason.Cache;
return (_a = cachedValues.evaluation_time) !== null && _a !== void 0 ? _a : 0;
};
StatsigStore.prototype.removeFromStorage = function (key) {
var _this = this;
StatsigAsyncStorage_1.default.removeItemAsync(key).catch(function (reason) {
return _this.sdkInternal.getErrorBoundary().logError('removeFromStorage', reason);
});
StatsigLocalStorage_1.default.removeItem(key);
};
StatsigStore.prototype.loadOverrides = function () {
if (this.sdkInternal.getOptions().getDisableLocalOverrides()) {
return;
}
var overrides = StatsigLocalStorage_1.default.getItem(Constants_1.OVERRIDES_STORE_KEY);
if (overrides != null) {
try {
this.overrides = JSON.parse(overrides);
}
catch (e) {
StatsigLocalStorage_1.default.removeItem(Constants_1.OVERRIDES_STORE_KEY);
}
}
};
StatsigStore.prototype.setEvaluationReason = function (evalReason) {
this.reason = evalReason;
};
StatsigStore.prototype.save = function (user, response, prefetchUsers) {
return __awaiter(this, void 0, void 0, function () {
var requestedUserCacheKey, initResponse, userValues, _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
requestedUserCacheKey = (0, Hashing_1.getUserCacheKey)(this.getStableID(), user, this.sdkInternal.getSDKKey());
initResponse = response;
if (initResponse.is_delta) {
return [2 /*return*/, this.saveInitDeltas(user, response, true, prefetchUsers)];
}
this.mergeInitializeResponseIntoUserMap(initResponse, this.values, requestedUserCacheKey, user, function (userValues) { return userValues; }, prefetchUsers);
userValues = this.getUserValues(requestedUserCacheKey);
if (userValues &&
requestedUserCacheKey &&
requestedUserCacheKey.v3 === this.userCacheKey.v3) {
this.userValues = userValues;
this.reason = EvaluationReason_1.EvaluationReason.Network;
}
_a = this;
return [4 /*yield*/, this.writeValuesToStorage(this.values)];
case 1:
_a.values = _b.sent();
return [2 /*return*/];
}
});
});
};
/**
* Persists the init values to storage, but DOES NOT update the state of the store.
*/
StatsigStore.prototype.saveWithoutUpdatingClientState = function (user, response, prefetchUsers) {
return __awaiter(this, void 0, void 0, function () {
var requestedUserCacheKey, initResponse, copiedValues;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
requestedUserCacheKey = (0, Hashing_1.getUserCacheKey)(this.getStableID(), user, this.sdkInternal.getSDKKey());
initResponse = response;
if (initResponse.is_delta) {
return [2 /*return*/, this.saveInitDeltas(user, response, false, prefetchUsers)];
}
copiedValues = JSON.parse(JSON.stringify(this.values));
this.mergeInitializeResponseIntoUserMap(initResponse, copiedValues, requestedUserCacheKey, user, function (userValues) { return userValues; }, prefetchUsers);
return [4 /*yield*/, this.writeValuesToStorage(copiedValues)];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
};
StatsigStore.prototype.getDeltasMergeFunction = function (mergedValues) {
var _this = this;
return function (deltas, key) {
var _a, _b, _c;
var baseValues = (_c = (_b = (_a = mergedValues[key.v3]) !== null && _a !== void 0 ? _a : mergedValues[key.v2]) !== null && _b !== void 0 ? _b : mergedValues[key.v1]) !== null && _c !== void 0 ? _c : _this.getDefaultUserCacheValues();
return _this.mergeUserCacheValues(baseValues, deltas);
};
};
StatsigStore.prototype.saveInitDeltas = function (user, response, updateState, prefetchUsers) {
var _a, _b, _c;
return __awaiter(this, void 0, void 0, function () {
var requestedUserCacheKey, initResponse, mergedValues, hasBadHash, badChecksum, hashChanged, cacheKeys, userValues, expectedFullHash, mergedConfigs, currentFullHash, _d;
var _this = this;
return __generator(this, function (_f) {
switch (_f.label) {
case 0:
requestedUserCacheKey = (0, Hashing_1.getUserCacheKey)(this.getStableID(), user, this.sdkInternal.getSDKKey());
initResponse = response;
mergedValues = JSON.parse(JSON.stringify(this.values));
// Merge delta values into the previous values
this.mergeInitializeResponseIntoUserMap(initResponse, mergedValues, requestedUserCacheKey, user, this.getDeltasMergeFunction(mergedValues), prefetchUsers);
hasBadHash = false;
badChecksum = undefined;
hashChanged = false;
cacheKeys = Object.keys((_a = initResponse.prefetched_user_values) !== null && _a !== void 0 ? _a : {});
cacheKeys.forEach(function (userKey) {
var _a;
var user = mergedValues[userKey];
var reponseForUser = (_a = initResponse.prefetched_user_values) === null || _a === void 0 ? void 0 : _a[userKey];
if (user && reponseForUser) {
removeDeletedKeysFromUserValues(reponseForUser, user);
var expectedFullHash_1 = reponseForUser.checksum;
var currentFullHash_1 = (0, Hashing_1.djb2HashForObject)({
feature_gates: mergedValues[userKey].feature_gates,
dynamic_configs: mergedValues[userKey].dynamic_configs,
layer_configs: mergedValues[userKey].layer_configs,
});
if (expectedFullHash_1 && expectedFullHash_1 !== currentFullHash_1) {
hasBadHash = true;
badChecksum = currentFullHash_1;
}
if (userValues.hash_used !== initResponse.hash_used) {
hashChanged = true;
}
}
});
userValues = (_c = (_b = mergedValues[requestedUserCacheKey.v3]) !== null && _b !== void 0 ? _b : mergedValues[requestedUserCacheKey.v2]) !== null && _c !== void 0 ? _c : mergedValues[requestedUserCacheKey.v1];
removeDeletedKeysFromUserValues(initResponse, userValues);
expectedFullHash = initResponse.checksum;
mergedConfigs = {
feature_gates: userValues.feature_gates,
dynamic_configs: userValues.dynamic_configs,
layer_configs: userValues.layer_configs,
};
currentFullHash = (0, Hashing_1.djb2HashForObject)(mergedConfigs);
if (expectedFullHash && expectedFullHash !== currentFullHash) {
hasBadHash = true;
badChecksum = currentFullHash;
}
if (userValues.hash_used !== initResponse.hash_used) {
hashChanged = true;
}
if (hasBadHash || hashChanged) {
if (initResponse.deltas_full_response != null) {
// retry
this.refetchAndSaveValues(user, prefetchUsers, undefined, badChecksum, hasBadHash, mergedConfigs, initResponse.deltas_full_response).catch(function (reason) {
return _this.sdkInternal
.getErrorBoundary()
.logError('refetchAndSaveValues', reason);
});
return [2 /*return*/];
}
// retry
this.refetchAndSaveValues(user, prefetchUsers, undefined, badChecksum, hasBadHash).catch(function (reason) {
return _this.sdkInternal
.getErrorBoundary()
.logError('refetchAndSaveValues', reason);
});
return [2 /*return*/];
}
if (!updateState) return [3 /*break*/, 2];
if (userValues && requestedUserCacheKey.v3 === this.userCacheKey.v3) {
this.userValues = userValues;
this.reason = EvaluationReason_1.EvaluationReason.Network;
}
_d = this;
return [4 /*yield*/, this.writeValuesToStorage(mergedValues)];
case 1:
_d.values = _f.sent();
return [3 /*break*/, 4];
case 2: return [4 /*yield*/, this.writeValuesToStorage(mergedValues)];
case 3:
_f.sent();
_f.label = 4;
case 4: return [2 /*return*/];
}
});
});
};
StatsigStore.prototype.refetchAndSaveValues = function (user, prefetchUsers, timeout, badChecksum, hadBadChecksum, badMergedConfigs, badFullResponse) {
var _a, _b, _c, _d;
if (timeout === void 0) { timeout = this.sdkInternal.getOptions().getInitTimeoutMs(); }
return __awaiter(this, void 0, void 0, function () {
var sinceTime, previousDerivedFields;
var _this = this;
return __generator(this, function (_f) {
sinceTime = this.getLastUpdateTime(user, String((_b = (_a = this.sdkInternal.getStatsigMetadata()) === null || _a === void 0 ? void 0 : _a.stableID) !== null && _b !== void 0 ? _b : ''));
previousDerivedFields = this.getPreviousDerivedFields(user, String((_d = (_c = this.sdkInternal.getStatsigMetadata()) === null || _c === void 0 ? void 0 : _c.stableID) !== null && _d !== void 0 ? _d : ''));
return [2 /*return*/, this.sdkInternal
.getNetwork()
.fetchValues({
user: user,
sinceTime: sinceTime,
timeout: timeout,
useDeltas: false,
prefetchUsers: prefetchUsers,
previousDerivedFields: previousDerivedFields,
hadBadDeltaChecksum: hadBadChecksum,
badChecksum: badChecksum,
badMergedConfigs: badMergedConfigs,
badFullResponse: badFullResponse,
})
.then(function (json) {
if (!(0, ResponseVerification_1.verifySDKKeyUsed)(json, _this.sdkInternal.getSDKKey(), _this.sdkInternal.getErrorBoundary())) {
return;
}
if (json === null || json === void 0 ? void 0 : json.has_updates) {
_this.saveWithoutUpdatingClientState(user, json, prefetchUsers).catch(function (reason) {
return _this.sdkInternal
.getErrorBoundary()
.logError('refetchAndSaveValues:then', reason);
});
}
})
.catch(function (reason) {
return _this.sdkInternal
.getErrorBoundary()
.logError('refetchAndSaveValues', reason);
})];
});
});
};
StatsigStore.prototype.getStableID = function () {
return this.sdkInternal.getStableID();
};
/**
* Merges the provided init configs into the provided config map, according to the provided merge function
*/
StatsigStore.prototype.mergeInitializeResponseIntoUserMap = function (data, configMap, requestedUserCacheKey, user, mergeFn, prefetchUsers) {
var _a;
if (data.prefetched_user_values) {
var cacheKeys = Object.keys(data.prefetched_user_values);
for (var _i = 0, cacheKeys_1 = cacheKeys; _i < cacheKeys_1.length; _i++) {
var key = cacheKeys_1[_i];
var prefetched = data.prefetched_user_values[key];
var values = mergeFn(this.convertAPIDataToCacheValues(prefetched, key), { v1: key, v2: key, v3: key });
if (prefetchUsers) {
var userHash = (0, Hashing_1.djb2HashForObject)(prefetchUsers[key]);
values.user_hash = userHash;
values.stableIDUsed = this.getStableID();
}
configMap[key] = values;
}
}
if (requestedUserCacheKey) {
var requestedUserValues = this.convertAPIDataToCacheValues(data, requestedUserCacheKey.v3);
var userHash = (0, Hashing_1.djb2HashForObject)(__assign(__assign({}, user), { stableID: String((_a = this.getStableID()) !== null && _a !== void 0 ? _a : '') }));
requestedUserValues.user_hash = userHash;
requestedUserValues.stableIDUsed = this.getStableID();
configMap[requestedUserCacheKey.v3] = mergeFn(requestedUserValues, requestedUserCacheKey);
}
};
StatsigStore.prototype.getDefaultUserCacheValues = function () {
return {
feature_gates: {},
layer_configs: {},
dynamic_configs: {},
sticky_experiments: {},
time: 0,
evaluation_time: 0,
derived_fields: {},
};
};
StatsigStore.prototype.mergeUserCacheValues = function (baseValues, valuesToMerge) {
return {
feature_gates: __assign(__assign({}, baseValues.feature_gates), valuesToMerge.feature_gates),
layer_configs: __assign(__assign({}, baseValues.layer_configs), valuesToMerge.layer_configs),
dynamic_configs: __assign(__assign({}, baseValues.dynamic_configs), valuesToMerge.dynamic_configs),
sticky_experiments: baseValues.sticky_experiments,
time: valuesToMerge.time,
evaluation_time: valuesToMerge.evaluation_time,
derived_fields: valuesToMerge.derived_fields,
hash_used: valuesToMerge.hash_used,
user_hash: valuesToMerge.user_hash,
stableIDUsed: valuesToMerge.stableIDUsed,
};
};
/**
* Writes the provided values to storage, truncating down to
* MAX_USER_VALUE_CACHED number entries.
* @returns The truncated entry list
*/
StatsigStore.prototype.writeValuesToStorage = function (valuesToWrite) {
return __awaiter(this, void 0, void 0, function () {
var filteredValues;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
// delete the older version of cache
if (valuesToWrite[this.userCacheKey.v3]) {
delete valuesToWrite[this.userCacheKey.v2];
delete valuesToWrite[this.userCacheKey.v1];
}
else if (valuesToWrite[this.userCacheKey.v2]) {
delete valuesToWrite[this.userCacheKey.v1];
}
filteredValues = Object.entries(valuesToWrite)
.sort(function (_a, _b) {
var _c, _d;
var a = _a[1];
var b = _b[1];
if (a == null) {
return 1;
}
if (b == null) {
return -1;
}
return (((_c = b === null || b === void 0 ? void 0 : b.evaluation_time) !== null && _c !== void 0 ? _c : b === null || b === void 0 ? void 0 : b.time) - ((_d = a === null || a === void 0 ? void 0 : a.evaluation_time) !== null && _d !== void 0 ? _d : a === null || a === void 0 ? void 0 : a.time));
})
.slice(0, MAX_USER_VALUE_CACHED);
valuesToWrite = Object.fromEntries(filteredValues);
if (!StatsigAsyncStorage_1.default.asyncStorage) return [3 /*break*/, 2];
return [4 /*yield*/, StatsigAsyncStorage_1.default.setItemAsync(Constants_1.INTERNAL_STORE_KEY, JSON.stringify(valuesToWrite))];
case 1:
_a.sent();
return [3 /*break*/, 3];
case 2:
StatsigLocalStorage_1.default.setItem(Constants_1.INTERNAL_STORE_KEY, JSON.stringify(valuesToWrite));
_a.label = 3;
case 3: return [2 /*return*/, valuesToWrite];
}
});
});
};
StatsigStore.prototype.checkGate = function (gateName, ignoreOverrides) {
var _a;
if (ignoreOverrides === void 0) { ignoreOverrides = false; }
var gateNameHash = this.getHashedSpecName(gateName);
var gateValue = {
name: gateName,
value: false,
rule_id: '',
secondary_exposures: [],
};
var details;
if (!ignoreOverrides && this.overrides.gates[gateName] != null) {
gateValue = {
name: gateName,
value: this.overrides.gates[gateName],
rule_id: 'override',
secondary_exposures: [],
};
details = this.getEvaluationDetails(false, EvaluationReason_1.EvaluationReason.LocalOverride);
}
else {
var value = (_a = this.userValues) === null || _a === void 0 ? void 0 : _a.feature_gates[gateNameHash];
if (value) {
gateValue = value;
}
details = this.getEvaluationDetails(value != null);
}
return { evaluationDetails: details, gate: gateValue };
};
StatsigStore.prototype.getConfig = function (configName, ignoreOverrides) {
var _a, _b;
if (ignoreOverrides === void 0) { ignoreOverrides = false; }
var configNameHash = this.getHashedSpecName(configName);
var configValue;
var details;
if (!ignoreOverrides && this.overrides.configs[configName] != null) {
details = this.getEvaluationDetails(false, EvaluationReason_1.EvaluationReason.LocalOverride);
configValue = new DynamicConfig_1.default(configName, this.overrides.configs[configName], 'override', details, [], '', this.makeOnConfigDefaultValueFallback(this.sdkInternal.getCurrentUser()));
}
else if (((_a = this.userValues) === null || _a === void 0 ? void 0 : _a.dynamic_configs[configNameHash]) != null) {
var rawConfigValue = (_b = this.userValues) === null || _b === void 0 ? void 0 : _b.dynamic_configs[configNameHash];
details = this.getEvaluationDetails(true);
configValue = this.createDynamicConfig(configName, rawConfigValue, details);
}
else {
details = this.getEvaluationDetails(false);
configValue = new DynamicConfig_1.default(configName, {}, '', details);
}
return configValue;
};
StatsigStore.prototype.getExperiment = function (expName, keepDeviceValue, ignoreOverrides) {
if (keepDeviceValue === void 0) { keepDeviceValue = false; }
if (ignoreOverrides === void 0) { ignoreOverrides = false; }
var exp;
var details;
if (!ignoreOverrides && this.overrides.configs[expName] != null) {
details = this.getEvaluationDetails(false, EvaluationReason_1.EvaluationReason.LocalOverride);
exp = new DynamicConfig_1.default(expName, this.overrides.configs[expName], 'override', details);
}
else {
var latestValue = this.getLatestValue(expName, 'dynamic_configs');
details = this.getEvaluationDetails(latestValue != null);
var finalValue = this.getPossiblyStickyValue(expName, latestValue, keepDeviceValue, false /* isLayer */, details);
exp = this.createDynamicConfig(expName, finalValue, details);
}
return exp;
};
StatsigStore.prototype.getLayer = function (logParameterFunction, layerName, keepDeviceValue) {
var _a, _b, _c, _d;
if (this.overrides.layers[layerName] != null) {
var details_1 = this.getEvaluationDetails(false, EvaluationReason_1.EvaluationReason.LocalOverride);
return Layer_1.default._create(layerName, (_a = this.overrides.layers[layerName]) !== null && _a !== void 0 ? _a : {}, 'override', details_1, logParameterFunction);
}
var latestValue = this.getLatestValue(layerName, 'layer_configs');
var details = this.getEvaluationDetails(latestValue != null);
var finalValue = this.getPossiblyStickyValue(layerName, latestValue, keepDeviceValue, true /* isLayer */, details);
return Layer_1.default._create(layerName, (_b = finalValue === null || finalValue === void 0 ? void 0 : finalValue.value) !== null && _b !== void 0 ? _b : {}, (_c = finalValue === null || finalValue === void 0 ? void 0 : finalValue.rule_id) !== null && _c !== void 0 ? _c : '', details, logParameterFunction, finalValue === null || finalValue === void 0 ? void 0 : finalValue.secondary_exposures, finalValue === null || finalValue === void 0 ? void 0 : finalValue.undelegated_secondary_exposures, (_d = finalValue === null || finalValue === void 0 ? void 0 : finalValue.allocated_experiment_name) !== null && _d !== void 0 ? _d : '', finalValue === null || finalValue === void 0 ? void 0 : finalValue.explicit_parameters, finalValue === null || finalValue === void 0 ? void 0 : finalValue.group_name);
};
StatsigStore.prototype.overrideConfig = function (configName, value) {
try {
JSON.stringify(value);
}
catch (e) {
console.warn('Failed to stringify given config override. Dropping', e);
return;
}
this.overrides.configs[configName] = value;
this.saveOverrides();
};
StatsigStore.prototype.overrideLayer = function (layerName, value) {
try {
JSON.stringify(value);
}
catch (e) {
console.warn('Failed to stringify given layer override. Dropping', e);
return;
}
this.overrides.layers[layerName] = value;
this.saveOverrides();
};
StatsigStore.prototype.overrideGate = function (gateName, value) {
this.overrides.gates[gateName] = value;
this.saveOverrides();
};
StatsigStore.prototype.removeGateOverride = function (gateName) {
if (gateName == null) {
this.overrides.gates = {};
}
else {
delete this.overrides.gates[gateName];
}
this.saveOverrides();
};
StatsigStore.prototype.removeConfigOverride = function (configName) {
if (configName == null) {
this.overrides.configs = {};
}
else {
delete this.overrides.configs[configName];
}
this.saveOverrides();
};
StatsigStore.prototype.removeLayerOverride = function (layerName) {
if (layerName == null) {
this.overrides.layers = {};
}
else {
delete this.overrides.layers[layerName];
}
this.saveOverrides();
};
StatsigStore.prototype.getAllOverrides = function () {
return this.overrides;
};
StatsigStore.prototype.saveOverrides = function () {
try {
StatsigLocalStorage_1.default.setItem(Constants_1.OVERRIDES_STORE_KEY, JSON.stringify(this.overrides));
}
catch (e) {
console.warn('Failed to persist gate/config overrides');
}
};
StatsigStore.prototype.getLatestValue = function (name, topLevelKey) {
var _a, _b, _c, _d, _f;
var hash = this.getHashedSpecName(name);
return ((_c = (_b = (_a = this.userValues) === null || _a === void 0 ? void 0 : _a[topLevelKey]) === null || _b === void 0 ? void 0 : _b[hash]) !== null && _c !== void 0 ? _c : (_f = (_d = this.userValues) === null || _d === void 0 ? void 0 : _d[topLevelKey]) === null || _f === void 0 ? void 0 : _f[name]);
};
// Sticky Logic: https://gist.github.com/daniel-statsig/3d8dfc9bdee531cffc96901c1a06a402
StatsigStore.prototype.getPossiblyStickyValue = function (name, latestValue, keepDeviceValue, isLayer, details) {
var _a;
var key = this.getHashedSpecName(name);
// We don't want sticky behavior. Clear any sticky values and return latest.
if (!keepDeviceValue) {
this.removeStickyValue(key);
return latestValue;
}
// If there is no sticky value, save latest as sticky and return latest.
var stickyValue = this.getStickyValue(key);
if (!stickyValue) {
this.attemptToSaveStickyValue(key, latestValue);
return latestValue;
}
// Get the latest config value. Layers require a lookup by allocated_experiment_name.
var latestExperimentValue = null;
if (isLayer) {
latestExperimentValue = this.getLatestValue((_a = stickyValue === null || stickyValue === void 0 ? void 0 : stickyValue.allocated_experiment_name) !== null && _a !== void 0 ? _a : '', 'dynamic_configs');
}
else {
latestExperimentValue = latestValue;
}
if ((latestExperimentValue === null || latestExperimentValue === void 0 ? void 0 : latestExperimentValue.is_experiment_active) == true) {
details.reason = EvaluationReason_1.EvaluationReason.Sticky;
return stickyValue;
}
if ((latestValue === null || latestValue === void 0 ? void 0 : latestValue.is_experiment_active) == true) {
this.attemptToSaveStickyValue(key, latestValue);
}
else {
this.removeStickyValue(key);
}
return latestValue;
};
StatsigStore.prototype.createDynamicConfig = function (name, apiConfig, details) {
var _a, _b, _c;
return new DynamicConfig_1.default(name, (_a = apiConfig === null || apiConfig === void 0 ? void 0 : apiConfig.value) !== null && _a !== void 0 ? _a : {}, (_b = apiConfig === null || apiConfig === void 0 ? void 0 : apiConfig.rule_id) !== null && _b !== void 0 ? _b : '', details, apiConfig === null || apiConfig === void 0 ? void 0 : apiConfig.secondary_exposures, (_c = apiConfig === null || apiConfig === void 0 ? void 0 : apiConfig.allocated_experiment_name) !== null && _c !== void 0 ? _c : '', this.makeOnConfigDefaultValueFallback(this.sdkInternal.getCurrentUser()), apiConfig === null || apiConfig === void 0 ? void 0 : apiConfig.group_name, apiConfig === null || apiConfig === void 0 ? void 0 : apiConfig.id_type, apiConfig === null || apiConfig === void 0 ? void 0 : apiConfig.is_experiment_active);
};
StatsigStore.prototype.getStickyValue = function (key) {
var _a, _b;
return ((_b = (_a = this.userValues) === null || _a === void 0 ? void 0 : _a.sticky_experiments[key]) !== null && _b !== void 0 ? _b : this.stickyDeviceExperiments[key]);
};
StatsigStore.prototype.attemptToSaveStickyValue = function (key, config) {
var _a;
if (!config ||
!config.is_user_in_experiment ||
!config.is_experiment_active) {
return;
}
if (config.is_device_based === true) {
// save sticky values in memory
this.stickyDeviceExperiments[key] = config;
}
else if ((_a = this.userValues) === null || _a === void 0 ? void 0 : _a.sticky_experiments) {
this.userValues.sticky_experiments[key] = config;
}
// also save to persistent storage
this.saveStickyValuesToStorage();
};
StatsigStore.prototype.removeStickyValue = function (key) {
var _a, _b, _c, _d;
if (Object.keys((_b = (_a = this.userValues) === null || _a === void 0 ? void 0 : _a.sticky_experiments) !== null && _b !== void 0 ? _b : {}).length === 0 &&
Object.keys((_c = this.stickyDeviceExperiments) !== null && _c !== void 0 ? _c : {}).length === 0) {
return;
}
(_d = this.userValues) === null || _d === void 0 ? true : delete _d.sticky_experiments[key];
delete this.stickyDeviceExperiments[key];
this.saveStickyValuesToStorage();
};
StatsigStore.prototype.saveStickyValuesToStorage = function () {
if (this.userPersistentStorageAdapter) {
this.saveStickyExperimentsToPersistentStorageAdapter();
}
else {
this.values[this.userCacheKey.v3] = this.userValues;
this.setItemToStorage(Constants_1.INTERNAL_STORE_KEY, JSON.stringify(this.values));
this.setItemToStorage(Constants_1.STICKY_DEVICE_EXPERIMENTS_KEY, JSON.stringify(this.stickyDeviceExperiments));
}
};
StatsigStore.prototype.getGlobalEvaluationDetails = function () {
var _a, _b;
return {
reason: (_a = this.reason) !== null && _a !== void 0 ? _a : EvaluationReason_1.EvaluationReason.Uninitialized,
time: (_b = this.userValues.evaluation_time) !== null && _b !== void 0 ? _b : 0,
};
};
StatsigStore.prototype.getEvaluationDetails = function (valueExists, reasonOverride) {
var _a;
if (valueExists) {
return {
reason: this.reason,
time: (_a = this.userValues.evaluation_time) !== null && _a !== void 0 ? _a : Date.now(),
};
}
else {
return {
reason: reasonOverride !== null && reasonOverride !== void 0 ? reasonOverride : (this.reason == EvaluationReason_1.EvaluationReason.Uninitialized
? EvaluationReason_1.EvaluationReason.Uninitialized
: EvaluationReason_1.EvaluationReason.Unrecognized),
time: Date.now(),
};
}
};
StatsigStore.prototype.resetUserValues = function () {
this.userValues = {
feature_gates: {},
dynamic_configs: {},
sticky_experiments: {},
layer_configs: {},
time: 0,
evaluation_time: 0,
derived_fields: {},
};
};
StatsigStore.prototype.getHashedSpecName = function (input) {
switch (this.userValues.hash_used) {
case 'djb2':
return (0, Hashing_1.djb2Hash)(input);
case 'none':
return input;
default:
return (0, Hashing_1.sha256Hash)(input);
}
};
StatsigStore.prototype.convertAPIDataToCacheValues = function (data, cacheKey) {
var _a, _b;
// Specifically pulling keys from data here to avoid pulling in unwanted keys
return {
feature_gates: data.feature_gates,
layer_configs: data.layer_configs,
dynamic_configs: data.dynamic_configs,
sticky_experiments: (_b = (_a = this.values[cacheKey]) === null || _a === void 0 ? void 0 : _a.sticky_experiments) !== null && _b !== void 0 ? _b : {},
time: data.time == null || isNaN(data.time) ? 0 : data.time,
evaluation_time: Date.now(),
hash_used: data.hash_used,
derived_fields: data.derived_fields,
};
};
StatsigStore.prototype.setItemToStorage = function (key, value) {
var _this = this;
if (StatsigAsyncStorage_1.default.asyncStorage) {
StatsigAsyncStorage_1.default.setItemAsync(key, value).catch(function (reason) {
void _this.sdkInternal
.getErrorBoundary()
.logError('setItemToStorage', reason);
});
}
else {
StatsigLocalStorage_1.default.setItem(key, value);
}
};
StatsigStore.prototype.makeOnConfigDefaultValueFallback = function (user) {
var _this = this;
return function (config, parameter, defaultValueType, valueType) {
if (!_this.isLoaded()) {
return;
}
_this.sdkInternal.getLogger().logConfigDefaultValueFallback(user, "Parameter " + parameter + " is a value of type " + valueType + ".\n Returning requested defaultValue type " + defaultValueType, {
name: config.getName(),
ruleID: config.getRuleID(),
parameter: parameter,
defaultValueType: defaultValueType,
valueType: valueType,
});
};
};
return StatsigStore;
}());
exports.default = StatsigStore;
function removeDeletedKeysFromUserValues(initResponse, userValues) {
var _a, _b, _c;
((_a = initResponse.deleted_configs) !== null && _a !== void 0 ? _a : []).forEach(function (key) {
delete userValues.dynamic_configs[key];
});
((_b = initResponse.deleted_gates) !== null && _b !== void 0 ? _b : []).forEach(function (key) {
delete userValues.feature_gates[key];
});
((_c = initResponse.deleted_layers) !== null && _c !== void 0 ? _c : []).forEach(function (key) {
delete userValues.layer_configs[key];
});
}