UNPKG

unleash-client

Version:
230 lines 8.57 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SUPPORTED_SPEC_VERSION = void 0; const events_1 = require("events"); const events_2 = require("../events"); const adaptive_fetcher_1 = require("./adaptive-fetcher"); exports.SUPPORTED_SPEC_VERSION = '5.2.0'; class Repository extends events_1.EventEmitter { // Etag property for backward compatibility get etag() { var _a, _b; return ((_b = (_a = this.fetcher).getEtag) === null || _b === void 0 ? void 0 : _b.call(_a)) || undefined; } set etag(value) { var _a, _b; (_b = (_a = this.fetcher).setEtag) === null || _b === void 0 ? void 0 : _b.call(_a, value); } constructor({ url, appName, instanceId, connectionId, projectName, refreshInterval = 15000, timeout, headers, customHeadersFunction, httpOptions, namePrefix, tags, bootstrapProvider, bootstrapOverride = true, storageProvider, eventSource, mode, }) { super(); this.ready = false; this.connected = false; this.stopped = false; this.data = {}; this.enhanceStrategies = (strategies) => { return strategies === null || strategies === void 0 ? void 0 : strategies.map((strategy) => { const { segments, ...restOfStrategy } = strategy; const enhancedSegments = segments === null || segments === void 0 ? void 0 : segments.map((segment) => this.getSegment(segment)); return { ...restOfStrategy, segments: enhancedSegments }; }); }; this.appName = appName; this.url = url; this.projectName = projectName; this.bootstrapProvider = bootstrapProvider; this.bootstrapOverride = bootstrapOverride; this.storageProvider = storageProvider; this.segments = new Map(); this.fetcher = new adaptive_fetcher_1.AdaptiveFetcher({ url, appName, instanceId, connectionId, refreshInterval, timeout, headers, customHeadersFunction, httpOptions, namePrefix, tags, projectName, mode, eventSource, onSave: this.save.bind(this), onSaveDelta: this.saveDelta.bind(this), }); this.setupFetchingStrategyEvents(); } setupFetchingStrategyEvents() { this.fetcher.on(events_2.UnleashEvents.Error, (err) => this.emit(events_2.UnleashEvents.Error, err)); this.fetcher.on(events_2.UnleashEvents.Warn, (msg) => this.emit(events_2.UnleashEvents.Warn, msg)); this.fetcher.on(events_2.UnleashEvents.Unchanged, () => this.emit(events_2.UnleashEvents.Unchanged)); this.fetcher.on(events_2.UnleashEvents.Mode, (data) => this.emit(events_2.UnleashEvents.Mode, data)); } validateFeature(feature) { const errors = []; if (!Array.isArray(feature.strategies)) { errors.push(`feature.strategies should be an array, but was ${typeof feature.strategies}`); } if (feature.variants && !Array.isArray(feature.variants)) { errors.push(`feature.variants should be an array, but was ${typeof feature.variants}`); } if (typeof feature.enabled !== 'boolean') { errors.push(`feature.enabled should be an boolean, but was ${typeof feature.enabled}`); } if (errors.length > 0) { const err = new Error(errors.join(', ')); this.emit(events_2.UnleashEvents.Error, err); } } async start() { await Promise.all([this.fetcher.start(), this.loadBackup(), this.loadBootstrap()]); } async loadBackup() { try { const content = await this.storageProvider.get(this.appName); if (this.ready) { return; } if (content && this.notEmpty(content)) { this.data = this.convertToMap(content.features); this.segments = this.createSegmentLookup(content.segments); this.setReady(); } } catch (err) { this.emit(events_2.UnleashEvents.Warn, err); } } setReady() { const doEmitReady = this.ready === false; this.ready = true; if (doEmitReady) { process.nextTick(() => { this.emit(events_2.UnleashEvents.Ready); }); } } createSegmentLookup(segments) { if (!segments) { return new Map(); } return new Map(segments.map((segment) => [segment.id, segment])); } async save(response, fromApi) { if (this.stopped) { return; } if (fromApi) { this.connected = true; this.data = this.convertToMap(response.features); this.segments = this.createSegmentLookup(response.segments); } else if (!this.connected) { // Only allow bootstrap if not connected this.data = this.convertToMap(response.features); this.segments = this.createSegmentLookup(response.segments); } this.setReady(); this.emit(events_2.UnleashEvents.Changed, [...response.features]); await this.storageProvider.set(this.appName, response); } async saveDelta(delta) { if (this.stopped) { return; } this.connected = true; delta.events.forEach((event) => { if (event.type === 'feature-updated') { this.data[event.feature.name] = event.feature; } else if (event.type === 'feature-removed') { delete this.data[event.featureName]; } else if (event.type === 'segment-updated') { this.segments.set(event.segment.id, event.segment); } else if (event.type === 'segment-removed') { this.segments.delete(event.segmentId); } else if (event.type === 'hydration') { this.data = this.convertToMap(event.features); this.segments = this.createSegmentLookup(event.segments); } }); this.setReady(); this.emit(events_2.UnleashEvents.Changed, Object.values(this.data)); await this.storageProvider.set(this.appName, { features: Object.values(this.data), segments: [...this.segments.values()], version: 0, }); } notEmpty(content) { return content.features.length > 0; } async loadBootstrap() { try { const content = await this.bootstrapProvider.readBootstrap(); if (!this.bootstrapOverride && this.ready) { // early exit if we already have backup data and should not override it. return; } if (content && this.notEmpty(content)) { await this.save(content, false); } } catch (err) { this.emit(events_2.UnleashEvents.Warn, `Unleash SDK was unable to load bootstrap. Message: ${err.message}`); } } convertToMap(features) { const obj = features.reduce((o, feature) => { const a = { ...o }; this.validateFeature(feature); a[feature.name] = feature; return a; }, {}); return obj; } stop() { this.stopped = true; this.fetcher.stop(); this.removeAllListeners(); } getSegment(segmentId) { return this.segments.get(segmentId); } getToggle(name) { return this.data[name]; } getToggles() { return Object.keys(this.data).map((key) => this.data[key]); } getTogglesWithSegmentData() { const toggles = this.getToggles(); return toggles.map((toggle) => { const { strategies, ...restOfToggle } = toggle; return { ...restOfToggle, strategies: this.enhanceStrategies(strategies) }; }); } getMode() { return this.fetcher.getMode(); } async setMode(mode) { await this.fetcher.setMode(mode); } // Compatibility methods for tests - delegate to fetching strategy getFailures() { return this.fetcher.getFailures(); } nextFetch() { return this.fetcher.nextFetch(); } async fetch() { return this.fetcher.fetch(); } } exports.default = Repository; //# sourceMappingURL=index.js.map