UNPKG

featurehub-javascript-client-sdk

Version:
310 lines 12.8 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ClientFeatureRepository = void 0; const feature_state_holders_1 = require("./feature_state_holders"); const models_1 = require("./models"); const strategy_matcher_1 = require("./strategy_matcher"); const feature_hub_config_1 = require("./feature_hub_config"); const featurehub_repository_1 = require("./featurehub_repository"); const listener_utils_1 = require("./listener_utils"); class ClientFeatureRepository { constructor(applyFeature) { this.hasReceivedInitialState = false; this.features = new Map(); this.analyticsCollectors = new Array(); this.readynessState = featurehub_repository_1.Readyness.NotReady; this._readinessListeners = new Map(); this._catchAndReleaseMode = false; this._catchReleaseStates = new Map(); this._newFeatureStateAvailableListeners = new Map(); this._matchers = []; this._applyFeature = applyFeature || new strategy_matcher_1.ApplyFeature(); } apply(strategies, key, featureValueId, context) { return this._applyFeature.apply(strategies, key, featureValueId, context); } get readyness() { return this.readynessState; } notify(state, data) { if (state !== null && state !== undefined) { switch (state) { case models_1.SSEResultState.Ack: case models_1.SSEResultState.Bye: break; case models_1.SSEResultState.DeleteFeature: this.deleteFeature(data); break; case models_1.SSEResultState.Failure: this.readynessState = featurehub_repository_1.Readyness.Failed; if (!this._catchAndReleaseMode) { this.broadcastReadynessState(false); } break; case models_1.SSEResultState.Feature: { const fs = data; if (this._catchAndReleaseMode) { this._catchUpdatedFeatures([fs], false); } else { if (this.featureUpdate(fs)) { this.triggerNewStateAvailable(); } } } break; case models_1.SSEResultState.Features: { const features = data.filter((f) => (f === null || f === void 0 ? void 0 : f.key) !== undefined).map((f) => f); if (this.hasReceivedInitialState && this._catchAndReleaseMode) { this._catchUpdatedFeatures(features, true); } else { let updated = false; features.forEach((f) => updated = this.featureUpdate(f) || updated); this._checkForDeletedFeatures(features); this.readynessState = featurehub_repository_1.Readyness.Ready; if (!this.hasReceivedInitialState) { this.hasReceivedInitialState = true; this.broadcastReadynessState(true); } else if (updated) { this.triggerNewStateAvailable(); } } } break; default: break; } } } _checkForDeletedFeatures(features) { const featureMatch = new Map(this.features); features.forEach(f => featureMatch.delete(f.key)); if (featureMatch.size > 0) { for (const k of featureMatch.keys()) { this.deleteFeature({ key: k }); } } } addValueInterceptor(matcher) { this._matchers.push(matcher); matcher.repository(this); } valueInterceptorMatched(key) { for (const matcher of this._matchers) { const m = matcher.matched(key); if (m === null || m === void 0 ? void 0 : m.value) { return m; } } return undefined; } addPostLoadNewFeatureStateAvailableListener(listener) { const pos = listener_utils_1.ListenerUtils.newListenerKey(this._newFeatureStateAvailableListeners); this._newFeatureStateAvailableListeners.set(pos, listener); if (this._catchReleaseStates.size > 0) { listener(this); } return pos; } removePostLoadNewFeatureStateAvailableListener(listener) { listener_utils_1.ListenerUtils.removeListener(this._newFeatureStateAvailableListeners, listener); } addReadynessListener(listener) { return this.addReadinessListener(listener); } addReadinessListener(listener, ignoreNotReadyOnRegister) { const pos = listener_utils_1.ListenerUtils.newListenerKey(this._readinessListeners); this._readinessListeners.set(pos, listener); if (!ignoreNotReadyOnRegister || (ignoreNotReadyOnRegister && this.readynessState != featurehub_repository_1.Readyness.NotReady)) { listener(this.readynessState, this.hasReceivedInitialState); } return pos; } removeReadinessListener(listener) { listener_utils_1.ListenerUtils.removeListener(this._readinessListeners, listener); } notReady() { this.readynessState = featurehub_repository_1.Readyness.NotReady; this.broadcastReadynessState(false); } broadcastReadynessState(firstState) { this._readinessListeners.forEach((l) => l(this.readynessState, firstState)); } addAnalyticCollector(collector) { this.analyticsCollectors.push(collector); } simpleFeatures() { const vals = new Map(); this.features.forEach((value, key) => { if (value.exists) { let val; switch (value.getType()) { case models_1.FeatureValueType.Boolean: val = value.flag ? 'true' : 'false'; break; case models_1.FeatureValueType.String: val = value.str; break; case models_1.FeatureValueType.Number: val = value.num; break; case models_1.FeatureValueType.Json: val = value.rawJson; break; default: val = undefined; } vals.set(key, val === undefined ? val : val.toString()); } }); return vals; } logAnalyticsEvent(action, other, ctx) { const featureStateAtCurrentTime = []; for (const fs of this.features.values()) { if (fs.isSet()) { const fsVal = ctx == null ? fs : fs.withContext(ctx); featureStateAtCurrentTime.push(fsVal.analyticsCopy()); } } this.analyticsCollectors.forEach((ac) => ac.logEvent(action, other || new Map(), featureStateAtCurrentTime)); } hasFeature(key) { return this.features.get(key); } feature(key) { let holder = this.features.get(key); if (holder === undefined) { holder = new feature_state_holders_1.FeatureStateBaseHolder(this, key); this.features.set(key, holder); } return holder; } getFeatureState(key) { return this.feature(key); } get catchAndReleaseMode() { return this._catchAndReleaseMode; } set catchAndReleaseMode(value) { if (this._catchAndReleaseMode !== value && !value) { this.release(true); } this._catchAndReleaseMode = value; } release(disableCatchAndRelease) { return __awaiter(this, void 0, void 0, function* () { while (this._catchReleaseStates.size > 0 || this._catchReleaseCheckForDeletesOnRelease !== undefined) { const states = [...this._catchReleaseStates.values()]; this._catchReleaseStates.clear(); states.forEach((fs) => this.featureUpdate(fs)); if (this._catchReleaseCheckForDeletesOnRelease) { this._checkForDeletedFeatures(this._catchReleaseCheckForDeletesOnRelease); this._catchReleaseCheckForDeletesOnRelease = undefined; } } if (disableCatchAndRelease === true) { this._catchAndReleaseMode = false; } }); } getFlag(key) { return this.feature(key).getFlag(); } getString(key) { return this.feature(key).getString(); } getJson(key) { return this.feature(key).getRawJson(); } getNumber(key) { return this.feature(key).getNumber(); } isSet(key) { return this.feature(key).isSet(); } _catchUpdatedFeatures(features, isFullList) { let updatedValues = false; if (isFullList) { this._catchReleaseCheckForDeletesOnRelease = features; } if (features && features.length > 0) { features.forEach((f) => { var _a; const existingFeature = this.features.get(f.key); if (!existingFeature || !existingFeature.exists || (existingFeature.getKey() && f.version > (((_a = existingFeature.getFeatureState()) === null || _a === void 0 ? void 0 : _a.version) || -1))) { const fs = this._catchReleaseStates.get(f.id); if (fs == null) { this._catchReleaseStates.set(f.id, f); updatedValues = true; } else { if (fs.version === undefined || (f.version !== undefined && f.version > fs.version)) { this._catchReleaseStates.set(f.id, f); updatedValues = true; } } } }); } if (updatedValues) { this.triggerNewStateAvailable(); } } triggerNewStateAvailable() { if (this.hasReceivedInitialState && this._newFeatureStateAvailableListeners.size > 0) { if (!this._catchAndReleaseMode || (this._catchReleaseStates.size > 0)) { this._newFeatureStateAvailableListeners.forEach((l) => { try { l(this); } catch (e) { feature_hub_config_1.fhLog.log('failed', e); } }); } } else { } } featureUpdate(fs) { if (fs === undefined || fs.key === undefined) { return false; } let holder = this.features.get(fs.key); if (holder === undefined) { const newFeature = new feature_state_holders_1.FeatureStateBaseHolder(this, fs.key, holder); this.features.set(fs.key, newFeature); holder = newFeature; } else if (holder.getFeatureState() !== undefined) { const fState = holder.getFeatureState(); if (fs.version < fState.version) { return false; } } return holder.setFeatureState(fs); } deleteFeature(featureState) { const holder = this.features.get(featureState.key); if (holder && ((featureState.version === undefined) || (featureState.version === 0) || (featureState.version >= holder.version))) { holder.setFeatureState(undefined); } } } exports.ClientFeatureRepository = ClientFeatureRepository; //# sourceMappingURL=client_feature_repository.js.map