@splitsoftware/splitio-commons 1.17.1-rc.3 → 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 -4
- package/cjs/sdkClient/sdkClientMethodCS.js +3 -7
- package/cjs/sdkClient/sdkClientMethodCSWithTT.js +3 -7
- package/cjs/sdkFactory/index.js +8 -13
- 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 -64
- package/cjs/storages/inLocalStorage/MySegmentsCacheInLocal.js +5 -5
- package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +9 -1
- package/cjs/storages/inLocalStorage/index.js +1 -6
- 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/RedisAdapter.js +1 -1
- package/cjs/storages/inRedis/SegmentsCacheInRedis.js +13 -19
- package/cjs/storages/pluggable/SegmentsCachePluggable.js +11 -32
- package/cjs/storages/pluggable/index.js +32 -37
- 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 +1 -1
- package/cjs/trackers/impressionsTracker.js +1 -1
- package/cjs/utils/settingsValidation/storage/storageCS.js +12 -1
- package/esm/readiness/readinessManager.js +13 -4
- package/esm/sdkClient/sdkClientMethodCS.js +3 -7
- package/esm/sdkClient/sdkClientMethodCSWithTT.js +3 -7
- package/esm/sdkFactory/index.js +9 -14
- 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 -61
- package/esm/storages/inLocalStorage/MySegmentsCacheInLocal.js +5 -5
- package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +9 -1
- package/esm/storages/inLocalStorage/index.js +2 -7
- 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/RedisAdapter.js +1 -1
- package/esm/storages/inRedis/SegmentsCacheInRedis.js +13 -19
- package/esm/storages/pluggable/SegmentsCachePluggable.js +11 -32
- package/esm/storages/pluggable/index.js +32 -37
- 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 +1 -1
- package/esm/trackers/impressionsTracker.js +1 -1
- package/esm/utils/settingsValidation/storage/storageCS.js +10 -0
- package/package.json +1 -1
- package/src/readiness/readinessManager.ts +11 -4
- package/src/readiness/types.ts +2 -0
- package/src/sdkClient/sdkClientMethodCS.ts +1 -6
- package/src/sdkClient/sdkClientMethodCSWithTT.ts +1 -6
- package/src/sdkFactory/index.ts +9 -15
- package/src/sdkFactory/types.ts +1 -2
- 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 -62
- package/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +5 -5
- package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +10 -1
- package/src/storages/inLocalStorage/index.ts +2 -8
- 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/RedisAdapter.ts +1 -1
- package/src/storages/inRedis/SegmentsCacheInRedis.ts +13 -22
- package/src/storages/pluggable/SegmentsCachePluggable.ts +11 -35
- package/src/storages/pluggable/index.ts +33 -38
- package/src/storages/types.ts +9 -11
- 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 +1 -1
- package/src/trackers/impressionsTracker.ts +1 -1
- package/src/types.ts +8 -9
- package/src/utils/settingsValidation/storage/storageCS.ts +13 -0
- package/types/readiness/types.d.ts +2 -0
- package/types/sdkFactory/types.d.ts +1 -2
- 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 -11
- package/types/sync/types.d.ts +1 -1
- package/types/types.d.ts +8 -8
- package/types/utils/settingsValidation/storage/storageCS.d.ts +5 -0
|
@@ -12,32 +12,19 @@ var SegmentsCachePluggable = /** @class */ (function () {
|
|
|
12
12
|
this.wrapper = wrapper;
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
* The returned promise is resolved
|
|
17
|
-
* or rejected if wrapper operation fails.
|
|
18
|
-
*/
|
|
19
|
-
SegmentsCachePluggable.prototype.addToSegment = function (name, segmentKeys) {
|
|
20
|
-
var segmentKey = this.keys.buildSegmentNameKey(name);
|
|
21
|
-
if (segmentKeys.length) {
|
|
22
|
-
return this.wrapper.addItems(segmentKey, segmentKeys);
|
|
23
|
-
}
|
|
24
|
-
else {
|
|
25
|
-
return Promise.resolve();
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
/**
|
|
29
|
-
* Remove a list of `segmentKeys` from the given segment `name`.
|
|
30
|
-
* The returned promise is resolved when the operation success
|
|
31
|
-
* or rejected if wrapper operation fails.
|
|
15
|
+
* Update the given segment `name` with the lists of `addedKeys`, `removedKeys` and `changeNumber`.
|
|
16
|
+
* The returned promise is resolved if the operation success, with `true` if the segment was updated (i.e., some key was added or removed),
|
|
17
|
+
* or rejected if it fails (e.g., wrapper operation fails).
|
|
32
18
|
*/
|
|
33
|
-
SegmentsCachePluggable.prototype.
|
|
19
|
+
SegmentsCachePluggable.prototype.update = function (name, addedKeys, removedKeys, changeNumber) {
|
|
34
20
|
var segmentKey = this.keys.buildSegmentNameKey(name);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
21
|
+
return Promise.all([
|
|
22
|
+
addedKeys.length && this.wrapper.addItems(segmentKey, addedKeys),
|
|
23
|
+
removedKeys.length && this.wrapper.removeItems(segmentKey, removedKeys),
|
|
24
|
+
this.wrapper.set(this.keys.buildSegmentTillKey(name), changeNumber + '')
|
|
25
|
+
]).then(function () {
|
|
26
|
+
return addedKeys.length > 0 || removedKeys.length > 0;
|
|
27
|
+
});
|
|
41
28
|
};
|
|
42
29
|
/**
|
|
43
30
|
* Returns a promise that resolves with a boolean value indicating if `key` is part of `name` segment.
|
|
@@ -46,14 +33,6 @@ var SegmentsCachePluggable = /** @class */ (function () {
|
|
|
46
33
|
SegmentsCachePluggable.prototype.isInSegment = function (name, key) {
|
|
47
34
|
return this.wrapper.itemContains(this.keys.buildSegmentNameKey(name), key);
|
|
48
35
|
};
|
|
49
|
-
/**
|
|
50
|
-
* Set till number for the given segment `name`.
|
|
51
|
-
* The returned promise is resolved when the operation success,
|
|
52
|
-
* or rejected if it fails (e.g., wrapper operation fails).
|
|
53
|
-
*/
|
|
54
|
-
SegmentsCachePluggable.prototype.setChangeNumber = function (name, changeNumber) {
|
|
55
|
-
return this.wrapper.set(this.keys.buildSegmentTillKey(name), changeNumber + '');
|
|
56
|
-
};
|
|
57
36
|
/**
|
|
58
37
|
* Get till number or -1 if it's not defined.
|
|
59
38
|
* The returned promise is resolved with the changeNumber or -1 if it doesn't exist or a wrapper operation fails.
|
|
@@ -55,7 +55,6 @@ export function PluggableStorage(options) {
|
|
|
55
55
|
var metadata = metadataBuilder(settings);
|
|
56
56
|
var keys = new KeyBuilderSS(prefix, metadata);
|
|
57
57
|
var wrapper = wrapperAdapter(log, options.wrapper);
|
|
58
|
-
var connectPromise;
|
|
59
58
|
var isSyncronizer = mode === undefined; // If mode is not defined, the synchronizer is running
|
|
60
59
|
var isPartialConsumer = mode === CONSUMER_PARTIAL_MODE;
|
|
61
60
|
var telemetry = shouldRecordTelemetry(params) || isSyncronizer ?
|
|
@@ -73,6 +72,37 @@ export function PluggableStorage(options) {
|
|
|
73
72
|
settings.core.key === undefined ? new UniqueKeysCacheInMemory() : new UniqueKeysCacheInMemoryCS() :
|
|
74
73
|
new UniqueKeysCachePluggable(log, keys.buildUniqueKeysKey(), wrapper) :
|
|
75
74
|
undefined;
|
|
75
|
+
// Connects to wrapper and emits SDK_READY event on main client
|
|
76
|
+
var connectPromise = wrapper.connect().then(function () {
|
|
77
|
+
if (isSyncronizer) {
|
|
78
|
+
// In standalone or producer mode, clear storage if SDK key or feature flag filter has changed
|
|
79
|
+
return wrapper.get(keys.buildHashKey()).then(function (hash) {
|
|
80
|
+
var currentHash = getStorageHash(settings);
|
|
81
|
+
if (hash !== currentHash) {
|
|
82
|
+
log.info(LOG_PREFIX + 'Storage HASH has changed (SDK key, flags filter criteria or flags spec version was modified). Clearing cache');
|
|
83
|
+
return wrapper.getKeysByPrefix(keys.prefix + ".").then(function (storageKeys) {
|
|
84
|
+
return Promise.all(storageKeys.map(function (storageKey) { return wrapper.del(storageKey); }));
|
|
85
|
+
}).then(function () { return wrapper.set(keys.buildHashKey(), currentHash); });
|
|
86
|
+
}
|
|
87
|
+
}).then(function () {
|
|
88
|
+
onReadyCb();
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
// Start periodic flush of async storages if not running synchronizer (producer mode)
|
|
93
|
+
if (impressionCountsCache && impressionCountsCache.start)
|
|
94
|
+
impressionCountsCache.start();
|
|
95
|
+
if (uniqueKeysCache && uniqueKeysCache.start)
|
|
96
|
+
uniqueKeysCache.start();
|
|
97
|
+
if (telemetry && telemetry.recordConfig)
|
|
98
|
+
telemetry.recordConfig();
|
|
99
|
+
onReadyCb();
|
|
100
|
+
}
|
|
101
|
+
}).catch(function (e) {
|
|
102
|
+
e = e || new Error('Error connecting wrapper');
|
|
103
|
+
onReadyCb(e);
|
|
104
|
+
return e; // Propagate error for shared clients
|
|
105
|
+
});
|
|
76
106
|
return {
|
|
77
107
|
splits: new SplitsCachePluggable(log, keys, wrapper, settings.sync.__splitFiltersValidation),
|
|
78
108
|
segments: new SegmentsCachePluggable(log, keys, wrapper),
|
|
@@ -81,41 +111,6 @@ export function PluggableStorage(options) {
|
|
|
81
111
|
events: isPartialConsumer ? promisifyEventsTrack(new EventsCacheInMemory(eventsQueueSize)) : new EventsCachePluggable(log, keys.buildEventsKey(), wrapper, metadata),
|
|
82
112
|
telemetry: telemetry,
|
|
83
113
|
uniqueKeys: uniqueKeysCache,
|
|
84
|
-
init: function () {
|
|
85
|
-
if (connectPromise)
|
|
86
|
-
return connectPromise;
|
|
87
|
-
// Connects to wrapper and emits SDK_READY event on main client
|
|
88
|
-
return connectPromise = wrapper.connect().then(function () {
|
|
89
|
-
if (isSyncronizer) {
|
|
90
|
-
// In standalone or producer mode, clear storage if SDK key or feature flag filter has changed
|
|
91
|
-
return wrapper.get(keys.buildHashKey()).then(function (hash) {
|
|
92
|
-
var currentHash = getStorageHash(settings);
|
|
93
|
-
if (hash !== currentHash) {
|
|
94
|
-
log.info(LOG_PREFIX + 'Storage HASH has changed (SDK key, flags filter criteria or flags spec version was modified). Clearing cache');
|
|
95
|
-
return wrapper.getKeysByPrefix(keys.prefix + ".").then(function (storageKeys) {
|
|
96
|
-
return Promise.all(storageKeys.map(function (storageKey) { return wrapper.del(storageKey); }));
|
|
97
|
-
}).then(function () { return wrapper.set(keys.buildHashKey(), currentHash); });
|
|
98
|
-
}
|
|
99
|
-
}).then(function () {
|
|
100
|
-
onReadyCb();
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
// Start periodic flush of async storages if not running synchronizer (producer mode)
|
|
105
|
-
if (impressionCountsCache && impressionCountsCache.start)
|
|
106
|
-
impressionCountsCache.start();
|
|
107
|
-
if (uniqueKeysCache && uniqueKeysCache.start)
|
|
108
|
-
uniqueKeysCache.start();
|
|
109
|
-
if (telemetry && telemetry.recordConfig)
|
|
110
|
-
telemetry.recordConfig();
|
|
111
|
-
onReadyCb();
|
|
112
|
-
}
|
|
113
|
-
}).catch(function (e) {
|
|
114
|
-
e = e || new Error('Error connecting wrapper');
|
|
115
|
-
onReadyCb(e);
|
|
116
|
-
return e; // Propagate error for shared clients
|
|
117
|
-
});
|
|
118
|
-
},
|
|
119
114
|
// Stop periodic flush and disconnect the underlying storage
|
|
120
115
|
destroy: function () {
|
|
121
116
|
return Promise.all(isSyncronizer ? [] : [
|
|
@@ -125,7 +120,7 @@ export function PluggableStorage(options) {
|
|
|
125
120
|
},
|
|
126
121
|
// emits SDK_READY event on shared clients and returns a reference to the storage
|
|
127
122
|
shared: function (_, onReadyCb) {
|
|
128
|
-
|
|
123
|
+
connectPromise.then(onReadyCb);
|
|
129
124
|
return __assign(__assign({}, this), {
|
|
130
125
|
// no-op destroy, to disconnect the wrapper only when the main client is destroyed
|
|
131
126
|
destroy: function () { } });
|
|
@@ -16,23 +16,30 @@ export function syncManagerOfflineFactory(splitsParserFactory) {
|
|
|
16
16
|
*/
|
|
17
17
|
return function (_a) {
|
|
18
18
|
var settings = _a.settings, readiness = _a.readiness, storage = _a.storage;
|
|
19
|
-
|
|
19
|
+
var mainSyncManager = fromObjectSyncTaskFactory(splitsParserFactory(), storage, readiness, settings);
|
|
20
|
+
var mainStart = mainSyncManager.start;
|
|
21
|
+
var sharedStarts = [];
|
|
22
|
+
return objectAssign(mainSyncManager, {
|
|
23
|
+
start: function () {
|
|
24
|
+
mainStart();
|
|
25
|
+
sharedStarts.forEach(function (cb) { return cb(); });
|
|
26
|
+
sharedStarts.length = 0;
|
|
27
|
+
},
|
|
20
28
|
// fake flush, that resolves immediately
|
|
21
29
|
flush: flush,
|
|
22
30
|
// [Only used for client-side]
|
|
23
31
|
shared: function (matchingKey, readinessManager) {
|
|
32
|
+
// In LOCALHOST mode, shared clients are ready in the next event-loop cycle than created
|
|
33
|
+
// SDK_READY cannot be emitted directly because this will not update the readiness status
|
|
34
|
+
function emitSdkReady() {
|
|
35
|
+
readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED); // SDK_SPLITS_ARRIVED emitted by main SyncManager
|
|
36
|
+
}
|
|
37
|
+
if (mainSyncManager.isRunning())
|
|
38
|
+
setTimeout(emitSdkReady);
|
|
39
|
+
else
|
|
40
|
+
sharedStarts.push(emitSdkReady);
|
|
24
41
|
return {
|
|
25
|
-
start: function () {
|
|
26
|
-
// In LOCALHOST mode, shared clients are ready in the next event-loop cycle than created
|
|
27
|
-
// SDK_READY cannot be emitted directly because this will not update the readiness status
|
|
28
|
-
setTimeout(function () {
|
|
29
|
-
readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED); // SDK_SPLITS_ARRIVED emitted by main SyncManager
|
|
30
|
-
}, 0);
|
|
31
|
-
},
|
|
32
42
|
stop: function () { },
|
|
33
|
-
isRunning: function () {
|
|
34
|
-
return true;
|
|
35
|
-
},
|
|
36
43
|
flush: flush,
|
|
37
44
|
};
|
|
38
45
|
}
|
|
@@ -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 } from '../../../readiness/constants';
|
|
4
|
+
import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_SPLITS_CACHE_LOADED } 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,8 +43,13 @@ 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
|
-
|
|
46
|
+
Promise.resolve(splitsCache.checkCache()).then(function (cacheReady) {
|
|
47
|
+
// Emits SDK_READY_FROM_CACHE
|
|
48
|
+
if (cacheReady)
|
|
49
|
+
readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
|
|
50
|
+
// Emits SDK_READY
|
|
51
|
+
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
|
|
52
|
+
});
|
|
48
53
|
}
|
|
49
54
|
return true;
|
|
50
55
|
});
|
|
@@ -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);
|
|
@@ -19,8 +19,8 @@ export function eventTrackerFactory(settings, eventsCache, whenInit, integration
|
|
|
19
19
|
if (tracked) {
|
|
20
20
|
log.info(EVENTS_TRACKER_SUCCESS, [msg]);
|
|
21
21
|
if (integrationsManager) {
|
|
22
|
-
// Wrap in a timeout because we don't want it to be blocking.
|
|
23
22
|
whenInit(function () {
|
|
23
|
+
// Wrap in a timeout because we don't want it to be blocking.
|
|
24
24
|
setTimeout(function () {
|
|
25
25
|
// copy of event, to avoid unexpected behaviour if modified by integrations
|
|
26
26
|
var eventDataCopy = objectAssign({}, eventData);
|
|
@@ -50,8 +50,8 @@ export function impressionsTrackerFactory(settings, impressionsCache, strategy,
|
|
|
50
50
|
hostname: hostname,
|
|
51
51
|
sdkLanguageVersion: version
|
|
52
52
|
};
|
|
53
|
-
// Wrap in a timeout because we don't want it to be blocking.
|
|
54
53
|
whenInit(function () {
|
|
54
|
+
// Wrap in a timeout because we don't want it to be blocking.
|
|
55
55
|
setTimeout(function () {
|
|
56
56
|
// integrationsManager.handleImpression does not throw errors
|
|
57
57
|
if (integrationsManager)
|
|
@@ -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:
|
|
@@ -55,7 +57,6 @@ export function readinessManagerFactory(
|
|
|
55
57
|
|
|
56
58
|
// emit SDK_READY_TIMED_OUT
|
|
57
59
|
let hasTimedout = false;
|
|
58
|
-
let readyTimeoutId: ReturnType<typeof setTimeout>;
|
|
59
60
|
|
|
60
61
|
function timeout() { // eslint-disable-next-line no-use-before-define
|
|
61
62
|
if (hasTimedout || isReady) return;
|
|
@@ -64,6 +65,12 @@ export function readinessManagerFactory(
|
|
|
64
65
|
gate.emit(SDK_READY_TIMED_OUT, 'Split SDK emitted SDK_READY_TIMED_OUT event.');
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
let readyTimeoutId: ReturnType<typeof setTimeout>;
|
|
69
|
+
if (readyTimeout > 0) {
|
|
70
|
+
if (splits.initialized) readyTimeoutId = setTimeout(timeout, readyTimeout);
|
|
71
|
+
else splits.initCallbacks.push(() => { readyTimeoutId = setTimeout(timeout, readyTimeout); });
|
|
72
|
+
}
|
|
73
|
+
|
|
67
74
|
// emit SDK_READY and SDK_UPDATE
|
|
68
75
|
let isReady = false;
|
|
69
76
|
splits.on(SDK_SPLITS_ARRIVED, checkIsReadyOrUpdate);
|
|
@@ -129,9 +136,9 @@ export function readinessManagerFactory(
|
|
|
129
136
|
setDestroyed() { isDestroyed = true; },
|
|
130
137
|
|
|
131
138
|
init() {
|
|
132
|
-
if (
|
|
133
|
-
|
|
134
|
-
|
|
139
|
+
if (splits.initialized) return;
|
|
140
|
+
splits.initialized = true;
|
|
141
|
+
splits.initCallbacks.forEach(cb => cb());
|
|
135
142
|
},
|
|
136
143
|
|
|
137
144
|
destroy() {
|
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 */
|
|
@@ -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 }
|
|
18
|
+
const { clients, storage, syncManager, sdkReadinessManager, settings: { core: { key }, log } } = params;
|
|
19
19
|
|
|
20
20
|
const mainClientInstance = clientCSDecorator(
|
|
21
21
|
log,
|
|
@@ -75,11 +75,6 @@ export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (key?: Spl
|
|
|
75
75
|
validKey
|
|
76
76
|
);
|
|
77
77
|
|
|
78
|
-
whenInit(() => {
|
|
79
|
-
sharedSdkReadiness.readinessManager.init();
|
|
80
|
-
sharedSyncManager && sharedSyncManager.start();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
78
|
log.info(NEW_SHARED_CLIENT);
|
|
84
79
|
} else {
|
|
85
80
|
log.debug(RETRIEVE_CLIENT_EXISTING);
|
|
@@ -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 }
|
|
20
|
+
const { clients, storage, syncManager, sdkReadinessManager, settings: { core: { key, trafficType }, log } } = params;
|
|
21
21
|
|
|
22
22
|
const mainClientInstance = clientCSDecorator(
|
|
23
23
|
log,
|
|
@@ -86,11 +86,6 @@ export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (key?: Spl
|
|
|
86
86
|
validTrafficType
|
|
87
87
|
);
|
|
88
88
|
|
|
89
|
-
whenInit(() => {
|
|
90
|
-
sharedSdkReadiness.readinessManager.init();
|
|
91
|
-
sharedSyncManager && sharedSyncManager.start();
|
|
92
|
-
});
|
|
93
|
-
|
|
94
89
|
log.info(NEW_SHARED_CLIENT);
|
|
95
90
|
} else {
|
|
96
91
|
log.debug(RETRIEVE_CLIENT_EXISTING);
|
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,18 +23,18 @@ 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,
|
|
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
32
|
// initialization
|
|
33
|
-
let
|
|
33
|
+
let hasInit = false;
|
|
34
34
|
const initCallbacks: (() => void)[] = [];
|
|
35
35
|
|
|
36
36
|
function whenInit(cb: () => void) {
|
|
37
|
-
if (
|
|
37
|
+
if (hasInit) cb();
|
|
38
38
|
else initCallbacks.push(cb);
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -52,11 +52,8 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
52
52
|
readiness.splits.emit(SDK_SPLITS_ARRIVED);
|
|
53
53
|
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
|
|
54
54
|
},
|
|
55
|
-
onReadyFromCacheCb: () => {
|
|
56
|
-
readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
|
|
57
|
-
}
|
|
58
55
|
});
|
|
59
|
-
|
|
56
|
+
// @TODO add support for dataloader: `if (params.dataLoader) params.dataLoader(storage);`
|
|
60
57
|
const clients: Record<string, IBasicClient> = {};
|
|
61
58
|
const telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now);
|
|
62
59
|
const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage, telemetryTracker });
|
|
@@ -82,7 +79,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
82
79
|
// splitApi is used by SyncManager and Browser signal listener
|
|
83
80
|
const splitApi = splitApiFactory && splitApiFactory(settings, platform, telemetryTracker);
|
|
84
81
|
|
|
85
|
-
const ctx: ISdkFactoryContext = { clients, splitApi, eventTracker, impressionsTracker, telemetryTracker, uniqueKeysTracker, sdkReadinessManager, readiness, settings, storage, platform
|
|
82
|
+
const ctx: ISdkFactoryContext = { clients, splitApi, eventTracker, impressionsTracker, telemetryTracker, uniqueKeysTracker, sdkReadinessManager, readiness, settings, storage, platform };
|
|
86
83
|
|
|
87
84
|
const syncManager = syncManagerFactory && syncManagerFactory(ctx as ISdkFactoryContextSync);
|
|
88
85
|
ctx.syncManager = syncManager;
|
|
@@ -96,13 +93,12 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
96
93
|
|
|
97
94
|
|
|
98
95
|
function init() {
|
|
99
|
-
if (
|
|
100
|
-
|
|
96
|
+
if (hasInit) return;
|
|
97
|
+
hasInit = true;
|
|
101
98
|
|
|
102
99
|
// We will just log and allow for the SDK to end up throwing an SDK_TIMEOUT event for devs to handle.
|
|
103
100
|
validateAndTrackApiKey(log, settings.core.authorizationKey);
|
|
104
101
|
readiness.init();
|
|
105
|
-
storage.init && storage.init();
|
|
106
102
|
uniqueKeysTracker && uniqueKeysTracker.start();
|
|
107
103
|
syncManager && syncManager.start();
|
|
108
104
|
signalListener && signalListener.start();
|
|
@@ -111,8 +107,6 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
111
107
|
initCallbacks.length = 0;
|
|
112
108
|
}
|
|
113
109
|
|
|
114
|
-
if (!isPure) init();
|
|
115
|
-
|
|
116
110
|
log.info(NEW_FACTORY);
|
|
117
111
|
|
|
118
112
|
// @ts-ignore
|
|
@@ -134,5 +128,5 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
134
128
|
destroy() {
|
|
135
129
|
return Promise.all(Object.keys(clients).map(key => clients[key].destroy())).then(() => { });
|
|
136
130
|
}
|
|
137
|
-
}, extraProps && extraProps(ctx),
|
|
131
|
+
}, extraProps && extraProps(ctx), lazyInit ? { init } : init());
|
|
138
132
|
}
|