UNPKG

statsig-js

Version:

Statsig JavaScript client SDK for single user environments.

926 lines (925 loc) 48.7 kB
"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]; }); }