configcat-common
Version:
ConfigCat is a configuration as a service that lets you manage your features and configurations without actually deploying new code.
221 lines (220 loc) • 11.2 kB
JavaScript
import { __awaiter, __extends, __generator } from "tslib";
import { ClientCacheState, ConfigServiceBase } from "./ConfigServiceBase";
import { AbortToken, delay } from "./Utils";
export var POLL_EXPIRATION_TOLERANCE_MS = 500;
var AutoPollConfigService = /** @class */ (function (_super) {
__extends(AutoPollConfigService, _super);
function AutoPollConfigService(configFetcher, options) {
var _this = _super.call(this, configFetcher, options) || this;
_this.signalInitialization = function () { };
_this.stopToken = new AbortToken();
_this.pollIntervalMs = options.pollIntervalSeconds * 1000;
// Due to the inaccuracy of the timer, some tolerance should be allowed when checking for
// cache expiration in the polling loop, otherwise some fetch operations may be missed.
_this.pollExpirationMs = _this.pollIntervalMs - POLL_EXPIRATION_TOLERANCE_MS;
var initialCacheSyncUp = _this.syncUpWithCache();
if (options.maxInitWaitTimeSeconds !== 0) {
_this.initialized = false;
// This promise will be resolved when
// 1. the cache contains a valid config at startup (see startRefreshWorker) or
// 2. config json is fetched the first time, regardless of success or failure (see onConfigUpdated).
var initSignalPromise = new Promise(function (resolve) { return _this.signalInitialization = resolve; });
// This promise will be resolved when either initialization ready is signalled by signalInitialization() or maxInitWaitTimeSeconds pass.
_this.initializationPromise = _this.waitForInitializationAsync(initSignalPromise).then(function (success) {
_this.initialized = true;
return success;
});
}
else {
_this.initialized = true;
_this.initializationPromise = Promise.resolve(false);
}
_this.readyPromise = _this.getReadyPromise(_this.initializationPromise, function (initializationPromise) { return __awaiter(_this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, initializationPromise];
case 1:
_a.sent();
return [2 /*return*/, this.getCacheState(this.options.cache.getInMemory())];
}
});
}); });
if (!options.offline) {
_this.startRefreshWorker(initialCacheSyncUp, _this.stopToken);
}
return _this;
}
AutoPollConfigService.prototype.waitForInitializationAsync = function (initSignalPromise) {
return __awaiter(this, void 0, void 0, function () {
var abortToken, success;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!(this.options.maxInitWaitTimeSeconds < 0)) return [3 /*break*/, 2];
return [4 /*yield*/, initSignalPromise];
case 1:
_a.sent();
return [2 /*return*/, true];
case 2:
abortToken = new AbortToken();
return [4 /*yield*/, Promise.race([
initSignalPromise.then(function () { return true; }),
delay(this.options.maxInitWaitTimeSeconds * 1000, abortToken).then(function () { return false; })
])];
case 3:
success = _a.sent();
abortToken.abort();
return [2 /*return*/, success];
}
});
});
};
AutoPollConfigService.prototype.getConfig = function () {
return __awaiter(this, void 0, void 0, function () {
function logSuccess(logger) {
logger.debug("AutoPollConfigService.getConfig() - returning value from cache.");
}
var cachedConfig;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
this.options.logger.debug("AutoPollConfigService.getConfig() called.");
if (!(!this.isOffline && !this.initialized)) return [3 /*break*/, 3];
return [4 /*yield*/, this.options.cache.get(this.cacheKey)];
case 1:
cachedConfig = _a.sent();
if (!cachedConfig.isExpired(this.pollIntervalMs)) {
logSuccess(this.options.logger);
return [2 /*return*/, cachedConfig];
}
this.options.logger.debug("AutoPollConfigService.getConfig() - cache is empty or expired, waiting for initialization.");
return [4 /*yield*/, this.initializationPromise];
case 2:
_a.sent();
_a.label = 3;
case 3: return [4 /*yield*/, this.options.cache.get(this.cacheKey)];
case 4:
cachedConfig = _a.sent();
if (!cachedConfig.isExpired(this.pollIntervalMs)) {
logSuccess(this.options.logger);
}
else {
this.options.logger.debug("AutoPollConfigService.getConfig() - cache is empty or expired.");
}
return [2 /*return*/, cachedConfig];
}
});
});
};
AutoPollConfigService.prototype.refreshConfigAsync = function () {
this.options.logger.debug("AutoPollConfigService.refreshConfigAsync() called.");
return _super.prototype.refreshConfigAsync.call(this);
};
AutoPollConfigService.prototype.dispose = function () {
this.options.logger.debug("AutoPollConfigService.dispose() called.");
_super.prototype.dispose.call(this);
if (!this.stopToken.aborted) {
this.stopRefreshWorker();
}
};
AutoPollConfigService.prototype.onConfigFetched = function (newConfig) {
_super.prototype.onConfigFetched.call(this, newConfig);
this.signalInitialization();
};
AutoPollConfigService.prototype.setOnlineCore = function () {
this.startRefreshWorker(null, this.stopToken);
};
AutoPollConfigService.prototype.setOfflineCore = function () {
this.stopRefreshWorker();
this.stopToken = new AbortToken();
};
AutoPollConfigService.prototype.startRefreshWorker = function (initialCacheSyncUp, stopToken) {
return __awaiter(this, void 0, void 0, function () {
var isFirstIteration, scheduledNextTimeMs, err_1, realNextTimeMs, err_2;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
this.options.logger.debug("AutoPollConfigService.startRefreshWorker() called.");
isFirstIteration = true;
_a.label = 1;
case 1:
if (!!stopToken.aborted) return [3 /*break*/, 11];
_a.label = 2;
case 2:
_a.trys.push([2, 9, , 10]);
scheduledNextTimeMs = new Date().getTime() + this.pollIntervalMs;
_a.label = 3;
case 3:
_a.trys.push([3, 5, , 6]);
return [4 /*yield*/, this.refreshWorkerLogic(isFirstIteration, initialCacheSyncUp)];
case 4:
_a.sent();
return [3 /*break*/, 6];
case 5:
err_1 = _a.sent();
this.options.logger.autoPollConfigServiceErrorDuringPolling(err_1);
return [3 /*break*/, 6];
case 6:
realNextTimeMs = scheduledNextTimeMs - new Date().getTime();
if (!(realNextTimeMs > 0)) return [3 /*break*/, 8];
return [4 /*yield*/, delay(realNextTimeMs, stopToken)];
case 7:
_a.sent();
_a.label = 8;
case 8: return [3 /*break*/, 10];
case 9:
err_2 = _a.sent();
this.options.logger.autoPollConfigServiceErrorDuringPolling(err_2);
return [3 /*break*/, 10];
case 10:
isFirstIteration = false;
initialCacheSyncUp = null; // allow GC to collect the Promise and its result
return [3 /*break*/, 1];
case 11: return [2 /*return*/];
}
});
});
};
AutoPollConfigService.prototype.stopRefreshWorker = function () {
this.options.logger.debug("AutoPollConfigService.stopRefreshWorker() called.");
this.stopToken.abort();
};
AutoPollConfigService.prototype.refreshWorkerLogic = function (isFirstIteration, initialCacheSyncUp) {
return __awaiter(this, void 0, void 0, function () {
var latestConfig;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
this.options.logger.debug("AutoPollConfigService.refreshWorkerLogic() - called.");
return [4 /*yield*/, (initialCacheSyncUp !== null && initialCacheSyncUp !== void 0 ? initialCacheSyncUp : this.options.cache.get(this.cacheKey))];
case 1:
latestConfig = _a.sent();
if (!latestConfig.isExpired(this.pollExpirationMs)) return [3 /*break*/, 4];
if (!(isFirstIteration ? !this.isOfflineExactly : !this.isOffline)) return [3 /*break*/, 3];
return [4 /*yield*/, this.refreshConfigCoreAsync(latestConfig)];
case 2:
_a.sent();
_a.label = 3;
case 3: return [3 /*break*/, 5];
case 4:
if (isFirstIteration) {
this.signalInitialization();
}
_a.label = 5;
case 5: return [2 /*return*/];
}
});
});
};
AutoPollConfigService.prototype.getCacheState = function (cachedConfig) {
if (cachedConfig.isEmpty) {
return ClientCacheState.NoFlagData;
}
if (cachedConfig.isExpired(this.pollIntervalMs)) {
return ClientCacheState.HasCachedFlagDataOnly;
}
return ClientCacheState.HasUpToDateFlagData;
};
return AutoPollConfigService;
}(ConfigServiceBase));
export { AutoPollConfigService };