@splitsoftware/splitio-commons
Version:
Split JavaScript SDK common components
180 lines (179 loc) • 10.3 kB
JavaScript
import { evaluateFeature, evaluateFeatures, evaluateFeaturesByFlagSets } from '../evaluator';
import { thenable } from '../utils/promise/thenable';
import { getMatching, getBucketing } from '../utils/key';
import { validateSplitExistence } from '../utils/inputValidation/splitExistence';
import { validateTrafficTypeExistence } from '../utils/inputValidation/trafficTypeExistence';
import { SDK_NOT_READY } from '../utils/labels';
import { CONTROL, TREATMENT, TREATMENTS, TREATMENT_WITH_CONFIG, TREATMENTS_WITH_CONFIG, TRACK, TREATMENTS_WITH_CONFIG_BY_FLAGSETS, TREATMENTS_BY_FLAGSETS, TREATMENTS_BY_FLAGSET, TREATMENTS_WITH_CONFIG_BY_FLAGSET, GET_TREATMENTS_WITH_CONFIG, GET_TREATMENTS_BY_FLAG_SETS, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, GET_TREATMENTS_BY_FLAG_SET, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, GET_TREATMENT_WITH_CONFIG, GET_TREATMENT, GET_TREATMENTS, TRACK_FN_LABEL } from '../utils/constants';
import { IMPRESSION, IMPRESSION_QUEUEING } from '../logger/constants';
import { isConsumerMode } from '../utils/settingsValidation/mode';
var treatmentNotReady = { treatment: CONTROL, label: SDK_NOT_READY };
function treatmentsNotReady(featureFlagNames) {
var evaluations = {};
featureFlagNames.forEach(function (featureFlagName) {
evaluations[featureFlagName] = treatmentNotReady;
});
return evaluations;
}
function stringify(options) {
if (options && options.properties) {
try {
return JSON.stringify(options.properties);
}
catch ( /* JSON.stringify should never throw with validated options, but handling just in case */_a) { /* JSON.stringify should never throw with validated options, but handling just in case */ }
}
}
/**
* Creator of base client with getTreatments and track methods.
*/
export function clientFactory(params) {
var readinessManager = params.sdkReadinessManager.readinessManager, storage = params.storage, settings = params.settings, impressionsTracker = params.impressionsTracker, eventTracker = params.eventTracker, telemetryTracker = params.telemetryTracker;
var log = settings.log, mode = settings.mode;
var isAsync = isConsumerMode(mode);
function getTreatment(key, featureFlagName, attributes, options, withConfig, methodName) {
if (withConfig === void 0) { withConfig = false; }
if (methodName === void 0) { methodName = GET_TREATMENT; }
var stopTelemetryTracker = telemetryTracker.trackEval(withConfig ? TREATMENT_WITH_CONFIG : TREATMENT);
var wrapUp = function (evaluationResult) {
var queue = [];
var treatment = processEvaluation(evaluationResult, featureFlagName, key, stringify(options), withConfig, methodName, queue);
impressionsTracker.track(queue, attributes);
stopTelemetryTracker(queue[0] && queue[0].imp.label);
return treatment;
};
var evaluation = readinessManager.isReady() || readinessManager.isReadyFromCache() ?
evaluateFeature(log, key, featureFlagName, attributes, storage) :
isAsync ? // If the SDK is not ready, treatment may be incorrect due to having splits but not segments data, or storage is not connected
Promise.resolve(treatmentNotReady) :
treatmentNotReady;
return thenable(evaluation) ? evaluation.then(function (res) { return wrapUp(res); }) : wrapUp(evaluation);
}
function getTreatmentWithConfig(key, featureFlagName, attributes, options) {
return getTreatment(key, featureFlagName, attributes, options, true, GET_TREATMENT_WITH_CONFIG);
}
function getTreatments(key, featureFlagNames, attributes, options, withConfig, methodName) {
if (withConfig === void 0) { withConfig = false; }
if (methodName === void 0) { methodName = GET_TREATMENTS; }
var stopTelemetryTracker = telemetryTracker.trackEval(withConfig ? TREATMENTS_WITH_CONFIG : TREATMENTS);
var wrapUp = function (evaluationResults) {
var queue = [];
var treatments = {};
var properties = stringify(options);
Object.keys(evaluationResults).forEach(function (featureFlagName) {
treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key, properties, withConfig, methodName, queue);
});
impressionsTracker.track(queue, attributes);
stopTelemetryTracker(queue[0] && queue[0].imp.label);
return treatments;
};
var evaluations = readinessManager.isReady() || readinessManager.isReadyFromCache() ?
evaluateFeatures(log, key, featureFlagNames, attributes, storage) :
isAsync ? // If the SDK is not ready, treatment may be incorrect due to having splits but not segments data, or storage is not connected
Promise.resolve(treatmentsNotReady(featureFlagNames)) :
treatmentsNotReady(featureFlagNames);
return thenable(evaluations) ? evaluations.then(function (res) { return wrapUp(res); }) : wrapUp(evaluations);
}
function getTreatmentsWithConfig(key, featureFlagNames, attributes, options) {
return getTreatments(key, featureFlagNames, attributes, options, true, GET_TREATMENTS_WITH_CONFIG);
}
function getTreatmentsByFlagSets(key, flagSetNames, attributes, options, withConfig, method, methodName) {
if (withConfig === void 0) { withConfig = false; }
if (method === void 0) { method = TREATMENTS_BY_FLAGSETS; }
if (methodName === void 0) { methodName = GET_TREATMENTS_BY_FLAG_SETS; }
var stopTelemetryTracker = telemetryTracker.trackEval(method);
var wrapUp = function (evaluationResults) {
var queue = [];
var treatments = {};
var properties = stringify(options);
Object.keys(evaluationResults).forEach(function (featureFlagName) {
treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key, properties, withConfig, methodName, queue);
});
impressionsTracker.track(queue, attributes);
stopTelemetryTracker(queue[0] && queue[0].imp.label);
return treatments;
};
var evaluations = readinessManager.isReady() || readinessManager.isReadyFromCache() ?
evaluateFeaturesByFlagSets(log, key, flagSetNames, attributes, storage, methodName) :
isAsync ?
Promise.resolve({}) :
{};
return thenable(evaluations) ? evaluations.then(function (res) { return wrapUp(res); }) : wrapUp(evaluations);
}
function getTreatmentsWithConfigByFlagSets(key, flagSetNames, attributes, options) {
return getTreatmentsByFlagSets(key, flagSetNames, attributes, options, true, TREATMENTS_WITH_CONFIG_BY_FLAGSETS, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS);
}
function getTreatmentsByFlagSet(key, flagSetName, attributes, options) {
return getTreatmentsByFlagSets(key, [flagSetName], attributes, options, false, TREATMENTS_BY_FLAGSET, GET_TREATMENTS_BY_FLAG_SET);
}
function getTreatmentsWithConfigByFlagSet(key, flagSetName, attributes, options) {
return getTreatmentsByFlagSets(key, [flagSetName], attributes, options, true, TREATMENTS_WITH_CONFIG_BY_FLAGSET, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET);
}
// Internal function
function processEvaluation(evaluation, featureFlagName, key, properties, withConfig, invokingMethodName, queue) {
var matchingKey = getMatching(key);
var bucketingKey = getBucketing(key);
var treatment = evaluation.treatment, label = evaluation.label, changeNumber = evaluation.changeNumber, _a = evaluation.config, config = _a === void 0 ? null : _a, impressionsDisabled = evaluation.impressionsDisabled;
log.info(IMPRESSION, [featureFlagName, matchingKey, treatment, label]);
if (validateSplitExistence(log, readinessManager, featureFlagName, label, invokingMethodName)) {
log.info(IMPRESSION_QUEUEING);
queue.push({
imp: {
feature: featureFlagName,
keyName: matchingKey,
treatment: treatment,
time: Date.now(),
bucketingKey: bucketingKey,
label: label,
changeNumber: changeNumber,
properties: properties
},
disabled: impressionsDisabled
});
}
if (withConfig) {
return {
treatment: treatment,
config: config
};
}
return treatment;
}
function track(key, trafficTypeName, eventTypeId, value, properties, size) {
if (size === void 0) { size = 1024; }
var stopTelemetryTracker = telemetryTracker.trackEval(TRACK);
var matchingKey = getMatching(key);
var timestamp = Date.now();
var eventData = {
eventTypeId: eventTypeId,
trafficTypeName: trafficTypeName,
value: value,
timestamp: timestamp,
key: matchingKey,
properties: properties
};
// This may be async but we only warn, we don't actually care if it is valid or not in terms of queueing the event.
validateTrafficTypeExistence(log, readinessManager, storage.splits, mode, trafficTypeName, TRACK_FN_LABEL);
var result = eventTracker.track(eventData, size);
if (thenable(result)) {
return result.then(function (result) {
stopTelemetryTracker();
return result;
});
}
else {
stopTelemetryTracker();
return result;
}
}
return {
getTreatment: getTreatment,
getTreatmentWithConfig: getTreatmentWithConfig,
getTreatments: getTreatments,
getTreatmentsWithConfig: getTreatmentsWithConfig,
getTreatmentsByFlagSets: getTreatmentsByFlagSets,
getTreatmentsWithConfigByFlagSets: getTreatmentsWithConfigByFlagSets,
getTreatmentsByFlagSet: getTreatmentsByFlagSet,
getTreatmentsWithConfigByFlagSet: getTreatmentsWithConfigByFlagSet,
track: track,
};
}