UNPKG

@splitsoftware/splitio-commons

Version:
158 lines (157 loc) 6.77 kB
import { objectAssign } from '../utils/lang/objectAssign'; import { SDK_DEFINITIONS_ARRIVED, SDK_DEFINITIONS_CACHE_LOADED, SDK_SEGMENTS_ARRIVED, SDK_READY_TIMED_OUT, SDK_READY_FROM_CACHE, SDK_UPDATE, SDK_READY } from './constants'; function definitionsEventEmitterFactory(EventEmitter) { var definitionsEventEmitter = objectAssign(new EventEmitter(), { definitionsArrived: false, definitionsCacheLoaded: false, hasInit: false, initCallbacks: [] }); // `isSplitKill` condition avoids an edge-case of wrongly emitting SDK_READY if: // - `/memberships` fetch and SPLIT_KILL occurs before `/splitChanges` fetch, and // - storage has cached splits (for which case `splitsStorage.killLocally` can return true) definitionsEventEmitter.on(SDK_DEFINITIONS_ARRIVED, function (metadata, isSplitKill) { if (!isSplitKill) definitionsEventEmitter.definitionsArrived = true; }); definitionsEventEmitter.once(SDK_DEFINITIONS_CACHE_LOADED, function () { definitionsEventEmitter.definitionsCacheLoaded = true; }); return definitionsEventEmitter; } function segmentsEventEmitterFactory(EventEmitter) { var segmentsEventEmitter = objectAssign(new EventEmitter(), { segmentsArrived: false }); segmentsEventEmitter.once(SDK_SEGMENTS_ARRIVED, function () { segmentsEventEmitter.segmentsArrived = true; }); return segmentsEventEmitter; } /** * Factory of readiness manager, which handles the ready / update event propagation. */ export function readinessManagerFactory(EventEmitter, settings, definitions, isShared) { if (definitions === void 0) { definitions = definitionsEventEmitterFactory(EventEmitter); } var readyTimeout = settings.startup.readyTimeout; var segments = segmentsEventEmitterFactory(EventEmitter); var gate = new EventEmitter(); var lastUpdate = 0; function syncLastUpdate() { var dateNow = Date.now(); // ensure lastUpdate is always increasing per event, is case Date.now() is mocked or its value is the same lastUpdate = dateNow > lastUpdate ? dateNow : lastUpdate + 1; } var metadataReady = { initialCacheLoad: true }; // emit SDK_READY_FROM_CACHE var isReadyFromCache = false; if (definitions.definitionsCacheLoaded) isReadyFromCache = true; // ready from cache, but doesn't emit SDK_READY_FROM_CACHE else definitions.once(SDK_DEFINITIONS_CACHE_LOADED, checkIsReadyFromCache); // emit SDK_READY_TIMED_OUT var hasTimedout = false; function timeout() { if (hasTimedout || isReady) return; hasTimedout = true; syncLastUpdate(); gate.emit(SDK_READY_TIMED_OUT, 'SDK client emitted SDK_READY_TIMED_OUT event.'); } // emit SDK_READY and SDK_UPDATE var isReady = false; definitions.on(SDK_DEFINITIONS_ARRIVED, checkIsReadyOrUpdate); segments.on(SDK_SEGMENTS_ARRIVED, checkIsReadyOrUpdate); var isDestroyed = false; var readyTimeoutId; function __init() { isDestroyed = false; if (readyTimeout > 0 && !isReady) readyTimeoutId = setTimeout(timeout, readyTimeout); } definitions.initCallbacks.push(__init); if (definitions.hasInit) __init(); function checkIsReadyFromCache(cacheMetadata) { metadataReady = cacheMetadata; isReadyFromCache = true; // Don't emit SDK_READY_FROM_CACHE if SDK_READY has been emitted if (!isReady && !isDestroyed) { try { syncLastUpdate(); gate.emit(SDK_READY_FROM_CACHE, cacheMetadata); } catch (e) { // throws user callback exceptions in next tick setTimeout(function () { throw e; }, 0); } } } function checkIsReadyOrUpdate(metadata) { if (isDestroyed) return; if (isReady) { try { syncLastUpdate(); gate.emit(SDK_UPDATE, metadata); } catch (e) { // throws user callback exceptions in next tick setTimeout(function () { throw e; }, 0); } } else { if (definitions.definitionsArrived && segments.segmentsArrived) { clearTimeout(readyTimeoutId); isReady = true; try { syncLastUpdate(); if (!isReadyFromCache) { isReadyFromCache = true; var metadataReadyFromCache = { initialCacheLoad: true, lastUpdateTimestamp: undefined // No cache timestamp when fresh install }; gate.emit(SDK_READY_FROM_CACHE, metadataReadyFromCache); } gate.emit(SDK_READY, metadataReady); } catch (e) { // throws user callback exceptions in next tick setTimeout(function () { throw e; }, 0); } } } } return { definitions: definitions, segments: segments, gate: gate, shared: function () { return readinessManagerFactory(EventEmitter, settings, definitions, true); }, // @TODO review/remove next methods when non-recoverable errors are reworked // Called on consumer mode, when storage fails to connect timeout: timeout, // Called on 403 error (client-side SDK key on server-side), to set the SDK as destroyed for // tracking and evaluations, while keeping event listeners to emit SDK_READY_TIMED_OUT event setDestroyed: function () { isDestroyed = true; }, init: function () { if (definitions.hasInit) return; definitions.hasInit = true; definitions.initCallbacks.forEach(function (cb) { return cb(); }); }, destroy: function () { isDestroyed = true; syncLastUpdate(); clearTimeout(readyTimeoutId); if (!isShared) definitions.hasInit = false; }, isReady: function () { return isReady; }, isReadyFromCache: function () { return isReadyFromCache; }, isTimedout: function () { return hasTimedout && !isReady; }, hasTimedout: function () { return hasTimedout; }, isDestroyed: function () { return isDestroyed; }, isOperational: function () { return (isReady || isReadyFromCache) && !isDestroyed; }, lastUpdate: function () { return lastUpdate; }, metadataReady: function () { return metadataReady; } }; }