@splitsoftware/splitio-commons 2.0.2 → 2.1.0-rc.0
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/README.md +2 -2
- package/cjs/listeners/node.js +2 -2
- package/cjs/storages/AbstractSplitsCacheAsync.js +0 -7
- package/cjs/storages/AbstractSplitsCacheSync.js +0 -7
- package/cjs/storages/KeyBuilderCS.js +3 -0
- package/cjs/storages/dataLoader.js +3 -2
- package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +1 -57
- package/cjs/storages/inLocalStorage/index.js +5 -3
- package/cjs/storages/inLocalStorage/validateCache.js +80 -0
- package/cjs/storages/pluggable/index.js +2 -1
- package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +3 -2
- package/cjs/sync/polling/updaters/splitChangesUpdater.js +1 -10
- package/cjs/sync/streaming/pushManager.js +8 -6
- package/cjs/sync/syncManagerOnline.js +10 -4
- package/cjs/trackers/eventTracker.js +1 -1
- package/cjs/trackers/impressionsTracker.js +1 -1
- package/cjs/utils/settingsValidation/index.js +1 -1
- package/cjs/utils/settingsValidation/storage/storageCS.js +1 -1
- package/esm/listeners/node.js +2 -2
- package/esm/storages/AbstractSplitsCacheAsync.js +0 -7
- package/esm/storages/AbstractSplitsCacheSync.js +0 -7
- package/esm/storages/KeyBuilderCS.js +3 -0
- package/esm/storages/dataLoader.js +2 -1
- package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +1 -57
- package/esm/storages/inLocalStorage/index.js +5 -3
- package/esm/storages/inLocalStorage/validateCache.js +76 -0
- package/esm/storages/pluggable/index.js +2 -1
- package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +3 -2
- package/esm/sync/polling/updaters/splitChangesUpdater.js +2 -11
- package/esm/sync/streaming/pushManager.js +8 -6
- package/esm/sync/syncManagerOnline.js +10 -4
- package/esm/trackers/eventTracker.js +1 -1
- package/esm/trackers/impressionsTracker.js +1 -1
- package/esm/utils/settingsValidation/index.js +1 -1
- package/esm/utils/settingsValidation/storage/storageCS.js +1 -1
- package/package.json +1 -1
- package/src/listeners/node.ts +2 -2
- package/src/storages/AbstractSplitsCacheAsync.ts +0 -8
- package/src/storages/AbstractSplitsCacheSync.ts +0 -8
- package/src/storages/KeyBuilderCS.ts +4 -0
- package/src/storages/dataLoader.ts +3 -1
- package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +1 -66
- package/src/storages/inLocalStorage/index.ts +8 -8
- package/src/storages/inLocalStorage/validateCache.ts +92 -0
- package/src/storages/pluggable/index.ts +2 -1
- package/src/storages/types.ts +1 -4
- package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +6 -5
- package/src/sync/polling/updaters/splitChangesUpdater.ts +2 -11
- package/src/sync/streaming/pushManager.ts +8 -6
- package/src/sync/syncManagerOnline.ts +11 -5
- package/src/trackers/eventTracker.ts +1 -1
- package/src/trackers/impressionsTracker.ts +1 -1
- package/src/utils/lang/index.ts +1 -1
- package/src/utils/settingsValidation/index.ts +1 -1
- package/src/utils/settingsValidation/storage/storageCS.ts +1 -1
- package/types/index.d.ts +1 -1
- package/types/splitio.d.ts +26 -2
- package/cjs/utils/constants/browser.js +0 -5
- package/esm/utils/constants/browser.js +0 -2
- package/src/utils/constants/browser.ts +0 -2
|
@@ -2,21 +2,17 @@ import { __extends } from "tslib";
|
|
|
2
2
|
import { AbstractSplitsCacheSync, usesSegments } from '../AbstractSplitsCacheSync';
|
|
3
3
|
import { isFiniteNumber, toNumber, isNaNNumber } from '../../utils/lang';
|
|
4
4
|
import { LOG_PREFIX } from './constants';
|
|
5
|
-
import { getStorageHash } from '../KeyBuilder';
|
|
6
5
|
import { setToArray } from '../../utils/lang/sets';
|
|
7
6
|
/**
|
|
8
7
|
* ISplitsCacheSync implementation that stores split definitions in browser LocalStorage.
|
|
9
8
|
*/
|
|
10
9
|
var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
11
10
|
__extends(SplitsCacheInLocal, _super);
|
|
12
|
-
function SplitsCacheInLocal(settings, keys
|
|
11
|
+
function SplitsCacheInLocal(settings, keys) {
|
|
13
12
|
var _this = _super.call(this) || this;
|
|
14
13
|
_this.keys = keys;
|
|
15
14
|
_this.log = settings.log;
|
|
16
|
-
_this.storageHash = getStorageHash(settings);
|
|
17
15
|
_this.flagSetsFilter = settings.sync.__splitFiltersValidation.groupedFilters.bySet;
|
|
18
|
-
_this._checkExpiration(expirationTimestamp);
|
|
19
|
-
_this._checkFilterQuery();
|
|
20
16
|
return _this;
|
|
21
17
|
}
|
|
22
18
|
SplitsCacheInLocal.prototype._decrementCount = function (key) {
|
|
@@ -64,7 +60,6 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
64
60
|
* We cannot simply call `localStorage.clear()` since that implies removing user items from the storage.
|
|
65
61
|
*/
|
|
66
62
|
SplitsCacheInLocal.prototype.clear = function () {
|
|
67
|
-
this.log.info(LOG_PREFIX + 'Flushing Splits data from localStorage');
|
|
68
63
|
// collect item keys
|
|
69
64
|
var len = localStorage.length;
|
|
70
65
|
var accum = [];
|
|
@@ -116,18 +111,6 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
116
111
|
return item && JSON.parse(item);
|
|
117
112
|
};
|
|
118
113
|
SplitsCacheInLocal.prototype.setChangeNumber = function (changeNumber) {
|
|
119
|
-
// when using a new split query, we must update it at the store
|
|
120
|
-
if (this.updateNewFilter) {
|
|
121
|
-
this.log.info(LOG_PREFIX + 'SDK key, flags filter criteria or flags spec version was modified. Updating cache');
|
|
122
|
-
var storageHashKey = this.keys.buildHashKey();
|
|
123
|
-
try {
|
|
124
|
-
localStorage.setItem(storageHashKey, this.storageHash);
|
|
125
|
-
}
|
|
126
|
-
catch (e) {
|
|
127
|
-
this.log.error(LOG_PREFIX + e);
|
|
128
|
-
}
|
|
129
|
-
this.updateNewFilter = false;
|
|
130
|
-
}
|
|
131
114
|
try {
|
|
132
115
|
localStorage.setItem(this.keys.buildSplitsTillKey(), changeNumber + '');
|
|
133
116
|
// update "last updated" timestamp with current time
|
|
@@ -178,45 +161,6 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
178
161
|
return true;
|
|
179
162
|
}
|
|
180
163
|
};
|
|
181
|
-
/**
|
|
182
|
-
* Check if the splits information is already stored in browser LocalStorage.
|
|
183
|
-
* In this function we could add more code to check if the data is valid.
|
|
184
|
-
* @override
|
|
185
|
-
*/
|
|
186
|
-
SplitsCacheInLocal.prototype.checkCache = function () {
|
|
187
|
-
return this.getChangeNumber() > -1;
|
|
188
|
-
};
|
|
189
|
-
/**
|
|
190
|
-
* Clean Splits cache if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
|
|
191
|
-
*
|
|
192
|
-
* @param expirationTimestamp - if the value is not a number, data will not be cleaned
|
|
193
|
-
*/
|
|
194
|
-
SplitsCacheInLocal.prototype._checkExpiration = function (expirationTimestamp) {
|
|
195
|
-
var value = localStorage.getItem(this.keys.buildLastUpdatedKey());
|
|
196
|
-
if (value !== null) {
|
|
197
|
-
value = parseInt(value, 10);
|
|
198
|
-
if (!isNaNNumber(value) && expirationTimestamp && value < expirationTimestamp)
|
|
199
|
-
this.clear();
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
|
-
// @TODO eventually remove `_checkFilterQuery`. Cache should be cleared at the storage level, reusing same logic than PluggableStorage
|
|
203
|
-
SplitsCacheInLocal.prototype._checkFilterQuery = function () {
|
|
204
|
-
var storageHashKey = this.keys.buildHashKey();
|
|
205
|
-
var storageHash = localStorage.getItem(storageHashKey);
|
|
206
|
-
if (storageHash !== this.storageHash) {
|
|
207
|
-
try {
|
|
208
|
-
// mark cache to update the new query filter on first successful splits fetch
|
|
209
|
-
this.updateNewFilter = true;
|
|
210
|
-
// if there is cache, clear it
|
|
211
|
-
if (this.checkCache())
|
|
212
|
-
this.clear();
|
|
213
|
-
}
|
|
214
|
-
catch (e) {
|
|
215
|
-
this.log.error(LOG_PREFIX + e);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
// if the filter didn't change, nothing is done
|
|
219
|
-
};
|
|
220
164
|
SplitsCacheInLocal.prototype.getNamesByFlagSets = function (flagSets) {
|
|
221
165
|
var _this = this;
|
|
222
166
|
return flagSets.map(function (flagSet) {
|
|
@@ -6,13 +6,13 @@ import { KeyBuilderCS, myLargeSegmentsKeyBuilder } from '../KeyBuilderCS';
|
|
|
6
6
|
import { isLocalStorageAvailable } from '../../utils/env/isLocalStorageAvailable';
|
|
7
7
|
import { SplitsCacheInLocal } from './SplitsCacheInLocal';
|
|
8
8
|
import { MySegmentsCacheInLocal } from './MySegmentsCacheInLocal';
|
|
9
|
-
import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../../utils/constants/browser';
|
|
10
9
|
import { InMemoryStorageCSFactory } from '../inMemory/InMemoryStorageCS';
|
|
11
10
|
import { LOG_PREFIX } from './constants';
|
|
12
11
|
import { DEBUG, NONE, STORAGE_LOCALSTORAGE } from '../../utils/constants';
|
|
13
12
|
import { shouldRecordTelemetry, TelemetryCacheInMemory } from '../inMemory/TelemetryCacheInMemory';
|
|
14
13
|
import { UniqueKeysCacheInMemoryCS } from '../inMemory/UniqueKeysCacheInMemoryCS';
|
|
15
14
|
import { getMatching } from '../../utils/key';
|
|
15
|
+
import { validateCache } from './validateCache';
|
|
16
16
|
/**
|
|
17
17
|
* InLocal storage factory for standalone client-side SplitFactory
|
|
18
18
|
*/
|
|
@@ -28,8 +28,7 @@ export function InLocalStorage(options) {
|
|
|
28
28
|
var settings = params.settings, _a = params.settings, log = _a.log, _b = _a.scheduler, impressionsQueueSize = _b.impressionsQueueSize, eventsQueueSize = _b.eventsQueueSize, impressionsMode = _a.sync.impressionsMode;
|
|
29
29
|
var matchingKey = getMatching(settings.core.key);
|
|
30
30
|
var keys = new KeyBuilderCS(prefix, matchingKey);
|
|
31
|
-
var
|
|
32
|
-
var splits = new SplitsCacheInLocal(settings, keys, expirationTimestamp);
|
|
31
|
+
var splits = new SplitsCacheInLocal(settings, keys);
|
|
33
32
|
var segments = new MySegmentsCacheInLocal(log, keys);
|
|
34
33
|
var largeSegments = new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey));
|
|
35
34
|
return {
|
|
@@ -41,6 +40,9 @@ export function InLocalStorage(options) {
|
|
|
41
40
|
events: new EventsCacheInMemory(eventsQueueSize),
|
|
42
41
|
telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
|
|
43
42
|
uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS() : undefined,
|
|
43
|
+
validateCache: function () {
|
|
44
|
+
return validateCache(options, settings, keys, splits, segments, largeSegments);
|
|
45
|
+
},
|
|
44
46
|
destroy: function () { },
|
|
45
47
|
// When using shared instantiation with MEMORY we reuse everything but segments (they are customer per key).
|
|
46
48
|
shared: function (matchingKey) {
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { isFiniteNumber, isNaNNumber } from '../../utils/lang';
|
|
2
|
+
import { getStorageHash } from '../KeyBuilder';
|
|
3
|
+
import { LOG_PREFIX } from './constants';
|
|
4
|
+
// milliseconds in a day
|
|
5
|
+
var DEFAULT_CACHE_EXPIRATION_IN_DAYS = 10;
|
|
6
|
+
var MILLIS_IN_A_DAY = 86400000;
|
|
7
|
+
/**
|
|
8
|
+
* Validates if cache should be cleared and sets the cache `hash` if needed.
|
|
9
|
+
*
|
|
10
|
+
* @returns `true` if cache should be cleared, `false` otherwise
|
|
11
|
+
*/
|
|
12
|
+
function validateExpiration(options, settings, keys, currentTimestamp, isThereCache) {
|
|
13
|
+
var log = settings.log;
|
|
14
|
+
// Check expiration
|
|
15
|
+
var lastUpdatedTimestamp = parseInt(localStorage.getItem(keys.buildLastUpdatedKey()), 10);
|
|
16
|
+
if (!isNaNNumber(lastUpdatedTimestamp)) {
|
|
17
|
+
var cacheExpirationInDays = isFiniteNumber(options.expirationDays) && options.expirationDays >= 1 ? options.expirationDays : DEFAULT_CACHE_EXPIRATION_IN_DAYS;
|
|
18
|
+
var expirationTimestamp = currentTimestamp - MILLIS_IN_A_DAY * cacheExpirationInDays;
|
|
19
|
+
if (lastUpdatedTimestamp < expirationTimestamp) {
|
|
20
|
+
log.info(LOG_PREFIX + 'Cache expired more than ' + cacheExpirationInDays + ' days ago. Cleaning up cache');
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// Check hash
|
|
25
|
+
var storageHashKey = keys.buildHashKey();
|
|
26
|
+
var storageHash = localStorage.getItem(storageHashKey);
|
|
27
|
+
var currentStorageHash = getStorageHash(settings);
|
|
28
|
+
if (storageHash !== currentStorageHash) {
|
|
29
|
+
try {
|
|
30
|
+
localStorage.setItem(storageHashKey, currentStorageHash);
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
log.error(LOG_PREFIX + e);
|
|
34
|
+
}
|
|
35
|
+
if (isThereCache) {
|
|
36
|
+
log.info(LOG_PREFIX + 'SDK key, flags filter criteria or flags spec version has changed. Cleaning up cache');
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
return false; // No cache to clear
|
|
40
|
+
}
|
|
41
|
+
// Clear on init
|
|
42
|
+
if (options.clearOnInit) {
|
|
43
|
+
var lastClearTimestamp = parseInt(localStorage.getItem(keys.buildLastClear()), 10);
|
|
44
|
+
if (isNaNNumber(lastClearTimestamp) || lastClearTimestamp < currentTimestamp - MILLIS_IN_A_DAY) {
|
|
45
|
+
log.info(LOG_PREFIX + 'clearOnInit was set and cache was not cleared in the last 24 hours. Cleaning up cache');
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Clean cache if:
|
|
52
|
+
* - it has expired, i.e., its `lastUpdated` timestamp is older than the given `expirationTimestamp`
|
|
53
|
+
* - its hash has changed, i.e., the SDK key, flags filter criteria or flags spec version was modified
|
|
54
|
+
* - `clearOnInit` was set and cache was not cleared in the last 24 hours
|
|
55
|
+
*
|
|
56
|
+
* @returns `true` if cache is ready to be used, `false` otherwise (cache was cleared or there is no cache)
|
|
57
|
+
*/
|
|
58
|
+
export function validateCache(options, settings, keys, splits, segments, largeSegments) {
|
|
59
|
+
var currentTimestamp = Date.now();
|
|
60
|
+
var isThereCache = splits.getChangeNumber() > -1;
|
|
61
|
+
if (validateExpiration(options, settings, keys, currentTimestamp, isThereCache)) {
|
|
62
|
+
splits.clear();
|
|
63
|
+
segments.clear();
|
|
64
|
+
largeSegments.clear();
|
|
65
|
+
// Update last clear timestamp
|
|
66
|
+
try {
|
|
67
|
+
localStorage.setItem(keys.buildLastClear(), currentTimestamp + '');
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
settings.log.error(LOG_PREFIX + e);
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
// Check if ready from cache
|
|
75
|
+
return isThereCache;
|
|
76
|
+
}
|
|
@@ -75,7 +75,8 @@ export function PluggableStorage(options) {
|
|
|
75
75
|
// Connects to wrapper and emits SDK_READY event on main client
|
|
76
76
|
var connectPromise = wrapper.connect().then(function () {
|
|
77
77
|
if (isSyncronizer) {
|
|
78
|
-
//
|
|
78
|
+
// @TODO reuse InLocalStorage::validateCache logic
|
|
79
|
+
// In standalone or producer mode, clear storage if SDK key, flags filter criteria or flags spec version was modified
|
|
79
80
|
return wrapper.get(keys.buildHashKey()).then(function (hash) {
|
|
80
81
|
var currentHash = getStorageHash(settings);
|
|
81
82
|
if (hash !== currentHash) {
|
|
@@ -43,9 +43,10 @@ export function fromObjectUpdaterFactory(splitsParser, storage, readiness, setti
|
|
|
43
43
|
readiness.splits.emit(SDK_SPLITS_ARRIVED);
|
|
44
44
|
if (startingUp) {
|
|
45
45
|
startingUp = false;
|
|
46
|
-
|
|
46
|
+
var isCacheLoaded_1 = storage.validateCache ? storage.validateCache() : false;
|
|
47
|
+
Promise.resolve().then(function () {
|
|
47
48
|
// Emits SDK_READY_FROM_CACHE
|
|
48
|
-
if (
|
|
49
|
+
if (isCacheLoaded_1)
|
|
49
50
|
readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
|
|
50
51
|
// Emits SDK_READY
|
|
51
52
|
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { timeout } from '../../../utils/promise/timeout';
|
|
2
|
-
import { SDK_SPLITS_ARRIVED
|
|
2
|
+
import { SDK_SPLITS_ARRIVED } from '../../../readiness/constants';
|
|
3
3
|
import { SYNC_SPLITS_FETCH, SYNC_SPLITS_NEW, SYNC_SPLITS_REMOVED, SYNC_SPLITS_SEGMENTS, SYNC_SPLITS_FETCH_FAILS, SYNC_SPLITS_FETCH_RETRY } from '../../../logger/constants';
|
|
4
4
|
import { startsWith } from '../../../utils/lang';
|
|
5
5
|
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);
|
|
@@ -306,12 +306,14 @@ export function pushManagerFactory(params, pollingManager) {
|
|
|
306
306
|
// Reconnects in case of a new client.
|
|
307
307
|
// Run in next event-loop cycle to save authentication calls
|
|
308
308
|
// in case multiple clients are created in the current cycle.
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
connectForNewClient
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
309
|
+
if (this.isRunning()) {
|
|
310
|
+
setTimeout(function checkForReconnect() {
|
|
311
|
+
if (connectForNewClient) {
|
|
312
|
+
connectForNewClient = false;
|
|
313
|
+
connectPush();
|
|
314
|
+
}
|
|
315
|
+
}, 0);
|
|
316
|
+
}
|
|
315
317
|
}
|
|
316
318
|
},
|
|
317
319
|
// [Only for client-side]
|
|
@@ -3,6 +3,7 @@ import { PUSH_SUBSYSTEM_UP, PUSH_SUBSYSTEM_DOWN } from './streaming/constants';
|
|
|
3
3
|
import { SYNC_START_POLLING, SYNC_CONTINUE_POLLING, SYNC_STOP_POLLING } from '../logger/constants';
|
|
4
4
|
import { isConsentGranted } from '../consent';
|
|
5
5
|
import { POLLING, STREAMING, SYNC_MODE_UPDATE } from '../utils/constants';
|
|
6
|
+
import { SDK_SPLITS_CACHE_LOADED } from '../readiness/constants';
|
|
6
7
|
/**
|
|
7
8
|
* Online SyncManager factory.
|
|
8
9
|
* Can be used for server-side API, and client-side API with or without multiple clients.
|
|
@@ -16,7 +17,7 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
16
17
|
* SyncManager factory for modular SDK
|
|
17
18
|
*/
|
|
18
19
|
return function (params) {
|
|
19
|
-
var settings = params.settings, _a = params.settings, log = _a.log, streamingEnabled = _a.streamingEnabled, syncEnabled = _a.sync.enabled, telemetryTracker = params.telemetryTracker;
|
|
20
|
+
var settings = params.settings, _a = params.settings, log = _a.log, streamingEnabled = _a.streamingEnabled, syncEnabled = _a.sync.enabled, telemetryTracker = params.telemetryTracker, storage = params.storage, readiness = params.readiness;
|
|
20
21
|
/** Polling Manager */
|
|
21
22
|
var pollingManager = pollingManagerFactory && pollingManagerFactory(params);
|
|
22
23
|
/** Push Manager */
|
|
@@ -64,6 +65,11 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
64
65
|
*/
|
|
65
66
|
start: function () {
|
|
66
67
|
running = true;
|
|
68
|
+
if (startFirstTime) {
|
|
69
|
+
var isCacheLoaded = storage.validateCache ? storage.validateCache() : false;
|
|
70
|
+
if (isCacheLoaded)
|
|
71
|
+
Promise.resolve().then(function () { readiness.splits.emit(SDK_SPLITS_CACHE_LOADED); });
|
|
72
|
+
}
|
|
67
73
|
// start syncing splits and segments
|
|
68
74
|
if (pollingManager) {
|
|
69
75
|
// If synchronization is disabled pushManager and pollingManager should not start
|
|
@@ -72,7 +78,6 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
72
78
|
// Doesn't call `syncAll` when the syncManager is resuming
|
|
73
79
|
if (startFirstTime) {
|
|
74
80
|
pollingManager.syncAll();
|
|
75
|
-
startFirstTime = false;
|
|
76
81
|
}
|
|
77
82
|
pushManager.start();
|
|
78
83
|
}
|
|
@@ -83,12 +88,12 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
83
88
|
else {
|
|
84
89
|
if (startFirstTime) {
|
|
85
90
|
pollingManager.syncAll();
|
|
86
|
-
startFirstTime = false;
|
|
87
91
|
}
|
|
88
92
|
}
|
|
89
93
|
}
|
|
90
94
|
// start periodic data recording (events, impressions, telemetry).
|
|
91
95
|
submitterManager.start(!isConsentGranted(settings));
|
|
96
|
+
startFirstTime = false;
|
|
92
97
|
},
|
|
93
98
|
/**
|
|
94
99
|
* Method used to stop/pause the syncManager.
|
|
@@ -115,6 +120,8 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
115
120
|
if (!pollingManager)
|
|
116
121
|
return;
|
|
117
122
|
var mySegmentsSyncTask = pollingManager.add(matchingKey, readinessManager, storage);
|
|
123
|
+
if (syncEnabled && pushManager)
|
|
124
|
+
pushManager.add(matchingKey, mySegmentsSyncTask);
|
|
118
125
|
if (running) {
|
|
119
126
|
if (syncEnabled) {
|
|
120
127
|
if (pushManager) {
|
|
@@ -128,7 +135,6 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
128
135
|
// of segments since `syncAll` was already executed when starting the main client
|
|
129
136
|
mySegmentsSyncTask.execute();
|
|
130
137
|
}
|
|
131
|
-
pushManager.add(matchingKey, mySegmentsSyncTask);
|
|
132
138
|
}
|
|
133
139
|
else {
|
|
134
140
|
if (storage.splits.usesSegments())
|
|
@@ -22,7 +22,7 @@ export function eventTrackerFactory(settings, eventsCache, whenInit, integration
|
|
|
22
22
|
whenInit(function () {
|
|
23
23
|
// Wrap in a timeout because we don't want it to be blocking.
|
|
24
24
|
setTimeout(function () {
|
|
25
|
-
// copy of event, to avoid unexpected
|
|
25
|
+
// copy of event, to avoid unexpected behavior if modified by integrations
|
|
26
26
|
var eventDataCopy = objectAssign({}, eventData);
|
|
27
27
|
if (properties)
|
|
28
28
|
eventDataCopy.properties = objectAssign({}, properties);
|
|
@@ -43,7 +43,7 @@ export function impressionsTrackerFactory(settings, impressionsCache, strategy,
|
|
|
43
43
|
if (impressionListener || integrationsManager) {
|
|
44
44
|
var _loop_1 = function (i) {
|
|
45
45
|
var impressionData = {
|
|
46
|
-
// copy of impression, to avoid unexpected
|
|
46
|
+
// copy of impression, to avoid unexpected behavior if modified by integrations or impressionListener
|
|
47
47
|
impression: objectAssign({}, impressionsToListener[i]),
|
|
48
48
|
attributes: attributes,
|
|
49
49
|
ip: ip,
|
|
@@ -136,7 +136,7 @@ export function settingsValidation(config, validationParams) {
|
|
|
136
136
|
withDefaults.core.key = 'localhost_key';
|
|
137
137
|
}
|
|
138
138
|
else {
|
|
139
|
-
// Keeping same
|
|
139
|
+
// Keeping same behavior than JS SDK: if settings key or TT are invalid,
|
|
140
140
|
// `false` value is used as bound key/TT of the default client, which leads to some issues.
|
|
141
141
|
// @ts-ignore, @TODO handle invalid keys as a non-recoverable error?
|
|
142
142
|
withDefaults.core.key = validateKey(log, maybeKey, LOG_PREFIX_CLIENT_INSTANTIATION);
|
|
@@ -3,7 +3,7 @@ import { ERROR_STORAGE_INVALID } from '../../../logger/constants';
|
|
|
3
3
|
import { LOCALHOST_MODE, STANDALONE_MODE, STORAGE_PLUGGABLE, STORAGE_LOCALSTORAGE, STORAGE_MEMORY } from '../../../utils/constants';
|
|
4
4
|
export function __InLocalStorageMockFactory(params) {
|
|
5
5
|
var result = InMemoryStorageCSFactory(params);
|
|
6
|
-
result.
|
|
6
|
+
result.validateCache = function () { return true; }; // to emit SDK_READY_FROM_CACHE
|
|
7
7
|
return result;
|
|
8
8
|
}
|
|
9
9
|
__InLocalStorageMockFactory.type = STORAGE_MEMORY;
|
package/package.json
CHANGED
package/src/listeners/node.ts
CHANGED
|
@@ -56,7 +56,7 @@ export class NodeSignalListener implements ISignalListener {
|
|
|
56
56
|
// Cleaned up, remove handlers.
|
|
57
57
|
this.stop();
|
|
58
58
|
|
|
59
|
-
// This handler prevented the default
|
|
59
|
+
// This handler prevented the default behavior, start again.
|
|
60
60
|
// eslint-disable-next-line no-undef
|
|
61
61
|
process.kill(process.pid, SIGTERM);
|
|
62
62
|
};
|
|
@@ -72,7 +72,7 @@ export class NodeSignalListener implements ISignalListener {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
if (thenable(handlerResult)) {
|
|
75
|
-
// Always exit, even with errors. The promise is returned for UT
|
|
75
|
+
// Always exit, even with errors. The promise is returned for UT purposes.
|
|
76
76
|
return handlerResult.then(wrapUp).catch(wrapUp);
|
|
77
77
|
} else {
|
|
78
78
|
wrapUp();
|
|
@@ -27,14 +27,6 @@ export abstract class AbstractSplitsCacheAsync implements ISplitsCacheAsync {
|
|
|
27
27
|
return Promise.resolve(true);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
/**
|
|
31
|
-
* Check if the splits information is already stored in cache.
|
|
32
|
-
* Noop, just keeping the interface. This is used by client-side implementations only.
|
|
33
|
-
*/
|
|
34
|
-
checkCache(): Promise<boolean> {
|
|
35
|
-
return Promise.resolve(false);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
30
|
/**
|
|
39
31
|
* Kill `name` split and set `defaultTreatment` and `changeNumber`.
|
|
40
32
|
* Used for SPLIT_KILL push notifications.
|
|
@@ -47,14 +47,6 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
|
|
|
47
47
|
|
|
48
48
|
abstract clear(): void
|
|
49
49
|
|
|
50
|
-
/**
|
|
51
|
-
* Check if the splits information is already stored in cache. This data can be preloaded.
|
|
52
|
-
* It is used as condition to emit SDK_SPLITS_CACHE_LOADED, and then SDK_READY_FROM_CACHE.
|
|
53
|
-
*/
|
|
54
|
-
checkCache(): boolean {
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
50
|
/**
|
|
59
51
|
* Kill `name` split and set `defaultTreatment` and `changeNumber`.
|
|
60
52
|
* Used for SPLIT_KILL push notifications.
|
|
@@ -43,6 +43,10 @@ export class KeyBuilderCS extends KeyBuilder implements MySegmentsKeyBuilder {
|
|
|
43
43
|
buildTillKey() {
|
|
44
44
|
return `${this.prefix}.${this.matchingKey}.segments.till`;
|
|
45
45
|
}
|
|
46
|
+
|
|
47
|
+
buildLastClear() {
|
|
48
|
+
return `${this.prefix}.lastClear`;
|
|
49
|
+
}
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
export function myLargeSegmentsKeyBuilder(prefix: string, matchingKey: string): MySegmentsKeyBuilder {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { PreloadedData } from '../types';
|
|
2
|
-
import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../utils/constants/browser';
|
|
3
2
|
import { DataLoader, ISegmentsCacheSync, ISplitsCacheSync } from './types';
|
|
4
3
|
|
|
4
|
+
// This value might be eventually set via a config parameter
|
|
5
|
+
const DEFAULT_CACHE_EXPIRATION_IN_MILLIS = 864000000; // 10 days
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
8
|
* Factory of client-side storage loader
|
|
7
9
|
*
|
|
@@ -5,7 +5,6 @@ import { KeyBuilderCS } from '../KeyBuilderCS';
|
|
|
5
5
|
import { ILogger } from '../../logger/types';
|
|
6
6
|
import { LOG_PREFIX } from './constants';
|
|
7
7
|
import { ISettings } from '../../types';
|
|
8
|
-
import { getStorageHash } from '../KeyBuilder';
|
|
9
8
|
import { setToArray } from '../../utils/lang/sets';
|
|
10
9
|
|
|
11
10
|
/**
|
|
@@ -15,21 +14,14 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
15
14
|
|
|
16
15
|
private readonly keys: KeyBuilderCS;
|
|
17
16
|
private readonly log: ILogger;
|
|
18
|
-
private readonly storageHash: string;
|
|
19
17
|
private readonly flagSetsFilter: string[];
|
|
20
18
|
private hasSync?: boolean;
|
|
21
|
-
private updateNewFilter?: boolean;
|
|
22
19
|
|
|
23
|
-
constructor(settings: ISettings, keys: KeyBuilderCS
|
|
20
|
+
constructor(settings: ISettings, keys: KeyBuilderCS) {
|
|
24
21
|
super();
|
|
25
22
|
this.keys = keys;
|
|
26
23
|
this.log = settings.log;
|
|
27
|
-
this.storageHash = getStorageHash(settings);
|
|
28
24
|
this.flagSetsFilter = settings.sync.__splitFiltersValidation.groupedFilters.bySet;
|
|
29
|
-
|
|
30
|
-
this._checkExpiration(expirationTimestamp);
|
|
31
|
-
|
|
32
|
-
this._checkFilterQuery();
|
|
33
25
|
}
|
|
34
26
|
|
|
35
27
|
private _decrementCount(key: string) {
|
|
@@ -79,8 +71,6 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
79
71
|
* We cannot simply call `localStorage.clear()` since that implies removing user items from the storage.
|
|
80
72
|
*/
|
|
81
73
|
clear() {
|
|
82
|
-
this.log.info(LOG_PREFIX + 'Flushing Splits data from localStorage');
|
|
83
|
-
|
|
84
74
|
// collect item keys
|
|
85
75
|
const len = localStorage.length;
|
|
86
76
|
const accum = [];
|
|
@@ -138,19 +128,6 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
138
128
|
}
|
|
139
129
|
|
|
140
130
|
setChangeNumber(changeNumber: number): boolean {
|
|
141
|
-
|
|
142
|
-
// when using a new split query, we must update it at the store
|
|
143
|
-
if (this.updateNewFilter) {
|
|
144
|
-
this.log.info(LOG_PREFIX + 'SDK key, flags filter criteria or flags spec version was modified. Updating cache');
|
|
145
|
-
const storageHashKey = this.keys.buildHashKey();
|
|
146
|
-
try {
|
|
147
|
-
localStorage.setItem(storageHashKey, this.storageHash);
|
|
148
|
-
} catch (e) {
|
|
149
|
-
this.log.error(LOG_PREFIX + e);
|
|
150
|
-
}
|
|
151
|
-
this.updateNewFilter = false;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
131
|
try {
|
|
155
132
|
localStorage.setItem(this.keys.buildSplitsTillKey(), changeNumber + '');
|
|
156
133
|
// update "last updated" timestamp with current time
|
|
@@ -212,48 +189,6 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
212
189
|
}
|
|
213
190
|
}
|
|
214
191
|
|
|
215
|
-
/**
|
|
216
|
-
* Check if the splits information is already stored in browser LocalStorage.
|
|
217
|
-
* In this function we could add more code to check if the data is valid.
|
|
218
|
-
* @override
|
|
219
|
-
*/
|
|
220
|
-
checkCache(): boolean {
|
|
221
|
-
return this.getChangeNumber() > -1;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Clean Splits cache if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
|
|
226
|
-
*
|
|
227
|
-
* @param expirationTimestamp - if the value is not a number, data will not be cleaned
|
|
228
|
-
*/
|
|
229
|
-
private _checkExpiration(expirationTimestamp?: number) {
|
|
230
|
-
let value: string | number | null = localStorage.getItem(this.keys.buildLastUpdatedKey());
|
|
231
|
-
if (value !== null) {
|
|
232
|
-
value = parseInt(value, 10);
|
|
233
|
-
if (!isNaNNumber(value) && expirationTimestamp && value < expirationTimestamp) this.clear();
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// @TODO eventually remove `_checkFilterQuery`. Cache should be cleared at the storage level, reusing same logic than PluggableStorage
|
|
238
|
-
private _checkFilterQuery() {
|
|
239
|
-
const storageHashKey = this.keys.buildHashKey();
|
|
240
|
-
const storageHash = localStorage.getItem(storageHashKey);
|
|
241
|
-
|
|
242
|
-
if (storageHash !== this.storageHash) {
|
|
243
|
-
try {
|
|
244
|
-
// mark cache to update the new query filter on first successful splits fetch
|
|
245
|
-
this.updateNewFilter = true;
|
|
246
|
-
|
|
247
|
-
// if there is cache, clear it
|
|
248
|
-
if (this.checkCache()) this.clear();
|
|
249
|
-
|
|
250
|
-
} catch (e) {
|
|
251
|
-
this.log.error(LOG_PREFIX + e);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
// if the filter didn't change, nothing is done
|
|
255
|
-
}
|
|
256
|
-
|
|
257
192
|
getNamesByFlagSets(flagSets: string[]): Set<string>[] {
|
|
258
193
|
return flagSets.map(flagSet => {
|
|
259
194
|
const flagSetKey = this.keys.buildFlagSetKey(flagSet);
|
|
@@ -7,22 +7,19 @@ import { KeyBuilderCS, myLargeSegmentsKeyBuilder } from '../KeyBuilderCS';
|
|
|
7
7
|
import { isLocalStorageAvailable } from '../../utils/env/isLocalStorageAvailable';
|
|
8
8
|
import { SplitsCacheInLocal } from './SplitsCacheInLocal';
|
|
9
9
|
import { MySegmentsCacheInLocal } from './MySegmentsCacheInLocal';
|
|
10
|
-
import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../../utils/constants/browser';
|
|
11
10
|
import { InMemoryStorageCSFactory } from '../inMemory/InMemoryStorageCS';
|
|
12
11
|
import { LOG_PREFIX } from './constants';
|
|
13
12
|
import { DEBUG, NONE, STORAGE_LOCALSTORAGE } from '../../utils/constants';
|
|
14
13
|
import { shouldRecordTelemetry, TelemetryCacheInMemory } from '../inMemory/TelemetryCacheInMemory';
|
|
15
14
|
import { UniqueKeysCacheInMemoryCS } from '../inMemory/UniqueKeysCacheInMemoryCS';
|
|
16
15
|
import { getMatching } from '../../utils/key';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
prefix?: string
|
|
20
|
-
}
|
|
16
|
+
import { validateCache } from './validateCache';
|
|
17
|
+
import SplitIO from '../../../types/splitio';
|
|
21
18
|
|
|
22
19
|
/**
|
|
23
20
|
* InLocal storage factory for standalone client-side SplitFactory
|
|
24
21
|
*/
|
|
25
|
-
export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyncFactory {
|
|
22
|
+
export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): IStorageSyncFactory {
|
|
26
23
|
|
|
27
24
|
const prefix = validatePrefix(options.prefix);
|
|
28
25
|
|
|
@@ -37,9 +34,8 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
|
|
|
37
34
|
const { settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode } } } = params;
|
|
38
35
|
const matchingKey = getMatching(settings.core.key);
|
|
39
36
|
const keys = new KeyBuilderCS(prefix, matchingKey);
|
|
40
|
-
const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
|
|
41
37
|
|
|
42
|
-
const splits = new SplitsCacheInLocal(settings, keys
|
|
38
|
+
const splits = new SplitsCacheInLocal(settings, keys);
|
|
43
39
|
const segments = new MySegmentsCacheInLocal(log, keys);
|
|
44
40
|
const largeSegments = new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey));
|
|
45
41
|
|
|
@@ -53,6 +49,10 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
|
|
|
53
49
|
telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
|
|
54
50
|
uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS() : undefined,
|
|
55
51
|
|
|
52
|
+
validateCache() {
|
|
53
|
+
return validateCache(options, settings, keys, splits, segments, largeSegments);
|
|
54
|
+
},
|
|
55
|
+
|
|
56
56
|
destroy() { },
|
|
57
57
|
|
|
58
58
|
// When using shared instantiation with MEMORY we reuse everything but segments (they are customer per key).
|