UNPKG

configcat-common

Version:

ConfigCat is a configuration as a service that lets you manage your features and configurations without actually deploying new code.

224 lines (223 loc) 11.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AutoPollConfigService = exports.POLL_EXPIRATION_TOLERANCE_MS = void 0; var tslib_1 = require("tslib"); var ConfigServiceBase_1 = require("./ConfigServiceBase"); var Utils_1 = require("./Utils"); exports.POLL_EXPIRATION_TOLERANCE_MS = 500; var AutoPollConfigService = /** @class */ (function (_super) { tslib_1.__extends(AutoPollConfigService, _super); function AutoPollConfigService(configFetcher, options) { var _this = _super.call(this, configFetcher, options) || this; _this.signalInitialization = function () { }; _this.stopToken = new Utils_1.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 - exports.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 tslib_1.__awaiter(_this, void 0, void 0, function () { return tslib_1.__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 tslib_1.__awaiter(this, void 0, void 0, function () { var abortToken, success; return tslib_1.__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 Utils_1.AbortToken(); return [4 /*yield*/, Promise.race([ initSignalPromise.then(function () { return true; }), Utils_1.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 tslib_1.__awaiter(this, void 0, void 0, function () { function logSuccess(logger) { logger.debug("AutoPollConfigService.getConfig() - returning value from cache."); } var cachedConfig; return tslib_1.__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 Utils_1.AbortToken(); }; AutoPollConfigService.prototype.startRefreshWorker = function (initialCacheSyncUp, stopToken) { return tslib_1.__awaiter(this, void 0, void 0, function () { var isFirstIteration, scheduledNextTimeMs, err_1, realNextTimeMs, err_2; return tslib_1.__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*/, Utils_1.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 tslib_1.__awaiter(this, void 0, void 0, function () { var latestConfig; return tslib_1.__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 ConfigServiceBase_1.ClientCacheState.NoFlagData; } if (cachedConfig.isExpired(this.pollIntervalMs)) { return ConfigServiceBase_1.ClientCacheState.HasCachedFlagDataOnly; } return ConfigServiceBase_1.ClientCacheState.HasUpToDateFlagData; }; return AutoPollConfigService; }(ConfigServiceBase_1.ConfigServiceBase)); exports.AutoPollConfigService = AutoPollConfigService;