@splitsoftware/splitio-commons 2.4.2-rc.1 → 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 (67) 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/trackers/impressionsTracker.js +4 -4
  20. package/cjs/utils/env/isLocalStorageAvailable.js +28 -5
  21. package/cjs/utils/settingsValidation/splitFilters.js +0 -6
  22. package/cjs/utils/settingsValidation/storage/storageCS.js +1 -1
  23. package/esm/logger/messages/error.js +1 -1
  24. package/esm/sdkClient/sdkClient.js +0 -1
  25. package/esm/sdkFactory/index.js +3 -10
  26. package/esm/services/splitHttpClient.js +1 -1
  27. package/esm/storages/AbstractMySegmentsCacheSync.js +31 -23
  28. package/esm/storages/AbstractSplitsCacheSync.js +6 -2
  29. package/esm/storages/inLocalStorage/MySegmentsCacheInLocal.js +16 -16
  30. package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +20 -19
  31. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +33 -37
  32. package/esm/storages/inLocalStorage/index.js +29 -14
  33. package/esm/storages/inLocalStorage/storageAdapter.js +44 -0
  34. package/esm/storages/inLocalStorage/validateCache.js +25 -23
  35. package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +2 -3
  36. package/esm/sync/polling/pollingManagerCS.js +5 -4
  37. package/esm/sync/polling/updaters/mySegmentsUpdater.js +3 -2
  38. package/esm/sync/polling/updaters/splitChangesUpdater.js +1 -1
  39. package/esm/sync/syncManagerOnline.js +31 -26
  40. package/esm/trackers/impressionsTracker.js +4 -4
  41. package/esm/utils/env/isLocalStorageAvailable.js +24 -3
  42. package/esm/utils/settingsValidation/splitFilters.js +0 -6
  43. package/esm/utils/settingsValidation/storage/storageCS.js +1 -1
  44. package/package.json +1 -1
  45. package/src/logger/messages/error.ts +1 -1
  46. package/src/sdkClient/sdkClient.ts +0 -1
  47. package/src/sdkFactory/index.ts +3 -13
  48. package/src/services/splitHttpClient.ts +1 -1
  49. package/src/storages/AbstractMySegmentsCacheSync.ts +26 -20
  50. package/src/storages/AbstractSplitsCacheSync.ts +8 -3
  51. package/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +18 -17
  52. package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +22 -20
  53. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +34 -37
  54. package/src/storages/inLocalStorage/index.ts +33 -16
  55. package/src/storages/inLocalStorage/storageAdapter.ts +50 -0
  56. package/src/storages/inLocalStorage/validateCache.ts +26 -23
  57. package/src/storages/types.ts +17 -1
  58. package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +1 -2
  59. package/src/sync/polling/pollingManagerCS.ts +5 -4
  60. package/src/sync/polling/updaters/mySegmentsUpdater.ts +3 -2
  61. package/src/sync/polling/updaters/splitChangesUpdater.ts +1 -1
  62. package/src/sync/syncManagerOnline.ts +30 -24
  63. package/src/trackers/impressionsTracker.ts +3 -3
  64. package/src/utils/env/isLocalStorageAvailable.ts +24 -3
  65. package/src/utils/settingsValidation/splitFilters.ts +0 -6
  66. package/src/utils/settingsValidation/storage/storageCS.ts +1 -1
  67. package/types/splitio.d.ts +57 -16
@@ -4,6 +4,7 @@ import { SYNC_START_POLLING, SYNC_CONTINUE_POLLING, SYNC_STOP_POLLING } from '..
4
4
  import { isConsentGranted } from '../consent';
5
5
  import { POLLING, STREAMING, SYNC_MODE_UPDATE } from '../utils/constants';
6
6
  import { SDK_SPLITS_CACHE_LOADED } from '../readiness/constants';
7
+ import { usesSegmentsSync } from '../storages/AbstractSplitsCacheSync';
7
8
  /**
8
9
  * Online SyncManager factory.
9
10
  * Can be used for server-side API, and client-side API with or without multiple clients.
@@ -65,35 +66,39 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
65
66
  */
66
67
  start: function () {
67
68
  running = true;
68
- if (startFirstTime) {
69
- var isCacheLoaded = storage.validateCache ? storage.validateCache() : false;
70
- if (isCacheLoaded)
71
- Promise.resolve().then(function () { readiness.splits.emit(SDK_SPLITS_CACHE_LOADED); });
72
- }
73
- // start syncing splits and segments
74
- if (pollingManager) {
75
- // If synchronization is disabled pushManager and pollingManager should not start
76
- if (syncEnabled) {
77
- if (pushManager) {
78
- // Doesn't call `syncAll` when the syncManager is resuming
79
- if (startFirstTime) {
80
- pollingManager.syncAll();
69
+ // @TODO once event, impression and telemetry storages support persistence, call when `validateCache` promise is resolved
70
+ submitterManager.start(!isConsentGranted(settings));
71
+ return Promise.resolve(storage.validateCache ? storage.validateCache() : false).then(function (isCacheLoaded) {
72
+ if (!running)
73
+ return;
74
+ if (startFirstTime) {
75
+ // Emits SDK_READY_FROM_CACHE
76
+ if (isCacheLoaded)
77
+ readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
78
+ }
79
+ // start syncing splits and segments
80
+ if (pollingManager) {
81
+ // If synchronization is disabled pushManager and pollingManager should not start
82
+ if (syncEnabled) {
83
+ if (pushManager) {
84
+ // Doesn't call `syncAll` when the syncManager is resuming
85
+ if (startFirstTime) {
86
+ pollingManager.syncAll();
87
+ }
88
+ pushManager.start();
89
+ }
90
+ else {
91
+ pollingManager.start();
81
92
  }
82
- pushManager.start();
83
93
  }
84
94
  else {
85
- pollingManager.start();
86
- }
87
- }
88
- else {
89
- if (startFirstTime) {
90
- pollingManager.syncAll();
95
+ if (startFirstTime) {
96
+ pollingManager.syncAll();
97
+ }
91
98
  }
92
99
  }
93
- }
94
- // start periodic data recording (events, impressions, telemetry).
95
- submitterManager.start(!isConsentGranted(settings));
96
- startFirstTime = false;
100
+ startFirstTime = false;
101
+ });
97
102
  },
98
103
  /**
99
104
  * Method used to stop/pause the syncManager.
@@ -127,7 +132,7 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
127
132
  if (pushManager) {
128
133
  if (pollingManager.isRunning()) {
129
134
  // if doing polling, we must start the periodic fetch of data
130
- if (storage.splits.usesSegments() || storage.rbSegments.usesSegments())
135
+ if (usesSegmentsSync(storage))
131
136
  mySegmentsSyncTask.start();
132
137
  }
133
138
  else {
@@ -137,7 +142,7 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
137
142
  }
138
143
  }
139
144
  else {
140
- if (storage.splits.usesSegments() || storage.rbSegments.usesSegments())
145
+ if (usesSegmentsSync(storage))
141
146
  mySegmentsSyncTask.start();
142
147
  }
143
148
  }
@@ -6,7 +6,7 @@ import { CONSENT_DECLINED, DEDUPED, QUEUED } from '../utils/constants';
6
6
  * Impressions tracker stores impressions in cache and pass them to the listener and integrations manager if provided.
7
7
  */
8
8
  export function impressionsTrackerFactory(settings, impressionsCache, noneStrategy, strategy, whenInit, integrationsManager, telemetryCache) {
9
- var log = settings.log, _a = settings.runtime, ip = _a.ip, hostname = _a.hostname, version = settings.version;
9
+ var log = settings.log, impressionListener = settings.impressionListener, _a = settings.runtime, ip = _a.ip, hostname = _a.hostname, version = settings.version;
10
10
  return {
11
11
  track: function (impressions, attributes) {
12
12
  if (settings.userConsent === CONSENT_DECLINED)
@@ -39,7 +39,7 @@ export function impressionsTrackerFactory(settings, impressionsCache, noneStrate
39
39
  }
40
40
  }
41
41
  // @TODO next block might be handled by the integration manager. In that case, the metadata object doesn't need to be passed in the constructor
42
- if (settings.impressionListener || integrationsManager) {
42
+ if (impressionListener || integrationsManager) {
43
43
  var _loop_1 = function (i) {
44
44
  var impressionData = {
45
45
  // copy of impression, to avoid unexpected behavior if modified by integrations or impressionListener
@@ -56,8 +56,8 @@ export function impressionsTrackerFactory(settings, impressionsCache, noneStrate
56
56
  if (integrationsManager)
57
57
  integrationsManager.handleImpression(impressionData);
58
58
  try { // @ts-ignore. An exception on the listeners should not break the SDK.
59
- if (settings.impressionListener)
60
- settings.impressionListener.logImpression(impressionData);
59
+ if (impressionListener)
60
+ impressionListener.logImpression(impressionData);
61
61
  }
62
62
  catch (err) {
63
63
  log.error(ERROR_IMPRESSIONS_LISTENER, [err]);
@@ -1,12 +1,33 @@
1
- /* eslint-disable no-undef */
2
1
  export function isLocalStorageAvailable() {
2
+ try {
3
+ // eslint-disable-next-line no-undef
4
+ return isValidStorageWrapper(localStorage);
5
+ }
6
+ catch (e) {
7
+ return false;
8
+ }
9
+ }
10
+ export function isValidStorageWrapper(wrapper) {
3
11
  var mod = '__SPLITSOFTWARE__';
4
12
  try {
5
- localStorage.setItem(mod, mod);
6
- localStorage.removeItem(mod);
13
+ wrapper.setItem(mod, mod);
14
+ wrapper.getItem(mod);
15
+ wrapper.removeItem(mod);
7
16
  return true;
8
17
  }
9
18
  catch (e) {
10
19
  return false;
11
20
  }
12
21
  }
22
+ export function isWebStorage(wrapper) {
23
+ if (typeof wrapper.length === 'number') {
24
+ try {
25
+ wrapper.key(0);
26
+ return true;
27
+ }
28
+ catch (e) {
29
+ return false;
30
+ }
31
+ }
32
+ return false;
33
+ }
@@ -58,12 +58,6 @@ function validateSplitFilter(log, type, values, maxLength) {
58
58
  /**
59
59
  * Returns a string representing the URL encoded query component of /splitChanges URL.
60
60
  *
61
- * The possible formats of the query string are:
62
- * - null: if all filters are empty
63
- * - '&names=<comma-separated-values>': if only `byPrefix` filter is undefined
64
- * - '&prefixes=<comma-separated-values>': if only `byName` filter is undefined
65
- * - '&names=<comma-separated-values>&prefixes=<comma-separated-values>': if no one is undefined
66
- *
67
61
  * @param groupedFilters - object of filters. Each filter must be a list of valid, unique and ordered string values.
68
62
  * @returns null or string with the `split filter query` component of the URL.
69
63
  */
@@ -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.4.2-rc.1",
3
+ "version": "2.4.2-rc.2",
4
4
  "description": "Split JavaScript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
@@ -14,7 +14,7 @@ export const codesError: [number, string][] = [
14
14
  [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'],
15
15
  [c.ERROR_STREAMING_SSE, c.LOG_PREFIX_SYNC_STREAMING + 'Failed to connect or error on streaming connection, with error message: %s'],
16
16
  [c.ERROR_STREAMING_AUTH, c.LOG_PREFIX_SYNC_STREAMING + 'Failed to authenticate for streaming. Error: %s.'],
17
- [c.ERROR_HTTP, 'Response status is not OK. Status: %s. URL: %s. Message: %s'],
17
+ [c.ERROR_HTTP, 'HTTP request failed with %s. URL: %s. Message: %s'],
18
18
  // client status
19
19
  [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.'],
20
20
  [c.ERROR_CLIENT_DESTROYED, '%s: Client has already been destroyed - no calls possible.'],
@@ -48,7 +48,6 @@ export function sdkClientFactory(params: ISdkFactoryContext, isSharedClient?: bo
48
48
 
49
49
  // Sdk destroy
50
50
  {
51
- __ctx: params,
52
51
  flush() {
53
52
  // @TODO define cooldown time
54
53
  return __cooldown(__flush, COOLDOWN_TIME_IN_MILLIS);
@@ -107,7 +107,8 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
107
107
 
108
108
  log.info(NEW_FACTORY);
109
109
 
110
- const factory = objectAssign({
110
+ // @ts-ignore
111
+ return objectAssign({
111
112
  // Split evaluation and event tracking engine
112
113
  client: clientMethod,
113
114
 
@@ -125,17 +126,6 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
125
126
  destroy() {
126
127
  hasInit = false;
127
128
  return Promise.all(Object.keys(clients).map(key => clients[key].destroy())).then(() => { });
128
- },
129
-
130
- __ctx: ctx
129
+ }
131
130
  }, extraProps && extraProps(ctx), lazyInit ? { init } : init());
132
-
133
- // append factory to global
134
- if (typeof window === 'object') { // @ts-ignore
135
- // eslint-disable-next-line no-undef
136
- (window.__HARNESS_FME__ = window.__HARNESS_FME__ || []).push(factory);
137
- }
138
-
139
- // @ts-ignore
140
- return factory;
141
131
  }
@@ -70,7 +70,7 @@ export function splitHttpClientFactory(settings: ISettings, { getOptions, getFet
70
70
  }
71
71
 
72
72
  if (!resp || resp.status !== 403) { // 403's log we'll be handled somewhere else.
73
- log[logErrorsAsInfo ? 'info' : 'error'](ERROR_HTTP, [resp ? resp.status : 'NO_STATUS', url, msg]);
73
+ log[logErrorsAsInfo ? 'info' : 'error'](ERROR_HTTP, [resp ? 'status code ' + resp.status : 'no status code', url, msg]);
74
74
  }
75
75
 
76
76
  const networkError: NetworkError = new Error(msg);
@@ -49,12 +49,10 @@ export abstract class AbstractMySegmentsCacheSync implements ISegmentsCacheSync
49
49
  * For client-side synchronizer: it resets or updates the cache.
50
50
  */
51
51
  resetSegments(segmentsData: MySegmentsData | IMySegmentsResponse): boolean {
52
- this.setChangeNumber(segmentsData.cn);
53
-
54
52
  const { added, removed } = segmentsData as MySegmentsData;
53
+ let isDiff = false;
55
54
 
56
55
  if (added && removed) {
57
- let isDiff = false;
58
56
 
59
57
  added.forEach(segment => {
60
58
  isDiff = this.addSegment(segment) || isDiff;
@@ -63,32 +61,40 @@ export abstract class AbstractMySegmentsCacheSync implements ISegmentsCacheSync
63
61
  removed.forEach(segment => {
64
62
  isDiff = this.removeSegment(segment) || isDiff;
65
63
  });
64
+ } else {
66
65
 
67
- return isDiff;
68
- }
66
+ const names = ((segmentsData as IMySegmentsResponse).k || []).map(s => s.n).sort();
67
+ const storedSegmentKeys = this.getRegisteredSegments().sort();
69
68
 
70
- const names = ((segmentsData as IMySegmentsResponse).k || []).map(s => s.n).sort();
71
- const storedSegmentKeys = this.getRegisteredSegments().sort();
69
+ // Extreme fast => everything is empty
70
+ if (!names.length && !storedSegmentKeys.length) {
71
+ isDiff = false;
72
+ } else {
72
73
 
73
- // Extreme fast => everything is empty
74
- if (!names.length && !storedSegmentKeys.length) return false;
74
+ let index = 0;
75
75
 
76
- let index = 0;
76
+ while (index < names.length && index < storedSegmentKeys.length && names[index] === storedSegmentKeys[index]) index++;
77
77
 
78
- while (index < names.length && index < storedSegmentKeys.length && names[index] === storedSegmentKeys[index]) index++;
78
+ // Quick path => no changes
79
+ if (index === names.length && index === storedSegmentKeys.length) {
80
+ isDiff = false;
81
+ } else {
79
82
 
80
- // Quick path => no changes
81
- if (index === names.length && index === storedSegmentKeys.length) return false;
83
+ // Slowest path => add and/or remove segments
84
+ for (let removeIndex = index; removeIndex < storedSegmentKeys.length; removeIndex++) {
85
+ this.removeSegment(storedSegmentKeys[removeIndex]);
86
+ }
82
87
 
83
- // Slowest path => add and/or remove segments
84
- for (let removeIndex = index; removeIndex < storedSegmentKeys.length; removeIndex++) {
85
- this.removeSegment(storedSegmentKeys[removeIndex]);
86
- }
88
+ for (let addIndex = index; addIndex < names.length; addIndex++) {
89
+ this.addSegment(names[addIndex]);
90
+ }
87
91
 
88
- for (let addIndex = index; addIndex < names.length; addIndex++) {
89
- this.addSegment(names[addIndex]);
92
+ isDiff = true;
93
+ }
94
+ }
90
95
  }
91
96
 
92
- return true;
97
+ this.setChangeNumber(segmentsData.cn);
98
+ return isDiff;
93
99
  }
94
100
  }
@@ -1,4 +1,4 @@
1
- import { ISplitsCacheSync } from './types';
1
+ import { ISplitsCacheSync, IStorageSync } from './types';
2
2
  import { IRBSegment, ISplit } from '../dtos/types';
3
3
  import { objectAssign } from '../utils/lang/objectAssign';
4
4
  import { IN_SEGMENT, IN_LARGE_SEGMENT } from '../utils/constants';
@@ -14,9 +14,10 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
14
14
  protected abstract setChangeNumber(changeNumber: number): boolean | void
15
15
 
16
16
  update(toAdd: ISplit[], toRemove: ISplit[], changeNumber: number): boolean {
17
+ let updated = toAdd.map(addedFF => this.addSplit(addedFF)).some(result => result);
18
+ updated = toRemove.map(removedFF => this.removeSplit(removedFF.name)).some(result => result) || updated;
17
19
  this.setChangeNumber(changeNumber);
18
- const updated = toAdd.map(addedFF => this.addSplit(addedFF)).some(result => result);
19
- return toRemove.map(removedFF => this.removeSplit(removedFF.name)).some(result => result) || updated;
20
+ return updated;
20
21
  }
21
22
 
22
23
  abstract getSplit(name: string): ISplit | null
@@ -88,3 +89,7 @@ export function usesSegments(ruleEntity: ISplit | IRBSegment) {
88
89
 
89
90
  return false;
90
91
  }
92
+
93
+ export function usesSegmentsSync(storage: Pick<IStorageSync, 'splits' | 'rbSegments'>) {
94
+ return storage.splits.usesSegments() || storage.rbSegments.usesSegments();
95
+ }
@@ -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; i < this.storage.length; 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,34 +5,37 @@ 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 {
29
+ let updated = toAdd.map(toAdd => this.add(toAdd)).some(result => result);
30
+ updated = toRemove.map(toRemove => this.remove(toRemove.name)).some(result => result) || updated;
27
31
  this.setChangeNumber(changeNumber);
28
- const updated = toAdd.map(toAdd => this.add(toAdd)).some(result => result);
29
- return toRemove.map(toRemove => this.remove(toRemove.name)).some(result => result) || updated;
32
+ return updated;
30
33
  }
31
34
 
32
35
  private setChangeNumber(changeNumber: number) {
33
36
  try {
34
- localStorage.setItem(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
35
- localStorage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
37
+ this.storage.setItem(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
38
+ this.storage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
36
39
  } catch (e) {
37
40
  this.log.error(LOG_PREFIX + e);
38
41
  }
@@ -40,20 +43,19 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
40
43
 
41
44
  private updateSegmentCount(diff: number) {
42
45
  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);
46
+ const count = toNumber(this.storage.getItem(segmentsCountKey)) + diff;
47
+ if (count > 0) this.storage.setItem(segmentsCountKey, count + '');
48
+ else this.storage.removeItem(segmentsCountKey);
47
49
  }
48
50
 
49
51
  private add(rbSegment: IRBSegment): boolean {
50
52
  try {
51
53
  const name = rbSegment.name;
52
54
  const rbSegmentKey = this.keys.buildRBSegmentKey(name);
53
- const rbSegmentFromLocalStorage = localStorage.getItem(rbSegmentKey);
54
- const previous = rbSegmentFromLocalStorage ? JSON.parse(rbSegmentFromLocalStorage) : null;
55
+ const rbSegmentFromStorage = this.storage.getItem(rbSegmentKey);
56
+ const previous = rbSegmentFromStorage ? JSON.parse(rbSegmentFromStorage) : null;
55
57
 
56
- localStorage.setItem(rbSegmentKey, JSON.stringify(rbSegment));
58
+ this.storage.setItem(rbSegmentKey, JSON.stringify(rbSegment));
57
59
 
58
60
  let usesSegmentsDiff = 0;
59
61
  if (previous && usesSegments(previous)) usesSegmentsDiff--;
@@ -72,7 +74,7 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
72
74
  const rbSegment = this.get(name);
73
75
  if (!rbSegment) return false;
74
76
 
75
- localStorage.removeItem(this.keys.buildRBSegmentKey(name));
77
+ this.storage.removeItem(this.keys.buildRBSegmentKey(name));
76
78
 
77
79
  if (usesSegments(rbSegment)) this.updateSegmentCount(-1);
78
80
 
@@ -84,13 +86,13 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
84
86
  }
85
87
 
86
88
  private getNames(): string[] {
87
- const len = localStorage.length;
89
+ const len = this.storage.length;
88
90
  const accum = [];
89
91
 
90
92
  let cur = 0;
91
93
 
92
94
  while (cur < len) {
93
- const key = localStorage.key(cur);
95
+ const key = this.storage.key(cur);
94
96
 
95
97
  if (key != null && this.keys.isRBSegmentKey(key)) accum.push(this.keys.extractKey(key));
96
98
 
@@ -101,7 +103,7 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
101
103
  }
102
104
 
103
105
  get(name: string): IRBSegment | null {
104
- const item = localStorage.getItem(this.keys.buildRBSegmentKey(name));
106
+ const item = this.storage.getItem(this.keys.buildRBSegmentKey(name));
105
107
  return item && JSON.parse(item);
106
108
  }
107
109
 
@@ -113,7 +115,7 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
113
115
 
114
116
  getChangeNumber(): number {
115
117
  const n = -1;
116
- let value: string | number | null = localStorage.getItem(this.keys.buildRBSegmentsTillKey());
118
+ let value: string | number | null = this.storage.getItem(this.keys.buildRBSegmentsTillKey());
117
119
 
118
120
  if (value !== null) {
119
121
  value = parseInt(value, 10);
@@ -125,7 +127,7 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
125
127
  }
126
128
 
127
129
  usesSegments(): boolean {
128
- const storedCount = localStorage.getItem(this.keys.buildSplitsWithSegmentCountKey());
130
+ const storedCount = this.storage.getItem(this.keys.buildSplitsWithSegmentCountKey());
129
131
  const splitsWithSegmentsCount = storedCount === null ? 0 : toNumber(storedCount);
130
132
 
131
133
  return isFiniteNumber(splitsWithSegmentsCount) ?