@splitsoftware/splitio-commons 1.17.1-rc.2 → 1.17.1-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES.txt +4 -3
- package/cjs/readiness/readinessManager.js +13 -2
- package/cjs/sdkClient/sdkClientMethodCS.js +0 -1
- package/cjs/sdkClient/sdkClientMethodCSWithTT.js +0 -1
- package/cjs/sdkFactory/index.js +27 -11
- package/cjs/storages/{AbstractSegmentsCacheSync.js → AbstractMySegmentsCacheSync.js} +15 -17
- package/cjs/storages/AbstractSplitsCacheAsync.js +7 -0
- package/cjs/storages/AbstractSplitsCacheSync.js +7 -0
- package/cjs/storages/dataLoader.js +32 -65
- package/cjs/storages/inLocalStorage/MySegmentsCacheInLocal.js +5 -5
- package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +9 -1
- package/cjs/storages/inLocalStorage/index.js +1 -4
- package/cjs/storages/inMemory/InMemoryStorageCS.js +5 -17
- package/cjs/storages/inMemory/MySegmentsCacheInMemory.js +5 -5
- package/cjs/storages/inMemory/SegmentsCacheInMemory.js +13 -27
- package/cjs/storages/inMemory/SplitsCacheInMemory.js +0 -1
- package/cjs/storages/inRedis/SegmentsCacheInRedis.js +13 -19
- package/cjs/storages/pluggable/SegmentsCachePluggable.js +11 -32
- package/cjs/sync/offline/syncManagerOffline.js +18 -11
- package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +7 -2
- package/cjs/sync/polling/pollingManagerSS.js +3 -3
- package/cjs/sync/polling/updaters/segmentChangesUpdater.js +12 -28
- package/cjs/sync/polling/updaters/splitChangesUpdater.js +10 -1
- package/cjs/sync/syncManagerOnline.js +20 -21
- package/cjs/trackers/eventTracker.js +12 -10
- package/cjs/trackers/impressionsTracker.js +16 -14
- package/cjs/trackers/uniqueKeysTracker.js +5 -3
- package/cjs/utils/settingsValidation/storage/storageCS.js +12 -1
- package/esm/readiness/readinessManager.js +13 -2
- package/esm/sdkClient/sdkClientMethodCS.js +0 -1
- package/esm/sdkClient/sdkClientMethodCSWithTT.js +0 -1
- package/esm/sdkFactory/index.js +28 -12
- package/esm/storages/{AbstractSegmentsCacheSync.js → AbstractMySegmentsCacheSync.js} +14 -16
- package/esm/storages/AbstractSplitsCacheAsync.js +7 -0
- package/esm/storages/AbstractSplitsCacheSync.js +7 -0
- package/esm/storages/dataLoader.js +30 -62
- package/esm/storages/inLocalStorage/MySegmentsCacheInLocal.js +5 -5
- package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +9 -1
- package/esm/storages/inLocalStorage/index.js +2 -5
- package/esm/storages/inMemory/InMemoryStorageCS.js +5 -17
- package/esm/storages/inMemory/MySegmentsCacheInMemory.js +5 -5
- package/esm/storages/inMemory/SegmentsCacheInMemory.js +13 -27
- package/esm/storages/inMemory/SplitsCacheInMemory.js +0 -1
- package/esm/storages/inRedis/SegmentsCacheInRedis.js +13 -19
- package/esm/storages/pluggable/SegmentsCachePluggable.js +11 -32
- package/esm/sync/offline/syncManagerOffline.js +18 -11
- package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +8 -3
- package/esm/sync/polling/pollingManagerSS.js +3 -3
- package/esm/sync/polling/updaters/segmentChangesUpdater.js +12 -28
- package/esm/sync/polling/updaters/splitChangesUpdater.js +11 -2
- package/esm/sync/syncManagerOnline.js +20 -21
- package/esm/trackers/eventTracker.js +12 -10
- package/esm/trackers/impressionsTracker.js +16 -14
- package/esm/trackers/uniqueKeysTracker.js +5 -3
- package/esm/utils/settingsValidation/storage/storageCS.js +10 -0
- package/package.json +1 -1
- package/src/readiness/readinessManager.ts +12 -3
- package/src/readiness/types.ts +3 -0
- package/src/sdkClient/sdkClientMethodCS.ts +0 -2
- package/src/sdkClient/sdkClientMethodCSWithTT.ts +0 -2
- package/src/sdkFactory/index.ts +30 -14
- package/src/sdkFactory/types.ts +2 -0
- package/src/storages/{AbstractSegmentsCacheSync.ts → AbstractMySegmentsCacheSync.ts} +13 -28
- package/src/storages/AbstractSplitsCacheAsync.ts +8 -0
- package/src/storages/AbstractSplitsCacheSync.ts +8 -0
- package/src/storages/dataLoader.ts +32 -63
- package/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +5 -5
- package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +10 -1
- package/src/storages/inLocalStorage/index.ts +2 -6
- package/src/storages/inMemory/InMemoryStorageCS.ts +5 -20
- package/src/storages/inMemory/MySegmentsCacheInMemory.ts +5 -5
- package/src/storages/inMemory/SegmentsCacheInMemory.ts +12 -26
- package/src/storages/inMemory/SplitsCacheInMemory.ts +0 -1
- package/src/storages/inRedis/SegmentsCacheInRedis.ts +13 -22
- package/src/storages/pluggable/SegmentsCachePluggable.ts +11 -35
- package/src/storages/types.ts +9 -10
- package/src/sync/offline/syncManagerOffline.ts +21 -13
- package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +7 -3
- package/src/sync/polling/pollingManagerSS.ts +2 -3
- package/src/sync/polling/updaters/segmentChangesUpdater.ts +13 -29
- package/src/sync/polling/updaters/splitChangesUpdater.ts +11 -3
- package/src/sync/syncManagerOnline.ts +17 -17
- package/src/sync/types.ts +1 -1
- package/src/trackers/eventTracker.ts +11 -8
- package/src/trackers/impressionsTracker.ts +13 -10
- package/src/trackers/types.ts +1 -0
- package/src/trackers/uniqueKeysTracker.ts +6 -4
- package/src/types.ts +8 -9
- package/src/utils/settingsValidation/storage/storageCS.ts +13 -0
- package/types/readiness/types.d.ts +3 -0
- package/types/sdkFactory/types.d.ts +1 -0
- package/types/storages/AbstractSplitsCacheAsync.d.ts +5 -0
- package/types/storages/AbstractSplitsCacheSync.d.ts +5 -0
- package/types/storages/dataLoader.d.ts +6 -17
- package/types/storages/inLocalStorage/MySegmentsCacheInLocal.d.ts +5 -5
- package/types/storages/inLocalStorage/SplitsCacheInLocal.d.ts +6 -0
- package/types/storages/inMemory/MySegmentsCacheInMemory.d.ts +5 -5
- package/types/storages/inMemory/SegmentsCacheInMemory.d.ts +5 -7
- package/types/storages/inMemory/SplitsCacheInMemory.d.ts +0 -1
- package/types/storages/inRedis/SegmentsCacheInRedis.d.ts +6 -3
- package/types/storages/pluggable/SegmentsCachePluggable.d.ts +4 -16
- package/types/storages/types.d.ts +7 -10
- package/types/sync/types.d.ts +1 -1
- package/types/trackers/eventTracker.d.ts +1 -1
- package/types/trackers/impressionsTracker.d.ts +1 -1
- package/types/trackers/types.d.ts +1 -0
- package/types/types.d.ts +8 -8
- package/types/utils/settingsValidation/storage/storageCS.d.ts +5 -0
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { splitsSyncTaskFactory } from './syncTasks/splitsSyncTask';
|
|
2
2
|
import { segmentsSyncTaskFactory } from './syncTasks/segmentsSyncTask';
|
|
3
|
-
import { thenable } from '../../utils/promise/thenable';
|
|
4
3
|
import { POLLING_START, POLLING_STOP, LOG_PREFIX_SYNC_POLLING } from '../../logger/constants';
|
|
5
4
|
/**
|
|
6
5
|
* Expose start / stop mechanism for pulling data from services.
|
|
@@ -19,9 +18,10 @@ export function pollingManagerSSFactory(params) {
|
|
|
19
18
|
log.debug(LOG_PREFIX_SYNC_POLLING + ("Splits will be refreshed each " + settings.scheduler.featuresRefreshRate + " millis"));
|
|
20
19
|
log.debug(LOG_PREFIX_SYNC_POLLING + ("Segments will be refreshed each " + settings.scheduler.segmentsRefreshRate + " millis"));
|
|
21
20
|
var startingUp = splitsSyncTask.start();
|
|
22
|
-
if (
|
|
21
|
+
if (startingUp) {
|
|
23
22
|
startingUp.then(function () {
|
|
24
|
-
|
|
23
|
+
if (splitsSyncTask.isRunning())
|
|
24
|
+
segmentsSyncTask.start();
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
27
|
},
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { findIndex } from '../../../utils/lang';
|
|
2
1
|
import { SDK_SEGMENTS_ARRIVED } from '../../../readiness/constants';
|
|
3
2
|
import { LOG_PREFIX_INSTANTIATION, LOG_PREFIX_SYNC_SEGMENTS } from '../../../logger/constants';
|
|
4
|
-
import { thenable } from '../../../utils/promise/thenable';
|
|
5
3
|
/**
|
|
6
4
|
* Factory of SegmentChanges updater, a task that:
|
|
7
5
|
* - fetches segment changes using `segmentChangesFetcher`
|
|
@@ -20,27 +18,16 @@ export function segmentChangesUpdaterFactory(log, segmentChangesFetcher, segment
|
|
|
20
18
|
var sincePromise = Promise.resolve(segments.getChangeNumber(segmentName));
|
|
21
19
|
return sincePromise.then(function (since) {
|
|
22
20
|
// if fetchOnlyNew flag, avoid processing already fetched segments
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
results.push(segments.removeFromSegment(segmentName, x.removed));
|
|
33
|
-
if (x.added.length > 0 || x.removed.length > 0) {
|
|
34
|
-
results.push(segments.setChangeNumber(segmentName, x.till));
|
|
35
|
-
changeNumber = x.till;
|
|
36
|
-
}
|
|
37
|
-
log.debug(LOG_PREFIX_SYNC_SEGMENTS + "Processed " + segmentName + " with till = " + x.till + ". Added: " + x.added.length + ". Removed: " + x.removed.length);
|
|
21
|
+
return fetchOnlyNew && since !== -1 ?
|
|
22
|
+
false :
|
|
23
|
+
segmentChangesFetcher(since, segmentName, noCache, till).then(function (changes) {
|
|
24
|
+
return Promise.all(changes.map(function (x) {
|
|
25
|
+
log.debug(LOG_PREFIX_SYNC_SEGMENTS + "Processing " + segmentName + " with till = " + x.till + ". Added: " + x.added.length + ". Removed: " + x.removed.length);
|
|
26
|
+
return segments.update(x.name, x.added, x.removed, x.till);
|
|
27
|
+
})).then(function (updates) {
|
|
28
|
+
return updates.some(function (update) { return update; });
|
|
29
|
+
});
|
|
38
30
|
});
|
|
39
|
-
// If at least one storage operation result is a promise, join all in a single promise.
|
|
40
|
-
if (results.some(function (result) { return thenable(result); }))
|
|
41
|
-
return Promise.all(results).then(function () { return changeNumber; });
|
|
42
|
-
return changeNumber;
|
|
43
|
-
});
|
|
44
31
|
});
|
|
45
32
|
}
|
|
46
33
|
/**
|
|
@@ -59,14 +46,11 @@ export function segmentChangesUpdaterFactory(log, segmentChangesFetcher, segment
|
|
|
59
46
|
// If not a segment name provided, read list of available segments names to be updated.
|
|
60
47
|
var segmentsPromise = Promise.resolve(segmentName ? [segmentName] : segments.getRegisteredSegments());
|
|
61
48
|
return segmentsPromise.then(function (segmentNames) {
|
|
62
|
-
// Async fetchers
|
|
63
|
-
var updaters =
|
|
64
|
-
for (var index = 0; index < segmentNames.length; index++) {
|
|
65
|
-
updaters.push(updateSegment(segmentNames[index], noCache, till, fetchOnlyNew));
|
|
66
|
-
}
|
|
49
|
+
// Async fetchers
|
|
50
|
+
var updaters = segmentNames.map(function (segmentName) { return updateSegment(segmentName, noCache, till, fetchOnlyNew); });
|
|
67
51
|
return Promise.all(updaters).then(function (shouldUpdateFlags) {
|
|
68
52
|
// if at least one segment fetch succeeded, mark segments ready
|
|
69
|
-
if (
|
|
53
|
+
if (shouldUpdateFlags.some(function (update) { return update; }) || readyOnAlreadyExistentState) {
|
|
70
54
|
readyOnAlreadyExistentState = false;
|
|
71
55
|
if (readiness)
|
|
72
56
|
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { _Set, setToArray } from '../../../utils/lang/sets';
|
|
2
2
|
import { timeout } from '../../../utils/promise/timeout';
|
|
3
|
-
import { SDK_SPLITS_ARRIVED } from '../../../readiness/constants';
|
|
3
|
+
import { SDK_SPLITS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../../../readiness/constants';
|
|
4
4
|
import { SYNC_SPLITS_FETCH, SYNC_SPLITS_NEW, SYNC_SPLITS_REMOVED, SYNC_SPLITS_SEGMENTS, SYNC_SPLITS_FETCH_FAILS, SYNC_SPLITS_FETCH_RETRY } from '../../../logger/constants';
|
|
5
5
|
import { startsWith } from '../../../utils/lang';
|
|
6
6
|
import { IN_SEGMENT } from '../../../utils/constants';
|
|
@@ -121,7 +121,7 @@ export function splitChangesUpdaterFactory(log, splitChangesFetcher, splits, seg
|
|
|
121
121
|
function _splitChangesUpdater(since, retry) {
|
|
122
122
|
if (retry === void 0) { retry = 0; }
|
|
123
123
|
log.debug(SYNC_SPLITS_FETCH, [since]);
|
|
124
|
-
|
|
124
|
+
var fetcherPromise = Promise.resolve(splitUpdateNotification ?
|
|
125
125
|
{ splits: [splitUpdateNotification.payload], till: splitUpdateNotification.changeNumber } :
|
|
126
126
|
splitChangesFetcher(since, noCache, till, _promiseDecorator))
|
|
127
127
|
.then(function (splitChanges) {
|
|
@@ -165,6 +165,15 @@ export function splitChangesUpdaterFactory(log, splitChangesFetcher, splits, seg
|
|
|
165
165
|
}
|
|
166
166
|
return false;
|
|
167
167
|
});
|
|
168
|
+
// After triggering the requests, if we have cached splits information let's notify that to emit SDK_READY_FROM_CACHE.
|
|
169
|
+
// Wrapping in a promise since checkCache can be async.
|
|
170
|
+
if (splitsEventEmitter && startingUp) {
|
|
171
|
+
Promise.resolve(splits.checkCache()).then(function (isCacheReady) {
|
|
172
|
+
if (isCacheReady)
|
|
173
|
+
splitsEventEmitter.emit(SDK_SPLITS_CACHE_LOADED);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return fetcherPromise;
|
|
168
177
|
}
|
|
169
178
|
var sincePromise = Promise.resolve(splits.getChangeNumber()); // `getChangeNumber` never rejects or throws error
|
|
170
179
|
return sincePromise.then(_splitChangesUpdater);
|
|
@@ -115,33 +115,32 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
115
115
|
if (!pollingManager)
|
|
116
116
|
return;
|
|
117
117
|
var mySegmentsSyncTask = pollingManager.add(matchingKey, readinessManager, storage);
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (pollingManager.isRunning()) {
|
|
124
|
-
// if doing polling, we must start the periodic fetch of data
|
|
125
|
-
if (storage.splits.usesSegments())
|
|
126
|
-
mySegmentsSyncTask.start();
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
// if not polling, we must execute the sync task for the initial fetch
|
|
130
|
-
// of segments since `syncAll` was already executed when starting the main client
|
|
131
|
-
mySegmentsSyncTask.execute();
|
|
132
|
-
}
|
|
133
|
-
pushManager.add(matchingKey, mySegmentsSyncTask);
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
118
|
+
if (running) {
|
|
119
|
+
if (syncEnabled) {
|
|
120
|
+
if (pushManager) {
|
|
121
|
+
if (pollingManager.isRunning()) {
|
|
122
|
+
// if doing polling, we must start the periodic fetch of data
|
|
136
123
|
if (storage.splits.usesSegments())
|
|
137
124
|
mySegmentsSyncTask.start();
|
|
138
125
|
}
|
|
126
|
+
else {
|
|
127
|
+
// if not polling, we must execute the sync task for the initial fetch
|
|
128
|
+
// of segments since `syncAll` was already executed when starting the main client
|
|
129
|
+
mySegmentsSyncTask.execute();
|
|
130
|
+
}
|
|
131
|
+
pushManager.add(matchingKey, mySegmentsSyncTask);
|
|
139
132
|
}
|
|
140
133
|
else {
|
|
141
|
-
if (
|
|
142
|
-
mySegmentsSyncTask.
|
|
134
|
+
if (storage.splits.usesSegments())
|
|
135
|
+
mySegmentsSyncTask.start();
|
|
143
136
|
}
|
|
144
|
-
}
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
if (!readinessManager.isReady())
|
|
140
|
+
mySegmentsSyncTask.execute();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
145
144
|
stop: function () {
|
|
146
145
|
// check in case `client.destroy()` has been invoked more than once for the same client
|
|
147
146
|
var mySegmentsSyncTask = pollingManager.get(matchingKey);
|
|
@@ -9,7 +9,7 @@ import { isConsumerMode } from '../utils/settingsValidation/mode';
|
|
|
9
9
|
* @param eventsCache cache to save events
|
|
10
10
|
* @param integrationsManager optional event handler used for integrations
|
|
11
11
|
*/
|
|
12
|
-
export function eventTrackerFactory(settings, eventsCache, integrationsManager, telemetryCache) {
|
|
12
|
+
export function eventTrackerFactory(settings, eventsCache, whenInit, integrationsManager, telemetryCache) {
|
|
13
13
|
var log = settings.log, mode = settings.mode;
|
|
14
14
|
var isAsync = isConsumerMode(mode);
|
|
15
15
|
function queueEventsCallback(eventData, tracked) {
|
|
@@ -19,15 +19,17 @@ export function eventTrackerFactory(settings, eventsCache, integrationsManager,
|
|
|
19
19
|
if (tracked) {
|
|
20
20
|
log.info(EVENTS_TRACKER_SUCCESS, [msg]);
|
|
21
21
|
if (integrationsManager) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
whenInit(function () {
|
|
23
|
+
// Wrap in a timeout because we don't want it to be blocking.
|
|
24
|
+
setTimeout(function () {
|
|
25
|
+
// copy of event, to avoid unexpected behaviour if modified by integrations
|
|
26
|
+
var eventDataCopy = objectAssign({}, eventData);
|
|
27
|
+
if (properties)
|
|
28
|
+
eventDataCopy.properties = objectAssign({}, properties);
|
|
29
|
+
// integrationsManager does not throw errors (they are internally handled by each integration module)
|
|
30
|
+
integrationsManager.handleEvent(eventDataCopy);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
35
|
else {
|
|
@@ -11,7 +11,7 @@ import { CONSENT_DECLINED, DEDUPED, QUEUED } from '../utils/constants';
|
|
|
11
11
|
* @param integrationsManager optional integrations manager
|
|
12
12
|
* @param strategy strategy for impressions tracking.
|
|
13
13
|
*/
|
|
14
|
-
export function impressionsTrackerFactory(settings, impressionsCache, strategy, integrationsManager, telemetryCache) {
|
|
14
|
+
export function impressionsTrackerFactory(settings, impressionsCache, strategy, whenInit, integrationsManager, telemetryCache) {
|
|
15
15
|
var log = settings.log, impressionListener = settings.impressionListener, _a = settings.runtime, ip = _a.ip, hostname = _a.hostname, version = settings.version;
|
|
16
16
|
return {
|
|
17
17
|
track: function (impressions, attributes) {
|
|
@@ -50,19 +50,21 @@ export function impressionsTrackerFactory(settings, impressionsCache, strategy,
|
|
|
50
50
|
hostname: hostname,
|
|
51
51
|
sdkLanguageVersion: version
|
|
52
52
|
};
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
integrationsManager
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
impressionListener
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
53
|
+
whenInit(function () {
|
|
54
|
+
// Wrap in a timeout because we don't want it to be blocking.
|
|
55
|
+
setTimeout(function () {
|
|
56
|
+
// integrationsManager.handleImpression does not throw errors
|
|
57
|
+
if (integrationsManager)
|
|
58
|
+
integrationsManager.handleImpression(impressionData);
|
|
59
|
+
try { // @ts-ignore. An exception on the listeners should not break the SDK.
|
|
60
|
+
if (impressionListener)
|
|
61
|
+
impressionListener.logImpression(impressionData);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
log.error(ERROR_IMPRESSIONS_LISTENER, [err]);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
});
|
|
66
68
|
};
|
|
67
69
|
for (var i = 0; i < impressionsToListenerCount; i++) {
|
|
68
70
|
_loop_1(i);
|
|
@@ -16,9 +16,6 @@ var noopFilterAdapter = {
|
|
|
16
16
|
export function uniqueKeysTrackerFactory(log, uniqueKeysCache, filterAdapter) {
|
|
17
17
|
if (filterAdapter === void 0) { filterAdapter = noopFilterAdapter; }
|
|
18
18
|
var intervalId;
|
|
19
|
-
if (filterAdapter.refreshRate) {
|
|
20
|
-
intervalId = setInterval(filterAdapter.clear, filterAdapter.refreshRate);
|
|
21
|
-
}
|
|
22
19
|
return {
|
|
23
20
|
track: function (key, featureName) {
|
|
24
21
|
if (!filterAdapter.add(key, featureName)) {
|
|
@@ -27,6 +24,11 @@ export function uniqueKeysTrackerFactory(log, uniqueKeysCache, filterAdapter) {
|
|
|
27
24
|
}
|
|
28
25
|
uniqueKeysCache.track(key, featureName);
|
|
29
26
|
},
|
|
27
|
+
start: function () {
|
|
28
|
+
if (filterAdapter.refreshRate) {
|
|
29
|
+
intervalId = setInterval(filterAdapter.clear, filterAdapter.refreshRate);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
30
32
|
stop: function () {
|
|
31
33
|
clearInterval(intervalId);
|
|
32
34
|
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { InMemoryStorageCSFactory } from '../../../storages/inMemory/InMemoryStorageCS';
|
|
2
2
|
import { ERROR_STORAGE_INVALID } from '../../../logger/constants';
|
|
3
3
|
import { LOCALHOST_MODE, STANDALONE_MODE, STORAGE_PLUGGABLE, STORAGE_LOCALSTORAGE, STORAGE_MEMORY } from '../../../utils/constants';
|
|
4
|
+
export function __InLocalStorageMockFactory(params) {
|
|
5
|
+
var result = InMemoryStorageCSFactory(params);
|
|
6
|
+
result.splits.checkCache = function () { return true; }; // to emit SDK_READY_FROM_CACHE
|
|
7
|
+
return result;
|
|
8
|
+
}
|
|
9
|
+
__InLocalStorageMockFactory.type = STORAGE_MEMORY;
|
|
4
10
|
/**
|
|
5
11
|
* This function validates `settings.storage` object
|
|
6
12
|
*
|
|
@@ -17,6 +23,10 @@ export function validateStorageCS(settings) {
|
|
|
17
23
|
storage = InMemoryStorageCSFactory;
|
|
18
24
|
log.error(ERROR_STORAGE_INVALID);
|
|
19
25
|
}
|
|
26
|
+
// In localhost mode with InLocalStorage, fallback to a mock InLocalStorage to emit SDK_READY_FROM_CACHE
|
|
27
|
+
if (mode === LOCALHOST_MODE && storage.type === STORAGE_LOCALSTORAGE) {
|
|
28
|
+
return __InLocalStorageMockFactory;
|
|
29
|
+
}
|
|
20
30
|
if ([LOCALHOST_MODE, STANDALONE_MODE].indexOf(mode) === -1) {
|
|
21
31
|
// Consumer modes require an async storage
|
|
22
32
|
if (storage.type !== STORAGE_PLUGGABLE)
|
package/package.json
CHANGED
|
@@ -7,6 +7,8 @@ function splitsEventEmitterFactory(EventEmitter: new () => IEventEmitter): ISpli
|
|
|
7
7
|
const splitsEventEmitter = objectAssign(new EventEmitter(), {
|
|
8
8
|
splitsArrived: false,
|
|
9
9
|
splitsCacheLoaded: false,
|
|
10
|
+
initialized: false,
|
|
11
|
+
initCallbacks: []
|
|
10
12
|
});
|
|
11
13
|
|
|
12
14
|
// `isSplitKill` condition avoids an edge-case of wrongly emitting SDK_READY if:
|
|
@@ -56,8 +58,8 @@ export function readinessManagerFactory(
|
|
|
56
58
|
// emit SDK_READY_TIMED_OUT
|
|
57
59
|
let hasTimedout = false;
|
|
58
60
|
|
|
59
|
-
function timeout() {
|
|
60
|
-
if (hasTimedout) return;
|
|
61
|
+
function timeout() { // eslint-disable-next-line no-use-before-define
|
|
62
|
+
if (hasTimedout || isReady) return;
|
|
61
63
|
hasTimedout = true;
|
|
62
64
|
syncLastUpdate();
|
|
63
65
|
gate.emit(SDK_READY_TIMED_OUT, 'Split SDK emitted SDK_READY_TIMED_OUT event.');
|
|
@@ -65,7 +67,8 @@ export function readinessManagerFactory(
|
|
|
65
67
|
|
|
66
68
|
let readyTimeoutId: ReturnType<typeof setTimeout>;
|
|
67
69
|
if (readyTimeout > 0) {
|
|
68
|
-
readyTimeoutId = setTimeout(timeout, readyTimeout);
|
|
70
|
+
if (splits.initialized) readyTimeoutId = setTimeout(timeout, readyTimeout);
|
|
71
|
+
else splits.initCallbacks.push(() => { readyTimeoutId = setTimeout(timeout, readyTimeout); });
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
// emit SDK_READY and SDK_UPDATE
|
|
@@ -132,6 +135,12 @@ export function readinessManagerFactory(
|
|
|
132
135
|
// tracking and evaluations, while keeping event listeners to emit SDK_READY_TIMED_OUT event
|
|
133
136
|
setDestroyed() { isDestroyed = true; },
|
|
134
137
|
|
|
138
|
+
init() {
|
|
139
|
+
if (splits.initialized) return;
|
|
140
|
+
splits.initialized = true;
|
|
141
|
+
splits.initCallbacks.forEach(cb => cb());
|
|
142
|
+
},
|
|
143
|
+
|
|
135
144
|
destroy() {
|
|
136
145
|
isDestroyed = true;
|
|
137
146
|
syncLastUpdate();
|
package/src/readiness/types.ts
CHANGED
|
@@ -12,6 +12,8 @@ export interface ISplitsEventEmitter extends IEventEmitter {
|
|
|
12
12
|
once(event: ISplitsEvent, listener: (...args: any[]) => void): this;
|
|
13
13
|
splitsArrived: boolean
|
|
14
14
|
splitsCacheLoaded: boolean
|
|
15
|
+
initialized: boolean,
|
|
16
|
+
initCallbacks: (() => void)[]
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
/** Segments data emitter */
|
|
@@ -59,6 +61,7 @@ export interface IReadinessManager {
|
|
|
59
61
|
timeout(): void,
|
|
60
62
|
setDestroyed(): void,
|
|
61
63
|
destroy(): void,
|
|
64
|
+
init(): void,
|
|
62
65
|
|
|
63
66
|
/** for client-side */
|
|
64
67
|
shared(): IReadinessManager,
|
package/src/sdkFactory/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { IBasicClient, SplitIO } from '../types';
|
|
|
7
7
|
import { validateAndTrackApiKey } from '../utils/inputValidation/apiKey';
|
|
8
8
|
import { createLoggerAPI } from '../logger/sdkLogger';
|
|
9
9
|
import { NEW_FACTORY, RETRIEVE_MANAGER } from '../logger/constants';
|
|
10
|
-
import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED
|
|
10
|
+
import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../readiness/constants';
|
|
11
11
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
12
12
|
import { strategyDebugFactory } from '../trackers/strategy/strategyDebug';
|
|
13
13
|
import { strategyOptimizedFactory } from '../trackers/strategy/strategyOptimized';
|
|
@@ -23,14 +23,20 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
23
23
|
const { settings, platform, storageFactory, splitApiFactory, extraProps,
|
|
24
24
|
syncManagerFactory, SignalListener, impressionsObserverFactory,
|
|
25
25
|
integrationsManagerFactory, sdkManagerFactory, sdkClientMethodFactory,
|
|
26
|
-
filterAdapterFactory } = params;
|
|
26
|
+
filterAdapterFactory, lazyInit } = params;
|
|
27
27
|
const { log, sync: { impressionsMode } } = settings;
|
|
28
28
|
|
|
29
29
|
// @TODO handle non-recoverable errors, such as, global `fetch` not available, invalid SDK Key, etc.
|
|
30
30
|
// On non-recoverable errors, we should mark the SDK as destroyed and not start synchronization.
|
|
31
31
|
|
|
32
|
-
//
|
|
33
|
-
|
|
32
|
+
// initialization
|
|
33
|
+
let hasInit = false;
|
|
34
|
+
const initCallbacks: (() => void)[] = [];
|
|
35
|
+
|
|
36
|
+
function whenInit(cb: () => void) {
|
|
37
|
+
if (hasInit) cb();
|
|
38
|
+
else initCallbacks.push(cb);
|
|
39
|
+
}
|
|
34
40
|
|
|
35
41
|
const sdkReadinessManager = sdkReadinessManagerFactory(platform.EventEmitter, settings);
|
|
36
42
|
const readiness = sdkReadinessManager.readinessManager;
|
|
@@ -46,11 +52,8 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
46
52
|
readiness.splits.emit(SDK_SPLITS_ARRIVED);
|
|
47
53
|
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
|
|
48
54
|
},
|
|
49
|
-
onReadyFromCacheCb: () => {
|
|
50
|
-
readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
|
|
51
|
-
}
|
|
52
55
|
});
|
|
53
|
-
|
|
56
|
+
// @TODO add support for dataloader: `if (params.dataLoader) params.dataLoader(storage);`
|
|
54
57
|
const clients: Record<string, IBasicClient> = {};
|
|
55
58
|
const telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now);
|
|
56
59
|
const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage, telemetryTracker });
|
|
@@ -70,8 +73,8 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
70
73
|
strategy = strategyDebugFactory(observer);
|
|
71
74
|
}
|
|
72
75
|
|
|
73
|
-
const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, strategy, integrationsManager, storage.telemetry);
|
|
74
|
-
const eventTracker = eventTrackerFactory(settings, storage.events, integrationsManager, storage.telemetry);
|
|
76
|
+
const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, strategy, whenInit, integrationsManager, storage.telemetry);
|
|
77
|
+
const eventTracker = eventTrackerFactory(settings, storage.events, whenInit, integrationsManager, storage.telemetry);
|
|
75
78
|
|
|
76
79
|
// splitApi is used by SyncManager and Browser signal listener
|
|
77
80
|
const splitApi = splitApiFactory && splitApiFactory(settings, platform, telemetryTracker);
|
|
@@ -88,8 +91,21 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
88
91
|
const clientMethod = sdkClientMethodFactory(ctx);
|
|
89
92
|
const managerInstance = sdkManagerFactory(settings, storage.splits, sdkReadinessManager);
|
|
90
93
|
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
|
|
95
|
+
function init() {
|
|
96
|
+
if (hasInit) return;
|
|
97
|
+
hasInit = true;
|
|
98
|
+
|
|
99
|
+
// We will just log and allow for the SDK to end up throwing an SDK_TIMEOUT event for devs to handle.
|
|
100
|
+
validateAndTrackApiKey(log, settings.core.authorizationKey);
|
|
101
|
+
readiness.init();
|
|
102
|
+
uniqueKeysTracker && uniqueKeysTracker.start();
|
|
103
|
+
syncManager && syncManager.start();
|
|
104
|
+
signalListener && signalListener.start();
|
|
105
|
+
|
|
106
|
+
initCallbacks.forEach((cb) => cb());
|
|
107
|
+
initCallbacks.length = 0;
|
|
108
|
+
}
|
|
93
109
|
|
|
94
110
|
log.info(NEW_FACTORY);
|
|
95
111
|
|
|
@@ -110,7 +126,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
110
126
|
settings,
|
|
111
127
|
|
|
112
128
|
destroy() {
|
|
113
|
-
return Promise.all(Object.keys(clients).map(key => clients[key].destroy())).then(() => {});
|
|
129
|
+
return Promise.all(Object.keys(clients).map(key => clients[key].destroy())).then(() => { });
|
|
114
130
|
}
|
|
115
|
-
}, extraProps && extraProps(ctx));
|
|
131
|
+
}, extraProps && extraProps(ctx), lazyInit ? { init } : init());
|
|
116
132
|
}
|
package/src/sdkFactory/types.ts
CHANGED
|
@@ -68,6 +68,8 @@ export interface ISdkFactoryContextAsync extends ISdkFactoryContext {
|
|
|
68
68
|
* Object parameter with the modules required to create an SDK factory instance
|
|
69
69
|
*/
|
|
70
70
|
export interface ISdkFactoryParams {
|
|
71
|
+
// If true, the `sdkFactory` is pure (no side effects), and the SDK instance includes a `init` method to run initialization side effects
|
|
72
|
+
lazyInit?: boolean,
|
|
71
73
|
|
|
72
74
|
// The settings must be already validated
|
|
73
75
|
settings: ISettings,
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
-
/* eslint-disable no-unused-vars */
|
|
3
1
|
import { IMySegmentsResponse } from '../dtos/types';
|
|
4
2
|
import { MySegmentsData } from '../sync/polling/types';
|
|
5
3
|
import { ISegmentsCacheSync } from './types';
|
|
@@ -8,18 +6,11 @@ import { ISegmentsCacheSync } from './types';
|
|
|
8
6
|
* This class provides a skeletal implementation of the ISegmentsCacheSync interface
|
|
9
7
|
* to minimize the effort required to implement this interface.
|
|
10
8
|
*/
|
|
11
|
-
export abstract class
|
|
12
|
-
/**
|
|
13
|
-
* For server-side synchronizer: add `segmentKeys` list of keys to `name` segment.
|
|
14
|
-
* For client-side synchronizer: add `name` segment to the cache. `segmentKeys` is undefined.
|
|
15
|
-
*/
|
|
16
|
-
abstract addToSegment(name: string, segmentKeys?: string[]): boolean
|
|
9
|
+
export abstract class AbstractMySegmentsCacheSync implements ISegmentsCacheSync {
|
|
17
10
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
*/
|
|
22
|
-
abstract removeFromSegment(name: string, segmentKeys?: string[]): boolean
|
|
11
|
+
protected abstract addSegment(name: string): boolean
|
|
12
|
+
protected abstract removeSegment(name: string): boolean
|
|
13
|
+
protected abstract setChangeNumber(changeNumber?: number): boolean | void
|
|
23
14
|
|
|
24
15
|
/**
|
|
25
16
|
* For server-side synchronizer: check if `key` is in `name` segment.
|
|
@@ -34,11 +25,10 @@ export abstract class AbstractSegmentsCacheSync implements ISegmentsCacheSync {
|
|
|
34
25
|
this.resetSegments({});
|
|
35
26
|
}
|
|
36
27
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
registerSegments(names: string[]): boolean { return false; }
|
|
28
|
+
|
|
29
|
+
// No-op. Not used in client-side.
|
|
30
|
+
registerSegments(): boolean { return false; }
|
|
31
|
+
update() { return false; }
|
|
42
32
|
|
|
43
33
|
/**
|
|
44
34
|
* For server-side synchronizer: get the list of segments to fetch changes.
|
|
@@ -52,11 +42,6 @@ export abstract class AbstractSegmentsCacheSync implements ISegmentsCacheSync {
|
|
|
52
42
|
*/
|
|
53
43
|
abstract getKeysCount(): number
|
|
54
44
|
|
|
55
|
-
/**
|
|
56
|
-
* For server-side synchronizer: change number of `name` segment.
|
|
57
|
-
* For client-side synchronizer: change number of mySegments.
|
|
58
|
-
*/
|
|
59
|
-
abstract setChangeNumber(name?: string, changeNumber?: number): boolean | void
|
|
60
45
|
abstract getChangeNumber(name: string): number
|
|
61
46
|
|
|
62
47
|
/**
|
|
@@ -64,7 +49,7 @@ export abstract class AbstractSegmentsCacheSync implements ISegmentsCacheSync {
|
|
|
64
49
|
* For client-side synchronizer: it resets or updates the cache.
|
|
65
50
|
*/
|
|
66
51
|
resetSegments(segmentsData: MySegmentsData | IMySegmentsResponse): boolean {
|
|
67
|
-
this.setChangeNumber(
|
|
52
|
+
this.setChangeNumber(segmentsData.cn);
|
|
68
53
|
|
|
69
54
|
const { added, removed } = segmentsData as MySegmentsData;
|
|
70
55
|
|
|
@@ -72,11 +57,11 @@ export abstract class AbstractSegmentsCacheSync implements ISegmentsCacheSync {
|
|
|
72
57
|
let isDiff = false;
|
|
73
58
|
|
|
74
59
|
added.forEach(segment => {
|
|
75
|
-
isDiff = this.
|
|
60
|
+
isDiff = this.addSegment(segment) || isDiff;
|
|
76
61
|
});
|
|
77
62
|
|
|
78
63
|
removed.forEach(segment => {
|
|
79
|
-
isDiff = this.
|
|
64
|
+
isDiff = this.removeSegment(segment) || isDiff;
|
|
80
65
|
});
|
|
81
66
|
|
|
82
67
|
return isDiff;
|
|
@@ -97,11 +82,11 @@ export abstract class AbstractSegmentsCacheSync implements ISegmentsCacheSync {
|
|
|
97
82
|
|
|
98
83
|
// Slowest path => add and/or remove segments
|
|
99
84
|
for (let removeIndex = index; removeIndex < storedSegmentKeys.length; removeIndex++) {
|
|
100
|
-
this.
|
|
85
|
+
this.removeSegment(storedSegmentKeys[removeIndex]);
|
|
101
86
|
}
|
|
102
87
|
|
|
103
88
|
for (let addIndex = index; addIndex < names.length; addIndex++) {
|
|
104
|
-
this.
|
|
89
|
+
this.addSegment(names[addIndex]);
|
|
105
90
|
}
|
|
106
91
|
|
|
107
92
|
return true;
|
|
@@ -28,6 +28,14 @@ export abstract class AbstractSplitsCacheAsync implements ISplitsCacheAsync {
|
|
|
28
28
|
return Promise.resolve(true);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Check if the splits information is already stored in cache.
|
|
33
|
+
* Noop, just keeping the interface. This is used by client-side implementations only.
|
|
34
|
+
*/
|
|
35
|
+
checkCache(): Promise<boolean> {
|
|
36
|
+
return Promise.resolve(false);
|
|
37
|
+
}
|
|
38
|
+
|
|
31
39
|
/**
|
|
32
40
|
* Kill `name` split and set `defaultTreatment` and `changeNumber`.
|
|
33
41
|
* Used for SPLIT_KILL push notifications.
|
|
@@ -48,6 +48,14 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
|
|
|
48
48
|
|
|
49
49
|
abstract clear(): void
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Check if the splits information is already stored in cache. This data can be preloaded.
|
|
53
|
+
* It is used as condition to emit SDK_SPLITS_CACHE_LOADED, and then SDK_READY_FROM_CACHE.
|
|
54
|
+
*/
|
|
55
|
+
checkCache(): boolean {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
51
59
|
/**
|
|
52
60
|
* Kill `name` split and set `defaultTreatment` and `changeNumber`.
|
|
53
61
|
* Used for SPLIT_KILL push notifications.
|