unleash-client
Version:
Unleash Client for Node
289 lines • 10.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const events_1 = require("events");
const request_1 = require("./request");
const details_json_1 = require("./details.json");
const url_utils_1 = require("./url-utils");
const events_2 = require("./events");
const helpers_1 = require("./helpers");
const repository_1 = require("./repository");
class Metrics extends events_1.EventEmitter {
constructor({ appName, instanceId, connectionId, strategies, metricsInterval = 0, metricsJitter = 0, disableMetrics = false, url, headers, customHeadersFunction, timeout, httpOptions, metricRegistry, }) {
super();
this.failures = 0;
this.disabled = disableMetrics;
this.metricsInterval = metricsInterval;
this.metricsJitter = metricsJitter;
this.appName = appName;
this.instanceId = instanceId;
this.connectionId = connectionId;
this.sdkVersion = details_json_1.sdkVersion;
this.strategies = strategies;
this.url = url;
this.headers = headers;
this.customHeadersFunction = customHeadersFunction;
this.started = new Date();
this.timeout = timeout;
this.bucket = this.createBucket();
this.httpOptions = httpOptions;
this.platformData = this.getPlatformData();
this.metricRegistry = metricRegistry;
}
getAppliedJitter() {
return (0, helpers_1.getAppliedJitter)(this.metricsJitter);
}
getFailures() {
return this.failures;
}
getInterval() {
if (this.metricsInterval === 0) {
return 0;
}
else {
return this.metricsInterval + this.failures * this.metricsInterval + this.getAppliedJitter();
}
}
startTimer() {
if (this.disabled || this.getInterval() === 0) {
return;
}
this.timer = setTimeout(() => {
this.sendMetrics();
}, this.getInterval());
if (process.env.NODE_ENV !== 'test' && typeof this.timer.unref === 'function') {
this.timer.unref();
}
}
start() {
if (this.metricsInterval > 0) {
this.startTimer();
this.registerInstance();
}
}
stop() {
if (this.timer) {
clearInterval(this.timer);
delete this.timer;
}
this.disabled = true;
}
async registerInstance() {
if (this.disabled) {
return false;
}
const url = (0, url_utils_1.resolveUrl)((0, url_utils_1.suffixSlash)(this.url), './client/register');
const payload = this.getClientData();
const headers = this.customHeadersFunction ? await this.customHeadersFunction() : this.headers;
try {
const res = await (0, request_1.post)({
url,
json: payload,
appName: this.appName,
instanceId: this.instanceId,
connectionId: this.connectionId,
headers,
timeout: this.timeout,
httpOptions: this.httpOptions,
});
if (!res.ok) {
// status code outside 200 range
this.emit(events_2.UnleashEvents.Warn, `${url} returning ${res.status}`, await res.text());
}
else {
this.emit(events_2.UnleashEvents.Registered, payload);
}
}
catch (err) {
this.emit(events_2.UnleashEvents.Warn, err);
}
return true;
}
configurationError(url, statusCode) {
this.emit(events_2.UnleashEvents.Warn, `${url} returning ${statusCode}, stopping metrics`);
this.metricsInterval = 0;
this.stop();
}
backoff(url, statusCode) {
this.failures = Math.min(10, this.failures + 1);
// eslint-disable-next-line max-len
this.emit(events_2.UnleashEvents.Warn, `${url} returning ${statusCode}. Backing off to ${this.failures} times normal interval`);
this.startTimer();
}
async sendMetrics() {
var _a, _b, _c, _d;
if (this.disabled) {
return;
}
const impactMetrics = ((_a = this.metricRegistry) === null || _a === void 0 ? void 0 : _a.collect()) || [];
if (this.bucketIsEmpty() && impactMetrics.length === 0) {
this.resetBucket();
this.startTimer();
(_b = this.metricRegistry) === null || _b === void 0 ? void 0 : _b.restore(impactMetrics);
return;
}
const url = (0, url_utils_1.resolveUrl)((0, url_utils_1.suffixSlash)(this.url), './client/metrics');
const payload = this.createMetricsData(impactMetrics);
const headers = this.customHeadersFunction ? await this.customHeadersFunction() : this.headers;
try {
const res = await (0, request_1.post)({
url,
json: payload,
appName: this.appName,
instanceId: this.instanceId,
connectionId: this.connectionId,
interval: this.metricsInterval,
headers,
timeout: this.timeout,
httpOptions: this.httpOptions,
});
if (!res.ok) {
if (res.status === 403 || res.status == 401) {
this.configurationError(url, res.status);
}
else if (res.status === 404 ||
res.status === 429 ||
res.status === 500 ||
res.status === 502 ||
res.status === 503 ||
res.status === 504) {
this.backoff(url, res.status);
}
this.restoreBucket(payload.bucket);
(_c = this.metricRegistry) === null || _c === void 0 ? void 0 : _c.restore(impactMetrics);
}
else {
this.emit(events_2.UnleashEvents.Sent, payload);
this.reduceBackoff();
}
}
catch (err) {
this.restoreBucket(payload.bucket);
(_d = this.metricRegistry) === null || _d === void 0 ? void 0 : _d.restore(impactMetrics);
this.emit(events_2.UnleashEvents.Warn, err);
this.startTimer();
}
}
reduceBackoff() {
this.failures = Math.max(0, this.failures - 1);
this.startTimer();
}
assertBucket(name) {
if (this.disabled) {
return;
}
if (!this.bucket.toggles[name]) {
this.bucket.toggles[name] = {
yes: 0,
no: 0,
variants: {},
};
}
}
count(name, enabled) {
if (this.disabled) {
return;
}
this.increaseCounter(name, enabled, 1);
this.emit(events_2.UnleashEvents.Count, name, enabled);
}
countVariant(name, variantName) {
if (this.disabled) {
return;
}
this.increaseVariantCounter(name, variantName, 1);
this.emit(events_2.UnleashEvents.CountVariant, name, variantName);
}
increaseCounter(name, enabled, inc = 1) {
if (inc === 0) {
return;
}
this.assertBucket(name);
this.bucket.toggles[name][enabled ? 'yes' : 'no'] += inc;
}
increaseVariantCounter(name, variantName, inc = 1) {
this.assertBucket(name);
if (this.bucket.toggles[name].variants[variantName]) {
this.bucket.toggles[name].variants[variantName] += inc;
}
else {
this.bucket.toggles[name].variants[variantName] = inc;
}
}
bucketIsEmpty() {
return Object.keys(this.bucket.toggles).length === 0;
}
createBucket() {
return {
start: new Date(),
stop: undefined,
toggles: {},
};
}
resetBucket() {
this.bucket = this.createBucket();
}
createMetricsData(impactMetrics) {
const bucket = { ...this.bucket, stop: new Date() };
this.resetBucket();
const base = {
appName: this.appName,
instanceId: this.instanceId,
connectionId: this.connectionId,
bucket,
platformName: this.platformData.name,
platformVersion: this.platformData.version,
yggdrasilVersion: null,
specVersion: repository_1.SUPPORTED_SPEC_VERSION,
};
if (impactMetrics.length > 0) {
base.impactMetrics = impactMetrics;
}
return base;
}
restoreBucket(bucket) {
if (this.disabled) {
return;
}
this.bucket.start = bucket.start;
const { toggles } = bucket;
Object.keys(toggles).forEach((toggleName) => {
const toggle = toggles[toggleName];
this.increaseCounter(toggleName, true, toggle.yes);
this.increaseCounter(toggleName, false, toggle.no);
Object.keys(toggle.variants).forEach((variant) => {
this.increaseVariantCounter(toggleName, variant, toggle.variants[variant]);
});
});
}
getClientData() {
return {
appName: this.appName,
instanceId: this.instanceId,
sdkVersion: this.sdkVersion,
strategies: this.strategies,
started: this.started,
interval: this.metricsInterval,
connectionId: this.connectionId,
platformName: this.platformData.name,
platformVersion: this.platformData.version,
yggdrasilVersion: null,
specVersion: repository_1.SUPPORTED_SPEC_VERSION,
};
}
getPlatformData() {
if (typeof Bun !== 'undefined') {
return { name: 'bun', version: Bun.version };
}
else if (typeof Deno !== 'undefined') {
return { name: 'deno', version: Deno.version.deno };
}
else if (typeof process !== 'undefined' && process.versions && process.versions.node) {
return { name: 'node', version: process.versions.node };
}
else {
return { name: 'unknown', version: 'unknown' };
}
}
}
exports.default = Metrics;
//# sourceMappingURL=metrics.js.map