@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.
Files changed (53) hide show
  1. package/CHANGES.txt +2 -7
  2. package/cjs/readiness/readinessManager.js +0 -6
  3. package/cjs/storages/AbstractSplitsCacheAsync.js +7 -0
  4. package/cjs/storages/AbstractSplitsCacheSync.js +7 -0
  5. package/cjs/storages/KeyBuilderCS.js +0 -3
  6. package/cjs/storages/dataLoader.js +2 -3
  7. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +57 -1
  8. package/cjs/storages/inLocalStorage/index.js +3 -5
  9. package/cjs/storages/inRedis/constants.js +1 -1
  10. package/cjs/storages/pluggable/index.js +1 -2
  11. package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +2 -3
  12. package/cjs/sync/polling/updaters/splitChangesUpdater.js +10 -1
  13. package/cjs/sync/syncManagerOnline.js +3 -8
  14. package/cjs/trackers/uniqueKeysTracker.js +1 -1
  15. package/cjs/utils/constants/browser.js +5 -0
  16. package/cjs/utils/settingsValidation/storage/storageCS.js +1 -1
  17. package/esm/readiness/readinessManager.js +0 -6
  18. package/esm/storages/AbstractSplitsCacheAsync.js +7 -0
  19. package/esm/storages/AbstractSplitsCacheSync.js +7 -0
  20. package/esm/storages/KeyBuilderCS.js +0 -3
  21. package/esm/storages/dataLoader.js +1 -2
  22. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +57 -1
  23. package/esm/storages/inLocalStorage/index.js +3 -5
  24. package/esm/storages/inRedis/constants.js +1 -1
  25. package/esm/storages/pluggable/index.js +1 -2
  26. package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +2 -3
  27. package/esm/sync/polling/updaters/splitChangesUpdater.js +11 -2
  28. package/esm/sync/syncManagerOnline.js +3 -8
  29. package/esm/trackers/uniqueKeysTracker.js +1 -1
  30. package/esm/utils/constants/browser.js +2 -0
  31. package/esm/utils/settingsValidation/storage/storageCS.js +1 -1
  32. package/package.json +1 -1
  33. package/src/readiness/readinessManager.ts +0 -5
  34. package/src/storages/AbstractSplitsCacheAsync.ts +8 -0
  35. package/src/storages/AbstractSplitsCacheSync.ts +8 -0
  36. package/src/storages/KeyBuilderCS.ts +0 -4
  37. package/src/storages/dataLoader.ts +1 -3
  38. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +66 -1
  39. package/src/storages/inLocalStorage/index.ts +8 -8
  40. package/src/storages/inRedis/constants.ts +1 -1
  41. package/src/storages/pluggable/index.ts +1 -2
  42. package/src/storages/types.ts +4 -1
  43. package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +5 -6
  44. package/src/sync/polling/updaters/splitChangesUpdater.ts +11 -2
  45. package/src/sync/syncManagerOnline.ts +3 -9
  46. package/src/trackers/uniqueKeysTracker.ts +1 -1
  47. package/src/utils/constants/browser.ts +2 -0
  48. package/src/utils/lang/index.ts +1 -1
  49. package/src/utils/settingsValidation/storage/storageCS.ts +1 -1
  50. package/types/splitio.d.ts +1 -25
  51. package/cjs/storages/inLocalStorage/validateCache.js +0 -79
  52. package/esm/storages/inLocalStorage/validateCache.js +0 -75
  53. package/src/storages/inLocalStorage/validateCache.ts +0 -91
package/CHANGES.txt CHANGED
@@ -1,10 +1,5 @@
1
- 2.1.0 (January XX, 2025)
2
- - Added `impressionsDisabled` property to SDK Manager's `SplitView` type.
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
- // This value might be eventually set via a config parameter
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 splits = new SplitsCacheInLocal_1.SplitsCacheInLocal(settings, keys);
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; // 300.000 ms = start after 5 mins
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
- // @TODO reuse InLocalStorage::validateCache logic
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
- var isCacheLoaded_1 = storage.validateCache ? storage.validateCache() : false;
50
- Promise.resolve().then(function () {
49
+ Promise.resolve(splitsCache.checkCache()).then(function (cacheReady) {
51
50
  // Emits SDK_READY_FROM_CACHE
52
- if (isCacheLoaded_1)
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
- return Promise.resolve(splitUpdateNotification ?
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, storage = params.storage, readiness = params.readiness;
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
- * Trackes uniques keys
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
  *
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_CACHE_EXPIRATION_IN_MILLIS = void 0;
4
+ // This value might be eventually set via a config parameter
5
+ exports.DEFAULT_CACHE_EXPIRATION_IN_MILLIS = 864000000; // 10 days
@@ -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.validateCache = function () { return true; }; // to emit SDK_READY_FROM_CACHE
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 };
@@ -1,5 +1,4 @@
1
- // This value might be eventually set via a config parameter
2
- var DEFAULT_CACHE_EXPIRATION_IN_MILLIS = 864000000; // 10 days
1
+ import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../utils/constants/browser';
3
2
  /**
4
3
  * Factory of client-side storage loader
5
4
  *
@@ -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 splits = new SplitsCacheInLocal(settings, keys);
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) {
@@ -1,4 +1,4 @@
1
1
  export var LOG_PREFIX = 'storage:redis: ';
2
2
  export var DEFAULT_CACHE_SIZE = 30000;
3
- export var REFRESH_RATE = 300000; // 300.000 ms = start after 5 mins
3
+ export var REFRESH_RATE = 300000; // 300000 ms = start after 5 mins
4
4
  export var TTL_REFRESH = 3600; // 1hr
@@ -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
- // @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
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
- var isCacheLoaded_1 = storage.validateCache ? storage.validateCache() : false;
47
- Promise.resolve().then(function () {
46
+ Promise.resolve(splitsCache.checkCache()).then(function (cacheReady) {
48
47
  // Emits SDK_READY_FROM_CACHE
49
- if (isCacheLoaded_1)
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
- return Promise.resolve(splitUpdateNotification ?
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);