unleash-client
Version:
Unleash Client for Node
189 lines • 8.39 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const events_1 = require("events");
const variant_1 = require("./variant");
const events_2 = require("./events");
class UnleashClient extends events_1.EventEmitter {
constructor(repository, strategies) {
super();
this.repository = repository;
this.strategies = strategies || [];
this.warnedStrategies = {};
this.warnedDependencies = {};
this.strategies.forEach((strategy) => {
if (!strategy ||
!strategy.name ||
!strategy.isEnabled ||
typeof strategy.isEnabled !== 'function') {
throw new Error('Invalid strategy data / interface');
}
});
}
getStrategy(name) {
return this.strategies.find((strategy) => strategy.name === name);
}
warnStrategyOnce(missingStrategy, name, strategies) {
if (!this.warnedStrategies[missingStrategy + name]) {
this.warnedStrategies[missingStrategy + name] = true;
this.emit(events_2.UnleashEvents.Warn, `Missing strategy "${missingStrategy}" for toggle "${name}". Ensure that "${strategies
.map(({ name: n }) => n)
.join(', ')}" are supported before using this toggle`);
}
}
warnDependencyOnce(missingDependency, name) {
if (!this.warnedDependencies[missingDependency + name]) {
this.warnedDependencies[missingDependency + name] = true;
this.emit(events_2.UnleashEvents.Warn, `Missing dependency "${missingDependency}" for toggle "${name}"`);
}
}
isParentDependencySatisfied(feature, context) {
var _a;
if (!((_a = feature === null || feature === void 0 ? void 0 : feature.dependencies) === null || _a === void 0 ? void 0 : _a.length)) {
return true;
}
return feature.dependencies.every((parent) => {
var _a, _b;
const parentToggle = this.repository.getToggle(parent.feature);
if (!parentToggle) {
this.warnDependencyOnce(parent.feature, feature.name);
return false;
}
if ((_a = parentToggle.dependencies) === null || _a === void 0 ? void 0 : _a.length) {
return false;
}
if (parent.enabled !== false) {
if ((_b = parent.variants) === null || _b === void 0 ? void 0 : _b.length) {
const { name, feature_enabled: featureEnabled } = this.getVariant(parent.feature, context);
return featureEnabled && parent.variants.includes(name);
}
return this.isEnabled(parent.feature, context, () => false);
}
return !this.isEnabled(parent.feature, context, () => false);
});
}
isEnabled(name, context, fallback) {
const feature = this.repository.getToggle(name);
const enabled = this.isFeatureEnabled(feature, context, fallback).enabled;
if (feature === null || feature === void 0 ? void 0 : feature.impressionData) {
this.emit(events_2.UnleashEvents.Impression, (0, events_2.createImpressionEvent)({
featureName: name,
context,
enabled,
eventType: 'isEnabled',
}));
}
return enabled;
}
isFeatureEnabled(feature, context, fallback) {
var _a;
if (!feature) {
return { enabled: fallback() };
}
if (!feature || !this.isParentDependencySatisfied(feature, context) || !feature.enabled) {
return { enabled: false };
}
if (!Array.isArray(feature.strategies)) {
const msg = `Malformed feature, strategies not an array, is a ${typeof feature.strategies}`;
this.emit(events_2.UnleashEvents.Error, new Error(msg));
return { enabled: false };
}
if (feature.strategies.length === 0) {
return { enabled: feature.enabled };
}
let strategyResult = { enabled: false };
(_a = feature.strategies) === null || _a === void 0 ? void 0 : _a.some((strategySelector) => {
const strategy = this.getStrategy(strategySelector.name);
if (!strategy) {
this.warnStrategyOnce(strategySelector.name, feature.name, feature.strategies || []);
return false;
}
const constraints = this.yieldConstraintsFor(strategySelector);
const result = strategy.getResult(strategySelector.parameters, context, constraints, strategySelector.variants);
if (result.enabled) {
strategyResult = result;
return true;
}
return false;
});
return strategyResult;
}
*yieldConstraintsFor(strategy) {
var _a;
if (strategy.constraints) {
yield* strategy.constraints;
}
const segments = (_a = strategy.segments) === null || _a === void 0 ? void 0 : _a.map((segmentId) => this.repository.getSegment(segmentId));
if (!segments) {
return;
}
yield* this.yieldSegmentConstraints(segments);
}
*yieldSegmentConstraints(segments) {
// eslint-disable-next-line no-restricted-syntax
for (const segment of segments) {
if (segment) {
// eslint-disable-next-line no-restricted-syntax
for (const constraint of segment === null || segment === void 0 ? void 0 : segment.constraints) {
yield constraint;
}
}
else {
yield undefined;
}
}
}
getVariant(name, context, fallbackVariant) {
const feature = this.repository.getToggle(name);
const variant = this.resolveVariant(feature, context, true, fallbackVariant);
if (feature === null || feature === void 0 ? void 0 : feature.impressionData) {
this.emit(events_2.UnleashEvents.Impression, (0, events_2.createImpressionEvent)({
featureName: name,
context,
enabled: variant.enabled,
eventType: 'getVariant',
variant: variant.name,
}));
}
return variant;
}
// This function is intended to close an issue in the proxy where feature enabled
// state gets checked twice when resolving a variant with random stickiness and
// gradual rollout. This is not intended for general use, prefer getVariant instead
forceGetVariant(name, context, fallbackVariant) {
const feature = this.repository.getToggle(name);
return this.resolveVariant(feature, context, true, fallbackVariant);
}
resolveVariant(feature, context, checkToggle, fallbackVariant) {
const fallback = fallbackVariant || variant_1.defaultVariant;
if (typeof feature === 'undefined') {
return { ...fallback, feature_enabled: false, featureEnabled: false };
}
let featureEnabled = !checkToggle;
if (checkToggle) {
const result = this.isFeatureEnabled(feature, context, () => !!(fallbackVariant === null || fallbackVariant === void 0 ? void 0 : fallbackVariant.enabled));
featureEnabled = result.enabled;
if (result.enabled && result.variant) {
return { ...result.variant, feature_enabled: featureEnabled, featureEnabled };
}
if (!result.enabled) {
return { ...fallback, feature_enabled: featureEnabled, featureEnabled };
}
}
if (!feature.variants || !Array.isArray(feature.variants) || feature.variants.length === 0) {
return { ...fallback, feature_enabled: featureEnabled, featureEnabled };
}
const variant = (0, variant_1.selectVariant)(feature, context);
if (variant === null) {
return { ...fallback, feature_enabled: featureEnabled, featureEnabled };
}
return {
name: variant.name,
payload: variant.payload,
enabled: true,
feature_enabled: featureEnabled,
featureEnabled,
};
}
}
exports.default = UnleashClient;
//# sourceMappingURL=client.js.map