flagsmith-nodejs
Version:
Flagsmith lets you manage features flags and remote config across web, mobile and server side applications. Deliver true Continuous Integration. Get builds out faster. Control who has access to new features.
78 lines (77 loc) • 2.94 kB
JavaScript
import { pino } from 'pino';
import { getUserAgent } from './utils.js';
export const ANALYTICS_ENDPOINT = './analytics/flags/';
/** Duration in seconds to wait before trying to flush collected data after {@link trackFeature} is called. **/
const ANALYTICS_TIMER = 10;
const DEFAULT_REQUEST_TIMEOUT_MS = 3000;
/**
* Tracks how often individual features are evaluated whenever {@link trackFeature} is called.
*
* Analytics data is posted after {@link trackFeature} is called and at least {@link ANALYTICS_TIMER} seconds have
* passed since the previous analytics API request was made (if any), or by calling {@link flush}.
*
* Data will stay in memory indefinitely until it can be successfully posted to the API.
* @see https://docs.flagsmith.com/advanced-use/flag-analytics.
*/
export class AnalyticsProcessor {
analyticsUrl;
environmentKey;
lastFlushed;
analyticsData;
requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS;
logger;
currentFlush;
customFetch;
constructor(data) {
this.analyticsUrl = data.analyticsUrl || data.baseApiUrl + ANALYTICS_ENDPOINT;
this.environmentKey = data.environmentKey;
this.lastFlushed = Date.now();
this.analyticsData = {};
this.requestTimeoutMs = data.requestTimeoutMs || this.requestTimeoutMs;
this.logger = data.logger || pino();
this.customFetch = data.fetch ?? fetch;
}
/**
* Try to flush pending collected data to the Flagsmith analytics API.
*/
async flush() {
if (this.currentFlush || !Object.keys(this.analyticsData).length) {
return;
}
try {
this.currentFlush = this.customFetch(this.analyticsUrl, {
method: 'POST',
body: JSON.stringify(this.analyticsData),
signal: AbortSignal.timeout(this.requestTimeoutMs),
headers: {
'Content-Type': 'application/json',
'X-Environment-Key': this.environmentKey,
'User-Agent': getUserAgent()
}
});
await this.currentFlush;
}
catch (error) {
// We don't want failing to write analytics to cause any exceptions in the main
// thread so we just swallow them here.
this.logger.warn('Failed to post analytics to Flagsmith API. Not clearing data, will retry.');
return;
}
finally {
this.currentFlush = undefined;
}
this.analyticsData = {};
this.lastFlushed = Date.now();
}
/**
* Track a single evaluation event for a feature.
*
* @see FlagsmithConfig.enableAnalytics
*/
trackFeature(featureName) {
this.analyticsData[featureName] = (this.analyticsData[featureName] || 0) + 1;
if (Date.now() - this.lastFlushed > ANALYTICS_TIMER * 1000) {
this.flush();
}
}
}