@splitsoftware/splitio-commons
Version:
Split JavaScript SDK common components
158 lines (157 loc) • 6.77 kB
JavaScript
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; }
};
}