UNPKG

unleash-client

Version:
289 lines 10.4 kB
"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