@splitsoftware/splitio-commons 2.4.2-rc.0 → 2.4.2-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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/AbstractMySegmentsCacheSync.js +31 -23
  7. package/cjs/storages/AbstractSplitsCacheSync.js +8 -3
  8. package/cjs/storages/inLocalStorage/MySegmentsCacheInLocal.js +16 -16
  9. package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +20 -19
  10. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +33 -37
  11. package/cjs/storages/inLocalStorage/index.js +28 -13
  12. package/cjs/storages/inLocalStorage/storageAdapter.js +48 -0
  13. package/cjs/storages/inLocalStorage/validateCache.js +25 -23
  14. package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +2 -3
  15. package/cjs/sync/polling/pollingManagerCS.js +5 -4
  16. package/cjs/sync/polling/updaters/mySegmentsUpdater.js +3 -2
  17. package/cjs/sync/polling/updaters/splitChangesUpdater.js +1 -1
  18. package/cjs/sync/syncManagerOnline.js +31 -26
  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/AbstractMySegmentsCacheSync.js +31 -23
  27. package/esm/storages/AbstractSplitsCacheSync.js +6 -2
  28. package/esm/storages/inLocalStorage/MySegmentsCacheInLocal.js +16 -16
  29. package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +20 -19
  30. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +33 -37
  31. package/esm/storages/inLocalStorage/index.js +29 -14
  32. package/esm/storages/inLocalStorage/storageAdapter.js +44 -0
  33. package/esm/storages/inLocalStorage/validateCache.js +25 -23
  34. package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +2 -3
  35. package/esm/sync/polling/pollingManagerCS.js +5 -4
  36. package/esm/sync/polling/updaters/mySegmentsUpdater.js +3 -2
  37. package/esm/sync/polling/updaters/splitChangesUpdater.js +1 -1
  38. package/esm/sync/syncManagerOnline.js +31 -26
  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/AbstractMySegmentsCacheSync.ts +26 -20
  48. package/src/storages/AbstractSplitsCacheSync.ts +8 -3
  49. package/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +18 -17
  50. package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +22 -20
  51. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +34 -37
  52. package/src/storages/inLocalStorage/index.ts +33 -16
  53. package/src/storages/inLocalStorage/storageAdapter.ts +50 -0
  54. package/src/storages/inLocalStorage/validateCache.ts +26 -23
  55. package/src/storages/types.ts +17 -1
  56. package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +1 -2
  57. package/src/sync/polling/pollingManagerCS.ts +5 -4
  58. package/src/sync/polling/updaters/mySegmentsUpdater.ts +3 -2
  59. package/src/sync/polling/updaters/splitChangesUpdater.ts +1 -1
  60. package/src/sync/syncManagerOnline.ts +30 -24
  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 +57 -16
package/CHANGES.txt CHANGED
@@ -1,3 +1,6 @@
1
+ 2.5.0 (August XX, 2025)
2
+ - Added `storage.wrapper` configuration option to allow the SDK to use a custom storage wrapper for the storage type `LOCALSTORAGE`. Default value is `window.localStorage`.
3
+
1
4
  2.4.1 (June 3, 2025)
2
5
  - Bugfix - Improved the Proxy fallback to flag spec version 1.2 to handle cases where the Proxy does not return an end-of-stream marker in 400 status code responses.
3
6
 
@@ -9,7 +12,7 @@
9
12
  - Updated the Redis storage to:
10
13
  - Avoid lazy require of the `ioredis` dependency when the SDK is initialized, and
11
14
  - Flag the SDK as ready from cache immediately to allow queueing feature flag evaluations before SDK_READY event is emitted (Reverted in v1.7.0).
12
- - Bugfix - Enhanced HTTP client module to implement timeouts for failing requests that might otherwise remain pending indefinitely on some Fetch API implementations.
15
+ - Bugfix - Enhanced HTTP client module to implement timeouts for failing requests that might otherwise remain pending indefinitely on some Fetch API implementations, pausing the SDK synchronization process.
13
16
 
14
17
  2.2.0 (March 28, 2025)
15
18
  - Added a new optional argument to the client `getTreatment` methods to allow passing additional evaluation options, such as a map of properties to append to the generated impressions sent to Split backend. Read more in our docs.
@@ -47,6 +50,15 @@
47
50
  - Removed internal ponyfills for `Map` and `Set` global objects, dropping support for IE and other outdated browsers. The SDK now requires the runtime environment to support these features natively or to provide a polyfill.
48
51
  - Removed the `sync.localhostMode` configuration option to plug the LocalhostMode module.
49
52
 
53
+ 1.17.1 (July 25, 2025)
54
+ - Updated the Redis storage to avoid lazy require of the `ioredis` dependency when the SDK is initialized.
55
+ - Updated some transitive dependencies for vulnerability fixes.
56
+ - Bugfix - Enhanced HTTP client module to implement timeouts for failing requests that might otherwise remain pending indefinitely on some Fetch API implementations, pausing the SDK synchronization process.
57
+ - Bugfix - Properly handle rejected promises when using targeting rules with segment matchers in consumer modes (e.g., Redis and Pluggable storages).
58
+ - Bugfix - Sanitize the `SplitSDKMachineName` header value to avoid exceptions on HTTP/S requests when it contains non ISO-8859-1 characters (Related to issue https://github.com/splitio/javascript-client/issues/847).
59
+ - Bugfix - Fixed an issue with the SDK_UPDATE event on server-side, where it was not being emitted if there was an empty segment and the SDK received a feature flag update notification.
60
+ - Bugfix - Fixed an issue with the server-side polling manager that caused dangling timers when the SDK was destroyed before it was ready.
61
+
50
62
  1.17.0 (September 6, 2024)
51
63
  - Added `sync.requestOptions.getHeaderOverrides` configuration option to enhance SDK HTTP request Headers for Authorization Frameworks.
52
64
  - Added `isTimedout` and `lastUpdate` properties to IStatusInterface to keep track of the timestamp of the last SDK event, used on React and Redux SDKs.
@@ -17,7 +17,7 @@ exports.codesError = [
17
17
  [c.ERROR_SYNC_OFFLINE_LOADING, c.LOG_PREFIX_SYNC_OFFLINE + 'There was an issue loading the mock feature flags data. No changes will be applied to the current cache. %s'],
18
18
  [c.ERROR_STREAMING_SSE, c.LOG_PREFIX_SYNC_STREAMING + 'Failed to connect or error on streaming connection, with error message: %s'],
19
19
  [c.ERROR_STREAMING_AUTH, c.LOG_PREFIX_SYNC_STREAMING + 'Failed to authenticate for streaming. Error: %s.'],
20
- [c.ERROR_HTTP, 'Response status is not OK. Status: %s. URL: %s. Message: %s'],
20
+ [c.ERROR_HTTP, 'HTTP request failed with %s. URL: %s. Message: %s'],
21
21
  // client status
22
22
  [c.ERROR_CLIENT_LISTENER, 'A listener was added for %s on the SDK, which has already fired and won\'t be emitted again. The callback won\'t be executed.'],
23
23
  [c.ERROR_CLIENT_DESTROYED, '%s: Client has already been destroyed - no calls possible.'],
@@ -38,7 +38,6 @@ function sdkClientFactory(params, isSharedClient) {
38
38
  (0, clientInputValidation_1.clientInputValidationDecorator)(settings, (0, client_1.clientFactory)(params), sdkReadinessManager.readinessManager),
39
39
  // Sdk destroy
40
40
  {
41
- __ctx: params,
42
41
  flush: function () {
43
42
  // @TODO define cooldown time
44
43
  return __cooldown(__flush, COOLDOWN_TIME_IN_MILLIS);
@@ -87,7 +87,8 @@ function sdkFactory(params) {
87
87
  initCallbacks.length = 0;
88
88
  }
89
89
  log.info(constants_1.NEW_FACTORY);
90
- var factory = (0, objectAssign_1.objectAssign)({
90
+ // @ts-ignore
91
+ return (0, objectAssign_1.objectAssign)({
91
92
  // Split evaluation and event tracking engine
92
93
  client: clientMethod,
93
94
  // Manager API to explore available information
@@ -101,15 +102,7 @@ function sdkFactory(params) {
101
102
  destroy: function () {
102
103
  hasInit = false;
103
104
  return Promise.all(Object.keys(clients).map(function (key) { return clients[key].destroy(); })).then(function () { });
104
- },
105
- __ctx: ctx
105
+ }
106
106
  }, extraProps && extraProps(ctx), lazyInit ? { init: init } : init());
107
- // append factory to global
108
- if (typeof window === 'object') { // @ts-ignore
109
- // eslint-disable-next-line no-undef
110
- (window.__HARNESS_FME__ = window.__HARNESS_FME__ || []).push(factory);
111
- }
112
- // @ts-ignore
113
- return factory;
114
107
  }
115
108
  exports.sdkFactory = sdkFactory;
@@ -69,7 +69,7 @@ function splitHttpClientFactory(settings, _a) {
69
69
  msg = error.message || 'Network Error';
70
70
  }
71
71
  if (!resp || resp.status !== 403) { // 403's log we'll be handled somewhere else.
72
- log[logErrorsAsInfo ? 'info' : 'error'](constants_1.ERROR_HTTP, [resp ? resp.status : 'NO_STATUS', url, msg]);
72
+ log[logErrorsAsInfo ? 'info' : 'error'](constants_1.ERROR_HTTP, [resp ? 'status code ' + resp.status : 'no status code', url, msg]);
73
73
  }
74
74
  var networkError = new Error(msg);
75
75
  // passes `undefined` as statusCode if not an HTTP error (resp === undefined)
@@ -23,37 +23,45 @@ var AbstractMySegmentsCacheSync = /** @class */ (function () {
23
23
  */
24
24
  AbstractMySegmentsCacheSync.prototype.resetSegments = function (segmentsData) {
25
25
  var _this = this;
26
- this.setChangeNumber(segmentsData.cn);
27
26
  var _a = segmentsData, added = _a.added, removed = _a.removed;
27
+ var isDiff = false;
28
28
  if (added && removed) {
29
- var isDiff_1 = false;
30
29
  added.forEach(function (segment) {
31
- isDiff_1 = _this.addSegment(segment) || isDiff_1;
30
+ isDiff = _this.addSegment(segment) || isDiff;
32
31
  });
33
32
  removed.forEach(function (segment) {
34
- isDiff_1 = _this.removeSegment(segment) || isDiff_1;
33
+ isDiff = _this.removeSegment(segment) || isDiff;
35
34
  });
36
- return isDiff_1;
37
- }
38
- var names = (segmentsData.k || []).map(function (s) { return s.n; }).sort();
39
- var storedSegmentKeys = this.getRegisteredSegments().sort();
40
- // Extreme fast => everything is empty
41
- if (!names.length && !storedSegmentKeys.length)
42
- return false;
43
- var index = 0;
44
- while (index < names.length && index < storedSegmentKeys.length && names[index] === storedSegmentKeys[index])
45
- index++;
46
- // Quick path => no changes
47
- if (index === names.length && index === storedSegmentKeys.length)
48
- return false;
49
- // Slowest path => add and/or remove segments
50
- for (var removeIndex = index; removeIndex < storedSegmentKeys.length; removeIndex++) {
51
- this.removeSegment(storedSegmentKeys[removeIndex]);
52
35
  }
53
- for (var addIndex = index; addIndex < names.length; addIndex++) {
54
- this.addSegment(names[addIndex]);
36
+ else {
37
+ var names = (segmentsData.k || []).map(function (s) { return s.n; }).sort();
38
+ var storedSegmentKeys = this.getRegisteredSegments().sort();
39
+ // Extreme fast => everything is empty
40
+ if (!names.length && !storedSegmentKeys.length) {
41
+ isDiff = false;
42
+ }
43
+ else {
44
+ var index = 0;
45
+ while (index < names.length && index < storedSegmentKeys.length && names[index] === storedSegmentKeys[index])
46
+ index++;
47
+ // Quick path => no changes
48
+ if (index === names.length && index === storedSegmentKeys.length) {
49
+ isDiff = false;
50
+ }
51
+ else {
52
+ // Slowest path => add and/or remove segments
53
+ for (var removeIndex = index; removeIndex < storedSegmentKeys.length; removeIndex++) {
54
+ this.removeSegment(storedSegmentKeys[removeIndex]);
55
+ }
56
+ for (var addIndex = index; addIndex < names.length; addIndex++) {
57
+ this.addSegment(names[addIndex]);
58
+ }
59
+ isDiff = true;
60
+ }
61
+ }
55
62
  }
56
- return true;
63
+ this.setChangeNumber(segmentsData.cn);
64
+ return isDiff;
57
65
  };
58
66
  return AbstractMySegmentsCacheSync;
59
67
  }());
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.usesSegments = exports.AbstractSplitsCacheSync = void 0;
3
+ exports.usesSegmentsSync = exports.usesSegments = exports.AbstractSplitsCacheSync = void 0;
4
4
  var objectAssign_1 = require("../utils/lang/objectAssign");
5
5
  var constants_1 = require("../utils/constants");
6
6
  /**
@@ -12,9 +12,10 @@ var AbstractSplitsCacheSync = /** @class */ (function () {
12
12
  }
13
13
  AbstractSplitsCacheSync.prototype.update = function (toAdd, toRemove, changeNumber) {
14
14
  var _this = this;
15
- this.setChangeNumber(changeNumber);
16
15
  var updated = toAdd.map(function (addedFF) { return _this.addSplit(addedFF); }).some(function (result) { return result; });
17
- return toRemove.map(function (removedFF) { return _this.removeSplit(removedFF.name); }).some(function (result) { return result; }) || updated;
16
+ updated = toRemove.map(function (removedFF) { return _this.removeSplit(removedFF.name); }).some(function (result) { return result; }) || updated;
17
+ this.setChangeNumber(changeNumber);
18
+ return updated;
18
19
  };
19
20
  AbstractSplitsCacheSync.prototype.getSplits = function (names) {
20
21
  var _this = this;
@@ -69,3 +70,7 @@ function usesSegments(ruleEntity) {
69
70
  return false;
70
71
  }
71
72
  exports.usesSegments = usesSegments;
73
+ function usesSegmentsSync(storage) {
74
+ return storage.splits.usesSegments() || storage.rbSegments.usesSegments();
75
+ }
76
+ exports.usesSegmentsSync = usesSegmentsSync;
@@ -7,19 +7,20 @@ var AbstractMySegmentsCacheSync_1 = require("../AbstractMySegmentsCacheSync");
7
7
  var constants_1 = require("./constants");
8
8
  var MySegmentsCacheInLocal = /** @class */ (function (_super) {
9
9
  (0, tslib_1.__extends)(MySegmentsCacheInLocal, _super);
10
- function MySegmentsCacheInLocal(log, keys) {
10
+ function MySegmentsCacheInLocal(log, keys, storage) {
11
11
  var _this = _super.call(this) || this;
12
12
  _this.log = log;
13
13
  _this.keys = keys;
14
+ _this.storage = storage;
14
15
  return _this;
15
16
  // There is not need to flush segments cache like splits cache, since resetSegments receives the up-to-date list of active segments
16
17
  }
17
18
  MySegmentsCacheInLocal.prototype.addSegment = function (name) {
18
19
  var segmentKey = this.keys.buildSegmentNameKey(name);
19
20
  try {
20
- if (localStorage.getItem(segmentKey) === constants_1.DEFINED)
21
+ if (this.storage.getItem(segmentKey) === constants_1.DEFINED)
21
22
  return false;
22
- localStorage.setItem(segmentKey, constants_1.DEFINED);
23
+ this.storage.setItem(segmentKey, constants_1.DEFINED);
23
24
  return true;
24
25
  }
25
26
  catch (e) {
@@ -30,9 +31,9 @@ var MySegmentsCacheInLocal = /** @class */ (function (_super) {
30
31
  MySegmentsCacheInLocal.prototype.removeSegment = function (name) {
31
32
  var segmentKey = this.keys.buildSegmentNameKey(name);
32
33
  try {
33
- if (localStorage.getItem(segmentKey) !== constants_1.DEFINED)
34
+ if (this.storage.getItem(segmentKey) !== constants_1.DEFINED)
34
35
  return false;
35
- localStorage.removeItem(segmentKey);
36
+ this.storage.removeItem(segmentKey);
36
37
  return true;
37
38
  }
38
39
  catch (e) {
@@ -41,17 +42,16 @@ var MySegmentsCacheInLocal = /** @class */ (function (_super) {
41
42
  }
42
43
  };
43
44
  MySegmentsCacheInLocal.prototype.isInSegment = function (name) {
44
- return localStorage.getItem(this.keys.buildSegmentNameKey(name)) === constants_1.DEFINED;
45
+ return this.storage.getItem(this.keys.buildSegmentNameKey(name)) === constants_1.DEFINED;
45
46
  };
46
47
  MySegmentsCacheInLocal.prototype.getRegisteredSegments = function () {
47
- var _this = this;
48
- // Scan current values from localStorage
49
- return Object.keys(localStorage).reduce(function (accum, key) {
50
- var segmentName = _this.keys.extractSegmentName(key);
48
+ var registeredSegments = [];
49
+ for (var i = 0; i < this.storage.length; i++) {
50
+ var segmentName = this.keys.extractSegmentName(this.storage.key(i));
51
51
  if (segmentName)
52
- accum.push(segmentName);
53
- return accum;
54
- }, []);
52
+ registeredSegments.push(segmentName);
53
+ }
54
+ return registeredSegments;
55
55
  };
56
56
  MySegmentsCacheInLocal.prototype.getKeysCount = function () {
57
57
  return 1;
@@ -59,9 +59,9 @@ var MySegmentsCacheInLocal = /** @class */ (function (_super) {
59
59
  MySegmentsCacheInLocal.prototype.setChangeNumber = function (changeNumber) {
60
60
  try {
61
61
  if (changeNumber)
62
- localStorage.setItem(this.keys.buildTillKey(), changeNumber + '');
62
+ this.storage.setItem(this.keys.buildTillKey(), changeNumber + '');
63
63
  else
64
- localStorage.removeItem(this.keys.buildTillKey());
64
+ this.storage.removeItem(this.keys.buildTillKey());
65
65
  }
66
66
  catch (e) {
67
67
  this.log.error(e);
@@ -69,7 +69,7 @@ var MySegmentsCacheInLocal = /** @class */ (function (_super) {
69
69
  };
70
70
  MySegmentsCacheInLocal.prototype.getChangeNumber = function () {
71
71
  var n = -1;
72
- var value = localStorage.getItem(this.keys.buildTillKey());
72
+ var value = this.storage.getItem(this.keys.buildTillKey());
73
73
  if (value !== null) {
74
74
  value = parseInt(value, 10);
75
75
  return (0, lang_1.isNaNNumber)(value) ? n : value;
@@ -6,25 +6,27 @@ var sets_1 = require("../../utils/lang/sets");
6
6
  var AbstractSplitsCacheSync_1 = require("../AbstractSplitsCacheSync");
7
7
  var constants_1 = require("./constants");
8
8
  var RBSegmentsCacheInLocal = /** @class */ (function () {
9
- function RBSegmentsCacheInLocal(settings, keys) {
9
+ function RBSegmentsCacheInLocal(settings, keys, storage) {
10
10
  this.keys = keys;
11
11
  this.log = settings.log;
12
+ this.storage = storage;
12
13
  }
13
14
  RBSegmentsCacheInLocal.prototype.clear = function () {
14
15
  var _this = this;
15
16
  this.getNames().forEach(function (name) { return _this.remove(name); });
16
- localStorage.removeItem(this.keys.buildRBSegmentsTillKey());
17
+ this.storage.removeItem(this.keys.buildRBSegmentsTillKey());
17
18
  };
18
19
  RBSegmentsCacheInLocal.prototype.update = function (toAdd, toRemove, changeNumber) {
19
20
  var _this = this;
20
- this.setChangeNumber(changeNumber);
21
21
  var updated = toAdd.map(function (toAdd) { return _this.add(toAdd); }).some(function (result) { return result; });
22
- return toRemove.map(function (toRemove) { return _this.remove(toRemove.name); }).some(function (result) { return result; }) || updated;
22
+ updated = toRemove.map(function (toRemove) { return _this.remove(toRemove.name); }).some(function (result) { return result; }) || updated;
23
+ this.setChangeNumber(changeNumber);
24
+ return updated;
23
25
  };
24
26
  RBSegmentsCacheInLocal.prototype.setChangeNumber = function (changeNumber) {
25
27
  try {
26
- localStorage.setItem(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
27
- localStorage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
28
+ this.storage.setItem(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
29
+ this.storage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
28
30
  }
29
31
  catch (e) {
30
32
  this.log.error(constants_1.LOG_PREFIX + e);
@@ -32,20 +34,19 @@ var RBSegmentsCacheInLocal = /** @class */ (function () {
32
34
  };
33
35
  RBSegmentsCacheInLocal.prototype.updateSegmentCount = function (diff) {
34
36
  var segmentsCountKey = this.keys.buildSplitsWithSegmentCountKey();
35
- var count = (0, lang_1.toNumber)(localStorage.getItem(segmentsCountKey)) + diff;
36
- // @ts-expect-error
37
+ var count = (0, lang_1.toNumber)(this.storage.getItem(segmentsCountKey)) + diff;
37
38
  if (count > 0)
38
- localStorage.setItem(segmentsCountKey, count);
39
+ this.storage.setItem(segmentsCountKey, count + '');
39
40
  else
40
- localStorage.removeItem(segmentsCountKey);
41
+ this.storage.removeItem(segmentsCountKey);
41
42
  };
42
43
  RBSegmentsCacheInLocal.prototype.add = function (rbSegment) {
43
44
  try {
44
45
  var name_1 = rbSegment.name;
45
46
  var rbSegmentKey = this.keys.buildRBSegmentKey(name_1);
46
- var rbSegmentFromLocalStorage = localStorage.getItem(rbSegmentKey);
47
- var previous = rbSegmentFromLocalStorage ? JSON.parse(rbSegmentFromLocalStorage) : null;
48
- localStorage.setItem(rbSegmentKey, JSON.stringify(rbSegment));
47
+ var rbSegmentFromStorage = this.storage.getItem(rbSegmentKey);
48
+ var previous = rbSegmentFromStorage ? JSON.parse(rbSegmentFromStorage) : null;
49
+ this.storage.setItem(rbSegmentKey, JSON.stringify(rbSegment));
49
50
  var usesSegmentsDiff = 0;
50
51
  if (previous && (0, AbstractSplitsCacheSync_1.usesSegments)(previous))
51
52
  usesSegmentsDiff--;
@@ -65,7 +66,7 @@ var RBSegmentsCacheInLocal = /** @class */ (function () {
65
66
  var rbSegment = this.get(name);
66
67
  if (!rbSegment)
67
68
  return false;
68
- localStorage.removeItem(this.keys.buildRBSegmentKey(name));
69
+ this.storage.removeItem(this.keys.buildRBSegmentKey(name));
69
70
  if ((0, AbstractSplitsCacheSync_1.usesSegments)(rbSegment))
70
71
  this.updateSegmentCount(-1);
71
72
  return true;
@@ -76,11 +77,11 @@ var RBSegmentsCacheInLocal = /** @class */ (function () {
76
77
  }
77
78
  };
78
79
  RBSegmentsCacheInLocal.prototype.getNames = function () {
79
- var len = localStorage.length;
80
+ var len = this.storage.length;
80
81
  var accum = [];
81
82
  var cur = 0;
82
83
  while (cur < len) {
83
- var key = localStorage.key(cur);
84
+ var key = this.storage.key(cur);
84
85
  if (key != null && this.keys.isRBSegmentKey(key))
85
86
  accum.push(this.keys.extractKey(key));
86
87
  cur++;
@@ -88,7 +89,7 @@ var RBSegmentsCacheInLocal = /** @class */ (function () {
88
89
  return accum;
89
90
  };
90
91
  RBSegmentsCacheInLocal.prototype.get = function (name) {
91
- var item = localStorage.getItem(this.keys.buildRBSegmentKey(name));
92
+ var item = this.storage.getItem(this.keys.buildRBSegmentKey(name));
92
93
  return item && JSON.parse(item);
93
94
  };
94
95
  RBSegmentsCacheInLocal.prototype.contains = function (names) {
@@ -98,7 +99,7 @@ var RBSegmentsCacheInLocal = /** @class */ (function () {
98
99
  };
99
100
  RBSegmentsCacheInLocal.prototype.getChangeNumber = function () {
100
101
  var n = -1;
101
- var value = localStorage.getItem(this.keys.buildRBSegmentsTillKey());
102
+ var value = this.storage.getItem(this.keys.buildRBSegmentsTillKey());
102
103
  if (value !== null) {
103
104
  value = parseInt(value, 10);
104
105
  return (0, lang_1.isNaNNumber)(value) ? n : value;
@@ -106,7 +107,7 @@ var RBSegmentsCacheInLocal = /** @class */ (function () {
106
107
  return n;
107
108
  };
108
109
  RBSegmentsCacheInLocal.prototype.usesSegments = function () {
109
- var storedCount = localStorage.getItem(this.keys.buildSplitsWithSegmentCountKey());
110
+ var storedCount = this.storage.getItem(this.keys.buildSplitsWithSegmentCountKey());
110
111
  var splitsWithSegmentsCount = storedCount === null ? 0 : (0, lang_1.toNumber)(storedCount);
111
112
  return (0, lang_1.isFiniteNumber)(splitsWithSegmentsCount) ?
112
113
  splitsWithSegmentsCount > 0 :
@@ -6,25 +6,22 @@ var AbstractSplitsCacheSync_1 = require("../AbstractSplitsCacheSync");
6
6
  var lang_1 = require("../../utils/lang");
7
7
  var constants_1 = require("./constants");
8
8
  var sets_1 = require("../../utils/lang/sets");
9
- /**
10
- * ISplitsCacheSync implementation that stores split definitions in browser LocalStorage.
11
- */
12
9
  var SplitsCacheInLocal = /** @class */ (function (_super) {
13
10
  (0, tslib_1.__extends)(SplitsCacheInLocal, _super);
14
- function SplitsCacheInLocal(settings, keys) {
11
+ function SplitsCacheInLocal(settings, keys, storage) {
15
12
  var _this = _super.call(this) || this;
16
13
  _this.keys = keys;
17
14
  _this.log = settings.log;
18
15
  _this.flagSetsFilter = settings.sync.__splitFiltersValidation.groupedFilters.bySet;
16
+ _this.storage = storage;
19
17
  return _this;
20
18
  }
21
19
  SplitsCacheInLocal.prototype._decrementCount = function (key) {
22
- var count = (0, lang_1.toNumber)(localStorage.getItem(key)) - 1;
23
- // @ts-expect-error
20
+ var count = (0, lang_1.toNumber)(this.storage.getItem(key)) - 1;
24
21
  if (count > 0)
25
- localStorage.setItem(key, count);
22
+ this.storage.setItem(key, count + '');
26
23
  else
27
- localStorage.removeItem(key);
24
+ this.storage.removeItem(key);
28
25
  };
29
26
  SplitsCacheInLocal.prototype._decrementCounts = function (split) {
30
27
  try {
@@ -42,12 +39,10 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
42
39
  SplitsCacheInLocal.prototype._incrementCounts = function (split) {
43
40
  try {
44
41
  var ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName);
45
- // @ts-expect-error
46
- localStorage.setItem(ttKey, (0, lang_1.toNumber)(localStorage.getItem(ttKey)) + 1);
42
+ this.storage.setItem(ttKey, ((0, lang_1.toNumber)(this.storage.getItem(ttKey)) + 1) + '');
47
43
  if ((0, AbstractSplitsCacheSync_1.usesSegments)(split)) {
48
44
  var segmentsCountKey = this.keys.buildSplitsWithSegmentCountKey();
49
- // @ts-expect-error
50
- localStorage.setItem(segmentsCountKey, (0, lang_1.toNumber)(localStorage.getItem(segmentsCountKey)) + 1);
45
+ this.storage.setItem(segmentsCountKey, ((0, lang_1.toNumber)(this.storage.getItem(segmentsCountKey)) + 1) + '');
51
46
  }
52
47
  }
53
48
  catch (e) {
@@ -59,17 +54,18 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
59
54
  * We cannot simply call `localStorage.clear()` since that implies removing user items from the storage.
60
55
  */
61
56
  SplitsCacheInLocal.prototype.clear = function () {
57
+ var _this = this;
62
58
  // collect item keys
63
- var len = localStorage.length;
59
+ var len = this.storage.length;
64
60
  var accum = [];
65
61
  for (var cur = 0; cur < len; cur++) {
66
- var key = localStorage.key(cur);
62
+ var key = this.storage.key(cur);
67
63
  if (key != null && this.keys.isSplitsCacheKey(key))
68
64
  accum.push(key);
69
65
  }
70
66
  // remove items
71
67
  accum.forEach(function (key) {
72
- localStorage.removeItem(key);
68
+ _this.storage.removeItem(key);
73
69
  });
74
70
  this.hasSync = false;
75
71
  };
@@ -77,13 +73,13 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
77
73
  try {
78
74
  var name_1 = split.name;
79
75
  var splitKey = this.keys.buildSplitKey(name_1);
80
- var splitFromLocalStorage = localStorage.getItem(splitKey);
81
- var previousSplit = splitFromLocalStorage ? JSON.parse(splitFromLocalStorage) : null;
76
+ var splitFromStorage = this.storage.getItem(splitKey);
77
+ var previousSplit = splitFromStorage ? JSON.parse(splitFromStorage) : null;
82
78
  if (previousSplit) {
83
79
  this._decrementCounts(previousSplit);
84
80
  this.removeFromFlagSets(previousSplit.name, previousSplit.sets);
85
81
  }
86
- localStorage.setItem(splitKey, JSON.stringify(split));
82
+ this.storage.setItem(splitKey, JSON.stringify(split));
87
83
  this._incrementCounts(split);
88
84
  this.addToFlagSets(split);
89
85
  return true;
@@ -98,7 +94,7 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
98
94
  var split = this.getSplit(name);
99
95
  if (!split)
100
96
  return false;
101
- localStorage.removeItem(this.keys.buildSplitKey(name));
97
+ this.storage.removeItem(this.keys.buildSplitKey(name));
102
98
  this._decrementCounts(split);
103
99
  this.removeFromFlagSets(split.name, split.sets);
104
100
  return true;
@@ -109,14 +105,14 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
109
105
  }
110
106
  };
111
107
  SplitsCacheInLocal.prototype.getSplit = function (name) {
112
- var item = localStorage.getItem(this.keys.buildSplitKey(name));
108
+ var item = this.storage.getItem(this.keys.buildSplitKey(name));
113
109
  return item && JSON.parse(item);
114
110
  };
115
111
  SplitsCacheInLocal.prototype.setChangeNumber = function (changeNumber) {
116
112
  try {
117
- localStorage.setItem(this.keys.buildSplitsTillKey(), changeNumber + '');
113
+ this.storage.setItem(this.keys.buildSplitsTillKey(), changeNumber + '');
118
114
  // update "last updated" timestamp with current time
119
- localStorage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
115
+ this.storage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
120
116
  this.hasSync = true;
121
117
  return true;
122
118
  }
@@ -127,7 +123,7 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
127
123
  };
128
124
  SplitsCacheInLocal.prototype.getChangeNumber = function () {
129
125
  var n = -1;
130
- var value = localStorage.getItem(this.keys.buildSplitsTillKey());
126
+ var value = this.storage.getItem(this.keys.buildSplitsTillKey());
131
127
  if (value !== null) {
132
128
  value = parseInt(value, 10);
133
129
  return (0, lang_1.isNaNNumber)(value) ? n : value;
@@ -135,11 +131,11 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
135
131
  return n;
136
132
  };
137
133
  SplitsCacheInLocal.prototype.getSplitNames = function () {
138
- var len = localStorage.length;
134
+ var len = this.storage.length;
139
135
  var accum = [];
140
136
  var cur = 0;
141
137
  while (cur < len) {
142
- var key = localStorage.key(cur);
138
+ var key = this.storage.key(cur);
143
139
  if (key != null && this.keys.isSplitKey(key))
144
140
  accum.push(this.keys.extractKey(key));
145
141
  cur++;
@@ -147,14 +143,14 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
147
143
  return accum;
148
144
  };
149
145
  SplitsCacheInLocal.prototype.trafficTypeExists = function (trafficType) {
150
- var ttCount = (0, lang_1.toNumber)(localStorage.getItem(this.keys.buildTrafficTypeKey(trafficType)));
146
+ var ttCount = (0, lang_1.toNumber)(this.storage.getItem(this.keys.buildTrafficTypeKey(trafficType)));
151
147
  return (0, lang_1.isFiniteNumber)(ttCount) && ttCount > 0;
152
148
  };
153
149
  SplitsCacheInLocal.prototype.usesSegments = function () {
154
150
  // If cache hasn't been synchronized with the cloud, assume we need them.
155
151
  if (!this.hasSync)
156
152
  return true;
157
- var storedCount = localStorage.getItem(this.keys.buildSplitsWithSegmentCountKey());
153
+ var storedCount = this.storage.getItem(this.keys.buildSplitsWithSegmentCountKey());
158
154
  var splitsWithSegmentsCount = storedCount === null ? 0 : (0, lang_1.toNumber)(storedCount);
159
155
  return (0, lang_1.isFiniteNumber)(splitsWithSegmentsCount) ?
160
156
  splitsWithSegmentsCount > 0 :
@@ -164,8 +160,8 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
164
160
  var _this = this;
165
161
  return flagSets.map(function (flagSet) {
166
162
  var flagSetKey = _this.keys.buildFlagSetKey(flagSet);
167
- var flagSetFromLocalStorage = localStorage.getItem(flagSetKey);
168
- return new Set(flagSetFromLocalStorage ? JSON.parse(flagSetFromLocalStorage) : []);
163
+ var flagSetFromStorage = _this.storage.getItem(flagSetKey);
164
+ return new Set(flagSetFromStorage ? JSON.parse(flagSetFromStorage) : []);
169
165
  });
170
166
  };
171
167
  SplitsCacheInLocal.prototype.addToFlagSets = function (featureFlag) {
@@ -176,10 +172,10 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
176
172
  if (_this.flagSetsFilter.length > 0 && !_this.flagSetsFilter.some(function (filterFlagSet) { return filterFlagSet === featureFlagSet; }))
177
173
  return;
178
174
  var flagSetKey = _this.keys.buildFlagSetKey(featureFlagSet);
179
- var flagSetFromLocalStorage = localStorage.getItem(flagSetKey);
180
- var flagSetCache = new Set(flagSetFromLocalStorage ? JSON.parse(flagSetFromLocalStorage) : []);
175
+ var flagSetFromStorage = _this.storage.getItem(flagSetKey);
176
+ var flagSetCache = new Set(flagSetFromStorage ? JSON.parse(flagSetFromStorage) : []);
181
177
  flagSetCache.add(featureFlag.name);
182
- localStorage.setItem(flagSetKey, JSON.stringify((0, sets_1.setToArray)(flagSetCache)));
178
+ _this.storage.setItem(flagSetKey, JSON.stringify((0, sets_1.setToArray)(flagSetCache)));
183
179
  });
184
180
  };
185
181
  SplitsCacheInLocal.prototype.removeFromFlagSets = function (featureFlagName, flagSets) {
@@ -192,16 +188,16 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
192
188
  };
193
189
  SplitsCacheInLocal.prototype.removeNames = function (flagSetName, featureFlagName) {
194
190
  var flagSetKey = this.keys.buildFlagSetKey(flagSetName);
195
- var flagSetFromLocalStorage = localStorage.getItem(flagSetKey);
196
- if (!flagSetFromLocalStorage)
191
+ var flagSetFromStorage = this.storage.getItem(flagSetKey);
192
+ if (!flagSetFromStorage)
197
193
  return;
198
- var flagSetCache = new Set(JSON.parse(flagSetFromLocalStorage));
194
+ var flagSetCache = new Set(JSON.parse(flagSetFromStorage));
199
195
  flagSetCache.delete(featureFlagName);
200
196
  if (flagSetCache.size === 0) {
201
- localStorage.removeItem(flagSetKey);
197
+ this.storage.removeItem(flagSetKey);
202
198
  return;
203
199
  }
204
- localStorage.setItem(flagSetKey, JSON.stringify((0, sets_1.setToArray)(flagSetCache)));
200
+ this.storage.setItem(flagSetKey, JSON.stringify((0, sets_1.setToArray)(flagSetCache)));
205
201
  };
206
202
  return SplitsCacheInLocal;
207
203
  }(AbstractSplitsCacheSync_1.AbstractSplitsCacheSync));
@@ -17,6 +17,20 @@ var TelemetryCacheInMemory_1 = require("../inMemory/TelemetryCacheInMemory");
17
17
  var UniqueKeysCacheInMemoryCS_1 = require("../inMemory/UniqueKeysCacheInMemoryCS");
18
18
  var key_1 = require("../../utils/key");
19
19
  var validateCache_1 = require("./validateCache");
20
+ var storageAdapter_1 = require("./storageAdapter");
21
+ function validateStorage(log, prefix, wrapper) {
22
+ if (wrapper) {
23
+ if ((0, isLocalStorageAvailable_1.isValidStorageWrapper)(wrapper)) {
24
+ return (0, isLocalStorageAvailable_1.isWebStorage)(wrapper) ?
25
+ wrapper : // localStorage and sessionStorage don't need adapter
26
+ (0, storageAdapter_1.storageAdapter)(log, prefix, wrapper);
27
+ }
28
+ log.warn(constants_1.LOG_PREFIX + 'Invalid storage provided. Falling back to LocalStorage API');
29
+ }
30
+ if ((0, isLocalStorageAvailable_1.isLocalStorageAvailable)())
31
+ return localStorage;
32
+ log.warn(constants_1.LOG_PREFIX + 'LocalStorage API is unavailable. Falling back to default MEMORY storage');
33
+ }
20
34
  /**
21
35
  * InLocal storage factory for standalone client-side SplitFactory
22
36
  */
@@ -24,18 +38,17 @@ function InLocalStorage(options) {
24
38
  if (options === void 0) { options = {}; }
25
39
  var prefix = (0, KeyBuilder_1.validatePrefix)(options.prefix);
26
40
  function InLocalStorageCSFactory(params) {
27
- // Fallback to InMemoryStorage if LocalStorage API is not available
28
- if (!(0, isLocalStorageAvailable_1.isLocalStorageAvailable)()) {
29
- params.settings.log.warn(constants_1.LOG_PREFIX + 'LocalStorage API is unavailable. Falling back to default MEMORY storage');
30
- return (0, InMemoryStorageCS_1.InMemoryStorageCSFactory)(params);
31
- }
32
41
  var settings = params.settings, _a = params.settings, log = _a.log, _b = _a.scheduler, impressionsQueueSize = _b.impressionsQueueSize, eventsQueueSize = _b.eventsQueueSize;
42
+ var storage = validateStorage(log, prefix, options.wrapper);
43
+ if (!storage)
44
+ return (0, InMemoryStorageCS_1.InMemoryStorageCSFactory)(params);
33
45
  var matchingKey = (0, key_1.getMatching)(settings.core.key);
34
46
  var keys = new KeyBuilderCS_1.KeyBuilderCS(prefix, matchingKey);
35
- var splits = new SplitsCacheInLocal_1.SplitsCacheInLocal(settings, keys);
36
- var rbSegments = new RBSegmentsCacheInLocal_1.RBSegmentsCacheInLocal(settings, keys);
37
- var segments = new MySegmentsCacheInLocal_1.MySegmentsCacheInLocal(log, keys);
38
- var largeSegments = new MySegmentsCacheInLocal_1.MySegmentsCacheInLocal(log, (0, KeyBuilderCS_1.myLargeSegmentsKeyBuilder)(prefix, matchingKey));
47
+ var splits = new SplitsCacheInLocal_1.SplitsCacheInLocal(settings, keys, storage);
48
+ var rbSegments = new RBSegmentsCacheInLocal_1.RBSegmentsCacheInLocal(settings, keys, storage);
49
+ var segments = new MySegmentsCacheInLocal_1.MySegmentsCacheInLocal(log, keys, storage);
50
+ var largeSegments = new MySegmentsCacheInLocal_1.MySegmentsCacheInLocal(log, (0, KeyBuilderCS_1.myLargeSegmentsKeyBuilder)(prefix, matchingKey), storage);
51
+ var validateCachePromise;
39
52
  return {
40
53
  splits: splits,
41
54
  rbSegments: rbSegments,
@@ -47,16 +60,18 @@ function InLocalStorage(options) {
47
60
  telemetry: (0, TelemetryCacheInMemory_1.shouldRecordTelemetry)(params) ? new TelemetryCacheInMemory_1.TelemetryCacheInMemory(splits, segments) : undefined,
48
61
  uniqueKeys: new UniqueKeysCacheInMemoryCS_1.UniqueKeysCacheInMemoryCS(),
49
62
  validateCache: function () {
50
- return (0, validateCache_1.validateCache)(options, settings, keys, splits, rbSegments, segments, largeSegments);
63
+ return validateCachePromise || (validateCachePromise = (0, validateCache_1.validateCache)(options, storage, settings, keys, splits, rbSegments, segments, largeSegments));
64
+ },
65
+ destroy: function () {
66
+ return storage.save && storage.save();
51
67
  },
52
- destroy: function () { },
53
68
  // When using shared instantiation with MEMORY we reuse everything but segments (they are customer per key).
54
69
  shared: function (matchingKey) {
55
70
  return {
56
71
  splits: this.splits,
57
72
  rbSegments: this.rbSegments,
58
- segments: new MySegmentsCacheInLocal_1.MySegmentsCacheInLocal(log, new KeyBuilderCS_1.KeyBuilderCS(prefix, matchingKey)),
59
- largeSegments: new MySegmentsCacheInLocal_1.MySegmentsCacheInLocal(log, (0, KeyBuilderCS_1.myLargeSegmentsKeyBuilder)(prefix, matchingKey)),
73
+ segments: new MySegmentsCacheInLocal_1.MySegmentsCacheInLocal(log, new KeyBuilderCS_1.KeyBuilderCS(prefix, matchingKey), storage),
74
+ largeSegments: new MySegmentsCacheInLocal_1.MySegmentsCacheInLocal(log, (0, KeyBuilderCS_1.myLargeSegmentsKeyBuilder)(prefix, matchingKey), storage),
60
75
  impressions: this.impressions,
61
76
  impressionCounts: this.impressionCounts,
62
77
  events: this.events,