UNPKG

unleash-client

Version:
270 lines 12 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Unleash = exports.UnleashEvents = exports.Strategy = void 0; const os_1 = require("os"); const events_1 = require("events"); const client_1 = require("./client"); const repository_1 = require("./repository"); const metrics_1 = require("./metrics"); const strategy_1 = require("./strategy"); Object.defineProperty(exports, "Strategy", { enumerable: true, get: function () { return strategy_1.Strategy; } }); const variant_1 = require("./variant"); const helpers_1 = require("./helpers"); const bootstrap_provider_1 = require("./repository/bootstrap-provider"); const events_2 = require("./events"); Object.defineProperty(exports, "UnleashEvents", { enumerable: true, get: function () { return events_2.UnleashEvents; } }); const storage_provider_file_1 = require("./repository/storage-provider-file"); const uuidv4_1 = require("./uuidv4"); const metric_types_1 = require("./impact-metrics/metric-types"); const BACKUP_PATH = (0, os_1.tmpdir)(); class Unleash extends events_1.EventEmitter { constructor({ appName, environment = 'default', projectName, instanceId, url, refreshInterval = 15 * 1000, metricsInterval = 60 * 1000, metricsJitter = 0, disableMetrics = false, backupPath = BACKUP_PATH, strategies = [], repository, namePrefix, customHeaders, customHeadersFunction, timeout, httpOptions, tags, bootstrap = {}, bootstrapOverride, storageProvider, disableAutoStart = false, skipInstanceCountWarning = false, experimentalMode = { type: 'polling', format: 'full' }, }) { super(); this.synchronized = false; this.ready = false; this.started = false; this.metricRegistry = new metric_types_1.InMemoryMetricRegistry(); Unleash.instanceCount++; this.on(events_2.UnleashEvents.Error, (error) => { // Only if there does not exist other listeners for this event. if (this.listenerCount(events_2.UnleashEvents.Error) === 1) { console.error(error); } }); if (!skipInstanceCountWarning && Unleash.instanceCount > 10) { process.nextTick(() => { const error = new Error('The unleash SDK has been initialized more than 10 times'); this.emit(events_2.UnleashEvents.Error, error); }); } if (!url) { throw new Error('Unleash API "url" is required'); } if (!appName) { throw new Error('Unleash client "appName" is required'); } const unleashUrl = this.cleanUnleashUrl(url); const unleashInstanceId = (0, helpers_1.generateInstanceId)(instanceId); const unleashConnectionId = (0, uuidv4_1.uuidv4)(); this.staticContext = { appName, environment }; const bootstrapProvider = (0, bootstrap_provider_1.resolveBootstrapProvider)(bootstrap, appName, unleashInstanceId); this.repository = repository || new repository_1.default({ projectName, url: unleashUrl, appName, instanceId: unleashInstanceId, connectionId: unleashConnectionId, refreshInterval, headers: customHeaders, customHeadersFunction, timeout, httpOptions, namePrefix, tags, bootstrapProvider, bootstrapOverride, mode: experimentalMode, storageProvider: storageProvider || new storage_provider_file_1.default(backupPath), }); this.repository.on(events_2.UnleashEvents.Ready, () => { this.ready = true; process.nextTick(() => { this.emit(events_2.UnleashEvents.Ready); }); }); this.repository.on(events_2.UnleashEvents.Error, (err) => { // eslint-disable-next-line no-param-reassign err.message = `Unleash Repository error: ${err.message}`; this.emit(events_2.UnleashEvents.Error, err); }); this.repository.on(events_2.UnleashEvents.Warn, (msg) => this.emit(events_2.UnleashEvents.Warn, msg)); this.repository.on(events_2.UnleashEvents.Unchanged, (msg) => this.emit(events_2.UnleashEvents.Unchanged, msg)); this.repository.on(events_2.UnleashEvents.Changed, (data) => { this.emit(events_2.UnleashEvents.Changed, data); // Only emit the fully synchronized event the first time. if (!this.synchronized) { this.synchronized = true; process.nextTick(() => this.emit(events_2.UnleashEvents.Synchronized)); } }); // setup client const supportedStrategies = strategies.concat(strategy_1.defaultStrategies); this.client = new client_1.default(this.repository, supportedStrategies); this.client.on(events_2.UnleashEvents.Error, (err) => this.emit(events_2.UnleashEvents.Error, err)); this.client.on(events_2.UnleashEvents.Impression, (e) => this.emit(events_2.UnleashEvents.Impression, e)); this.metrics = new metrics_1.default({ disableMetrics, appName, instanceId: unleashInstanceId, connectionId: unleashConnectionId, strategies: supportedStrategies.map((strategy) => strategy.name), metricsInterval, metricsJitter, url: unleashUrl, headers: customHeaders, customHeadersFunction, timeout, httpOptions, metricRegistry: this.metricRegistry, }); this.metrics.on(events_2.UnleashEvents.Error, (err) => { // eslint-disable-next-line no-param-reassign err.message = `Unleash Metrics error: ${err.message}`; this.emit(events_2.UnleashEvents.Error, err); }); this.metrics.on(events_2.UnleashEvents.Warn, (msg) => this.emit(events_2.UnleashEvents.Warn, msg)); this.metrics.on(events_2.UnleashEvents.Sent, (payload) => this.emit(events_2.UnleashEvents.Sent, payload)); this.metrics.on(events_2.UnleashEvents.Count, (name, enabled) => { this.emit(events_2.UnleashEvents.Count, name, enabled); }); this.metrics.on(events_2.UnleashEvents.Registered, (payload) => { this.emit(events_2.UnleashEvents.Registered, payload); }); if (!disableAutoStart) { process.nextTick(async () => this.start()); } } /** * Will only give you an instance the first time you call the method, * and then return the same instance. * @param config The Unleash Config. * @returns the Unleash instance */ static getInstance(config) { const cleanConfig = { ...config, // Remove complex objects repository: undefined, customHeadersFunction: undefined, storageProvider: undefined, }; const configSignature = (0, helpers_1.generateHashOfConfig)(cleanConfig); if (Unleash.instance) { if (configSignature !== Unleash.configSignature) { throw new Error('You already have an Unleash instance with a different configuration.'); } return Unleash.instance; } const instance = new Unleash(config); Unleash.instance = instance; Unleash.configSignature = configSignature; return instance; } cleanUnleashUrl(url) { let unleashUrl = url; if (unleashUrl.endsWith('/features')) { const oldUrl = unleashUrl; process.nextTick(() => this.emit(events_2.UnleashEvents.Warn, `Unleash server URL "${oldUrl}" should no longer link directly to /features`)); unleashUrl = unleashUrl.replace(/\/features$/, ''); } if (!unleashUrl.endsWith('/')) { unleashUrl += '/'; } return unleashUrl; } isSynchronized() { return this.synchronized; } async start() { if (this.started) return; this.started = true; await Promise.all([this.repository.start(), this.metrics.start()]); } destroy() { this.repository.stop(); this.metrics.stop(); Unleash.instance = undefined; Unleash.configSignature = undefined; Unleash.instanceCount--; } isEnabled(name, context = {}, fallback) { const enhancedContext = { ...this.staticContext, ...context }; const fallbackFunc = (0, helpers_1.createFallbackFunction)(name, enhancedContext, fallback); let result; if (this.ready) { result = this.client.isEnabled(name, enhancedContext, fallbackFunc); } else { result = fallbackFunc(); this.emit(events_2.UnleashEvents.Warn, `Unleash has not been initialized yet. isEnabled(${name}) defaulted to ${result}`); } this.count(name, result); return result; } getVariant(name, context = {}, fallbackVariant) { const enhancedContext = { ...this.staticContext, ...context }; let variant; if (this.ready) { variant = this.client.getVariant(name, enhancedContext, fallbackVariant); } else { variant = typeof fallbackVariant !== 'undefined' ? { ...fallbackVariant, feature_enabled: false, featureEnabled: false } : { ...variant_1.defaultVariant, featureEnabled: variant_1.defaultVariant.feature_enabled }; this.emit(events_2.UnleashEvents.Warn, `Unleash has not been initialized yet. isEnabled(${name}) defaulted to ${variant}`); } if (variant.name) { this.countVariant(name, variant.name); } this.count(name, Boolean(variant.feature_enabled)); return variant; } forceGetVariant(name, context = {}, fallbackVariant) { const enhancedContext = { ...this.staticContext, ...context }; let variant; if (this.ready) { variant = this.client.forceGetVariant(name, enhancedContext, fallbackVariant); } else { variant = typeof fallbackVariant !== 'undefined' ? { ...fallbackVariant, feature_enabled: false } : variant_1.defaultVariant; this.emit(events_2.UnleashEvents.Warn, `Unleash has not been initialized yet. isEnabled(${name}) defaulted to ${variant}`); } if (variant.name) { this.countVariant(name, variant.name); } this.count(name, variant.feature_enabled || false); return variant; } getFeatureToggleDefinition(toggleName) { return this.repository.getToggle(toggleName); } getFeatureToggleDefinitions(withFullSegments) { if (withFullSegments === true) { return this.repository.getTogglesWithSegmentData(); } return this.repository.getToggles(); } count(toggleName, enabled) { this.metrics.count(toggleName, enabled); } countVariant(toggleName, variantName) { this.metrics.countVariant(toggleName, variantName); } flushMetrics() { return this.metrics.sendMetrics(); } async destroyWithFlush() { await this.flushMetrics(); this.destroy(); } /** * Experimental: Change the fetching mode at runtime * @param mode The new mode to switch to ('polling' or 'streaming') */ async setExperimentalMode(mode) { if (this.repository.setMode) { return this.repository.setMode(mode); } throw new Error('setMode is not supported by this repository implementation'); } } exports.Unleash = Unleash; Unleash.instanceCount = 0; //# sourceMappingURL=unleash.js.map