@splitsoftware/splitio-commons 2.5.0 → 2.5.1-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGES.txt +12 -0
  2. package/cjs/storages/inLocalStorage/MySegmentsCacheInLocal.js +16 -16
  3. package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +17 -17
  4. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +33 -37
  5. package/cjs/storages/inLocalStorage/index.js +31 -13
  6. package/cjs/storages/inLocalStorage/storageAdapter.js +54 -0
  7. package/cjs/storages/inLocalStorage/validateCache.js +28 -23
  8. package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +2 -3
  9. package/cjs/sync/polling/updaters/mySegmentsUpdater.js +2 -0
  10. package/cjs/sync/polling/updaters/splitChangesUpdater.js +2 -0
  11. package/cjs/sync/syncManagerOnline.js +28 -24
  12. package/cjs/utils/env/isLocalStorageAvailable.js +22 -1
  13. package/cjs/utils/settingsValidation/storage/storageCS.js +1 -1
  14. package/esm/storages/inLocalStorage/MySegmentsCacheInLocal.js +16 -16
  15. package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +17 -17
  16. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +33 -37
  17. package/esm/storages/inLocalStorage/index.js +32 -14
  18. package/esm/storages/inLocalStorage/storageAdapter.js +50 -0
  19. package/esm/storages/inLocalStorage/validateCache.js +28 -23
  20. package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +2 -3
  21. package/esm/sync/polling/updaters/mySegmentsUpdater.js +2 -0
  22. package/esm/sync/polling/updaters/splitChangesUpdater.js +2 -0
  23. package/esm/sync/syncManagerOnline.js +28 -24
  24. package/esm/utils/env/isLocalStorageAvailable.js +19 -0
  25. package/esm/utils/settingsValidation/storage/storageCS.js +1 -1
  26. package/package.json +1 -1
  27. package/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +18 -17
  28. package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +19 -18
  29. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +34 -37
  30. package/src/storages/inLocalStorage/index.ts +37 -16
  31. package/src/storages/inLocalStorage/storageAdapter.ts +62 -0
  32. package/src/storages/inLocalStorage/validateCache.ts +29 -23
  33. package/src/storages/types.ts +19 -1
  34. package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +1 -2
  35. package/src/sync/polling/updaters/mySegmentsUpdater.ts +2 -0
  36. package/src/sync/polling/updaters/splitChangesUpdater.ts +3 -1
  37. package/src/sync/syncManagerOnline.ts +27 -22
  38. package/src/utils/env/isLocalStorageAvailable.ts +20 -0
  39. package/src/utils/settingsValidation/storage/storageCS.ts +1 -1
  40. package/types/splitio.d.ts +30 -0
@@ -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, initialRolloutPlan = settings.initialRolloutPlan;
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);
@@ -31,6 +31,8 @@ export function mySegmentsUpdaterFactory(log, mySegmentsFetcher, storage, segmen
31
31
  shouldNotifyUpdate = segments.resetSegments(segmentsData.ms || {});
32
32
  shouldNotifyUpdate = largeSegments.resetSegments(segmentsData.ls || {}) || shouldNotifyUpdate;
33
33
  }
34
+ if (storage.save)
35
+ storage.save();
34
36
  // Notify update if required
35
37
  if (usesSegmentsSync(storage) && (shouldNotifyUpdate || readyOnAlreadyExistentState)) {
36
38
  readyOnAlreadyExistentState = false;
@@ -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))))
@@ -66,35 +66,39 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
66
66
  */
67
67
  start: function () {
68
68
  running = true;
69
- if (startFirstTime) {
70
- var isCacheLoaded = storage.validateCache ? storage.validateCache() : false;
71
- if (isCacheLoaded)
72
- Promise.resolve().then(function () { readiness.splits.emit(SDK_SPLITS_CACHE_LOADED); });
73
- }
74
- // start syncing splits and segments
75
- if (pollingManager) {
76
- // If synchronization is disabled pushManager and pollingManager should not start
77
- if (syncEnabled) {
78
- if (pushManager) {
79
- // Doesn't call `syncAll` when the syncManager is resuming
80
- if (startFirstTime) {
81
- 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();
82
92
  }
83
- pushManager.start();
84
93
  }
85
94
  else {
86
- pollingManager.start();
87
- }
88
- }
89
- else {
90
- if (startFirstTime) {
91
- pollingManager.syncAll();
95
+ if (startFirstTime) {
96
+ pollingManager.syncAll();
97
+ }
92
98
  }
93
99
  }
94
- }
95
- // start periodic data recording (events, impressions, telemetry).
96
- submitterManager.start(!isConsentGranted(settings));
97
- startFirstTime = false;
100
+ startFirstTime = false;
101
+ });
98
102
  },
99
103
  /**
100
104
  * Method used to stop/pause the syncManager.
@@ -10,3 +10,22 @@ export function isLocalStorageAvailable() {
10
10
  return false;
11
11
  }
12
12
  }
13
+ export function isValidStorageWrapper(wrapper) {
14
+ return wrapper !== null &&
15
+ typeof wrapper === 'object' &&
16
+ typeof wrapper.setItem === 'function' &&
17
+ typeof wrapper.getItem === 'function' &&
18
+ typeof wrapper.removeItem === 'function';
19
+ }
20
+ export function isWebStorage(wrapper) {
21
+ if (typeof wrapper.length === 'number') {
22
+ try {
23
+ wrapper.key(0);
24
+ return true;
25
+ }
26
+ catch (e) {
27
+ return false;
28
+ }
29
+ }
30
+ return false;
31
+ }
@@ -3,7 +3,7 @@ import { ERROR_STORAGE_INVALID } from '../../../logger/constants';
3
3
  import { LOCALHOST_MODE, STANDALONE_MODE, STORAGE_PLUGGABLE, STORAGE_LOCALSTORAGE, STORAGE_MEMORY } from '../../../utils/constants';
4
4
  export function __InLocalStorageMockFactory(params) {
5
5
  var result = InMemoryStorageCSFactory(params);
6
- result.validateCache = function () { return true; }; // to emit SDK_READY_FROM_CACHE
6
+ result.validateCache = function () { return Promise.resolve(true); }; // to emit SDK_READY_FROM_CACHE
7
7
  return result;
8
8
  }
9
9
  __InLocalStorageMockFactory.type = STORAGE_MEMORY;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splitsoftware/splitio-commons",
3
- "version": "2.5.0",
3
+ "version": "2.5.1-rc.0",
4
4
  "description": "Split JavaScript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
@@ -3,16 +3,19 @@ import { isNaNNumber } from '../../utils/lang';
3
3
  import { AbstractMySegmentsCacheSync } from '../AbstractMySegmentsCacheSync';
4
4
  import type { MySegmentsKeyBuilder } from '../KeyBuilderCS';
5
5
  import { LOG_PREFIX, DEFINED } from './constants';
6
+ import { StorageAdapter } from '../types';
6
7
 
7
8
  export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
8
9
 
9
10
  private readonly keys: MySegmentsKeyBuilder;
10
11
  private readonly log: ILogger;
12
+ private readonly storage: StorageAdapter;
11
13
 
12
- constructor(log: ILogger, keys: MySegmentsKeyBuilder) {
14
+ constructor(log: ILogger, keys: MySegmentsKeyBuilder, storage: StorageAdapter) {
13
15
  super();
14
16
  this.log = log;
15
17
  this.keys = keys;
18
+ this.storage = storage;
16
19
  // There is not need to flush segments cache like splits cache, since resetSegments receives the up-to-date list of active segments
17
20
  }
18
21
 
@@ -20,8 +23,8 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
20
23
  const segmentKey = this.keys.buildSegmentNameKey(name);
21
24
 
22
25
  try {
23
- if (localStorage.getItem(segmentKey) === DEFINED) return false;
24
- localStorage.setItem(segmentKey, DEFINED);
26
+ if (this.storage.getItem(segmentKey) === DEFINED) return false;
27
+ this.storage.setItem(segmentKey, DEFINED);
25
28
  return true;
26
29
  } catch (e) {
27
30
  this.log.error(LOG_PREFIX + e);
@@ -33,8 +36,8 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
33
36
  const segmentKey = this.keys.buildSegmentNameKey(name);
34
37
 
35
38
  try {
36
- if (localStorage.getItem(segmentKey) !== DEFINED) return false;
37
- localStorage.removeItem(segmentKey);
39
+ if (this.storage.getItem(segmentKey) !== DEFINED) return false;
40
+ this.storage.removeItem(segmentKey);
38
41
  return true;
39
42
  } catch (e) {
40
43
  this.log.error(LOG_PREFIX + e);
@@ -43,18 +46,16 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
43
46
  }
44
47
 
45
48
  isInSegment(name: string): boolean {
46
- return localStorage.getItem(this.keys.buildSegmentNameKey(name)) === DEFINED;
49
+ return this.storage.getItem(this.keys.buildSegmentNameKey(name)) === DEFINED;
47
50
  }
48
51
 
49
52
  getRegisteredSegments(): string[] {
50
- // Scan current values from localStorage
51
- return Object.keys(localStorage).reduce((accum, key) => {
52
- let segmentName = this.keys.extractSegmentName(key);
53
-
54
- if (segmentName) accum.push(segmentName);
55
-
56
- return accum;
57
- }, [] as string[]);
53
+ const registeredSegments: string[] = [];
54
+ for (let i = 0, len = this.storage.length; i < len; i++) {
55
+ const segmentName = this.keys.extractSegmentName(this.storage.key(i)!);
56
+ if (segmentName) registeredSegments.push(segmentName);
57
+ }
58
+ return registeredSegments;
58
59
  }
59
60
 
60
61
  getKeysCount() {
@@ -63,8 +64,8 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
63
64
 
64
65
  protected setChangeNumber(changeNumber?: number) {
65
66
  try {
66
- if (changeNumber) localStorage.setItem(this.keys.buildTillKey(), changeNumber + '');
67
- else localStorage.removeItem(this.keys.buildTillKey());
67
+ if (changeNumber) this.storage.setItem(this.keys.buildTillKey(), changeNumber + '');
68
+ else this.storage.removeItem(this.keys.buildTillKey());
68
69
  } catch (e) {
69
70
  this.log.error(e);
70
71
  }
@@ -72,7 +73,7 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
72
73
 
73
74
  getChangeNumber() {
74
75
  const n = -1;
75
- let value: string | number | null = localStorage.getItem(this.keys.buildTillKey());
76
+ let value: string | number | null = this.storage.getItem(this.keys.buildTillKey());
76
77
 
77
78
  if (value !== null) {
78
79
  value = parseInt(value, 10);
@@ -5,22 +5,24 @@ import { isFiniteNumber, isNaNNumber, toNumber } from '../../utils/lang';
5
5
  import { setToArray } from '../../utils/lang/sets';
6
6
  import { usesSegments } from '../AbstractSplitsCacheSync';
7
7
  import { KeyBuilderCS } from '../KeyBuilderCS';
8
- import { IRBSegmentsCacheSync } from '../types';
8
+ import { IRBSegmentsCacheSync, StorageAdapter } from '../types';
9
9
  import { LOG_PREFIX } from './constants';
10
10
 
11
11
  export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
12
12
 
13
13
  private readonly keys: KeyBuilderCS;
14
14
  private readonly log: ILogger;
15
+ private readonly storage: StorageAdapter;
15
16
 
16
- constructor(settings: ISettings, keys: KeyBuilderCS) {
17
+ constructor(settings: ISettings, keys: KeyBuilderCS, storage: StorageAdapter) {
17
18
  this.keys = keys;
18
19
  this.log = settings.log;
20
+ this.storage = storage;
19
21
  }
20
22
 
21
23
  clear() {
22
24
  this.getNames().forEach(name => this.remove(name));
23
- localStorage.removeItem(this.keys.buildRBSegmentsTillKey());
25
+ this.storage.removeItem(this.keys.buildRBSegmentsTillKey());
24
26
  }
25
27
 
26
28
  update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): boolean {
@@ -31,8 +33,8 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
31
33
 
32
34
  private setChangeNumber(changeNumber: number) {
33
35
  try {
34
- localStorage.setItem(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
35
- localStorage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
36
+ this.storage.setItem(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
37
+ this.storage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
36
38
  } catch (e) {
37
39
  this.log.error(LOG_PREFIX + e);
38
40
  }
@@ -40,20 +42,19 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
40
42
 
41
43
  private updateSegmentCount(diff: number) {
42
44
  const segmentsCountKey = this.keys.buildSplitsWithSegmentCountKey();
43
- const count = toNumber(localStorage.getItem(segmentsCountKey)) + diff;
44
- // @ts-expect-error
45
- if (count > 0) localStorage.setItem(segmentsCountKey, count);
46
- else localStorage.removeItem(segmentsCountKey);
45
+ const count = toNumber(this.storage.getItem(segmentsCountKey)) + diff;
46
+ if (count > 0) this.storage.setItem(segmentsCountKey, count + '');
47
+ else this.storage.removeItem(segmentsCountKey);
47
48
  }
48
49
 
49
50
  private add(rbSegment: IRBSegment): boolean {
50
51
  try {
51
52
  const name = rbSegment.name;
52
53
  const rbSegmentKey = this.keys.buildRBSegmentKey(name);
53
- const rbSegmentFromLocalStorage = localStorage.getItem(rbSegmentKey);
54
- const previous = rbSegmentFromLocalStorage ? JSON.parse(rbSegmentFromLocalStorage) : null;
54
+ const rbSegmentFromStorage = this.storage.getItem(rbSegmentKey);
55
+ const previous = rbSegmentFromStorage ? JSON.parse(rbSegmentFromStorage) : null;
55
56
 
56
- localStorage.setItem(rbSegmentKey, JSON.stringify(rbSegment));
57
+ this.storage.setItem(rbSegmentKey, JSON.stringify(rbSegment));
57
58
 
58
59
  let usesSegmentsDiff = 0;
59
60
  if (previous && usesSegments(previous)) usesSegmentsDiff--;
@@ -72,7 +73,7 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
72
73
  const rbSegment = this.get(name);
73
74
  if (!rbSegment) return false;
74
75
 
75
- localStorage.removeItem(this.keys.buildRBSegmentKey(name));
76
+ this.storage.removeItem(this.keys.buildRBSegmentKey(name));
76
77
 
77
78
  if (usesSegments(rbSegment)) this.updateSegmentCount(-1);
78
79
 
@@ -84,13 +85,13 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
84
85
  }
85
86
 
86
87
  private getNames(): string[] {
87
- const len = localStorage.length;
88
+ const len = this.storage.length;
88
89
  const accum = [];
89
90
 
90
91
  let cur = 0;
91
92
 
92
93
  while (cur < len) {
93
- const key = localStorage.key(cur);
94
+ const key = this.storage.key(cur);
94
95
 
95
96
  if (key != null && this.keys.isRBSegmentKey(key)) accum.push(this.keys.extractKey(key));
96
97
 
@@ -101,7 +102,7 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
101
102
  }
102
103
 
103
104
  get(name: string): IRBSegment | null {
104
- const item = localStorage.getItem(this.keys.buildRBSegmentKey(name));
105
+ const item = this.storage.getItem(this.keys.buildRBSegmentKey(name));
105
106
  return item && JSON.parse(item);
106
107
  }
107
108
 
@@ -117,7 +118,7 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
117
118
 
118
119
  getChangeNumber(): number {
119
120
  const n = -1;
120
- let value: string | number | null = localStorage.getItem(this.keys.buildRBSegmentsTillKey());
121
+ let value: string | number | null = this.storage.getItem(this.keys.buildRBSegmentsTillKey());
121
122
 
122
123
  if (value !== null) {
123
124
  value = parseInt(value, 10);
@@ -129,7 +130,7 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
129
130
  }
130
131
 
131
132
  usesSegments(): boolean {
132
- const storedCount = localStorage.getItem(this.keys.buildSplitsWithSegmentCountKey());
133
+ const storedCount = this.storage.getItem(this.keys.buildSplitsWithSegmentCountKey());
133
134
  const splitsWithSegmentsCount = storedCount === null ? 0 : toNumber(storedCount);
134
135
 
135
136
  return isFiniteNumber(splitsWithSegmentsCount) ?
@@ -6,29 +6,28 @@ import { ILogger } from '../../logger/types';
6
6
  import { LOG_PREFIX } from './constants';
7
7
  import { ISettings } from '../../types';
8
8
  import { setToArray } from '../../utils/lang/sets';
9
+ import { StorageAdapter } from '../types';
9
10
 
10
- /**
11
- * ISplitsCacheSync implementation that stores split definitions in browser LocalStorage.
12
- */
13
11
  export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
14
12
 
15
13
  private readonly keys: KeyBuilderCS;
16
14
  private readonly log: ILogger;
17
15
  private readonly flagSetsFilter: string[];
18
16
  private hasSync?: boolean;
17
+ private readonly storage: StorageAdapter;
19
18
 
20
- constructor(settings: ISettings, keys: KeyBuilderCS) {
19
+ constructor(settings: ISettings, keys: KeyBuilderCS, storage: StorageAdapter) {
21
20
  super();
22
21
  this.keys = keys;
23
22
  this.log = settings.log;
24
23
  this.flagSetsFilter = settings.sync.__splitFiltersValidation.groupedFilters.bySet;
24
+ this.storage = storage;
25
25
  }
26
26
 
27
27
  private _decrementCount(key: string) {
28
- const count = toNumber(localStorage.getItem(key)) - 1;
29
- // @ts-expect-error
30
- if (count > 0) localStorage.setItem(key, count);
31
- else localStorage.removeItem(key);
28
+ const count = toNumber(this.storage.getItem(key)) - 1;
29
+ if (count > 0) this.storage.setItem(key, count + '');
30
+ else this.storage.removeItem(key);
32
31
  }
33
32
 
34
33
  private _decrementCounts(split: ISplit) {
@@ -48,13 +47,11 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
48
47
  private _incrementCounts(split: ISplit) {
49
48
  try {
50
49
  const ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName);
51
- // @ts-expect-error
52
- localStorage.setItem(ttKey, toNumber(localStorage.getItem(ttKey)) + 1);
50
+ this.storage.setItem(ttKey, (toNumber(this.storage.getItem(ttKey)) + 1) + '');
53
51
 
54
52
  if (usesSegments(split)) {
55
53
  const segmentsCountKey = this.keys.buildSplitsWithSegmentCountKey();
56
- // @ts-expect-error
57
- localStorage.setItem(segmentsCountKey, toNumber(localStorage.getItem(segmentsCountKey)) + 1);
54
+ this.storage.setItem(segmentsCountKey, (toNumber(this.storage.getItem(segmentsCountKey)) + 1) + '');
58
55
  }
59
56
  } catch (e) {
60
57
  this.log.error(LOG_PREFIX + e);
@@ -68,15 +65,15 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
68
65
  */
69
66
  clear() {
70
67
  // collect item keys
71
- const len = localStorage.length;
68
+ const len = this.storage.length;
72
69
  const accum = [];
73
70
  for (let cur = 0; cur < len; cur++) {
74
- const key = localStorage.key(cur);
71
+ const key = this.storage.key(cur);
75
72
  if (key != null && this.keys.isSplitsCacheKey(key)) accum.push(key);
76
73
  }
77
74
  // remove items
78
75
  accum.forEach(key => {
79
- localStorage.removeItem(key);
76
+ this.storage.removeItem(key);
80
77
  });
81
78
 
82
79
  this.hasSync = false;
@@ -86,15 +83,15 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
86
83
  try {
87
84
  const name = split.name;
88
85
  const splitKey = this.keys.buildSplitKey(name);
89
- const splitFromLocalStorage = localStorage.getItem(splitKey);
90
- const previousSplit = splitFromLocalStorage ? JSON.parse(splitFromLocalStorage) : null;
86
+ const splitFromStorage = this.storage.getItem(splitKey);
87
+ const previousSplit = splitFromStorage ? JSON.parse(splitFromStorage) : null;
91
88
 
92
89
  if (previousSplit) {
93
90
  this._decrementCounts(previousSplit);
94
91
  this.removeFromFlagSets(previousSplit.name, previousSplit.sets);
95
92
  }
96
93
 
97
- localStorage.setItem(splitKey, JSON.stringify(split));
94
+ this.storage.setItem(splitKey, JSON.stringify(split));
98
95
 
99
96
  this._incrementCounts(split);
100
97
  this.addToFlagSets(split);
@@ -111,7 +108,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
111
108
  const split = this.getSplit(name);
112
109
  if (!split) return false;
113
110
 
114
- localStorage.removeItem(this.keys.buildSplitKey(name));
111
+ this.storage.removeItem(this.keys.buildSplitKey(name));
115
112
 
116
113
  this._decrementCounts(split);
117
114
  this.removeFromFlagSets(split.name, split.sets);
@@ -124,15 +121,15 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
124
121
  }
125
122
 
126
123
  getSplit(name: string): ISplit | null {
127
- const item = localStorage.getItem(this.keys.buildSplitKey(name));
124
+ const item = this.storage.getItem(this.keys.buildSplitKey(name));
128
125
  return item && JSON.parse(item);
129
126
  }
130
127
 
131
128
  setChangeNumber(changeNumber: number): boolean {
132
129
  try {
133
- localStorage.setItem(this.keys.buildSplitsTillKey(), changeNumber + '');
130
+ this.storage.setItem(this.keys.buildSplitsTillKey(), changeNumber + '');
134
131
  // update "last updated" timestamp with current time
135
- localStorage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
132
+ this.storage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
136
133
  this.hasSync = true;
137
134
  return true;
138
135
  } catch (e) {
@@ -143,7 +140,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
143
140
 
144
141
  getChangeNumber(): number {
145
142
  const n = -1;
146
- let value: string | number | null = localStorage.getItem(this.keys.buildSplitsTillKey());
143
+ let value: string | number | null = this.storage.getItem(this.keys.buildSplitsTillKey());
147
144
 
148
145
  if (value !== null) {
149
146
  value = parseInt(value, 10);
@@ -155,13 +152,13 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
155
152
  }
156
153
 
157
154
  getSplitNames(): string[] {
158
- const len = localStorage.length;
155
+ const len = this.storage.length;
159
156
  const accum = [];
160
157
 
161
158
  let cur = 0;
162
159
 
163
160
  while (cur < len) {
164
- const key = localStorage.key(cur);
161
+ const key = this.storage.key(cur);
165
162
 
166
163
  if (key != null && this.keys.isSplitKey(key)) accum.push(this.keys.extractKey(key));
167
164
 
@@ -172,7 +169,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
172
169
  }
173
170
 
174
171
  trafficTypeExists(trafficType: string): boolean {
175
- const ttCount = toNumber(localStorage.getItem(this.keys.buildTrafficTypeKey(trafficType)));
172
+ const ttCount = toNumber(this.storage.getItem(this.keys.buildTrafficTypeKey(trafficType)));
176
173
  return isFiniteNumber(ttCount) && ttCount > 0;
177
174
  }
178
175
 
@@ -180,7 +177,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
180
177
  // If cache hasn't been synchronized with the cloud, assume we need them.
181
178
  if (!this.hasSync) return true;
182
179
 
183
- const storedCount = localStorage.getItem(this.keys.buildSplitsWithSegmentCountKey());
180
+ const storedCount = this.storage.getItem(this.keys.buildSplitsWithSegmentCountKey());
184
181
  const splitsWithSegmentsCount = storedCount === null ? 0 : toNumber(storedCount);
185
182
 
186
183
  return isFiniteNumber(splitsWithSegmentsCount) ?
@@ -191,9 +188,9 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
191
188
  getNamesByFlagSets(flagSets: string[]): Set<string>[] {
192
189
  return flagSets.map(flagSet => {
193
190
  const flagSetKey = this.keys.buildFlagSetKey(flagSet);
194
- const flagSetFromLocalStorage = localStorage.getItem(flagSetKey);
191
+ const flagSetFromStorage = this.storage.getItem(flagSetKey);
195
192
 
196
- return new Set(flagSetFromLocalStorage ? JSON.parse(flagSetFromLocalStorage) : []);
193
+ return new Set(flagSetFromStorage ? JSON.parse(flagSetFromStorage) : []);
197
194
  });
198
195
  }
199
196
 
@@ -206,12 +203,12 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
206
203
 
207
204
  const flagSetKey = this.keys.buildFlagSetKey(featureFlagSet);
208
205
 
209
- const flagSetFromLocalStorage = localStorage.getItem(flagSetKey);
206
+ const flagSetFromStorage = this.storage.getItem(flagSetKey);
210
207
 
211
- const flagSetCache = new Set(flagSetFromLocalStorage ? JSON.parse(flagSetFromLocalStorage) : []);
208
+ const flagSetCache = new Set(flagSetFromStorage ? JSON.parse(flagSetFromStorage) : []);
212
209
  flagSetCache.add(featureFlag.name);
213
210
 
214
- localStorage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache)));
211
+ this.storage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache)));
215
212
  });
216
213
  }
217
214
 
@@ -226,19 +223,19 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
226
223
  private removeNames(flagSetName: string, featureFlagName: string) {
227
224
  const flagSetKey = this.keys.buildFlagSetKey(flagSetName);
228
225
 
229
- const flagSetFromLocalStorage = localStorage.getItem(flagSetKey);
226
+ const flagSetFromStorage = this.storage.getItem(flagSetKey);
230
227
 
231
- if (!flagSetFromLocalStorage) return;
228
+ if (!flagSetFromStorage) return;
232
229
 
233
- const flagSetCache = new Set(JSON.parse(flagSetFromLocalStorage));
230
+ const flagSetCache = new Set(JSON.parse(flagSetFromStorage));
234
231
  flagSetCache.delete(featureFlagName);
235
232
 
236
233
  if (flagSetCache.size === 0) {
237
- localStorage.removeItem(flagSetKey);
234
+ this.storage.removeItem(flagSetKey);
238
235
  return;
239
236
  }
240
237
 
241
- localStorage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache)));
238
+ this.storage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache)));
242
239
  }
243
240
 
244
241
  }