myfflags-sdk
Version:
SDK cliente para o SaaS de My F* Flags
164 lines (159 loc) • 5.42 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var CryptoJS = require('crypto-js');
/**
* MFFS (My F* Flags) SDK Class
* Classe principal para interagir com o sistema de flags
*/
class MFFS {
constructor(config) {
this.cache = new Map();
this.config = {
environment: 'production',
cacheTimeout: 5 * 60 * 1000, // 5 minutos
...config
};
}
/**
* Verifica se uma feature flag está habilitada para um usuário
*/
async isFeatureEnabled(flagName, userContext) {
try {
const flags = await this.getFlags();
const flag = flags.find(f => f.name === flagName);
if (!flag || !flag.enabled) {
return false;
}
// Verificar se está habilitada para o ambiente atual
if (!flag.environments[this.config.environment]) {
return false;
}
// Aplicar regras de targeting
if (!this.evaluateTargetingRules(flag.targetingRules, userContext)) {
return false;
}
// Aplicar rollout percentual usando hash determinístico
return this.evaluateRolloutPercentage(flag, userContext.userId);
}
catch (error) {
console.error('Error evaluating feature flag:', error);
return false;
}
}
/**
* Busca todas as flags habilitadas para um usuário
*/
async getEnabledFeatures(userContext) {
try {
const flags = await this.getFlags();
const enabledFlags = [];
for (const flag of flags) {
if (await this.isFeatureEnabled(flag.name, userContext)) {
enabledFlags.push(flag.name);
}
}
return enabledFlags;
}
catch (error) {
console.error('Error getting enabled features:', error);
return [];
}
}
/**
* Busca flags do servidor (com cache)
*/
async getFlags() {
const cacheKey = this.config.environment;
const cached = this.cache.get(cacheKey);
// Verificar se o cache ainda é válido
if (cached && Date.now() - cached.timestamp < this.config.cacheTimeout) {
return cached.flags;
}
try {
const response = await fetch(`${this.config.apiUrl}/api/flags/public/${this.config.environment}`, {
headers: this.config.apiKey ? {
'Authorization': `Bearer ${this.config.apiKey}`
} : {}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
const flags = data.flags || [];
// Atualizar cache
this.cache.set(cacheKey, {
flags,
timestamp: Date.now()
});
return flags;
}
catch (error) {
console.error('Error fetching flags:', error);
// Retornar cache anterior se disponível
if (cached) {
return cached.flags;
}
return [];
}
}
/**
* Avalia regras de targeting
*/
evaluateTargetingRules(rules, userContext) {
if (rules.length === 0) {
return true;
}
return rules.every(rule => {
const userValue = userContext[rule.attribute];
if (userValue === undefined) {
return false;
}
const userValueStr = String(userValue);
const ruleValueStr = rule.value;
switch (rule.operator) {
case 'equals':
return userValueStr === ruleValueStr;
case 'not_equals':
return userValueStr !== ruleValueStr;
case 'contains':
return userValueStr.toLowerCase().includes(ruleValueStr.toLowerCase());
case 'not_contains':
return !userValueStr.toLowerCase().includes(ruleValueStr.toLowerCase());
case 'greater_than':
return parseFloat(userValueStr) > parseFloat(ruleValueStr);
case 'less_than':
return parseFloat(userValueStr) < parseFloat(ruleValueStr);
default:
return false;
}
});
}
/**
* Avalia rollout percentual usando hash determinístico
*/
evaluateRolloutPercentage(flag, userId) {
// Criar hash determinístico baseado no userId + flagName
const hashInput = `${userId}:${flag.name}`;
const hash = CryptoJS.MD5(hashInput).toString();
// Converter os primeiros 8 caracteres do hash em número
const hashNumber = parseInt(hash.substring(0, 8), 16);
const percentage = hashNumber % 100;
return percentage < flag.rolloutPercentage;
}
/**
* Limpa o cache
*/
clearCache() {
this.cache.clear();
}
/**
* Atualiza a configuração
*/
updateConfig(newConfig) {
this.config = { ...this.config, ...newConfig };
this.clearCache();
}
}
exports.MFFS = MFFS;
exports.default = MFFS;
//# sourceMappingURL=index.js.map