@splitsoftware/splitio-commons 2.1.1-rc.1 → 2.1.1-rc.2
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 +5 -1
- package/cjs/readiness/readinessManager.js +6 -0
- 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 +11 -69
- package/cjs/storages/inLocalStorage/index.js +5 -3
- package/cjs/storages/inLocalStorage/validateCache.js +79 -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 -12
- package/cjs/sync/syncManagerOnline.js +8 -3
- package/cjs/trackers/strategy/strategyDebug.js +2 -0
- package/cjs/utils/settingsValidation/storage/storageCS.js +1 -1
- package/esm/readiness/readinessManager.js +6 -0
- 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 +11 -69
- package/esm/storages/inLocalStorage/index.js +5 -3
- package/esm/storages/inLocalStorage/validateCache.js +75 -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 -13
- package/esm/sync/syncManagerOnline.js +8 -3
- package/esm/trackers/strategy/strategyDebug.js +2 -0
- package/esm/utils/settingsValidation/storage/storageCS.js +1 -1
- package/package.json +1 -1
- package/src/readiness/readinessManager.ts +5 -0
- 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 +13 -78
- package/src/storages/inLocalStorage/index.ts +8 -8
- package/src/storages/inLocalStorage/validateCache.ts +91 -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 -13
- package/src/sync/syncManagerOnline.ts +9 -3
- package/src/trackers/strategy/strategyDebug.ts +2 -0
- package/src/utils/lang/index.ts +1 -1
- package/src/utils/settingsValidation/storage/storageCS.ts +1 -1
- package/types/splitio.d.ts +28 -1
- 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) {
|
|
@@ -29,13 +25,11 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
29
25
|
};
|
|
30
26
|
SplitsCacheInLocal.prototype._decrementCounts = function (split) {
|
|
31
27
|
try {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
this._decrementCount(segmentsCountKey);
|
|
38
|
-
}
|
|
28
|
+
var ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName);
|
|
29
|
+
this._decrementCount(ttKey);
|
|
30
|
+
if (usesSegments(split)) {
|
|
31
|
+
var segmentsCountKey = this.keys.buildSplitsWithSegmentCountKey();
|
|
32
|
+
this._decrementCount(segmentsCountKey);
|
|
39
33
|
}
|
|
40
34
|
}
|
|
41
35
|
catch (e) {
|
|
@@ -64,7 +58,6 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
64
58
|
* We cannot simply call `localStorage.clear()` since that implies removing user items from the storage.
|
|
65
59
|
*/
|
|
66
60
|
SplitsCacheInLocal.prototype.clear = function () {
|
|
67
|
-
this.log.info(LOG_PREFIX + 'Flushing Splits data from localStorage');
|
|
68
61
|
// collect item keys
|
|
69
62
|
var len = localStorage.length;
|
|
70
63
|
var accum = [];
|
|
@@ -85,11 +78,12 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
85
78
|
var splitKey = this.keys.buildSplitKey(name_1);
|
|
86
79
|
var splitFromLocalStorage = localStorage.getItem(splitKey);
|
|
87
80
|
var previousSplit = splitFromLocalStorage ? JSON.parse(splitFromLocalStorage) : null;
|
|
81
|
+
if (previousSplit) {
|
|
82
|
+
this._decrementCounts(previousSplit);
|
|
83
|
+
this.removeFromFlagSets(previousSplit.name, previousSplit.sets);
|
|
84
|
+
}
|
|
88
85
|
localStorage.setItem(splitKey, JSON.stringify(split));
|
|
89
86
|
this._incrementCounts(split);
|
|
90
|
-
this._decrementCounts(previousSplit);
|
|
91
|
-
if (previousSplit)
|
|
92
|
-
this.removeFromFlagSets(previousSplit.name, previousSplit.sets);
|
|
93
87
|
this.addToFlagSets(split);
|
|
94
88
|
return true;
|
|
95
89
|
}
|
|
@@ -105,8 +99,7 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
105
99
|
return false;
|
|
106
100
|
localStorage.removeItem(this.keys.buildSplitKey(name));
|
|
107
101
|
this._decrementCounts(split);
|
|
108
|
-
|
|
109
|
-
this.removeFromFlagSets(split.name, split.sets);
|
|
102
|
+
this.removeFromFlagSets(split.name, split.sets);
|
|
110
103
|
return true;
|
|
111
104
|
}
|
|
112
105
|
catch (e) {
|
|
@@ -119,18 +112,6 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
119
112
|
return item && JSON.parse(item);
|
|
120
113
|
};
|
|
121
114
|
SplitsCacheInLocal.prototype.setChangeNumber = function (changeNumber) {
|
|
122
|
-
// when using a new split query, we must update it at the store
|
|
123
|
-
if (this.updateNewFilter) {
|
|
124
|
-
this.log.info(LOG_PREFIX + 'SDK key, flags filter criteria or flags spec version was modified. Updating cache');
|
|
125
|
-
var storageHashKey = this.keys.buildHashKey();
|
|
126
|
-
try {
|
|
127
|
-
localStorage.setItem(storageHashKey, this.storageHash);
|
|
128
|
-
}
|
|
129
|
-
catch (e) {
|
|
130
|
-
this.log.error(LOG_PREFIX + e);
|
|
131
|
-
}
|
|
132
|
-
this.updateNewFilter = false;
|
|
133
|
-
}
|
|
134
115
|
try {
|
|
135
116
|
localStorage.setItem(this.keys.buildSplitsTillKey(), changeNumber + '');
|
|
136
117
|
// update "last updated" timestamp with current time
|
|
@@ -181,45 +162,6 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
181
162
|
return true;
|
|
182
163
|
}
|
|
183
164
|
};
|
|
184
|
-
/**
|
|
185
|
-
* Check if the splits information is already stored in browser LocalStorage.
|
|
186
|
-
* In this function we could add more code to check if the data is valid.
|
|
187
|
-
* @override
|
|
188
|
-
*/
|
|
189
|
-
SplitsCacheInLocal.prototype.checkCache = function () {
|
|
190
|
-
return this.getChangeNumber() > -1;
|
|
191
|
-
};
|
|
192
|
-
/**
|
|
193
|
-
* Clean Splits cache if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
|
|
194
|
-
*
|
|
195
|
-
* @param expirationTimestamp - if the value is not a number, data will not be cleaned
|
|
196
|
-
*/
|
|
197
|
-
SplitsCacheInLocal.prototype._checkExpiration = function (expirationTimestamp) {
|
|
198
|
-
var value = localStorage.getItem(this.keys.buildLastUpdatedKey());
|
|
199
|
-
if (value !== null) {
|
|
200
|
-
value = parseInt(value, 10);
|
|
201
|
-
if (!isNaNNumber(value) && expirationTimestamp && value < expirationTimestamp)
|
|
202
|
-
this.clear();
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
// @TODO eventually remove `_checkFilterQuery`. Cache should be cleared at the storage level, reusing same logic than PluggableStorage
|
|
206
|
-
SplitsCacheInLocal.prototype._checkFilterQuery = function () {
|
|
207
|
-
var storageHashKey = this.keys.buildHashKey();
|
|
208
|
-
var storageHash = localStorage.getItem(storageHashKey);
|
|
209
|
-
if (storageHash !== this.storageHash) {
|
|
210
|
-
try {
|
|
211
|
-
// mark cache to update the new query filter on first successful splits fetch
|
|
212
|
-
this.updateNewFilter = true;
|
|
213
|
-
// if there is cache, clear it
|
|
214
|
-
if (this.checkCache())
|
|
215
|
-
this.clear();
|
|
216
|
-
}
|
|
217
|
-
catch (e) {
|
|
218
|
-
this.log.error(LOG_PREFIX + e);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
// if the filter didn't change, nothing is done
|
|
222
|
-
};
|
|
223
165
|
SplitsCacheInLocal.prototype.getNamesByFlagSets = function (flagSets) {
|
|
224
166
|
var _this = this;
|
|
225
167
|
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 { 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;
|
|
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: new UniqueKeysCacheInMemoryCS(),
|
|
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,75 @@
|
|
|
1
|
+
import { isFiniteNumber, isNaNNumber } from '../../utils/lang';
|
|
2
|
+
import { getStorageHash } from '../KeyBuilder';
|
|
3
|
+
import { LOG_PREFIX } from './constants';
|
|
4
|
+
var DEFAULT_CACHE_EXPIRATION_IN_DAYS = 10;
|
|
5
|
+
var MILLIS_IN_A_DAY = 86400000;
|
|
6
|
+
/**
|
|
7
|
+
* Validates if cache should be cleared and sets the cache `hash` if needed.
|
|
8
|
+
*
|
|
9
|
+
* @returns `true` if cache should be cleared, `false` otherwise
|
|
10
|
+
*/
|
|
11
|
+
function validateExpiration(options, settings, keys, currentTimestamp, isThereCache) {
|
|
12
|
+
var log = settings.log;
|
|
13
|
+
// Check expiration
|
|
14
|
+
var lastUpdatedTimestamp = parseInt(localStorage.getItem(keys.buildLastUpdatedKey()), 10);
|
|
15
|
+
if (!isNaNNumber(lastUpdatedTimestamp)) {
|
|
16
|
+
var cacheExpirationInDays = isFiniteNumber(options.expirationDays) && options.expirationDays >= 1 ? options.expirationDays : DEFAULT_CACHE_EXPIRATION_IN_DAYS;
|
|
17
|
+
var expirationTimestamp = currentTimestamp - MILLIS_IN_A_DAY * cacheExpirationInDays;
|
|
18
|
+
if (lastUpdatedTimestamp < expirationTimestamp) {
|
|
19
|
+
log.info(LOG_PREFIX + 'Cache expired more than ' + cacheExpirationInDays + ' days ago. Cleaning up cache');
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// Check hash
|
|
24
|
+
var storageHashKey = keys.buildHashKey();
|
|
25
|
+
var storageHash = localStorage.getItem(storageHashKey);
|
|
26
|
+
var currentStorageHash = getStorageHash(settings);
|
|
27
|
+
if (storageHash !== currentStorageHash) {
|
|
28
|
+
try {
|
|
29
|
+
localStorage.setItem(storageHashKey, currentStorageHash);
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
log.error(LOG_PREFIX + e);
|
|
33
|
+
}
|
|
34
|
+
if (isThereCache) {
|
|
35
|
+
log.info(LOG_PREFIX + 'SDK key, flags filter criteria, or flags spec version has changed. Cleaning up cache');
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
return false; // No cache to clear
|
|
39
|
+
}
|
|
40
|
+
// Clear on init
|
|
41
|
+
if (options.clearOnInit) {
|
|
42
|
+
var lastClearTimestamp = parseInt(localStorage.getItem(keys.buildLastClear()), 10);
|
|
43
|
+
if (isNaNNumber(lastClearTimestamp) || lastClearTimestamp < currentTimestamp - MILLIS_IN_A_DAY) {
|
|
44
|
+
log.info(LOG_PREFIX + 'clearOnInit was set and cache was not cleared in the last 24 hours. Cleaning up cache');
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Clean cache if:
|
|
51
|
+
* - it has expired, i.e., its `lastUpdated` timestamp is older than the given `expirationTimestamp`
|
|
52
|
+
* - its hash has changed, i.e., the SDK key, flags filter criteria or flags spec version was modified
|
|
53
|
+
* - `clearOnInit` was set and cache was not cleared in the last 24 hours
|
|
54
|
+
*
|
|
55
|
+
* @returns `true` if cache is ready to be used, `false` otherwise (cache was cleared or there is no cache)
|
|
56
|
+
*/
|
|
57
|
+
export function validateCache(options, settings, keys, splits, segments, largeSegments) {
|
|
58
|
+
var currentTimestamp = Date.now();
|
|
59
|
+
var isThereCache = splits.getChangeNumber() > -1;
|
|
60
|
+
if (validateExpiration(options, settings, keys, currentTimestamp, isThereCache)) {
|
|
61
|
+
splits.clear();
|
|
62
|
+
segments.clear();
|
|
63
|
+
largeSegments.clear();
|
|
64
|
+
// Update last clear timestamp
|
|
65
|
+
try {
|
|
66
|
+
localStorage.setItem(keys.buildLastClear(), currentTimestamp + '');
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
settings.log.error(LOG_PREFIX + e);
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
// Check if ready from cache
|
|
74
|
+
return isThereCache;
|
|
75
|
+
}
|
|
@@ -71,7 +71,8 @@ export function PluggableStorage(options) {
|
|
|
71
71
|
// Connects to wrapper and emits SDK_READY event on main client
|
|
72
72
|
var connectPromise = wrapper.connect().then(function () {
|
|
73
73
|
if (isSynchronizer) {
|
|
74
|
-
//
|
|
74
|
+
// @TODO reuse InLocalStorage::validateCache logic
|
|
75
|
+
// In standalone or producer mode, clear storage if SDK key, flags filter criteria or flags spec version was modified
|
|
75
76
|
return wrapper.get(keys.buildHashKey()).then(function (hash) {
|
|
76
77
|
var currentHash = getStorageHash(settings);
|
|
77
78
|
if (hash !== currentHash) {
|
|
@@ -42,9 +42,10 @@ export function fromObjectUpdaterFactory(splitsParser, storage, readiness, setti
|
|
|
42
42
|
readiness.splits.emit(SDK_SPLITS_ARRIVED);
|
|
43
43
|
if (startingUp) {
|
|
44
44
|
startingUp = false;
|
|
45
|
-
|
|
45
|
+
var isCacheLoaded_1 = storage.validateCache ? storage.validateCache() : false;
|
|
46
|
+
Promise.resolve().then(function () {
|
|
46
47
|
// Emits SDK_READY_FROM_CACHE
|
|
47
|
-
if (
|
|
48
|
+
if (isCacheLoaded_1)
|
|
48
49
|
readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
|
|
49
50
|
// Emits SDK_READY
|
|
50
51
|
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_UPDATE, 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';
|
|
@@ -111,15 +111,13 @@ export function splitChangesUpdaterFactory(log, splitChangesFetcher, storage, sp
|
|
|
111
111
|
function _splitChangesUpdater(since, retry) {
|
|
112
112
|
if (retry === void 0) { retry = 0; }
|
|
113
113
|
log.debug(SYNC_SPLITS_FETCH, [since]);
|
|
114
|
-
|
|
114
|
+
return Promise.resolve(splitUpdateNotification ?
|
|
115
115
|
{ splits: [splitUpdateNotification.payload], till: splitUpdateNotification.changeNumber } :
|
|
116
116
|
splitChangesFetcher(since, noCache, till, _promiseDecorator))
|
|
117
117
|
.then(function (splitChanges) {
|
|
118
118
|
startingUp = false;
|
|
119
119
|
var mutation = computeSplitsMutation(splitChanges.splits, splitFiltersValidation);
|
|
120
120
|
log.debug(SYNC_SPLITS_UPDATE, [mutation.added.length, mutation.removed.length, mutation.segments.length]);
|
|
121
|
-
// Write into storage
|
|
122
|
-
// @TODO call `setChangeNumber` only if the other storage operations have succeeded, in order to keep storage consistency
|
|
123
121
|
return Promise.all([
|
|
124
122
|
splits.update(mutation.added, mutation.removed, splitChanges.till),
|
|
125
123
|
segments.registerSegments(mutation.segments)
|
|
@@ -151,15 +149,6 @@ export function splitChangesUpdaterFactory(log, splitChangesFetcher, storage, sp
|
|
|
151
149
|
}
|
|
152
150
|
return false;
|
|
153
151
|
});
|
|
154
|
-
// After triggering the requests, if we have cached splits information let's notify that to emit SDK_READY_FROM_CACHE.
|
|
155
|
-
// Wrapping in a promise since checkCache can be async.
|
|
156
|
-
if (splitsEventEmitter && startingUp) {
|
|
157
|
-
Promise.resolve(splits.checkCache()).then(function (isCacheReady) {
|
|
158
|
-
if (isCacheReady)
|
|
159
|
-
splitsEventEmitter.emit(SDK_SPLITS_CACHE_LOADED);
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
return fetcherPromise;
|
|
163
152
|
}
|
|
164
153
|
var sincePromise = Promise.resolve(splits.getChangeNumber()); // `getChangeNumber` never rejects or throws error
|
|
165
154
|
return sincePromise.then(_splitChangesUpdater);
|
|
@@ -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.
|
|
@@ -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
|
@@ -3,6 +3,7 @@ import { ISettings } from '../types';
|
|
|
3
3
|
import SplitIO from '../../types/splitio';
|
|
4
4
|
import { SDK_SPLITS_ARRIVED, SDK_SPLITS_CACHE_LOADED, SDK_SEGMENTS_ARRIVED, SDK_READY_TIMED_OUT, SDK_READY_FROM_CACHE, SDK_UPDATE, SDK_READY } from './constants';
|
|
5
5
|
import { IReadinessEventEmitter, IReadinessManager, ISegmentsEventEmitter, ISplitsEventEmitter } from './types';
|
|
6
|
+
import { STORAGE_LOCALSTORAGE } from '../utils/constants';
|
|
6
7
|
|
|
7
8
|
function splitsEventEmitterFactory(EventEmitter: new () => SplitIO.IEventEmitter): ISplitsEventEmitter {
|
|
8
9
|
const splitsEventEmitter = objectAssign(new EventEmitter(), {
|
|
@@ -114,6 +115,10 @@ export function readinessManagerFactory(
|
|
|
114
115
|
isReady = true;
|
|
115
116
|
try {
|
|
116
117
|
syncLastUpdate();
|
|
118
|
+
if (!isReadyFromCache && settings.storage?.type === STORAGE_LOCALSTORAGE) {
|
|
119
|
+
isReadyFromCache = true;
|
|
120
|
+
gate.emit(SDK_READY_FROM_CACHE);
|
|
121
|
+
}
|
|
117
122
|
gate.emit(SDK_READY);
|
|
118
123
|
} catch (e) {
|
|
119
124
|
// throws user callback exceptions in next tick
|
|
@@ -37,14 +37,6 @@ export abstract class AbstractSplitsCacheAsync implements ISplitsCacheAsync {
|
|
|
37
37
|
return Promise.resolve(true);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
/**
|
|
41
|
-
* Check if the splits information is already stored in cache.
|
|
42
|
-
* Noop, just keeping the interface. This is used by client-side implementations only.
|
|
43
|
-
*/
|
|
44
|
-
checkCache(): Promise<boolean> {
|
|
45
|
-
return Promise.resolve(false);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
40
|
/**
|
|
49
41
|
* Kill `name` split and set `defaultTreatment` and `changeNumber`.
|
|
50
42
|
* Used for SPLIT_KILL push notifications.
|
|
@@ -43,14 +43,6 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
|
|
|
43
43
|
|
|
44
44
|
abstract clear(): void
|
|
45
45
|
|
|
46
|
-
/**
|
|
47
|
-
* Check if the splits information is already stored in cache. This data can be preloaded.
|
|
48
|
-
* It is used as condition to emit SDK_SPLITS_CACHE_LOADED, and then SDK_READY_FROM_CACHE.
|
|
49
|
-
*/
|
|
50
|
-
checkCache(): boolean {
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
46
|
/**
|
|
55
47
|
* Kill `name` split and set `defaultTreatment` and `changeNumber`.
|
|
56
48
|
* Used for SPLIT_KILL push notifications.
|
|
@@ -50,6 +50,10 @@ export class KeyBuilderCS extends KeyBuilder implements MySegmentsKeyBuilder {
|
|
|
50
50
|
buildSplitsWithSegmentCountKey() {
|
|
51
51
|
return `${this.prefix}.splits.usingSegments`;
|
|
52
52
|
}
|
|
53
|
+
|
|
54
|
+
buildLastClear() {
|
|
55
|
+
return `${this.prefix}.lastClear`;
|
|
56
|
+
}
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
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) {
|
|
@@ -39,16 +31,14 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
39
31
|
else localStorage.removeItem(key);
|
|
40
32
|
}
|
|
41
33
|
|
|
42
|
-
private _decrementCounts(split: ISplit
|
|
34
|
+
private _decrementCounts(split: ISplit) {
|
|
43
35
|
try {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
this._decrementCount(ttKey);
|
|
36
|
+
const ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName);
|
|
37
|
+
this._decrementCount(ttKey);
|
|
47
38
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
39
|
+
if (usesSegments(split)) {
|
|
40
|
+
const segmentsCountKey = this.keys.buildSplitsWithSegmentCountKey();
|
|
41
|
+
this._decrementCount(segmentsCountKey);
|
|
52
42
|
}
|
|
53
43
|
} catch (e) {
|
|
54
44
|
this.log.error(LOG_PREFIX + e);
|
|
@@ -79,8 +69,6 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
79
69
|
* We cannot simply call `localStorage.clear()` since that implies removing user items from the storage.
|
|
80
70
|
*/
|
|
81
71
|
clear() {
|
|
82
|
-
this.log.info(LOG_PREFIX + 'Flushing Splits data from localStorage');
|
|
83
|
-
|
|
84
72
|
// collect item keys
|
|
85
73
|
const len = localStorage.length;
|
|
86
74
|
const accum = [];
|
|
@@ -103,12 +91,14 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
103
91
|
const splitFromLocalStorage = localStorage.getItem(splitKey);
|
|
104
92
|
const previousSplit = splitFromLocalStorage ? JSON.parse(splitFromLocalStorage) : null;
|
|
105
93
|
|
|
94
|
+
if (previousSplit) {
|
|
95
|
+
this._decrementCounts(previousSplit);
|
|
96
|
+
this.removeFromFlagSets(previousSplit.name, previousSplit.sets);
|
|
97
|
+
}
|
|
98
|
+
|
|
106
99
|
localStorage.setItem(splitKey, JSON.stringify(split));
|
|
107
100
|
|
|
108
101
|
this._incrementCounts(split);
|
|
109
|
-
this._decrementCounts(previousSplit);
|
|
110
|
-
|
|
111
|
-
if (previousSplit) this.removeFromFlagSets(previousSplit.name, previousSplit.sets);
|
|
112
102
|
this.addToFlagSets(split);
|
|
113
103
|
|
|
114
104
|
return true;
|
|
@@ -126,7 +116,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
126
116
|
localStorage.removeItem(this.keys.buildSplitKey(name));
|
|
127
117
|
|
|
128
118
|
this._decrementCounts(split);
|
|
129
|
-
|
|
119
|
+
this.removeFromFlagSets(split.name, split.sets);
|
|
130
120
|
|
|
131
121
|
return true;
|
|
132
122
|
} catch (e) {
|
|
@@ -141,19 +131,6 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
141
131
|
}
|
|
142
132
|
|
|
143
133
|
setChangeNumber(changeNumber: number): boolean {
|
|
144
|
-
|
|
145
|
-
// when using a new split query, we must update it at the store
|
|
146
|
-
if (this.updateNewFilter) {
|
|
147
|
-
this.log.info(LOG_PREFIX + 'SDK key, flags filter criteria or flags spec version was modified. Updating cache');
|
|
148
|
-
const storageHashKey = this.keys.buildHashKey();
|
|
149
|
-
try {
|
|
150
|
-
localStorage.setItem(storageHashKey, this.storageHash);
|
|
151
|
-
} catch (e) {
|
|
152
|
-
this.log.error(LOG_PREFIX + e);
|
|
153
|
-
}
|
|
154
|
-
this.updateNewFilter = false;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
134
|
try {
|
|
158
135
|
localStorage.setItem(this.keys.buildSplitsTillKey(), changeNumber + '');
|
|
159
136
|
// update "last updated" timestamp with current time
|
|
@@ -215,48 +192,6 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
215
192
|
}
|
|
216
193
|
}
|
|
217
194
|
|
|
218
|
-
/**
|
|
219
|
-
* Check if the splits information is already stored in browser LocalStorage.
|
|
220
|
-
* In this function we could add more code to check if the data is valid.
|
|
221
|
-
* @override
|
|
222
|
-
*/
|
|
223
|
-
checkCache(): boolean {
|
|
224
|
-
return this.getChangeNumber() > -1;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Clean Splits cache if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
|
|
229
|
-
*
|
|
230
|
-
* @param expirationTimestamp - if the value is not a number, data will not be cleaned
|
|
231
|
-
*/
|
|
232
|
-
private _checkExpiration(expirationTimestamp?: number) {
|
|
233
|
-
let value: string | number | null = localStorage.getItem(this.keys.buildLastUpdatedKey());
|
|
234
|
-
if (value !== null) {
|
|
235
|
-
value = parseInt(value, 10);
|
|
236
|
-
if (!isNaNNumber(value) && expirationTimestamp && value < expirationTimestamp) this.clear();
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// @TODO eventually remove `_checkFilterQuery`. Cache should be cleared at the storage level, reusing same logic than PluggableStorage
|
|
241
|
-
private _checkFilterQuery() {
|
|
242
|
-
const storageHashKey = this.keys.buildHashKey();
|
|
243
|
-
const storageHash = localStorage.getItem(storageHashKey);
|
|
244
|
-
|
|
245
|
-
if (storageHash !== this.storageHash) {
|
|
246
|
-
try {
|
|
247
|
-
// mark cache to update the new query filter on first successful splits fetch
|
|
248
|
-
this.updateNewFilter = true;
|
|
249
|
-
|
|
250
|
-
// if there is cache, clear it
|
|
251
|
-
if (this.checkCache()) this.clear();
|
|
252
|
-
|
|
253
|
-
} catch (e) {
|
|
254
|
-
this.log.error(LOG_PREFIX + e);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
// if the filter didn't change, nothing is done
|
|
258
|
-
}
|
|
259
|
-
|
|
260
195
|
getNamesByFlagSets(flagSets: string[]): Set<string>[] {
|
|
261
196
|
return flagSets.map(flagSet => {
|
|
262
197
|
const flagSetKey = this.keys.buildFlagSetKey(flagSet);
|