UNPKG

featurehub-javascript-client-sdk

Version:
329 lines 12.3 kB
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()); }); }; import { SSEResultState } from './models'; import { fhLog } from './feature_hub_config'; import { sha256 } from 'cross-sha256'; import * as base64 from '@juanelas/base64'; export 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 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)); } } export 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 (_) { 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); } } }; }); } } BrowserPollingService.httpRequestor = () => { return new XMLHttpRequest(); }; BrowserPollingService.localStorageRequestor = () => { if (window.localStorage) { return localStorage; } return { getItem: () => null, setItem: () => { } }; }; export 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)); 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; 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; } fhLog.trace('polling restarting'); if (this._currentTimer) { clearTimeout(this._currentTimer); this._currentTimer = undefined; } this._pollFunc(); } _pollFunc() { this._pollingService.poll() .then(() => { fhLog.trace('poll successful'); this._readyNextPoll(); if (this._pollPromiseResolve !== undefined) { try { this._pollPromiseResolve(); } catch (e) { fhLog.error('Failed to process resolve', e); } } this._pollPromiseReject = undefined; this._pollPromiseResolve = undefined; }) .catch((status) => { fhLog.trace('poll failed', status); if (status === 404 || status == 400) { if (status == 404) { fhLog.error('The API Key provided does not exist, stopping polling.'); } this._repository.notify(SSEResultState.Failure, null); this._startable = false; this.stop(); if (this._pollPromiseReject) { try { this._pollPromiseReject(status); } catch (e) { fhLog.error('Failed to process reject', e); } } this._pollPromiseReject = undefined; this._pollPromiseResolve = undefined; } else { this._readyNextPoll(); if (status == 503) { fhLog.log('The backend is not ready, waiting for the next poll.'); } } }).finally(() => { }); } _readyNextPoll() { var _a; if (this._pollingService && this._pollingService.frequency > 0) { fhLog.trace('starting timer for poll', this._pollingService.frequency); this._currentTimer = setTimeout(() => this._restartTimer(), this._pollingService.frequency); } else { 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(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(SSEResultState.Features, features); } } } FeatureHubPollingClient.pollingClientProvider = (opt, url, freq, callback) => new BrowserPollingService(opt, url, freq, callback); //# sourceMappingURL=polling_sdk.js.map