@splitsoftware/splitio-commons 2.1.0-rc.2 → 2.1.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/CHANGES.txt +2 -7
- package/cjs/readiness/readinessManager.js +0 -6
- package/cjs/storages/AbstractSplitsCacheAsync.js +7 -0
- package/cjs/storages/AbstractSplitsCacheSync.js +7 -0
- package/cjs/storages/KeyBuilderCS.js +0 -3
- package/cjs/storages/dataLoader.js +2 -3
- package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +57 -1
- package/cjs/storages/inLocalStorage/index.js +3 -5
- package/cjs/storages/inRedis/constants.js +1 -1
- package/cjs/storages/pluggable/index.js +1 -2
- package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +2 -3
- package/cjs/sync/polling/updaters/splitChangesUpdater.js +10 -1
- package/cjs/sync/syncManagerOnline.js +3 -8
- package/cjs/trackers/uniqueKeysTracker.js +1 -1
- package/cjs/utils/constants/browser.js +5 -0
- package/cjs/utils/settingsValidation/storage/storageCS.js +1 -1
- package/esm/readiness/readinessManager.js +0 -6
- package/esm/storages/AbstractSplitsCacheAsync.js +7 -0
- package/esm/storages/AbstractSplitsCacheSync.js +7 -0
- package/esm/storages/KeyBuilderCS.js +0 -3
- package/esm/storages/dataLoader.js +1 -2
- package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +57 -1
- package/esm/storages/inLocalStorage/index.js +3 -5
- package/esm/storages/inRedis/constants.js +1 -1
- package/esm/storages/pluggable/index.js +1 -2
- package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +2 -3
- package/esm/sync/polling/updaters/splitChangesUpdater.js +11 -2
- package/esm/sync/syncManagerOnline.js +3 -8
- package/esm/trackers/uniqueKeysTracker.js +1 -1
- package/esm/utils/constants/browser.js +2 -0
- package/esm/utils/settingsValidation/storage/storageCS.js +1 -1
- package/package.json +1 -1
- package/src/readiness/readinessManager.ts +0 -5
- package/src/storages/AbstractSplitsCacheAsync.ts +8 -0
- package/src/storages/AbstractSplitsCacheSync.ts +8 -0
- package/src/storages/KeyBuilderCS.ts +0 -4
- package/src/storages/dataLoader.ts +1 -3
- package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +66 -1
- package/src/storages/inLocalStorage/index.ts +8 -8
- package/src/storages/inRedis/constants.ts +1 -1
- package/src/storages/pluggable/index.ts +1 -2
- package/src/storages/types.ts +4 -1
- package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +5 -6
- package/src/sync/polling/updaters/splitChangesUpdater.ts +11 -2
- package/src/sync/syncManagerOnline.ts +3 -9
- package/src/trackers/uniqueKeysTracker.ts +1 -1
- package/src/utils/constants/browser.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 +1 -25
- package/cjs/storages/inLocalStorage/validateCache.js +0 -79
- package/esm/storages/inLocalStorage/validateCache.js +0 -75
- package/src/storages/inLocalStorage/validateCache.ts +0 -91
package/CHANGES.txt
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
2.1.0 (January
|
|
2
|
-
- Added
|
|
3
|
-
- Added two new configuration options for the SDK storage in browsers when using storage type `LOCALSTORAGE`:
|
|
4
|
-
- `storage.expirationDays` to specify the validity period of the rollout cache.
|
|
5
|
-
- `storage.clearOnInit` to clear the rollout cache on SDK initialization.
|
|
6
|
-
- Updated implementation of the impressions tracker and strategies to support feature flags with impressions tracking disabled.
|
|
7
|
-
- Updated SDK_READY_FROM_CACHE event when using the `LOCALSTORAGE` storage type to be emitted alongside the SDK_READY event if it has not already been emitted.
|
|
1
|
+
2.1.0 (January 17, 2025)
|
|
2
|
+
- Added support for the new impressions tracking toggle available on feature flags, both respecting the setting and including the new field being returned on `SplitView` type objects. Read more in our docs.
|
|
8
3
|
|
|
9
4
|
2.0.3 (January 9, 2025)
|
|
10
5
|
- Bugfixing - Properly handle rejected promises when using targeting rules with segment matchers in consumer modes (e.g., Redis and Pluggable storages).
|
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.readinessManagerFactory = void 0;
|
|
4
4
|
var objectAssign_1 = require("../utils/lang/objectAssign");
|
|
5
5
|
var constants_1 = require("./constants");
|
|
6
|
-
var constants_2 = require("../utils/constants");
|
|
7
6
|
function splitsEventEmitterFactory(EventEmitter) {
|
|
8
7
|
var splitsEventEmitter = (0, objectAssign_1.objectAssign)(new EventEmitter(), {
|
|
9
8
|
splitsArrived: false,
|
|
@@ -84,7 +83,6 @@ function readinessManagerFactory(EventEmitter, settings, splits, isShared) {
|
|
|
84
83
|
}
|
|
85
84
|
}
|
|
86
85
|
function checkIsReadyOrUpdate(diff) {
|
|
87
|
-
var _a;
|
|
88
86
|
if (isDestroyed)
|
|
89
87
|
return;
|
|
90
88
|
if (isReady) {
|
|
@@ -103,10 +101,6 @@ function readinessManagerFactory(EventEmitter, settings, splits, isShared) {
|
|
|
103
101
|
isReady = true;
|
|
104
102
|
try {
|
|
105
103
|
syncLastUpdate();
|
|
106
|
-
if (!isReadyFromCache && ((_a = settings.storage) === null || _a === void 0 ? void 0 : _a.type) === constants_2.STORAGE_LOCALSTORAGE) {
|
|
107
|
-
isReadyFromCache = true;
|
|
108
|
-
gate.emit(constants_1.SDK_READY_FROM_CACHE);
|
|
109
|
-
}
|
|
110
104
|
gate.emit(constants_1.SDK_READY);
|
|
111
105
|
}
|
|
112
106
|
catch (e) {
|
|
@@ -14,6 +14,13 @@ var AbstractSplitsCacheAsync = /** @class */ (function () {
|
|
|
14
14
|
AbstractSplitsCacheAsync.prototype.usesSegments = function () {
|
|
15
15
|
return Promise.resolve(true);
|
|
16
16
|
};
|
|
17
|
+
/**
|
|
18
|
+
* Check if the splits information is already stored in cache.
|
|
19
|
+
* Noop, just keeping the interface. This is used by client-side implementations only.
|
|
20
|
+
*/
|
|
21
|
+
AbstractSplitsCacheAsync.prototype.checkCache = function () {
|
|
22
|
+
return Promise.resolve(false);
|
|
23
|
+
};
|
|
17
24
|
/**
|
|
18
25
|
* Kill `name` split and set `defaultTreatment` and `changeNumber`.
|
|
19
26
|
* Used for SPLIT_KILL push notifications.
|
|
@@ -30,6 +30,13 @@ var AbstractSplitsCacheSync = /** @class */ (function () {
|
|
|
30
30
|
var _this = this;
|
|
31
31
|
return this.getSplitNames().map(function (key) { return _this.getSplit(key); });
|
|
32
32
|
};
|
|
33
|
+
/**
|
|
34
|
+
* Check if the splits information is already stored in cache. This data can be preloaded.
|
|
35
|
+
* It is used as condition to emit SDK_SPLITS_CACHE_LOADED, and then SDK_READY_FROM_CACHE.
|
|
36
|
+
*/
|
|
37
|
+
AbstractSplitsCacheSync.prototype.checkCache = function () {
|
|
38
|
+
return false;
|
|
39
|
+
};
|
|
33
40
|
/**
|
|
34
41
|
* Kill `name` split and set `defaultTreatment` and `changeNumber`.
|
|
35
42
|
* Used for SPLIT_KILL push notifications.
|
|
@@ -32,9 +32,6 @@ var KeyBuilderCS = /** @class */ (function (_super) {
|
|
|
32
32
|
KeyBuilderCS.prototype.buildTillKey = function () {
|
|
33
33
|
return this.prefix + "." + this.matchingKey + ".segments.till";
|
|
34
34
|
};
|
|
35
|
-
KeyBuilderCS.prototype.buildLastClear = function () {
|
|
36
|
-
return this.prefix + ".lastClear";
|
|
37
|
-
};
|
|
38
35
|
return KeyBuilderCS;
|
|
39
36
|
}(KeyBuilder_1.KeyBuilder));
|
|
40
37
|
exports.KeyBuilderCS = KeyBuilderCS;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.dataLoaderFactory = void 0;
|
|
4
|
-
|
|
5
|
-
var DEFAULT_CACHE_EXPIRATION_IN_MILLIS = 864000000; // 10 days
|
|
4
|
+
var browser_1 = require("../utils/constants/browser");
|
|
6
5
|
/**
|
|
7
6
|
* Factory of client-side storage loader
|
|
8
7
|
*
|
|
@@ -26,7 +25,7 @@ function dataLoaderFactory(preloadedData) {
|
|
|
26
25
|
return;
|
|
27
26
|
var _a = preloadedData.lastUpdated, lastUpdated = _a === void 0 ? -1 : _a, _b = preloadedData.segmentsData, segmentsData = _b === void 0 ? {} : _b, _c = preloadedData.since, since = _c === void 0 ? -1 : _c, _d = preloadedData.splitsData, splitsData = _d === void 0 ? {} : _d;
|
|
28
27
|
var storedSince = storage.splits.getChangeNumber();
|
|
29
|
-
var expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
|
|
28
|
+
var expirationTimestamp = Date.now() - browser_1.DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
|
|
30
29
|
// Do not load data if current localStorage data is more recent,
|
|
31
30
|
// or if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
|
|
32
31
|
if (storedSince > since || lastUpdated < expirationTimestamp)
|
|
@@ -5,17 +5,21 @@ var tslib_1 = require("tslib");
|
|
|
5
5
|
var AbstractSplitsCacheSync_1 = require("../AbstractSplitsCacheSync");
|
|
6
6
|
var lang_1 = require("../../utils/lang");
|
|
7
7
|
var constants_1 = require("./constants");
|
|
8
|
+
var KeyBuilder_1 = require("../KeyBuilder");
|
|
8
9
|
var sets_1 = require("../../utils/lang/sets");
|
|
9
10
|
/**
|
|
10
11
|
* ISplitsCacheSync implementation that stores split definitions in browser LocalStorage.
|
|
11
12
|
*/
|
|
12
13
|
var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
13
14
|
(0, tslib_1.__extends)(SplitsCacheInLocal, _super);
|
|
14
|
-
function SplitsCacheInLocal(settings, keys) {
|
|
15
|
+
function SplitsCacheInLocal(settings, keys, expirationTimestamp) {
|
|
15
16
|
var _this = _super.call(this) || this;
|
|
16
17
|
_this.keys = keys;
|
|
17
18
|
_this.log = settings.log;
|
|
19
|
+
_this.storageHash = (0, KeyBuilder_1.getStorageHash)(settings);
|
|
18
20
|
_this.flagSetsFilter = settings.sync.__splitFiltersValidation.groupedFilters.bySet;
|
|
21
|
+
_this._checkExpiration(expirationTimestamp);
|
|
22
|
+
_this._checkFilterQuery();
|
|
19
23
|
return _this;
|
|
20
24
|
}
|
|
21
25
|
SplitsCacheInLocal.prototype._decrementCount = function (key) {
|
|
@@ -63,6 +67,7 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
63
67
|
* We cannot simply call `localStorage.clear()` since that implies removing user items from the storage.
|
|
64
68
|
*/
|
|
65
69
|
SplitsCacheInLocal.prototype.clear = function () {
|
|
70
|
+
this.log.info(constants_1.LOG_PREFIX + 'Flushing Splits data from localStorage');
|
|
66
71
|
// collect item keys
|
|
67
72
|
var len = localStorage.length;
|
|
68
73
|
var accum = [];
|
|
@@ -114,6 +119,18 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
114
119
|
return item && JSON.parse(item);
|
|
115
120
|
};
|
|
116
121
|
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(constants_1.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(constants_1.LOG_PREFIX + e);
|
|
131
|
+
}
|
|
132
|
+
this.updateNewFilter = false;
|
|
133
|
+
}
|
|
117
134
|
try {
|
|
118
135
|
localStorage.setItem(this.keys.buildSplitsTillKey(), changeNumber + '');
|
|
119
136
|
// update "last updated" timestamp with current time
|
|
@@ -164,6 +181,45 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
164
181
|
return true;
|
|
165
182
|
}
|
|
166
183
|
};
|
|
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 (!(0, lang_1.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(constants_1.LOG_PREFIX + e);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// if the filter didn't change, nothing is done
|
|
222
|
+
};
|
|
167
223
|
SplitsCacheInLocal.prototype.getNamesByFlagSets = function (flagSets) {
|
|
168
224
|
var _this = this;
|
|
169
225
|
return flagSets.map(function (flagSet) {
|
|
@@ -9,13 +9,13 @@ var KeyBuilderCS_1 = require("../KeyBuilderCS");
|
|
|
9
9
|
var isLocalStorageAvailable_1 = require("../../utils/env/isLocalStorageAvailable");
|
|
10
10
|
var SplitsCacheInLocal_1 = require("./SplitsCacheInLocal");
|
|
11
11
|
var MySegmentsCacheInLocal_1 = require("./MySegmentsCacheInLocal");
|
|
12
|
+
var browser_1 = require("../../utils/constants/browser");
|
|
12
13
|
var InMemoryStorageCS_1 = require("../inMemory/InMemoryStorageCS");
|
|
13
14
|
var constants_1 = require("./constants");
|
|
14
15
|
var constants_2 = require("../../utils/constants");
|
|
15
16
|
var TelemetryCacheInMemory_1 = require("../inMemory/TelemetryCacheInMemory");
|
|
16
17
|
var UniqueKeysCacheInMemoryCS_1 = require("../inMemory/UniqueKeysCacheInMemoryCS");
|
|
17
18
|
var key_1 = require("../../utils/key");
|
|
18
|
-
var validateCache_1 = require("./validateCache");
|
|
19
19
|
/**
|
|
20
20
|
* InLocal storage factory for standalone client-side SplitFactory
|
|
21
21
|
*/
|
|
@@ -31,7 +31,8 @@ function InLocalStorage(options) {
|
|
|
31
31
|
var settings = params.settings, _a = params.settings, log = _a.log, _b = _a.scheduler, impressionsQueueSize = _b.impressionsQueueSize, eventsQueueSize = _b.eventsQueueSize;
|
|
32
32
|
var matchingKey = (0, key_1.getMatching)(settings.core.key);
|
|
33
33
|
var keys = new KeyBuilderCS_1.KeyBuilderCS(prefix, matchingKey);
|
|
34
|
-
var
|
|
34
|
+
var expirationTimestamp = Date.now() - browser_1.DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
|
|
35
|
+
var splits = new SplitsCacheInLocal_1.SplitsCacheInLocal(settings, keys, expirationTimestamp);
|
|
35
36
|
var segments = new MySegmentsCacheInLocal_1.MySegmentsCacheInLocal(log, keys);
|
|
36
37
|
var largeSegments = new MySegmentsCacheInLocal_1.MySegmentsCacheInLocal(log, (0, KeyBuilderCS_1.myLargeSegmentsKeyBuilder)(prefix, matchingKey));
|
|
37
38
|
return {
|
|
@@ -43,9 +44,6 @@ function InLocalStorage(options) {
|
|
|
43
44
|
events: new EventsCacheInMemory_1.EventsCacheInMemory(eventsQueueSize),
|
|
44
45
|
telemetry: (0, TelemetryCacheInMemory_1.shouldRecordTelemetry)(params) ? new TelemetryCacheInMemory_1.TelemetryCacheInMemory(splits, segments) : undefined,
|
|
45
46
|
uniqueKeys: new UniqueKeysCacheInMemoryCS_1.UniqueKeysCacheInMemoryCS(),
|
|
46
|
-
validateCache: function () {
|
|
47
|
-
return (0, validateCache_1.validateCache)(options, settings, keys, splits, segments, largeSegments);
|
|
48
|
-
},
|
|
49
47
|
destroy: function () { },
|
|
50
48
|
// When using shared instantiation with MEMORY we reuse everything but segments (they are customer per key).
|
|
51
49
|
shared: function (matchingKey) {
|
|
@@ -3,5 +3,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.TTL_REFRESH = exports.REFRESH_RATE = exports.DEFAULT_CACHE_SIZE = exports.LOG_PREFIX = void 0;
|
|
4
4
|
exports.LOG_PREFIX = 'storage:redis: ';
|
|
5
5
|
exports.DEFAULT_CACHE_SIZE = 30000;
|
|
6
|
-
exports.REFRESH_RATE = 300000; //
|
|
6
|
+
exports.REFRESH_RATE = 300000; // 300000 ms = start after 5 mins
|
|
7
7
|
exports.TTL_REFRESH = 3600; // 1hr
|
|
@@ -74,8 +74,7 @@ function PluggableStorage(options) {
|
|
|
74
74
|
// Connects to wrapper and emits SDK_READY event on main client
|
|
75
75
|
var connectPromise = wrapper.connect().then(function () {
|
|
76
76
|
if (isSynchronizer) {
|
|
77
|
-
//
|
|
78
|
-
// In standalone or producer mode, clear storage if SDK key, flags filter criteria or flags spec version was modified
|
|
77
|
+
// In standalone or producer mode, clear storage if SDK key or feature flag filter has changed
|
|
79
78
|
return wrapper.get(keys.buildHashKey()).then(function (hash) {
|
|
80
79
|
var currentHash = (0, KeyBuilder_1.getStorageHash)(settings);
|
|
81
80
|
if (hash !== currentHash) {
|
|
@@ -46,10 +46,9 @@ function fromObjectUpdaterFactory(splitsParser, storage, readiness, settings) {
|
|
|
46
46
|
readiness.splits.emit(constants_2.SDK_SPLITS_ARRIVED);
|
|
47
47
|
if (startingUp) {
|
|
48
48
|
startingUp = false;
|
|
49
|
-
|
|
50
|
-
Promise.resolve().then(function () {
|
|
49
|
+
Promise.resolve(splitsCache.checkCache()).then(function (cacheReady) {
|
|
51
50
|
// Emits SDK_READY_FROM_CACHE
|
|
52
|
-
if (
|
|
51
|
+
if (cacheReady)
|
|
53
52
|
readiness.splits.emit(constants_2.SDK_SPLITS_CACHE_LOADED);
|
|
54
53
|
// Emits SDK_READY
|
|
55
54
|
readiness.segments.emit(constants_2.SDK_SEGMENTS_ARRIVED);
|
|
@@ -126,7 +126,7 @@ function splitChangesUpdaterFactory(log, splitChangesFetcher, splits, segments,
|
|
|
126
126
|
function _splitChangesUpdater(since, retry) {
|
|
127
127
|
if (retry === void 0) { retry = 0; }
|
|
128
128
|
log.debug(constants_2.SYNC_SPLITS_FETCH, [since]);
|
|
129
|
-
|
|
129
|
+
var fetcherPromise = Promise.resolve(splitUpdateNotification ?
|
|
130
130
|
{ splits: [splitUpdateNotification.payload], till: splitUpdateNotification.changeNumber } :
|
|
131
131
|
splitChangesFetcher(since, noCache, till, _promiseDecorator))
|
|
132
132
|
.then(function (splitChanges) {
|
|
@@ -170,6 +170,15 @@ function splitChangesUpdaterFactory(log, splitChangesFetcher, splits, segments,
|
|
|
170
170
|
}
|
|
171
171
|
return false;
|
|
172
172
|
});
|
|
173
|
+
// After triggering the requests, if we have cached splits information let's notify that to emit SDK_READY_FROM_CACHE.
|
|
174
|
+
// Wrapping in a promise since checkCache can be async.
|
|
175
|
+
if (splitsEventEmitter && startingUp) {
|
|
176
|
+
Promise.resolve(splits.checkCache()).then(function (isCacheReady) {
|
|
177
|
+
if (isCacheReady)
|
|
178
|
+
splitsEventEmitter.emit(constants_1.SDK_SPLITS_CACHE_LOADED);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
return fetcherPromise;
|
|
173
182
|
}
|
|
174
183
|
var sincePromise = Promise.resolve(splits.getChangeNumber()); // `getChangeNumber` never rejects or throws error
|
|
175
184
|
return sincePromise.then(_splitChangesUpdater);
|
|
@@ -6,7 +6,6 @@ var constants_1 = require("./streaming/constants");
|
|
|
6
6
|
var constants_2 = require("../logger/constants");
|
|
7
7
|
var consent_1 = require("../consent");
|
|
8
8
|
var constants_3 = require("../utils/constants");
|
|
9
|
-
var constants_4 = require("../readiness/constants");
|
|
10
9
|
/**
|
|
11
10
|
* Online SyncManager factory.
|
|
12
11
|
* Can be used for server-side API, and client-side API with or without multiple clients.
|
|
@@ -20,7 +19,7 @@ function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFactory) {
|
|
|
20
19
|
* SyncManager factory for modular SDK
|
|
21
20
|
*/
|
|
22
21
|
return function (params) {
|
|
23
|
-
var settings = params.settings, _a = params.settings, log = _a.log, streamingEnabled = _a.streamingEnabled, syncEnabled = _a.sync.enabled, telemetryTracker = params.telemetryTracker
|
|
22
|
+
var settings = params.settings, _a = params.settings, log = _a.log, streamingEnabled = _a.streamingEnabled, syncEnabled = _a.sync.enabled, telemetryTracker = params.telemetryTracker;
|
|
24
23
|
/** Polling Manager */
|
|
25
24
|
var pollingManager = pollingManagerFactory && pollingManagerFactory(params);
|
|
26
25
|
/** Push Manager */
|
|
@@ -68,11 +67,6 @@ function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFactory) {
|
|
|
68
67
|
*/
|
|
69
68
|
start: function () {
|
|
70
69
|
running = true;
|
|
71
|
-
if (startFirstTime) {
|
|
72
|
-
var isCacheLoaded = storage.validateCache ? storage.validateCache() : false;
|
|
73
|
-
if (isCacheLoaded)
|
|
74
|
-
Promise.resolve().then(function () { readiness.splits.emit(constants_4.SDK_SPLITS_CACHE_LOADED); });
|
|
75
|
-
}
|
|
76
70
|
// start syncing splits and segments
|
|
77
71
|
if (pollingManager) {
|
|
78
72
|
// If synchronization is disabled pushManager and pollingManager should not start
|
|
@@ -81,6 +75,7 @@ function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFactory) {
|
|
|
81
75
|
// Doesn't call `syncAll` when the syncManager is resuming
|
|
82
76
|
if (startFirstTime) {
|
|
83
77
|
pollingManager.syncAll();
|
|
78
|
+
startFirstTime = false;
|
|
84
79
|
}
|
|
85
80
|
pushManager.start();
|
|
86
81
|
}
|
|
@@ -91,12 +86,12 @@ function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFactory) {
|
|
|
91
86
|
else {
|
|
92
87
|
if (startFirstTime) {
|
|
93
88
|
pollingManager.syncAll();
|
|
89
|
+
startFirstTime = false;
|
|
94
90
|
}
|
|
95
91
|
}
|
|
96
92
|
}
|
|
97
93
|
// start periodic data recording (events, impressions, telemetry).
|
|
98
94
|
submitterManager.start(!(0, consent_1.isConsentGranted)(settings));
|
|
99
|
-
startFirstTime = false;
|
|
100
95
|
},
|
|
101
96
|
/**
|
|
102
97
|
* Method used to stop/pause the syncManager.
|
|
@@ -8,7 +8,7 @@ var noopFilterAdapter = {
|
|
|
8
8
|
clear: function () { }
|
|
9
9
|
};
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* Tracks uniques keys
|
|
12
12
|
* Unique Keys Tracker will be in charge of checking if the MTK was already sent to the BE in the last period
|
|
13
13
|
* or schedule to be sent; if not it will be added in an internal cache and sent in the next post.
|
|
14
14
|
*
|
|
@@ -6,7 +6,7 @@ var constants_1 = require("../../../logger/constants");
|
|
|
6
6
|
var constants_2 = require("../../../utils/constants");
|
|
7
7
|
function __InLocalStorageMockFactory(params) {
|
|
8
8
|
var result = (0, InMemoryStorageCS_1.InMemoryStorageCSFactory)(params);
|
|
9
|
-
result.
|
|
9
|
+
result.splits.checkCache = function () { return true; }; // to emit SDK_READY_FROM_CACHE
|
|
10
10
|
return result;
|
|
11
11
|
}
|
|
12
12
|
exports.__InLocalStorageMockFactory = __InLocalStorageMockFactory;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
2
2
|
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';
|
|
3
|
-
import { STORAGE_LOCALSTORAGE } from '../utils/constants';
|
|
4
3
|
function splitsEventEmitterFactory(EventEmitter) {
|
|
5
4
|
var splitsEventEmitter = objectAssign(new EventEmitter(), {
|
|
6
5
|
splitsArrived: false,
|
|
@@ -81,7 +80,6 @@ export function readinessManagerFactory(EventEmitter, settings, splits, isShared
|
|
|
81
80
|
}
|
|
82
81
|
}
|
|
83
82
|
function checkIsReadyOrUpdate(diff) {
|
|
84
|
-
var _a;
|
|
85
83
|
if (isDestroyed)
|
|
86
84
|
return;
|
|
87
85
|
if (isReady) {
|
|
@@ -100,10 +98,6 @@ export function readinessManagerFactory(EventEmitter, settings, splits, isShared
|
|
|
100
98
|
isReady = true;
|
|
101
99
|
try {
|
|
102
100
|
syncLastUpdate();
|
|
103
|
-
if (!isReadyFromCache && ((_a = settings.storage) === null || _a === void 0 ? void 0 : _a.type) === STORAGE_LOCALSTORAGE) {
|
|
104
|
-
isReadyFromCache = true;
|
|
105
|
-
gate.emit(SDK_READY_FROM_CACHE);
|
|
106
|
-
}
|
|
107
101
|
gate.emit(SDK_READY);
|
|
108
102
|
}
|
|
109
103
|
catch (e) {
|
|
@@ -11,6 +11,13 @@ var AbstractSplitsCacheAsync = /** @class */ (function () {
|
|
|
11
11
|
AbstractSplitsCacheAsync.prototype.usesSegments = function () {
|
|
12
12
|
return Promise.resolve(true);
|
|
13
13
|
};
|
|
14
|
+
/**
|
|
15
|
+
* Check if the splits information is already stored in cache.
|
|
16
|
+
* Noop, just keeping the interface. This is used by client-side implementations only.
|
|
17
|
+
*/
|
|
18
|
+
AbstractSplitsCacheAsync.prototype.checkCache = function () {
|
|
19
|
+
return Promise.resolve(false);
|
|
20
|
+
};
|
|
14
21
|
/**
|
|
15
22
|
* Kill `name` split and set `defaultTreatment` and `changeNumber`.
|
|
16
23
|
* Used for SPLIT_KILL push notifications.
|
|
@@ -27,6 +27,13 @@ var AbstractSplitsCacheSync = /** @class */ (function () {
|
|
|
27
27
|
var _this = this;
|
|
28
28
|
return this.getSplitNames().map(function (key) { return _this.getSplit(key); });
|
|
29
29
|
};
|
|
30
|
+
/**
|
|
31
|
+
* Check if the splits information is already stored in cache. This data can be preloaded.
|
|
32
|
+
* It is used as condition to emit SDK_SPLITS_CACHE_LOADED, and then SDK_READY_FROM_CACHE.
|
|
33
|
+
*/
|
|
34
|
+
AbstractSplitsCacheSync.prototype.checkCache = function () {
|
|
35
|
+
return false;
|
|
36
|
+
};
|
|
30
37
|
/**
|
|
31
38
|
* Kill `name` split and set `defaultTreatment` and `changeNumber`.
|
|
32
39
|
* Used for SPLIT_KILL push notifications.
|
|
@@ -29,9 +29,6 @@ var KeyBuilderCS = /** @class */ (function (_super) {
|
|
|
29
29
|
KeyBuilderCS.prototype.buildTillKey = function () {
|
|
30
30
|
return this.prefix + "." + this.matchingKey + ".segments.till";
|
|
31
31
|
};
|
|
32
|
-
KeyBuilderCS.prototype.buildLastClear = function () {
|
|
33
|
-
return this.prefix + ".lastClear";
|
|
34
|
-
};
|
|
35
32
|
return KeyBuilderCS;
|
|
36
33
|
}(KeyBuilder));
|
|
37
34
|
export { KeyBuilderCS };
|
|
@@ -2,17 +2,21 @@ 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';
|
|
5
6
|
import { setToArray } from '../../utils/lang/sets';
|
|
6
7
|
/**
|
|
7
8
|
* ISplitsCacheSync implementation that stores split definitions in browser LocalStorage.
|
|
8
9
|
*/
|
|
9
10
|
var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
10
11
|
__extends(SplitsCacheInLocal, _super);
|
|
11
|
-
function SplitsCacheInLocal(settings, keys) {
|
|
12
|
+
function SplitsCacheInLocal(settings, keys, expirationTimestamp) {
|
|
12
13
|
var _this = _super.call(this) || this;
|
|
13
14
|
_this.keys = keys;
|
|
14
15
|
_this.log = settings.log;
|
|
16
|
+
_this.storageHash = getStorageHash(settings);
|
|
15
17
|
_this.flagSetsFilter = settings.sync.__splitFiltersValidation.groupedFilters.bySet;
|
|
18
|
+
_this._checkExpiration(expirationTimestamp);
|
|
19
|
+
_this._checkFilterQuery();
|
|
16
20
|
return _this;
|
|
17
21
|
}
|
|
18
22
|
SplitsCacheInLocal.prototype._decrementCount = function (key) {
|
|
@@ -60,6 +64,7 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
60
64
|
* We cannot simply call `localStorage.clear()` since that implies removing user items from the storage.
|
|
61
65
|
*/
|
|
62
66
|
SplitsCacheInLocal.prototype.clear = function () {
|
|
67
|
+
this.log.info(LOG_PREFIX + 'Flushing Splits data from localStorage');
|
|
63
68
|
// collect item keys
|
|
64
69
|
var len = localStorage.length;
|
|
65
70
|
var accum = [];
|
|
@@ -111,6 +116,18 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
111
116
|
return item && JSON.parse(item);
|
|
112
117
|
};
|
|
113
118
|
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
|
+
}
|
|
114
131
|
try {
|
|
115
132
|
localStorage.setItem(this.keys.buildSplitsTillKey(), changeNumber + '');
|
|
116
133
|
// update "last updated" timestamp with current time
|
|
@@ -161,6 +178,45 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
161
178
|
return true;
|
|
162
179
|
}
|
|
163
180
|
};
|
|
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
|
+
};
|
|
164
220
|
SplitsCacheInLocal.prototype.getNamesByFlagSets = function (flagSets) {
|
|
165
221
|
var _this = this;
|
|
166
222
|
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';
|
|
9
10
|
import { InMemoryStorageCSFactory } from '../inMemory/InMemoryStorageCS';
|
|
10
11
|
import { LOG_PREFIX } from './constants';
|
|
11
12
|
import { STORAGE_LOCALSTORAGE } from '../../utils/constants';
|
|
12
13
|
import { shouldRecordTelemetry, TelemetryCacheInMemory } from '../inMemory/TelemetryCacheInMemory';
|
|
13
14
|
import { UniqueKeysCacheInMemoryCS } from '../inMemory/UniqueKeysCacheInMemoryCS';
|
|
14
15
|
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,7 +28,8 @@ 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
|
|
31
|
+
var expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
|
|
32
|
+
var splits = new SplitsCacheInLocal(settings, keys, expirationTimestamp);
|
|
32
33
|
var segments = new MySegmentsCacheInLocal(log, keys);
|
|
33
34
|
var largeSegments = new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey));
|
|
34
35
|
return {
|
|
@@ -40,9 +41,6 @@ export function InLocalStorage(options) {
|
|
|
40
41
|
events: new EventsCacheInMemory(eventsQueueSize),
|
|
41
42
|
telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
|
|
42
43
|
uniqueKeys: new UniqueKeysCacheInMemoryCS(),
|
|
43
|
-
validateCache: function () {
|
|
44
|
-
return validateCache(options, settings, keys, splits, segments, largeSegments);
|
|
45
|
-
},
|
|
46
44
|
destroy: function () { },
|
|
47
45
|
// When using shared instantiation with MEMORY we reuse everything but segments (they are customer per key).
|
|
48
46
|
shared: function (matchingKey) {
|
|
@@ -71,8 +71,7 @@ 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
|
-
//
|
|
75
|
-
// In standalone or producer mode, clear storage if SDK key, flags filter criteria or flags spec version was modified
|
|
74
|
+
// In standalone or producer mode, clear storage if SDK key or feature flag filter has changed
|
|
76
75
|
return wrapper.get(keys.buildHashKey()).then(function (hash) {
|
|
77
76
|
var currentHash = getStorageHash(settings);
|
|
78
77
|
if (hash !== currentHash) {
|
|
@@ -43,10 +43,9 @@ 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
|
-
Promise.resolve().then(function () {
|
|
46
|
+
Promise.resolve(splitsCache.checkCache()).then(function (cacheReady) {
|
|
48
47
|
// Emits SDK_READY_FROM_CACHE
|
|
49
|
-
if (
|
|
48
|
+
if (cacheReady)
|
|
50
49
|
readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
|
|
51
50
|
// Emits SDK_READY
|
|
52
51
|
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { timeout } from '../../../utils/promise/timeout';
|
|
2
|
-
import { SDK_SPLITS_ARRIVED } from '../../../readiness/constants';
|
|
2
|
+
import { SDK_SPLITS_ARRIVED, SDK_SPLITS_CACHE_LOADED } 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
|
+
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);
|