featurehub-javascript-client-sdk
Version:
FeatureHub client/browser SDK
310 lines • 12.8 kB
JavaScript
"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