@splitsoftware/splitio-commons 2.4.2-rc.1 → 2.4.2-rc.3

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 (64) hide show
  1. package/CHANGES.txt +13 -1
  2. package/cjs/logger/messages/error.js +1 -1
  3. package/cjs/sdkClient/sdkClient.js +0 -1
  4. package/cjs/sdkFactory/index.js +3 -10
  5. package/cjs/services/splitHttpClient.js +1 -1
  6. package/cjs/storages/AbstractSplitsCacheSync.js +5 -1
  7. package/cjs/storages/inLocalStorage/MySegmentsCacheInLocal.js +16 -16
  8. package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +17 -17
  9. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +33 -37
  10. package/cjs/storages/inLocalStorage/index.js +31 -13
  11. package/cjs/storages/inLocalStorage/storageAdapter.js +54 -0
  12. package/cjs/storages/inLocalStorage/validateCache.js +28 -23
  13. package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +2 -3
  14. package/cjs/sync/polling/pollingManagerCS.js +5 -4
  15. package/cjs/sync/polling/updaters/mySegmentsUpdater.js +5 -2
  16. package/cjs/sync/polling/updaters/splitChangesUpdater.js +3 -1
  17. package/cjs/sync/syncManagerOnline.js +31 -26
  18. package/cjs/trackers/impressionsTracker.js +4 -4
  19. package/cjs/utils/env/isLocalStorageAvailable.js +28 -5
  20. package/cjs/utils/settingsValidation/splitFilters.js +0 -6
  21. package/cjs/utils/settingsValidation/storage/storageCS.js +1 -1
  22. package/esm/logger/messages/error.js +1 -1
  23. package/esm/sdkClient/sdkClient.js +0 -1
  24. package/esm/sdkFactory/index.js +3 -10
  25. package/esm/services/splitHttpClient.js +1 -1
  26. package/esm/storages/AbstractSplitsCacheSync.js +3 -0
  27. package/esm/storages/inLocalStorage/MySegmentsCacheInLocal.js +16 -16
  28. package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +17 -17
  29. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +33 -37
  30. package/esm/storages/inLocalStorage/index.js +32 -14
  31. package/esm/storages/inLocalStorage/storageAdapter.js +50 -0
  32. package/esm/storages/inLocalStorage/validateCache.js +28 -23
  33. package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +2 -3
  34. package/esm/sync/polling/pollingManagerCS.js +5 -4
  35. package/esm/sync/polling/updaters/mySegmentsUpdater.js +5 -2
  36. package/esm/sync/polling/updaters/splitChangesUpdater.js +3 -1
  37. package/esm/sync/syncManagerOnline.js +31 -26
  38. package/esm/trackers/impressionsTracker.js +4 -4
  39. package/esm/utils/env/isLocalStorageAvailable.js +24 -3
  40. package/esm/utils/settingsValidation/splitFilters.js +0 -6
  41. package/esm/utils/settingsValidation/storage/storageCS.js +1 -1
  42. package/package.json +1 -1
  43. package/src/logger/messages/error.ts +1 -1
  44. package/src/sdkClient/sdkClient.ts +0 -1
  45. package/src/sdkFactory/index.ts +3 -13
  46. package/src/services/splitHttpClient.ts +1 -1
  47. package/src/storages/AbstractSplitsCacheSync.ts +5 -1
  48. package/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +18 -17
  49. package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +19 -18
  50. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +34 -37
  51. package/src/storages/inLocalStorage/index.ts +37 -16
  52. package/src/storages/inLocalStorage/storageAdapter.ts +62 -0
  53. package/src/storages/inLocalStorage/validateCache.ts +29 -23
  54. package/src/storages/types.ts +19 -1
  55. package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +1 -2
  56. package/src/sync/polling/pollingManagerCS.ts +5 -4
  57. package/src/sync/polling/updaters/mySegmentsUpdater.ts +5 -2
  58. package/src/sync/polling/updaters/splitChangesUpdater.ts +4 -2
  59. package/src/sync/syncManagerOnline.ts +30 -24
  60. package/src/trackers/impressionsTracker.ts +3 -3
  61. package/src/utils/env/isLocalStorageAvailable.ts +24 -3
  62. package/src/utils/settingsValidation/splitFilters.ts +0 -6
  63. package/src/utils/settingsValidation/storage/storageCS.ts +1 -1
  64. package/types/splitio.d.ts +72 -16
@@ -3,25 +3,22 @@ import { AbstractSplitsCacheSync, usesSegments } from '../AbstractSplitsCacheSyn
3
3
  import { isFiniteNumber, toNumber, isNaNNumber } from '../../utils/lang';
4
4
  import { LOG_PREFIX } from './constants';
5
5
  import { setToArray } from '../../utils/lang/sets';
6
- /**
7
- * ISplitsCacheSync implementation that stores split definitions in browser LocalStorage.
8
- */
9
6
  var SplitsCacheInLocal = /** @class */ (function (_super) {
10
7
  __extends(SplitsCacheInLocal, _super);
11
- function SplitsCacheInLocal(settings, keys) {
8
+ function SplitsCacheInLocal(settings, keys, storage) {
12
9
  var _this = _super.call(this) || this;
13
10
  _this.keys = keys;
14
11
  _this.log = settings.log;
15
12
  _this.flagSetsFilter = settings.sync.__splitFiltersValidation.groupedFilters.bySet;
13
+ _this.storage = storage;
16
14
  return _this;
17
15
  }
18
16
  SplitsCacheInLocal.prototype._decrementCount = function (key) {
19
- var count = toNumber(localStorage.getItem(key)) - 1;
20
- // @ts-expect-error
17
+ var count = toNumber(this.storage.getItem(key)) - 1;
21
18
  if (count > 0)
22
- localStorage.setItem(key, count);
19
+ this.storage.setItem(key, count + '');
23
20
  else
24
- localStorage.removeItem(key);
21
+ this.storage.removeItem(key);
25
22
  };
26
23
  SplitsCacheInLocal.prototype._decrementCounts = function (split) {
27
24
  try {
@@ -39,12 +36,10 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
39
36
  SplitsCacheInLocal.prototype._incrementCounts = function (split) {
40
37
  try {
41
38
  var ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName);
42
- // @ts-expect-error
43
- localStorage.setItem(ttKey, toNumber(localStorage.getItem(ttKey)) + 1);
39
+ this.storage.setItem(ttKey, (toNumber(this.storage.getItem(ttKey)) + 1) + '');
44
40
  if (usesSegments(split)) {
45
41
  var segmentsCountKey = this.keys.buildSplitsWithSegmentCountKey();
46
- // @ts-expect-error
47
- localStorage.setItem(segmentsCountKey, toNumber(localStorage.getItem(segmentsCountKey)) + 1);
42
+ this.storage.setItem(segmentsCountKey, (toNumber(this.storage.getItem(segmentsCountKey)) + 1) + '');
48
43
  }
49
44
  }
50
45
  catch (e) {
@@ -56,17 +51,18 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
56
51
  * We cannot simply call `localStorage.clear()` since that implies removing user items from the storage.
57
52
  */
58
53
  SplitsCacheInLocal.prototype.clear = function () {
54
+ var _this = this;
59
55
  // collect item keys
60
- var len = localStorage.length;
56
+ var len = this.storage.length;
61
57
  var accum = [];
62
58
  for (var cur = 0; cur < len; cur++) {
63
- var key = localStorage.key(cur);
59
+ var key = this.storage.key(cur);
64
60
  if (key != null && this.keys.isSplitsCacheKey(key))
65
61
  accum.push(key);
66
62
  }
67
63
  // remove items
68
64
  accum.forEach(function (key) {
69
- localStorage.removeItem(key);
65
+ _this.storage.removeItem(key);
70
66
  });
71
67
  this.hasSync = false;
72
68
  };
@@ -74,13 +70,13 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
74
70
  try {
75
71
  var name_1 = split.name;
76
72
  var splitKey = this.keys.buildSplitKey(name_1);
77
- var splitFromLocalStorage = localStorage.getItem(splitKey);
78
- var previousSplit = splitFromLocalStorage ? JSON.parse(splitFromLocalStorage) : null;
73
+ var splitFromStorage = this.storage.getItem(splitKey);
74
+ var previousSplit = splitFromStorage ? JSON.parse(splitFromStorage) : null;
79
75
  if (previousSplit) {
80
76
  this._decrementCounts(previousSplit);
81
77
  this.removeFromFlagSets(previousSplit.name, previousSplit.sets);
82
78
  }
83
- localStorage.setItem(splitKey, JSON.stringify(split));
79
+ this.storage.setItem(splitKey, JSON.stringify(split));
84
80
  this._incrementCounts(split);
85
81
  this.addToFlagSets(split);
86
82
  return true;
@@ -95,7 +91,7 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
95
91
  var split = this.getSplit(name);
96
92
  if (!split)
97
93
  return false;
98
- localStorage.removeItem(this.keys.buildSplitKey(name));
94
+ this.storage.removeItem(this.keys.buildSplitKey(name));
99
95
  this._decrementCounts(split);
100
96
  this.removeFromFlagSets(split.name, split.sets);
101
97
  return true;
@@ -106,14 +102,14 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
106
102
  }
107
103
  };
108
104
  SplitsCacheInLocal.prototype.getSplit = function (name) {
109
- var item = localStorage.getItem(this.keys.buildSplitKey(name));
105
+ var item = this.storage.getItem(this.keys.buildSplitKey(name));
110
106
  return item && JSON.parse(item);
111
107
  };
112
108
  SplitsCacheInLocal.prototype.setChangeNumber = function (changeNumber) {
113
109
  try {
114
- localStorage.setItem(this.keys.buildSplitsTillKey(), changeNumber + '');
110
+ this.storage.setItem(this.keys.buildSplitsTillKey(), changeNumber + '');
115
111
  // update "last updated" timestamp with current time
116
- localStorage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
112
+ this.storage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
117
113
  this.hasSync = true;
118
114
  return true;
119
115
  }
@@ -124,7 +120,7 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
124
120
  };
125
121
  SplitsCacheInLocal.prototype.getChangeNumber = function () {
126
122
  var n = -1;
127
- var value = localStorage.getItem(this.keys.buildSplitsTillKey());
123
+ var value = this.storage.getItem(this.keys.buildSplitsTillKey());
128
124
  if (value !== null) {
129
125
  value = parseInt(value, 10);
130
126
  return isNaNNumber(value) ? n : value;
@@ -132,11 +128,11 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
132
128
  return n;
133
129
  };
134
130
  SplitsCacheInLocal.prototype.getSplitNames = function () {
135
- var len = localStorage.length;
131
+ var len = this.storage.length;
136
132
  var accum = [];
137
133
  var cur = 0;
138
134
  while (cur < len) {
139
- var key = localStorage.key(cur);
135
+ var key = this.storage.key(cur);
140
136
  if (key != null && this.keys.isSplitKey(key))
141
137
  accum.push(this.keys.extractKey(key));
142
138
  cur++;
@@ -144,14 +140,14 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
144
140
  return accum;
145
141
  };
146
142
  SplitsCacheInLocal.prototype.trafficTypeExists = function (trafficType) {
147
- var ttCount = toNumber(localStorage.getItem(this.keys.buildTrafficTypeKey(trafficType)));
143
+ var ttCount = toNumber(this.storage.getItem(this.keys.buildTrafficTypeKey(trafficType)));
148
144
  return isFiniteNumber(ttCount) && ttCount > 0;
149
145
  };
150
146
  SplitsCacheInLocal.prototype.usesSegments = function () {
151
147
  // If cache hasn't been synchronized with the cloud, assume we need them.
152
148
  if (!this.hasSync)
153
149
  return true;
154
- var storedCount = localStorage.getItem(this.keys.buildSplitsWithSegmentCountKey());
150
+ var storedCount = this.storage.getItem(this.keys.buildSplitsWithSegmentCountKey());
155
151
  var splitsWithSegmentsCount = storedCount === null ? 0 : toNumber(storedCount);
156
152
  return isFiniteNumber(splitsWithSegmentsCount) ?
157
153
  splitsWithSegmentsCount > 0 :
@@ -161,8 +157,8 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
161
157
  var _this = this;
162
158
  return flagSets.map(function (flagSet) {
163
159
  var flagSetKey = _this.keys.buildFlagSetKey(flagSet);
164
- var flagSetFromLocalStorage = localStorage.getItem(flagSetKey);
165
- return new Set(flagSetFromLocalStorage ? JSON.parse(flagSetFromLocalStorage) : []);
160
+ var flagSetFromStorage = _this.storage.getItem(flagSetKey);
161
+ return new Set(flagSetFromStorage ? JSON.parse(flagSetFromStorage) : []);
166
162
  });
167
163
  };
168
164
  SplitsCacheInLocal.prototype.addToFlagSets = function (featureFlag) {
@@ -173,10 +169,10 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
173
169
  if (_this.flagSetsFilter.length > 0 && !_this.flagSetsFilter.some(function (filterFlagSet) { return filterFlagSet === featureFlagSet; }))
174
170
  return;
175
171
  var flagSetKey = _this.keys.buildFlagSetKey(featureFlagSet);
176
- var flagSetFromLocalStorage = localStorage.getItem(flagSetKey);
177
- var flagSetCache = new Set(flagSetFromLocalStorage ? JSON.parse(flagSetFromLocalStorage) : []);
172
+ var flagSetFromStorage = _this.storage.getItem(flagSetKey);
173
+ var flagSetCache = new Set(flagSetFromStorage ? JSON.parse(flagSetFromStorage) : []);
178
174
  flagSetCache.add(featureFlag.name);
179
- localStorage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache)));
175
+ _this.storage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache)));
180
176
  });
181
177
  };
182
178
  SplitsCacheInLocal.prototype.removeFromFlagSets = function (featureFlagName, flagSets) {
@@ -189,16 +185,16 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
189
185
  };
190
186
  SplitsCacheInLocal.prototype.removeNames = function (flagSetName, featureFlagName) {
191
187
  var flagSetKey = this.keys.buildFlagSetKey(flagSetName);
192
- var flagSetFromLocalStorage = localStorage.getItem(flagSetKey);
193
- if (!flagSetFromLocalStorage)
188
+ var flagSetFromStorage = this.storage.getItem(flagSetKey);
189
+ if (!flagSetFromStorage)
194
190
  return;
195
- var flagSetCache = new Set(JSON.parse(flagSetFromLocalStorage));
191
+ var flagSetCache = new Set(JSON.parse(flagSetFromStorage));
196
192
  flagSetCache.delete(featureFlagName);
197
193
  if (flagSetCache.size === 0) {
198
- localStorage.removeItem(flagSetKey);
194
+ this.storage.removeItem(flagSetKey);
199
195
  return;
200
196
  }
201
- localStorage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache)));
197
+ this.storage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache)));
202
198
  };
203
199
  return SplitsCacheInLocal;
204
200
  }(AbstractSplitsCacheSync));
@@ -3,7 +3,7 @@ import { ImpressionCountsCacheInMemory } from '../inMemory/ImpressionCountsCache
3
3
  import { EventsCacheInMemory } from '../inMemory/EventsCacheInMemory';
4
4
  import { validatePrefix } from '../KeyBuilder';
5
5
  import { KeyBuilderCS, myLargeSegmentsKeyBuilder } from '../KeyBuilderCS';
6
- import { isLocalStorageAvailable } from '../../utils/env/isLocalStorageAvailable';
6
+ import { isLocalStorageAvailable, isValidStorageWrapper, isWebStorage } from '../../utils/env/isLocalStorageAvailable';
7
7
  import { SplitsCacheInLocal } from './SplitsCacheInLocal';
8
8
  import { RBSegmentsCacheInLocal } from './RBSegmentsCacheInLocal';
9
9
  import { MySegmentsCacheInLocal } from './MySegmentsCacheInLocal';
@@ -14,6 +14,20 @@ import { shouldRecordTelemetry, TelemetryCacheInMemory } from '../inMemory/Telem
14
14
  import { UniqueKeysCacheInMemoryCS } from '../inMemory/UniqueKeysCacheInMemoryCS';
15
15
  import { getMatching } from '../../utils/key';
16
16
  import { validateCache } from './validateCache';
17
+ import { storageAdapter } from './storageAdapter';
18
+ function validateStorage(log, prefix, wrapper) {
19
+ if (wrapper) {
20
+ if (isValidStorageWrapper(wrapper)) {
21
+ return isWebStorage(wrapper) ?
22
+ wrapper : // localStorage and sessionStorage don't need adapter
23
+ storageAdapter(log, prefix, wrapper);
24
+ }
25
+ log.warn(LOG_PREFIX + 'Invalid storage provided. Falling back to LocalStorage API');
26
+ }
27
+ if (isLocalStorageAvailable())
28
+ return localStorage;
29
+ log.warn(LOG_PREFIX + 'LocalStorage API is unavailable. Falling back to default MEMORY storage');
30
+ }
17
31
  /**
18
32
  * InLocal storage factory for standalone client-side SplitFactory
19
33
  */
@@ -21,18 +35,17 @@ export function InLocalStorage(options) {
21
35
  if (options === void 0) { options = {}; }
22
36
  var prefix = validatePrefix(options.prefix);
23
37
  function InLocalStorageCSFactory(params) {
24
- // Fallback to InMemoryStorage if LocalStorage API is not available
25
- if (!isLocalStorageAvailable()) {
26
- params.settings.log.warn(LOG_PREFIX + 'LocalStorage API is unavailable. Falling back to default MEMORY storage');
27
- return InMemoryStorageCSFactory(params);
28
- }
29
38
  var settings = params.settings, _a = params.settings, log = _a.log, _b = _a.scheduler, impressionsQueueSize = _b.impressionsQueueSize, eventsQueueSize = _b.eventsQueueSize;
39
+ var storage = validateStorage(log, prefix, options.wrapper);
40
+ if (!storage)
41
+ return InMemoryStorageCSFactory(params);
30
42
  var matchingKey = getMatching(settings.core.key);
31
43
  var keys = new KeyBuilderCS(prefix, matchingKey);
32
- var splits = new SplitsCacheInLocal(settings, keys);
33
- var rbSegments = new RBSegmentsCacheInLocal(settings, keys);
34
- var segments = new MySegmentsCacheInLocal(log, keys);
35
- var largeSegments = new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey));
44
+ var splits = new SplitsCacheInLocal(settings, keys, storage);
45
+ var rbSegments = new RBSegmentsCacheInLocal(settings, keys, storage);
46
+ var segments = new MySegmentsCacheInLocal(log, keys, storage);
47
+ var largeSegments = new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey), storage);
48
+ var validateCachePromise;
36
49
  return {
37
50
  splits: splits,
38
51
  rbSegments: rbSegments,
@@ -44,16 +57,21 @@ export function InLocalStorage(options) {
44
57
  telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
45
58
  uniqueKeys: new UniqueKeysCacheInMemoryCS(),
46
59
  validateCache: function () {
47
- return validateCache(options, settings, keys, splits, rbSegments, segments, largeSegments);
60
+ return validateCachePromise || (validateCachePromise = validateCache(options, storage, settings, keys, splits, rbSegments, segments, largeSegments));
61
+ },
62
+ save: function () {
63
+ return storage.save && storage.save();
64
+ },
65
+ destroy: function () {
66
+ return storage.whenSaved && storage.whenSaved();
48
67
  },
49
- destroy: function () { },
50
68
  // When using shared instantiation with MEMORY we reuse everything but segments (they are customer per key).
51
69
  shared: function (matchingKey) {
52
70
  return {
53
71
  splits: this.splits,
54
72
  rbSegments: this.rbSegments,
55
- segments: new MySegmentsCacheInLocal(log, new KeyBuilderCS(prefix, matchingKey)),
56
- largeSegments: new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey)),
73
+ segments: new MySegmentsCacheInLocal(log, new KeyBuilderCS(prefix, matchingKey), storage),
74
+ largeSegments: new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey), storage),
57
75
  impressions: this.impressions,
58
76
  impressionCounts: this.impressionCounts,
59
77
  events: this.events,
@@ -0,0 +1,50 @@
1
+ import { LOG_PREFIX } from './constants';
2
+ export function storageAdapter(log, prefix, wrapper) {
3
+ var keys = [];
4
+ var cache = {};
5
+ var loadPromise;
6
+ var savePromise = Promise.resolve();
7
+ return {
8
+ load: function () {
9
+ return loadPromise || (loadPromise = Promise.resolve().then(function () {
10
+ return wrapper.getItem(prefix);
11
+ }).then(function (storedCache) {
12
+ cache = JSON.parse(storedCache || '{}');
13
+ keys = Object.keys(cache);
14
+ }).catch(function (e) {
15
+ log.error(LOG_PREFIX + 'Rejected promise calling wrapper `getItem` method, with error: ' + e);
16
+ }));
17
+ },
18
+ save: function () {
19
+ return savePromise = savePromise.then(function () {
20
+ return Promise.resolve(wrapper.setItem(prefix, JSON.stringify(cache)));
21
+ }).catch(function (e) {
22
+ log.error(LOG_PREFIX + 'Rejected promise calling wrapper `setItem` method, with error: ' + e);
23
+ });
24
+ },
25
+ whenSaved: function () {
26
+ return savePromise;
27
+ },
28
+ get length() {
29
+ return keys.length;
30
+ },
31
+ getItem: function (key) {
32
+ return cache[key] || null;
33
+ },
34
+ key: function (index) {
35
+ return keys[index] || null;
36
+ },
37
+ removeItem: function (key) {
38
+ var index = keys.indexOf(key);
39
+ if (index === -1)
40
+ return;
41
+ keys.splice(index, 1);
42
+ delete cache[key];
43
+ },
44
+ setItem: function (key, value) {
45
+ if (keys.indexOf(key) === -1)
46
+ keys.push(key);
47
+ cache[key] = value;
48
+ }
49
+ };
50
+ }
@@ -8,10 +8,10 @@ var MILLIS_IN_A_DAY = 86400000;
8
8
  *
9
9
  * @returns `true` if cache should be cleared, `false` otherwise
10
10
  */
11
- function validateExpiration(options, settings, keys, currentTimestamp, isThereCache) {
11
+ function validateExpiration(options, storage, settings, keys, currentTimestamp, isThereCache) {
12
12
  var log = settings.log;
13
13
  // Check expiration
14
- var lastUpdatedTimestamp = parseInt(localStorage.getItem(keys.buildLastUpdatedKey()), 10);
14
+ var lastUpdatedTimestamp = parseInt(storage.getItem(keys.buildLastUpdatedKey()), 10);
15
15
  if (!isNaNNumber(lastUpdatedTimestamp)) {
16
16
  var cacheExpirationInDays = isFiniteNumber(options.expirationDays) && options.expirationDays >= 1 ? options.expirationDays : DEFAULT_CACHE_EXPIRATION_IN_DAYS;
17
17
  var expirationTimestamp = currentTimestamp - MILLIS_IN_A_DAY * cacheExpirationInDays;
@@ -22,11 +22,11 @@ function validateExpiration(options, settings, keys, currentTimestamp, isThereCa
22
22
  }
23
23
  // Check hash
24
24
  var storageHashKey = keys.buildHashKey();
25
- var storageHash = localStorage.getItem(storageHashKey);
25
+ var storageHash = storage.getItem(storageHashKey);
26
26
  var currentStorageHash = getStorageHash(settings);
27
27
  if (storageHash !== currentStorageHash) {
28
28
  try {
29
- localStorage.setItem(storageHashKey, currentStorageHash);
29
+ storage.setItem(storageHashKey, currentStorageHash);
30
30
  }
31
31
  catch (e) {
32
32
  log.error(LOG_PREFIX + e);
@@ -39,7 +39,7 @@ function validateExpiration(options, settings, keys, currentTimestamp, isThereCa
39
39
  }
40
40
  // Clear on init
41
41
  if (options.clearOnInit) {
42
- var lastClearTimestamp = parseInt(localStorage.getItem(keys.buildLastClear()), 10);
42
+ var lastClearTimestamp = parseInt(storage.getItem(keys.buildLastClear()), 10);
43
43
  if (isNaNNumber(lastClearTimestamp) || lastClearTimestamp < currentTimestamp - MILLIS_IN_A_DAY) {
44
44
  log.info(LOG_PREFIX + 'clearOnInit was set and cache was not cleared in the last 24 hours. Cleaning up cache');
45
45
  return true;
@@ -54,23 +54,28 @@ function validateExpiration(options, settings, keys, currentTimestamp, isThereCa
54
54
  *
55
55
  * @returns `true` if cache is ready to be used, `false` otherwise (cache was cleared or there is no cache)
56
56
  */
57
- export function validateCache(options, settings, keys, splits, rbSegments, 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
- rbSegments.clear();
63
- segments.clear();
64
- largeSegments.clear();
65
- // Update last clear timestamp
66
- try {
67
- localStorage.setItem(keys.buildLastClear(), currentTimestamp + '');
68
- }
69
- catch (e) {
70
- settings.log.error(LOG_PREFIX + e);
57
+ export function validateCache(options, storage, settings, keys, splits, rbSegments, segments, largeSegments) {
58
+ return Promise.resolve(storage.load && storage.load()).then(function () {
59
+ var currentTimestamp = Date.now();
60
+ var isThereCache = splits.getChangeNumber() > -1;
61
+ if (validateExpiration(options, storage, settings, keys, currentTimestamp, isThereCache)) {
62
+ splits.clear();
63
+ rbSegments.clear();
64
+ segments.clear();
65
+ largeSegments.clear();
66
+ // Update last clear timestamp
67
+ try {
68
+ storage.setItem(keys.buildLastClear(), currentTimestamp + '');
69
+ }
70
+ catch (e) {
71
+ settings.log.error(LOG_PREFIX + e);
72
+ }
73
+ // Persist clear
74
+ if (storage.save)
75
+ storage.save();
76
+ return false;
71
77
  }
72
- return false;
73
- }
74
- // Check if ready from cache
75
- return isThereCache;
78
+ // Check if ready from cache
79
+ return isThereCache;
80
+ });
76
81
  }
@@ -42,10 +42,9 @@ export function fromObjectUpdaterFactory(splitsParser, storage, readiness, setti
42
42
  readiness.splits.emit(SDK_SPLITS_ARRIVED);
43
43
  if (startingUp) {
44
44
  startingUp = false;
45
- var isCacheLoaded_1 = storage.validateCache ? storage.validateCache() : false;
46
- Promise.resolve().then(function () {
45
+ Promise.resolve(storage.validateCache ? storage.validateCache() : false).then(function (isCacheLoaded) {
47
46
  // Emits SDK_READY_FROM_CACHE
48
- if (isCacheLoaded_1)
47
+ if (isCacheLoaded)
49
48
  readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
50
49
  // Emits SDK_READY
51
50
  readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
@@ -4,6 +4,7 @@ import { splitsSyncTaskFactory } from './syncTasks/splitsSyncTask';
4
4
  import { getMatching } from '../../utils/key';
5
5
  import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../../readiness/constants';
6
6
  import { POLLING_SMART_PAUSING, POLLING_START, POLLING_STOP } from '../../logger/constants';
7
+ import { usesSegmentsSync } from '../../storages/AbstractSplitsCacheSync';
7
8
  /**
8
9
  * Expose start / stop mechanism for polling data from services.
9
10
  * For client-side API with multiple clients.
@@ -31,7 +32,7 @@ export function pollingManagerCSFactory(params) {
31
32
  readiness.splits.on(SDK_SPLITS_ARRIVED, function () {
32
33
  if (!splitsSyncTask.isRunning())
33
34
  return; // noop if not doing polling
34
- var usingSegments = storage.splits.usesSegments() || storage.rbSegments.usesSegments();
35
+ var usingSegments = usesSegmentsSync(storage);
35
36
  if (usingSegments !== mySegmentsSyncTask.isRunning()) {
36
37
  log.info(POLLING_SMART_PAUSING, [usingSegments ? 'ON' : 'OFF']);
37
38
  if (usingSegments) {
@@ -46,10 +47,10 @@ export function pollingManagerCSFactory(params) {
46
47
  var mySegmentsSyncTask = mySegmentsSyncTaskFactory(splitApi.fetchMemberships, storage, readiness, settings, matchingKey);
47
48
  // smart ready
48
49
  function smartReady() {
49
- if (!readiness.isReady() && !storage.splits.usesSegments() && !storage.rbSegments.usesSegments())
50
+ if (!readiness.isReady() && !usesSegmentsSync(storage))
50
51
  readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
51
52
  }
52
- if (!storage.splits.usesSegments() && !storage.rbSegments.usesSegments())
53
+ if (!usesSegmentsSync(storage))
53
54
  setTimeout(smartReady, 0);
54
55
  else
55
56
  readiness.splits.once(SDK_SPLITS_ARRIVED, smartReady);
@@ -63,7 +64,7 @@ export function pollingManagerCSFactory(params) {
63
64
  start: function () {
64
65
  log.info(POLLING_START);
65
66
  splitsSyncTask.start();
66
- if (storage.splits.usesSegments() || storage.rbSegments.usesSegments())
67
+ if (usesSegmentsSync(storage))
67
68
  startMySegmentsSyncTasks();
68
69
  },
69
70
  // Stop periodic fetching (polling)
@@ -2,6 +2,7 @@ import { timeout } from '../../../utils/promise/timeout';
2
2
  import { SDK_SEGMENTS_ARRIVED } from '../../../readiness/constants';
3
3
  import { SYNC_MYSEGMENTS_FETCH_RETRY } from '../../../logger/constants';
4
4
  import { MEMBERSHIPS_LS_UPDATE } from '../../streaming/constants';
5
+ import { usesSegmentsSync } from '../../../storages/AbstractSplitsCacheSync';
5
6
  /**
6
7
  * factory of MySegments updater, a task that:
7
8
  * - fetches mySegments using `mySegmentsFetcher`
@@ -9,7 +10,7 @@ import { MEMBERSHIPS_LS_UPDATE } from '../../streaming/constants';
9
10
  * - uses `segmentsEventEmitter` to emit events related to segments data updates
10
11
  */
11
12
  export function mySegmentsUpdaterFactory(log, mySegmentsFetcher, storage, segmentsEventEmitter, requestTimeoutBeforeReady, retriesOnFailureBeforeReady, matchingKey) {
12
- var splits = storage.splits, rbSegments = storage.rbSegments, segments = storage.segments, largeSegments = storage.largeSegments;
13
+ var segments = storage.segments, largeSegments = storage.largeSegments;
13
14
  var readyOnAlreadyExistentState = true;
14
15
  var startingUp = true;
15
16
  /** timeout and telemetry decorator for `splitChangesFetcher` promise */
@@ -30,8 +31,10 @@ export function mySegmentsUpdaterFactory(log, mySegmentsFetcher, storage, segmen
30
31
  shouldNotifyUpdate = segments.resetSegments(segmentsData.ms || {});
31
32
  shouldNotifyUpdate = largeSegments.resetSegments(segmentsData.ls || {}) || shouldNotifyUpdate;
32
33
  }
34
+ if (storage.save)
35
+ storage.save();
33
36
  // Notify update if required
34
- if ((splits.usesSegments() || rbSegments.usesSegments()) && (shouldNotifyUpdate || readyOnAlreadyExistentState)) {
37
+ if (usesSegmentsSync(storage) && (shouldNotifyUpdate || readyOnAlreadyExistentState)) {
35
38
  readyOnAlreadyExistentState = false;
36
39
  segmentsEventEmitter.emit(SDK_SEGMENTS_ARRIVED);
37
40
  }
@@ -42,7 +42,7 @@ export function parseSegments(ruleEntity, matcherType) {
42
42
  }
43
43
  /**
44
44
  * If there are defined filters and one feature flag doesn't match with them, its status is changed to 'ARCHIVE' to avoid storing it
45
- * If there are set filter defined, names filter is ignored
45
+ * If there is `bySet` filter, `byName` and `byPrefix` filters are ignored
46
46
  *
47
47
  * @param featureFlag - feature flag to be evaluated
48
48
  * @param filters - splitFiltersValidation bySet | byName
@@ -149,6 +149,8 @@ export function splitChangesUpdaterFactory(log, splitChangesFetcher, storage, sp
149
149
  segments.registerSegments(setToArray(usedSegments))
150
150
  ]).then(function (_a) {
151
151
  var ffChanged = _a[0], rbsChanged = _a[1];
152
+ if (storage.save)
153
+ storage.save();
152
154
  if (splitsEventEmitter) {
153
155
  // To emit SDK_SPLITS_ARRIVED for server-side SDK, we must check that all registered segments have been fetched
154
156
  return Promise.resolve(!splitsEventEmitter.splitsArrived || ((ffChanged || rbsChanged) && (isClientSide || checkAllSegmentsExist(segments))))
@@ -4,6 +4,7 @@ import { SYNC_START_POLLING, SYNC_CONTINUE_POLLING, SYNC_STOP_POLLING } from '..
4
4
  import { isConsentGranted } from '../consent';
5
5
  import { POLLING, STREAMING, SYNC_MODE_UPDATE } from '../utils/constants';
6
6
  import { SDK_SPLITS_CACHE_LOADED } from '../readiness/constants';
7
+ import { usesSegmentsSync } from '../storages/AbstractSplitsCacheSync';
7
8
  /**
8
9
  * Online SyncManager factory.
9
10
  * Can be used for server-side API, and client-side API with or without multiple clients.
@@ -65,35 +66,39 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
65
66
  */
66
67
  start: function () {
67
68
  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
- }
73
- // start syncing splits and segments
74
- if (pollingManager) {
75
- // If synchronization is disabled pushManager and pollingManager should not start
76
- if (syncEnabled) {
77
- if (pushManager) {
78
- // Doesn't call `syncAll` when the syncManager is resuming
79
- if (startFirstTime) {
80
- pollingManager.syncAll();
69
+ // @TODO once event, impression and telemetry storages support persistence, call when `validateCache` promise is resolved
70
+ submitterManager.start(!isConsentGranted(settings));
71
+ return Promise.resolve(storage.validateCache ? storage.validateCache() : false).then(function (isCacheLoaded) {
72
+ if (!running)
73
+ return;
74
+ if (startFirstTime) {
75
+ // Emits SDK_READY_FROM_CACHE
76
+ if (isCacheLoaded)
77
+ readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
78
+ }
79
+ // start syncing splits and segments
80
+ if (pollingManager) {
81
+ // If synchronization is disabled pushManager and pollingManager should not start
82
+ if (syncEnabled) {
83
+ if (pushManager) {
84
+ // Doesn't call `syncAll` when the syncManager is resuming
85
+ if (startFirstTime) {
86
+ pollingManager.syncAll();
87
+ }
88
+ pushManager.start();
89
+ }
90
+ else {
91
+ pollingManager.start();
81
92
  }
82
- pushManager.start();
83
93
  }
84
94
  else {
85
- pollingManager.start();
86
- }
87
- }
88
- else {
89
- if (startFirstTime) {
90
- pollingManager.syncAll();
95
+ if (startFirstTime) {
96
+ pollingManager.syncAll();
97
+ }
91
98
  }
92
99
  }
93
- }
94
- // start periodic data recording (events, impressions, telemetry).
95
- submitterManager.start(!isConsentGranted(settings));
96
- startFirstTime = false;
100
+ startFirstTime = false;
101
+ });
97
102
  },
98
103
  /**
99
104
  * Method used to stop/pause the syncManager.
@@ -127,7 +132,7 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
127
132
  if (pushManager) {
128
133
  if (pollingManager.isRunning()) {
129
134
  // if doing polling, we must start the periodic fetch of data
130
- if (storage.splits.usesSegments() || storage.rbSegments.usesSegments())
135
+ if (usesSegmentsSync(storage))
131
136
  mySegmentsSyncTask.start();
132
137
  }
133
138
  else {
@@ -137,7 +142,7 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
137
142
  }
138
143
  }
139
144
  else {
140
- if (storage.splits.usesSegments() || storage.rbSegments.usesSegments())
145
+ if (usesSegmentsSync(storage))
141
146
  mySegmentsSyncTask.start();
142
147
  }
143
148
  }
@@ -6,7 +6,7 @@ import { CONSENT_DECLINED, DEDUPED, QUEUED } from '../utils/constants';
6
6
  * Impressions tracker stores impressions in cache and pass them to the listener and integrations manager if provided.
7
7
  */
8
8
  export function impressionsTrackerFactory(settings, impressionsCache, noneStrategy, strategy, whenInit, integrationsManager, telemetryCache) {
9
- var log = settings.log, _a = settings.runtime, ip = _a.ip, hostname = _a.hostname, version = settings.version;
9
+ var log = settings.log, impressionListener = settings.impressionListener, _a = settings.runtime, ip = _a.ip, hostname = _a.hostname, version = settings.version;
10
10
  return {
11
11
  track: function (impressions, attributes) {
12
12
  if (settings.userConsent === CONSENT_DECLINED)
@@ -39,7 +39,7 @@ export function impressionsTrackerFactory(settings, impressionsCache, noneStrate
39
39
  }
40
40
  }
41
41
  // @TODO next block might be handled by the integration manager. In that case, the metadata object doesn't need to be passed in the constructor
42
- if (settings.impressionListener || integrationsManager) {
42
+ if (impressionListener || integrationsManager) {
43
43
  var _loop_1 = function (i) {
44
44
  var impressionData = {
45
45
  // copy of impression, to avoid unexpected behavior if modified by integrations or impressionListener
@@ -56,8 +56,8 @@ export function impressionsTrackerFactory(settings, impressionsCache, noneStrate
56
56
  if (integrationsManager)
57
57
  integrationsManager.handleImpression(impressionData);
58
58
  try { // @ts-ignore. An exception on the listeners should not break the SDK.
59
- if (settings.impressionListener)
60
- settings.impressionListener.logImpression(impressionData);
59
+ if (impressionListener)
60
+ impressionListener.logImpression(impressionData);
61
61
  }
62
62
  catch (err) {
63
63
  log.error(ERROR_IMPRESSIONS_LISTENER, [err]);