UNPKG

featurehub-javascript-client-sdk

Version:
358 lines 14 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; 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.FeatureHubPollingClient = exports.BrowserPollingService = exports.PollingBase = void 0; const models_1 = require("./models"); const feature_hub_config_1 = require("./feature_hub_config"); const cross_sha256_1 = require("cross-sha256"); const base64 = __importStar(require("@juanelas/base64")); class PollingBase { constructor(url, frequency, callback) { this._stopped = false; this._busy = false; this._outstandingPromises = []; this.url = url; this._frequency = frequency; this._shaHeader = '0'; this._callback = callback; this._busy = false; } attributeHeader(header) { this._header = header; this._shaHeader = (header === undefined || header.length === 0) ? '0' : base64.encode(new cross_sha256_1.sha256().update(header).digest(), true, false); return new Promise((resolve) => { resolve(); }); } stop() { this._stopped = true; } get frequency() { return this._frequency; } parseCacheControl(cacheHeader) { const maxAge = cacheHeader === null || cacheHeader === void 0 ? void 0 : cacheHeader.match(/max-age=(\d+)/); if (maxAge) { const newFreq = parseInt(maxAge[1], 10); if (newFreq > 0) { this._frequency = newFreq * 1000; } } } delayTimer() { return __awaiter(this, void 0, void 0, function* () { return new Promise(((resolve) => { resolve(); })); }); } get busy() { return this._busy; } resolveOutstanding() { const outstanding = [...this._outstandingPromises]; this._outstandingPromises = []; outstanding.forEach(e => e.resolve()); } rejectOutstanding(result) { const outstanding = [...this._outstandingPromises]; this._outstandingPromises = []; outstanding.forEach(e => e.reject(result)); } } exports.PollingBase = PollingBase; class BrowserPollingService extends PollingBase { constructor(options, url, frequency, callback) { super(url, frequency, callback); this._options = options; } loadLocalState(url) { if (url !== this.localStorageLastUrl) { this.localStorageLastUrl = url; const storedData = BrowserPollingService.localStorageRequestor().getItem(url); if (storedData) { try { const data = JSON.parse(storedData); if (data.e) { this._callback(data.e); } } catch (_) { } } } } poll() { if (this._busy) { return new Promise((resolve, reject) => { this._outstandingPromises.push({ resolve: resolve, reject: reject }); }); } if (this._stopped) { return new Promise((resolve) => { resolve(); }); } return new Promise((resolve, reject) => { const calculatedUrl = `${this.url}&contextSha=${this._shaHeader}`; this.loadLocalState(this.url); const req = BrowserPollingService.httpRequestor(); req.open('GET', calculatedUrl); req.setRequestHeader('Content-type', 'application/json'); if (this._etag) { req.setRequestHeader('if-none-match', this._etag); } if (this._header) { req.setRequestHeader('x-featurehub', this._header); } req.send(); req.onreadystatechange = () => { if (req.readyState === 4) { if (req.status === 200 || req.status == 236) { this._etag = req.getResponseHeader('etag'); this.parseCacheControl(req.getResponseHeader('cache-control')); const environments = JSON.parse(req.responseText); try { BrowserPollingService.localStorageRequestor().setItem(this.url, JSON.stringify({ e: environments })); } catch (_) { feature_hub_config_1.fhLog.error('featurehub: unable to cache features'); } this._callback(environments); this._stopped = (req.status === 236); this._busy = false; this.resolveOutstanding(); resolve(); } else if (req.status == 304) { this._busy = false; this.resolveOutstanding(); resolve(); } else { this._busy = false; this.rejectOutstanding(req.status); reject(req.status); } } }; }); } } exports.BrowserPollingService = BrowserPollingService; BrowserPollingService.httpRequestor = () => { return new XMLHttpRequest(); }; BrowserPollingService.localStorageRequestor = () => { if (window.localStorage) { return localStorage; } return { getItem: () => null, setItem: () => { } }; }; class FeatureHubPollingClient { constructor(repository, config, frequency, options = {}) { this._startable = true; this._frequency = frequency; this._repository = repository; this._options = options; this._config = config; this._url = config.getHost() + 'features?' + config.getApiKeys().map(e => 'apiKey=' + encodeURIComponent(e)).join('&'); } _initService() { if (this._pollingService === undefined && this._startable) { this._pollingService = FeatureHubPollingClient.pollingClientProvider(this._options, this._url, this._frequency, (e) => this.response(e)); feature_hub_config_1.fhLog.trace(`featurehub: initialized polling client to ${this._url}`); } } contextChange(header) { return __awaiter(this, void 0, void 0, function* () { if (!this._config.clientEvaluated()) { if (this._xHeader !== header) { this._xHeader = header; this._initService(); if (this._pollingService) { yield this._pollingService.attributeHeader(header); } this._restartTimer(); } } return new Promise((resolve) => resolve()); }); } clientEvaluated() { return this._config.clientEvaluated(); } requiresReplacementOnHeaderChange() { return false; } close() { this.stop(); } stop() { var _a; feature_hub_config_1.fhLog.trace('polling stopping'); if (this._currentTimer) { clearTimeout(this._currentTimer); this._currentTimer = undefined; } if (this._pollPromiseReject !== undefined) { this._pollPromiseReject('Never came live'); } (_a = this._pollingService) === null || _a === void 0 ? void 0 : _a.stop(); this._pollingService = undefined; } poll() { var _a; if (this._pollPromiseResolve !== undefined || ((_a = this._pollingService) === null || _a === void 0 ? void 0 : _a.busy)) { return new Promise((resolve) => resolve()); } if (!this._startable) { return new Promise((_, reject) => reject()); } this._initService(); return new Promise((resolve, reject) => { this._pollPromiseReject = reject; this._pollPromiseResolve = resolve; this._restartTimer(); }); } get canStart() { return this._startable; } get pollingFrequency() { var _a; return (_a = this._pollingService) === null || _a === void 0 ? void 0 : _a.frequency; } get active() { var _a; return ((_a = this._pollingService) === null || _a === void 0 ? void 0 : _a.busy) || this._currentTimer !== undefined; } get awaitingFirstSuccess() { return this._pollPromiseReject !== undefined; } _restartTimer() { var _a; if (this._pollingService === undefined || ((_a = this._pollingService) === null || _a === void 0 ? void 0 : _a.busy) || !this._startable) { return; } feature_hub_config_1.fhLog.trace('polling restarting'); if (this._currentTimer) { clearTimeout(this._currentTimer); this._currentTimer = undefined; } this._pollFunc(); } _pollFunc() { this._pollingService.poll() .then(() => { feature_hub_config_1.fhLog.trace('poll successful'); this._readyNextPoll(); if (this._pollPromiseResolve !== undefined) { try { this._pollPromiseResolve(); } catch (e) { feature_hub_config_1.fhLog.error('Failed to process resolve', e); } } this._pollPromiseReject = undefined; this._pollPromiseResolve = undefined; }) .catch((status) => { feature_hub_config_1.fhLog.trace('poll failed', status); if (status === 404 || status == 400) { if (status == 404) { feature_hub_config_1.fhLog.error('The API Key provided does not exist, stopping polling.'); } this._repository.notify(models_1.SSEResultState.Failure, null); this._startable = false; this.stop(); if (this._pollPromiseReject) { try { this._pollPromiseReject(status); } catch (e) { feature_hub_config_1.fhLog.error('Failed to process reject', e); } } this._pollPromiseReject = undefined; this._pollPromiseResolve = undefined; } else { this._readyNextPoll(); if (status == 503) { feature_hub_config_1.fhLog.log('The backend is not ready, waiting for the next poll.'); } } }).finally(() => { }); } _readyNextPoll() { var _a; if (this._pollingService && this._pollingService.frequency > 0) { feature_hub_config_1.fhLog.trace('starting timer for poll', this._pollingService.frequency); this._currentTimer = setTimeout(() => this._restartTimer(), this._pollingService.frequency); } else { feature_hub_config_1.fhLog.trace('no polling service or 0 frequency, stopping polling.', this._pollingService === undefined, (_a = this._pollingService) === null || _a === void 0 ? void 0 : _a.frequency); } } response(environments) { if (environments.length === 0) { this._startable = false; this.stop(); this._repository.notify(models_1.SSEResultState.Failure, null); } else { const features = new Array(); environments.forEach(e => { if (e.features.length > 0) { e.features.forEach(f => { f.environmentId = e.id; }); features.push(...e.features); } }); this._repository.notify(models_1.SSEResultState.Features, features); } } } exports.FeatureHubPollingClient = FeatureHubPollingClient; FeatureHubPollingClient.pollingClientProvider = (opt, url, freq, callback) => new BrowserPollingService(opt, url, freq, callback); //# sourceMappingURL=polling_sdk.js.map