eehitus-feature-flag-client
Version:
A feature flag client for Node.js and React
176 lines (175 loc) • 6.8 kB
JavaScript
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());
});
};
import axios from "axios";
// Use consistent import pattern for the axiosInstance
let axiosInstance;
function loadAxios() {
try {
if (typeof window === "undefined" || typeof require !== "undefined") {
// Server-side or environments where require is available
const axiosModule = axios;
return axiosModule.create();
}
else {
// Client-side with ES modules
return axios.create();
}
}
catch (error) {
throw new Error("Axios is a peer dependency and is missing. Please install it by running:\n\n" +
" yarn add axios\n\n" +
"or\n\n" +
" npm install axios\n\n");
}
}
// Initialize the axiosInstance
axiosInstance = loadAxios();
export class FeatureFlagClient {
constructor(url, appId, environment, options = {}) {
var _a, _b;
this.featureFlags = {};
this.intervalId = null;
this.isBrowser = typeof window !== "undefined";
this.isLoading = false;
this.hasLoaded = false;
this.url = url;
this.appId = appId;
this.environment = environment;
// Set options with defaults
this.enableBrowserPolling = (_a = options.enableBrowserPolling) !== null && _a !== void 0 ? _a : false;
this.pollingInterval = (_b = options.pollingInterval) !== null && _b !== void 0 ? _b : 60000; // Default to 1 minute
if (!this.isBrowser) {
// For server, immediately fetch and set up polling
this.fetchFeatureFlags();
this.intervalId = setInterval(() => this.fetchFeatureFlags(), this.pollingInterval);
}
else {
// For browser, fetch once at initialization
this.fetchOnce();
// Set up polling for browser if enabled
if (this.enableBrowserPolling) {
this.startPolling();
}
}
}
fetchFeatureFlags() {
return __awaiter(this, void 0, void 0, function* () {
// Set loading state
this.isLoading = true;
try {
// Use the new /flags/active endpoint
const activeEndpoint = `${this.url}/flags/active`;
console.log("Fetching flags from:", activeEndpoint);
const response = yield axiosInstance.get(activeEndpoint, {
params: {
app: this.appId,
environment: this.environment,
},
});
const newFlags = {};
response.data.forEach((flag) => {
newFlags[flag.name] = true; // All returned flags are active
});
// Update the flags
this.featureFlags = newFlags;
// Log the fetched flags for debugging
console.log("Fetched flags:", this.featureFlags);
// Update loading state
this.isLoading = false;
this.hasLoaded = true;
return this.featureFlags;
}
catch (error) {
console.error("Error fetching feature flags:", error);
this.isLoading = false;
return this.featureFlags; // Return current flags on error
}
});
}
fetchOnce() {
return __awaiter(this, void 0, void 0, function* () {
// Only fetch if we haven't loaded before
if (!this.hasLoaded && !this.isLoading) {
return this.fetchFeatureFlags();
}
else if (this.isLoading) {
// Wait for current fetch to finish
yield new Promise((resolve) => {
const checkLoaded = () => {
if (!this.isLoading) {
resolve(true);
}
else {
setTimeout(checkLoaded, 50);
}
};
checkLoaded();
});
}
return this.featureFlags;
});
}
fetchFresh() {
return __awaiter(this, void 0, void 0, function* () {
return this.fetchFeatureFlags();
});
}
ensureFlagsLoaded() {
return __awaiter(this, void 0, void 0, function* () {
if (!this.hasLoaded) {
yield this.fetchOnce();
}
});
}
flagEnabled(flagName) {
var _a;
// Debug logging
console.log("Checking flag:", flagName, "Current flags:", this.featureFlags);
// Return flag value or false if not found
return Boolean((_a = this.featureFlags[flagName]) !== null && _a !== void 0 ? _a : false);
}
getFlagEnabledAsync(flagName) {
return __awaiter(this, void 0, void 0, function* () {
// Ensure flags are loaded before checking
yield this.ensureFlagsLoaded();
return this.flagEnabled(flagName);
});
}
stopFetching() {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
console.log('Feature flag polling stopped');
}
}
startPolling(interval) {
// Stop any existing interval
this.stopFetching();
// Update interval if provided
if (interval) {
this.pollingInterval = interval;
}
// Start new interval
this.intervalId = setInterval(() => this.fetchFeatureFlags(), this.pollingInterval);
console.log(`Feature flag polling started with interval: ${this.pollingInterval}ms`);
}
// Add method to check if polling is active
isPollingActive() {
return this.intervalId !== null;
}
// Add method to update polling interval
setPollingInterval(interval) {
this.pollingInterval = interval;
// If polling is active, restart with new interval
if (this.isPollingActive()) {
this.startPolling();
}
}
}