@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
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)
@@ -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
  /**
@@ -69,3 +69,7 @@ function usesSegments(ruleEntity) {
69
69
  return false;
70
70
  }
71
71
  exports.usesSegments = usesSegments;
72
+ function usesSegmentsSync(storage) {
73
+ return storage.splits.usesSegments() || storage.rbSegments.usesSegments();
74
+ }
75
+ 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, len = this.storage.length; i < len; 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,14 +6,15 @@ 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;
@@ -23,8 +24,8 @@ var RBSegmentsCacheInLocal = /** @class */ (function () {
23
24
  };
24
25
  RBSegmentsCacheInLocal.prototype.setChangeNumber = function (changeNumber) {
25
26
  try {
26
- localStorage.setItem(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
27
- localStorage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
27
+ this.storage.setItem(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
28
+ this.storage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
28
29
  }
29
30
  catch (e) {
30
31
  this.log.error(constants_1.LOG_PREFIX + e);
@@ -32,20 +33,19 @@ var RBSegmentsCacheInLocal = /** @class */ (function () {
32
33
  };
33
34
  RBSegmentsCacheInLocal.prototype.updateSegmentCount = function (diff) {
34
35
  var segmentsCountKey = this.keys.buildSplitsWithSegmentCountKey();
35
- var count = (0, lang_1.toNumber)(localStorage.getItem(segmentsCountKey)) + diff;
36
- // @ts-expect-error
36
+ var count = (0, lang_1.toNumber)(this.storage.getItem(segmentsCountKey)) + diff;
37
37
  if (count > 0)
38
- localStorage.setItem(segmentsCountKey, count);
38
+ this.storage.setItem(segmentsCountKey, count + '');
39
39
  else
40
- localStorage.removeItem(segmentsCountKey);
40
+ this.storage.removeItem(segmentsCountKey);
41
41
  };
42
42
  RBSegmentsCacheInLocal.prototype.add = function (rbSegment) {
43
43
  try {
44
44
  var name_1 = rbSegment.name;
45
45
  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));
46
+ var rbSegmentFromStorage = this.storage.getItem(rbSegmentKey);
47
+ var previous = rbSegmentFromStorage ? JSON.parse(rbSegmentFromStorage) : null;
48
+ this.storage.setItem(rbSegmentKey, JSON.stringify(rbSegment));
49
49
  var usesSegmentsDiff = 0;
50
50
  if (previous && (0, AbstractSplitsCacheSync_1.usesSegments)(previous))
51
51
  usesSegmentsDiff--;
@@ -65,7 +65,7 @@ var RBSegmentsCacheInLocal = /** @class */ (function () {
65
65
  var rbSegment = this.get(name);
66
66
  if (!rbSegment)
67
67
  return false;
68
- localStorage.removeItem(this.keys.buildRBSegmentKey(name));
68
+ this.storage.removeItem(this.keys.buildRBSegmentKey(name));
69
69
  if ((0, AbstractSplitsCacheSync_1.usesSegments)(rbSegment))
70
70
  this.updateSegmentCount(-1);
71
71
  return true;
@@ -76,11 +76,11 @@ var RBSegmentsCacheInLocal = /** @class */ (function () {
76
76
  }
77
77
  };
78
78
  RBSegmentsCacheInLocal.prototype.getNames = function () {
79
- var len = localStorage.length;
79
+ var len = this.storage.length;
80
80
  var accum = [];
81
81
  var cur = 0;
82
82
  while (cur < len) {
83
- var key = localStorage.key(cur);
83
+ var key = this.storage.key(cur);
84
84
  if (key != null && this.keys.isRBSegmentKey(key))
85
85
  accum.push(this.keys.extractKey(key));
86
86
  cur++;
@@ -88,7 +88,7 @@ var RBSegmentsCacheInLocal = /** @class */ (function () {
88
88
  return accum;
89
89
  };
90
90
  RBSegmentsCacheInLocal.prototype.get = function (name) {
91
- var item = localStorage.getItem(this.keys.buildRBSegmentKey(name));
91
+ var item = this.storage.getItem(this.keys.buildRBSegmentKey(name));
92
92
  return item && JSON.parse(item);
93
93
  };
94
94
  RBSegmentsCacheInLocal.prototype.contains = function (names) {
@@ -98,7 +98,7 @@ var RBSegmentsCacheInLocal = /** @class */ (function () {
98
98
  };
99
99
  RBSegmentsCacheInLocal.prototype.getChangeNumber = function () {
100
100
  var n = -1;
101
- var value = localStorage.getItem(this.keys.buildRBSegmentsTillKey());
101
+ var value = this.storage.getItem(this.keys.buildRBSegmentsTillKey());
102
102
  if (value !== null) {
103
103
  value = parseInt(value, 10);
104
104
  return (0, lang_1.isNaNNumber)(value) ? n : value;
@@ -106,7 +106,7 @@ var RBSegmentsCacheInLocal = /** @class */ (function () {
106
106
  return n;
107
107
  };
108
108
  RBSegmentsCacheInLocal.prototype.usesSegments = function () {
109
- var storedCount = localStorage.getItem(this.keys.buildSplitsWithSegmentCountKey());
109
+ var storedCount = this.storage.getItem(this.keys.buildSplitsWithSegmentCountKey());
110
110
  var splitsWithSegmentsCount = storedCount === null ? 0 : (0, lang_1.toNumber)(storedCount);
111
111
  return (0, lang_1.isFiniteNumber)(splitsWithSegmentsCount) ?
112
112
  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,21 @@ 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
+ save: function () {
66
+ return storage.save && storage.save();
67
+ },
68
+ destroy: function () {
69
+ return storage.whenSaved && storage.whenSaved();
51
70
  },
52
- destroy: function () { },
53
71
  // When using shared instantiation with MEMORY we reuse everything but segments (they are customer per key).
54
72
  shared: function (matchingKey) {
55
73
  return {
56
74
  splits: this.splits,
57
75
  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)),
76
+ segments: new MySegmentsCacheInLocal_1.MySegmentsCacheInLocal(log, new KeyBuilderCS_1.KeyBuilderCS(prefix, matchingKey), storage),
77
+ largeSegments: new MySegmentsCacheInLocal_1.MySegmentsCacheInLocal(log, (0, KeyBuilderCS_1.myLargeSegmentsKeyBuilder)(prefix, matchingKey), storage),
60
78
  impressions: this.impressions,
61
79
  impressionCounts: this.impressionCounts,
62
80
  events: this.events,
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.storageAdapter = void 0;
4
+ var constants_1 = require("./constants");
5
+ function storageAdapter(log, prefix, wrapper) {
6
+ var keys = [];
7
+ var cache = {};
8
+ var loadPromise;
9
+ var savePromise = Promise.resolve();
10
+ return {
11
+ load: function () {
12
+ return loadPromise || (loadPromise = Promise.resolve().then(function () {
13
+ return wrapper.getItem(prefix);
14
+ }).then(function (storedCache) {
15
+ cache = JSON.parse(storedCache || '{}');
16
+ keys = Object.keys(cache);
17
+ }).catch(function (e) {
18
+ log.error(constants_1.LOG_PREFIX + 'Rejected promise calling wrapper `getItem` method, with error: ' + e);
19
+ }));
20
+ },
21
+ save: function () {
22
+ return savePromise = savePromise.then(function () {
23
+ return Promise.resolve(wrapper.setItem(prefix, JSON.stringify(cache)));
24
+ }).catch(function (e) {
25
+ log.error(constants_1.LOG_PREFIX + 'Rejected promise calling wrapper `setItem` method, with error: ' + e);
26
+ });
27
+ },
28
+ whenSaved: function () {
29
+ return savePromise;
30
+ },
31
+ get length() {
32
+ return keys.length;
33
+ },
34
+ getItem: function (key) {
35
+ return cache[key] || null;
36
+ },
37
+ key: function (index) {
38
+ return keys[index] || null;
39
+ },
40
+ removeItem: function (key) {
41
+ var index = keys.indexOf(key);
42
+ if (index === -1)
43
+ return;
44
+ keys.splice(index, 1);
45
+ delete cache[key];
46
+ },
47
+ setItem: function (key, value) {
48
+ if (keys.indexOf(key) === -1)
49
+ keys.push(key);
50
+ cache[key] = value;
51
+ }
52
+ };
53
+ }
54
+ exports.storageAdapter = storageAdapter;