@splitsoftware/splitio-commons 1.17.1-rc.1 → 1.17.1-rc.3
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 +1 -0
- package/cjs/readiness/readinessManager.js +7 -5
- package/cjs/sdkClient/sdkClientMethodCS.js +7 -4
- package/cjs/sdkClient/sdkClientMethodCSWithTT.js +7 -4
- package/cjs/sdkFactory/index.js +33 -10
- package/cjs/storages/AbstractSplitsCacheAsync.js +0 -7
- package/cjs/storages/AbstractSplitsCacheSync.js +0 -7
- package/cjs/storages/dataLoader.js +64 -32
- package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +1 -9
- package/cjs/storages/inLocalStorage/index.js +6 -1
- package/cjs/storages/inMemory/InMemoryStorageCS.js +16 -4
- package/cjs/storages/inRedis/RedisAdapter.js +1 -1
- package/cjs/storages/pluggable/index.js +37 -32
- package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +2 -7
- package/cjs/sync/polling/updaters/splitChangesUpdater.js +1 -10
- package/cjs/trackers/eventTracker.js +11 -9
- package/cjs/trackers/impressionsTracker.js +15 -13
- package/cjs/trackers/uniqueKeysTracker.js +5 -3
- package/cjs/utils/settingsValidation/storage/storageCS.js +1 -12
- package/esm/readiness/readinessManager.js +7 -5
- package/esm/sdkClient/sdkClientMethodCS.js +7 -4
- package/esm/sdkClient/sdkClientMethodCSWithTT.js +7 -4
- package/esm/sdkFactory/index.js +34 -11
- package/esm/storages/AbstractSplitsCacheAsync.js +0 -7
- package/esm/storages/AbstractSplitsCacheSync.js +0 -7
- package/esm/storages/dataLoader.js +61 -30
- package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +1 -9
- package/esm/storages/inLocalStorage/index.js +7 -2
- package/esm/storages/inMemory/InMemoryStorageCS.js +16 -4
- package/esm/storages/inRedis/RedisAdapter.js +1 -1
- package/esm/storages/pluggable/index.js +37 -32
- package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +3 -8
- package/esm/sync/polling/updaters/splitChangesUpdater.js +2 -11
- package/esm/trackers/eventTracker.js +11 -9
- package/esm/trackers/impressionsTracker.js +15 -13
- package/esm/trackers/uniqueKeysTracker.js +5 -3
- package/esm/utils/settingsValidation/storage/storageCS.js +0 -10
- package/package.json +1 -1
- package/src/readiness/readinessManager.ts +9 -7
- package/src/readiness/types.ts +1 -0
- package/src/sdkClient/sdkClientMethodCS.ts +5 -2
- package/src/sdkClient/sdkClientMethodCSWithTT.ts +5 -2
- package/src/sdkFactory/index.ts +37 -12
- package/src/sdkFactory/types.ts +3 -0
- package/src/storages/AbstractSplitsCacheAsync.ts +0 -8
- package/src/storages/AbstractSplitsCacheSync.ts +0 -8
- package/src/storages/dataLoader.ts +62 -32
- package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +1 -10
- package/src/storages/inLocalStorage/index.ts +8 -2
- package/src/storages/inMemory/InMemoryStorageCS.ts +19 -4
- package/src/storages/inRedis/RedisAdapter.ts +1 -1
- package/src/storages/pluggable/index.ts +38 -33
- package/src/storages/types.ts +2 -6
- package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +3 -7
- package/src/sync/polling/updaters/splitChangesUpdater.ts +3 -11
- package/src/trackers/eventTracker.ts +10 -7
- package/src/trackers/impressionsTracker.ts +12 -9
- package/src/trackers/types.ts +1 -0
- package/src/trackers/uniqueKeysTracker.ts +6 -4
- package/src/types.ts +9 -8
- package/src/utils/settingsValidation/storage/storageCS.ts +0 -13
- package/types/readiness/types.d.ts +1 -0
- package/types/sdkFactory/types.d.ts +2 -0
- package/types/storages/AbstractSplitsCacheAsync.d.ts +0 -5
- package/types/storages/AbstractSplitsCacheSync.d.ts +0 -5
- package/types/storages/dataLoader.d.ts +17 -6
- package/types/storages/inLocalStorage/SplitsCacheInLocal.d.ts +0 -6
- package/types/storages/types.d.ts +2 -4
- 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 +0 -5
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { forOwn } from '../../../utils/lang';
|
|
2
2
|
import { syncTaskFactory } from '../../syncTask';
|
|
3
3
|
import { CONTROL } from '../../../utils/constants';
|
|
4
|
-
import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED
|
|
4
|
+
import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../../../readiness/constants';
|
|
5
5
|
import { SYNC_OFFLINE_DATA, ERROR_SYNC_OFFLINE_LOADING } from '../../../logger/constants';
|
|
6
6
|
/**
|
|
7
7
|
* Offline equivalent of `splitChangesUpdaterFactory`
|
|
@@ -43,13 +43,8 @@ export function fromObjectUpdaterFactory(splitsParser, storage, readiness, setti
|
|
|
43
43
|
readiness.splits.emit(SDK_SPLITS_ARRIVED);
|
|
44
44
|
if (startingUp) {
|
|
45
45
|
startingUp = false;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (cacheReady)
|
|
49
|
-
readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
|
|
50
|
-
// Emits SDK_READY
|
|
51
|
-
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
|
|
52
|
-
});
|
|
46
|
+
// Emits SDK_READY
|
|
47
|
+
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
|
|
53
48
|
}
|
|
54
49
|
return true;
|
|
55
50
|
});
|
|
@@ -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
|
|
3
|
+
import { SDK_SPLITS_ARRIVED } 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
|
+
return Promise.resolve(splitUpdateNotification ?
|
|
125
125
|
{ splits: [splitUpdateNotification.payload], till: splitUpdateNotification.changeNumber } :
|
|
126
126
|
splitChangesFetcher(since, noCache, till, _promiseDecorator))
|
|
127
127
|
.then(function (splitChanges) {
|
|
@@ -165,15 +165,6 @@ 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;
|
|
177
168
|
}
|
|
178
169
|
var sincePromise = Promise.resolve(splits.getChangeNumber()); // `getChangeNumber` never rejects or throws error
|
|
179
170
|
return sincePromise.then(_splitChangesUpdater);
|
|
@@ -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) {
|
|
@@ -20,14 +20,16 @@ export function eventTrackerFactory(settings, eventsCache, integrationsManager,
|
|
|
20
20
|
log.info(EVENTS_TRACKER_SUCCESS, [msg]);
|
|
21
21
|
if (integrationsManager) {
|
|
22
22
|
// Wrap in a timeout because we don't want it to be blocking.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
23
|
+
whenInit(function () {
|
|
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) {
|
|
@@ -51,18 +51,20 @@ export function impressionsTrackerFactory(settings, impressionsCache, strategy,
|
|
|
51
51
|
sdkLanguageVersion: version
|
|
52
52
|
};
|
|
53
53
|
// Wrap in a timeout because we don't want it to be blocking.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
integrationsManager
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
impressionListener
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
54
|
+
whenInit(function () {
|
|
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,12 +1,6 @@
|
|
|
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;
|
|
10
4
|
/**
|
|
11
5
|
* This function validates `settings.storage` object
|
|
12
6
|
*
|
|
@@ -23,10 +17,6 @@ export function validateStorageCS(settings) {
|
|
|
23
17
|
storage = InMemoryStorageCSFactory;
|
|
24
18
|
log.error(ERROR_STORAGE_INVALID);
|
|
25
19
|
}
|
|
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
|
-
}
|
|
30
20
|
if ([LOCALHOST_MODE, STANDALONE_MODE].indexOf(mode) === -1) {
|
|
31
21
|
// Consumer modes require an async storage
|
|
32
22
|
if (storage.type !== STORAGE_PLUGGABLE)
|
package/package.json
CHANGED
|
@@ -55,19 +55,15 @@ export function readinessManagerFactory(
|
|
|
55
55
|
|
|
56
56
|
// emit SDK_READY_TIMED_OUT
|
|
57
57
|
let hasTimedout = false;
|
|
58
|
+
let readyTimeoutId: ReturnType<typeof setTimeout>;
|
|
58
59
|
|
|
59
|
-
function timeout() {
|
|
60
|
-
if (hasTimedout) return;
|
|
60
|
+
function timeout() { // eslint-disable-next-line no-use-before-define
|
|
61
|
+
if (hasTimedout || isReady) return;
|
|
61
62
|
hasTimedout = true;
|
|
62
63
|
syncLastUpdate();
|
|
63
64
|
gate.emit(SDK_READY_TIMED_OUT, 'Split SDK emitted SDK_READY_TIMED_OUT event.');
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
let readyTimeoutId: ReturnType<typeof setTimeout>;
|
|
67
|
-
if (readyTimeout > 0) {
|
|
68
|
-
readyTimeoutId = setTimeout(timeout, readyTimeout);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
67
|
// emit SDK_READY and SDK_UPDATE
|
|
72
68
|
let isReady = false;
|
|
73
69
|
splits.on(SDK_SPLITS_ARRIVED, checkIsReadyOrUpdate);
|
|
@@ -132,6 +128,12 @@ export function readinessManagerFactory(
|
|
|
132
128
|
// tracking and evaluations, while keeping event listeners to emit SDK_READY_TIMED_OUT event
|
|
133
129
|
setDestroyed() { isDestroyed = true; },
|
|
134
130
|
|
|
131
|
+
init() {
|
|
132
|
+
if (readyTimeout > 0) {
|
|
133
|
+
readyTimeoutId = setTimeout(timeout, readyTimeout);
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
|
|
135
137
|
destroy() {
|
|
136
138
|
isDestroyed = true;
|
|
137
139
|
syncLastUpdate();
|
package/src/readiness/types.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { buildInstanceId } from './identity';
|
|
|
15
15
|
* Therefore, clients don't have a bound TT for the track method.
|
|
16
16
|
*/
|
|
17
17
|
export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (key?: SplitIO.SplitKey) => SplitIO.ICsClient {
|
|
18
|
-
const { clients, storage, syncManager, sdkReadinessManager, settings: { core: { key }, log } } = params;
|
|
18
|
+
const { clients, storage, syncManager, sdkReadinessManager, settings: { core: { key }, log }, whenInit } = params;
|
|
19
19
|
|
|
20
20
|
const mainClientInstance = clientCSDecorator(
|
|
21
21
|
log,
|
|
@@ -75,7 +75,10 @@ export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (key?: Spl
|
|
|
75
75
|
validKey
|
|
76
76
|
);
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
whenInit(() => {
|
|
79
|
+
sharedSdkReadiness.readinessManager.init();
|
|
80
|
+
sharedSyncManager && sharedSyncManager.start();
|
|
81
|
+
});
|
|
79
82
|
|
|
80
83
|
log.info(NEW_SHARED_CLIENT);
|
|
81
84
|
} else {
|
|
@@ -17,7 +17,7 @@ import { buildInstanceId } from './identity';
|
|
|
17
17
|
* (default client) or the client method (shared clients).
|
|
18
18
|
*/
|
|
19
19
|
export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (key?: SplitIO.SplitKey, trafficType?: string) => SplitIO.ICsClient {
|
|
20
|
-
const { clients, storage, syncManager, sdkReadinessManager, settings: { core: { key, trafficType }, log } } = params;
|
|
20
|
+
const { clients, storage, syncManager, sdkReadinessManager, settings: { core: { key, trafficType }, log }, whenInit } = params;
|
|
21
21
|
|
|
22
22
|
const mainClientInstance = clientCSDecorator(
|
|
23
23
|
log,
|
|
@@ -86,7 +86,10 @@ export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (key?: Spl
|
|
|
86
86
|
validTrafficType
|
|
87
87
|
);
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
whenInit(() => {
|
|
90
|
+
sharedSdkReadiness.readinessManager.init();
|
|
91
|
+
sharedSyncManager && sharedSyncManager.start();
|
|
92
|
+
});
|
|
90
93
|
|
|
91
94
|
log.info(NEW_SHARED_CLIENT);
|
|
92
95
|
} else {
|
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 } from '../readiness/constants';
|
|
10
|
+
import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_SPLITS_CACHE_LOADED } 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, isPure } = 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 isInit = false;
|
|
34
|
+
const initCallbacks: (() => void)[] = [];
|
|
35
|
+
|
|
36
|
+
function whenInit(cb: () => void) {
|
|
37
|
+
if (isInit) cb();
|
|
38
|
+
else initCallbacks.push(cb);
|
|
39
|
+
}
|
|
34
40
|
|
|
35
41
|
const sdkReadinessManager = sdkReadinessManagerFactory(platform.EventEmitter, settings);
|
|
36
42
|
const readiness = sdkReadinessManager.readinessManager;
|
|
@@ -46,8 +52,11 @@ 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
|
},
|
|
55
|
+
onReadyFromCacheCb: () => {
|
|
56
|
+
readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
|
|
57
|
+
}
|
|
49
58
|
});
|
|
50
|
-
|
|
59
|
+
|
|
51
60
|
const clients: Record<string, IBasicClient> = {};
|
|
52
61
|
const telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now);
|
|
53
62
|
const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage, telemetryTracker });
|
|
@@ -67,13 +76,13 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
67
76
|
strategy = strategyDebugFactory(observer);
|
|
68
77
|
}
|
|
69
78
|
|
|
70
|
-
const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, strategy, integrationsManager, storage.telemetry);
|
|
71
|
-
const eventTracker = eventTrackerFactory(settings, storage.events, integrationsManager, storage.telemetry);
|
|
79
|
+
const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, strategy, whenInit, integrationsManager, storage.telemetry);
|
|
80
|
+
const eventTracker = eventTrackerFactory(settings, storage.events, whenInit, integrationsManager, storage.telemetry);
|
|
72
81
|
|
|
73
82
|
// splitApi is used by SyncManager and Browser signal listener
|
|
74
83
|
const splitApi = splitApiFactory && splitApiFactory(settings, platform, telemetryTracker);
|
|
75
84
|
|
|
76
|
-
const ctx: ISdkFactoryContext = { clients, splitApi, eventTracker, impressionsTracker, telemetryTracker, uniqueKeysTracker, sdkReadinessManager, readiness, settings, storage, platform };
|
|
85
|
+
const ctx: ISdkFactoryContext = { clients, splitApi, eventTracker, impressionsTracker, telemetryTracker, uniqueKeysTracker, sdkReadinessManager, readiness, settings, storage, platform, whenInit };
|
|
77
86
|
|
|
78
87
|
const syncManager = syncManagerFactory && syncManagerFactory(ctx as ISdkFactoryContextSync);
|
|
79
88
|
ctx.syncManager = syncManager;
|
|
@@ -85,8 +94,24 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
85
94
|
const clientMethod = sdkClientMethodFactory(ctx);
|
|
86
95
|
const managerInstance = sdkManagerFactory(settings, storage.splits, sdkReadinessManager);
|
|
87
96
|
|
|
88
|
-
|
|
89
|
-
|
|
97
|
+
|
|
98
|
+
function init() {
|
|
99
|
+
if (isInit) return;
|
|
100
|
+
isInit = true;
|
|
101
|
+
|
|
102
|
+
// We will just log and allow for the SDK to end up throwing an SDK_TIMEOUT event for devs to handle.
|
|
103
|
+
validateAndTrackApiKey(log, settings.core.authorizationKey);
|
|
104
|
+
readiness.init();
|
|
105
|
+
storage.init && storage.init();
|
|
106
|
+
uniqueKeysTracker && uniqueKeysTracker.start();
|
|
107
|
+
syncManager && syncManager.start();
|
|
108
|
+
signalListener && signalListener.start();
|
|
109
|
+
|
|
110
|
+
initCallbacks.forEach((cb) => cb());
|
|
111
|
+
initCallbacks.length = 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!isPure) init();
|
|
90
115
|
|
|
91
116
|
log.info(NEW_FACTORY);
|
|
92
117
|
|
|
@@ -107,7 +132,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
107
132
|
settings,
|
|
108
133
|
|
|
109
134
|
destroy() {
|
|
110
|
-
return Promise.all(Object.keys(clients).map(key => clients[key].destroy())).then(() => {});
|
|
135
|
+
return Promise.all(Object.keys(clients).map(key => clients[key].destroy())).then(() => { });
|
|
111
136
|
}
|
|
112
|
-
}, extraProps && extraProps(ctx));
|
|
137
|
+
}, extraProps && extraProps(ctx), isPure && { init });
|
|
113
138
|
}
|
package/src/sdkFactory/types.ts
CHANGED
|
@@ -50,6 +50,7 @@ export interface ISdkFactoryContext {
|
|
|
50
50
|
splitApi?: ISplitApi
|
|
51
51
|
syncManager?: ISyncManager,
|
|
52
52
|
clients: Record<string, IBasicClient>,
|
|
53
|
+
whenInit(cb: () => void): void
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
export interface ISdkFactoryContextSync extends ISdkFactoryContext {
|
|
@@ -68,6 +69,8 @@ export interface ISdkFactoryContextAsync extends ISdkFactoryContext {
|
|
|
68
69
|
* Object parameter with the modules required to create an SDK factory instance
|
|
69
70
|
*/
|
|
70
71
|
export interface ISdkFactoryParams {
|
|
72
|
+
// If true, the `sdkFactory` is pure (no side effects), and the SDK instance includes a `init` method to run initialization side effects
|
|
73
|
+
isPure?: boolean,
|
|
71
74
|
|
|
72
75
|
// The settings must be already validated
|
|
73
76
|
settings: ISettings,
|
|
@@ -28,14 +28,6 @@ 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
|
-
|
|
39
31
|
/**
|
|
40
32
|
* Kill `name` split and set `defaultTreatment` and `changeNumber`.
|
|
41
33
|
* Used for SPLIT_KILL push notifications.
|
|
@@ -48,14 +48,6 @@ 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
|
-
|
|
59
51
|
/**
|
|
60
52
|
* Kill `name` split and set `defaultTreatment` and `changeNumber`.
|
|
61
53
|
* Used for SPLIT_KILL push notifications.
|
|
@@ -1,55 +1,85 @@
|
|
|
1
1
|
import { SplitIO } from '../types';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { ISegmentsCacheSync, ISplitsCacheSync, IStorageSync } from './types';
|
|
3
|
+
import { setToArray, ISet } from '../utils/lang/sets';
|
|
4
|
+
import { getMatching } from '../utils/key';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
|
-
*
|
|
7
|
+
* Storage-agnostic adaptation of `loadDataIntoLocalStorage` function
|
|
8
|
+
* (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js)
|
|
7
9
|
*
|
|
8
|
-
* @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader
|
|
9
|
-
*
|
|
10
|
-
* @
|
|
10
|
+
* @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader and extended with a `mySegmentsData` property.
|
|
11
|
+
* @param storage object containing `splits` and `segments` cache (client-side variant)
|
|
12
|
+
* @param userKey user key (matching key) of the provided MySegmentsCache
|
|
13
|
+
*
|
|
14
|
+
* @TODO extend to load largeSegments
|
|
15
|
+
* @TODO extend to load data on shared mySegments storages. Be specific when emitting SDK_READY_FROM_CACHE on shared clients. Maybe the serializer should provide the `useSegments` flag.
|
|
16
|
+
* @TODO add logs, and input validation in this module, in favor of size reduction.
|
|
17
|
+
* @TODO unit tests
|
|
11
18
|
*/
|
|
12
|
-
export function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
*
|
|
18
|
-
* @param storage object containing `splits` and `segments` cache (client-side variant)
|
|
19
|
-
* @param userId user key string of the provided MySegmentsCache
|
|
20
|
-
*
|
|
21
|
-
* @TODO extend to support SegmentsCache (server-side variant) by making `userId` optional and adding the corresponding logic.
|
|
22
|
-
* @TODO extend to load data on shared mySegments storages. Be specific when emitting SDK_READY_FROM_CACHE on shared clients. Maybe the serializer should provide the `useSegments` flag.
|
|
23
|
-
*/
|
|
24
|
-
return function loadData(storage: { splits: ISplitsCacheSync, segments: ISegmentsCacheSync }, userId: string) {
|
|
25
|
-
// Do not load data if current preloadedData is empty
|
|
26
|
-
if (Object.keys(preloadedData).length === 0) return;
|
|
27
|
-
|
|
28
|
-
const { lastUpdated = -1, segmentsData = {}, since = -1, splitsData = {} } = preloadedData;
|
|
19
|
+
export function loadData(preloadedData: SplitIO.PreloadedData, storage: { splits?: ISplitsCacheSync, segments: ISegmentsCacheSync, largeSegments?: ISegmentsCacheSync }, matchingKey?: string) {
|
|
20
|
+
// Do not load data if current preloadedData is empty
|
|
21
|
+
if (Object.keys(preloadedData).length === 0) return;
|
|
22
|
+
|
|
23
|
+
const { segmentsData = {}, since = -1, splitsData = [] } = preloadedData;
|
|
29
24
|
|
|
25
|
+
if (storage.splits) {
|
|
30
26
|
const storedSince = storage.splits.getChangeNumber();
|
|
31
|
-
const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
|
|
32
27
|
|
|
33
|
-
// Do not load data if current
|
|
34
|
-
|
|
35
|
-
if (storedSince > since || lastUpdated < expirationTimestamp) return;
|
|
28
|
+
// Do not load data if current data is more recent
|
|
29
|
+
if (storedSince > since) return;
|
|
36
30
|
|
|
37
31
|
// cleaning up the localStorage data, since some cached splits might need be part of the preloaded data
|
|
38
32
|
storage.splits.clear();
|
|
39
33
|
storage.splits.setChangeNumber(since);
|
|
40
34
|
|
|
41
35
|
// splitsData in an object where the property is the split name and the pertaining value is a stringified json of its data
|
|
42
|
-
storage.splits.addSplits(
|
|
36
|
+
storage.splits.addSplits(splitsData.map(split => ([split.name, split])));
|
|
37
|
+
}
|
|
43
38
|
|
|
44
|
-
|
|
45
|
-
let mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[
|
|
39
|
+
if (matchingKey) { // add mySegments data (client-side)
|
|
40
|
+
let mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[matchingKey];
|
|
46
41
|
if (!mySegmentsData) {
|
|
47
42
|
// segmentsData in an object where the property is the segment name and the pertaining value is a stringified object that contains the `added` array of userIds
|
|
48
43
|
mySegmentsData = Object.keys(segmentsData).filter(segmentName => {
|
|
49
|
-
const
|
|
50
|
-
return
|
|
44
|
+
const matchingKeys = segmentsData[segmentName];
|
|
45
|
+
return matchingKeys.indexOf(matchingKey) > -1;
|
|
51
46
|
});
|
|
52
47
|
}
|
|
53
48
|
storage.segments.resetSegments({ k: mySegmentsData.map(s => ({ n: s })) });
|
|
49
|
+
} else { // add segments data (server-side)
|
|
50
|
+
Object.keys(segmentsData).filter(segmentName => {
|
|
51
|
+
const matchingKeys = segmentsData[segmentName];
|
|
52
|
+
storage.segments.addToSegment(segmentName, matchingKeys);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function getSnapshot(storage: IStorageSync, userKeys?: SplitIO.SplitKey[]): SplitIO.PreloadedData {
|
|
58
|
+
return {
|
|
59
|
+
// lastUpdated: Date.now(),
|
|
60
|
+
since: storage.splits.getChangeNumber(),
|
|
61
|
+
splitsData: storage.splits.getAll(),
|
|
62
|
+
segmentsData: userKeys ?
|
|
63
|
+
undefined : // @ts-ignore accessing private prop
|
|
64
|
+
Object.keys(storage.segments.segmentCache).reduce((prev, cur) => { // @ts-ignore accessing private prop
|
|
65
|
+
prev[cur] = setToArray(storage.segments.segmentCache[cur] as ISet<string>);
|
|
66
|
+
return prev;
|
|
67
|
+
}, {}),
|
|
68
|
+
mySegmentsData: userKeys ?
|
|
69
|
+
userKeys.reduce<Record<string, string[]>>((prev, userKey) => {
|
|
70
|
+
prev[getMatching(userKey)] = storage.shared ?
|
|
71
|
+
// Client-side segments
|
|
72
|
+
// @ts-ignore accessing private prop
|
|
73
|
+
Object.keys(storage.shared(userKey).segments.segmentCache) :
|
|
74
|
+
// Server-side segments
|
|
75
|
+
// @ts-ignore accessing private prop
|
|
76
|
+
Object.keys(storage.segments.segmentCache).reduce<string[]>((prev, segmentName) => { // @ts-ignore accessing private prop
|
|
77
|
+
return storage.segments.segmentCache[segmentName].has(userKey) ?
|
|
78
|
+
prev.concat(segmentName) :
|
|
79
|
+
prev;
|
|
80
|
+
}, []);
|
|
81
|
+
return prev;
|
|
82
|
+
}, {}) :
|
|
83
|
+
undefined
|
|
54
84
|
};
|
|
55
85
|
}
|
|
@@ -217,15 +217,6 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
/**
|
|
221
|
-
* Check if the splits information is already stored in browser LocalStorage.
|
|
222
|
-
* In this function we could add more code to check if the data is valid.
|
|
223
|
-
* @override
|
|
224
|
-
*/
|
|
225
|
-
checkCache(): boolean {
|
|
226
|
-
return this.getChangeNumber() > -1;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
220
|
/**
|
|
230
221
|
* Clean Splits cache if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
|
|
231
222
|
*
|
|
@@ -250,7 +241,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
250
241
|
this.updateNewFilter = true;
|
|
251
242
|
|
|
252
243
|
// if there is cache, clear it
|
|
253
|
-
if (this.
|
|
244
|
+
if (this.getChangeNumber() > -1) this.clear();
|
|
254
245
|
|
|
255
246
|
} catch (e) {
|
|
256
247
|
this.log.error(LOG_PREFIX + e);
|
|
@@ -12,7 +12,7 @@ import { SplitsCacheInMemory } from '../inMemory/SplitsCacheInMemory';
|
|
|
12
12
|
import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../../utils/constants/browser';
|
|
13
13
|
import { InMemoryStorageCSFactory } from '../inMemory/InMemoryStorageCS';
|
|
14
14
|
import { LOG_PREFIX } from './constants';
|
|
15
|
-
import { DEBUG, NONE, STORAGE_LOCALSTORAGE } from '../../utils/constants';
|
|
15
|
+
import { DEBUG, LOCALHOST_MODE, NONE, STORAGE_LOCALSTORAGE } from '../../utils/constants';
|
|
16
16
|
import { shouldRecordTelemetry, TelemetryCacheInMemory } from '../inMemory/TelemetryCacheInMemory';
|
|
17
17
|
import { UniqueKeysCacheInMemoryCS } from '../inMemory/UniqueKeysCacheInMemoryCS';
|
|
18
18
|
import { getMatching } from '../../utils/key';
|
|
@@ -36,7 +36,7 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
|
|
|
36
36
|
return InMemoryStorageCSFactory(params);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
const { settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode, __splitFiltersValidation } } } = params;
|
|
39
|
+
const { onReadyFromCacheCb, settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode, __splitFiltersValidation } } } = params;
|
|
40
40
|
const matchingKey = getMatching(settings.core.key);
|
|
41
41
|
const keys = new KeyBuilderCS(prefix, matchingKey);
|
|
42
42
|
const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
|
|
@@ -55,6 +55,12 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
|
|
|
55
55
|
telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
|
|
56
56
|
uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS() : undefined,
|
|
57
57
|
|
|
58
|
+
init() {
|
|
59
|
+
if (settings.mode === LOCALHOST_MODE || splits.getChangeNumber() > -1) {
|
|
60
|
+
Promise.resolve().then(onReadyFromCacheCb);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
|
|
58
64
|
destroy() {
|
|
59
65
|
this.splits = new SplitsCacheInMemory(__splitFiltersValidation);
|
|
60
66
|
this.segments = new MySegmentsCacheInMemory();
|