@splitsoftware/splitio-commons 2.4.2-rc.3 → 2.5.0-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 (51) hide show
  1. package/CHANGES.txt +2 -10
  2. package/cjs/storages/dataLoader.js +102 -43
  3. package/cjs/storages/inLocalStorage/MySegmentsCacheInLocal.js +16 -16
  4. package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +21 -17
  5. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +37 -33
  6. package/cjs/storages/inLocalStorage/index.js +13 -31
  7. package/cjs/storages/inLocalStorage/validateCache.js +23 -28
  8. package/cjs/storages/inMemory/InMemoryStorageCS.js +32 -14
  9. package/cjs/storages/inMemory/RBSegmentsCacheInMemory.js +4 -0
  10. package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +3 -2
  11. package/cjs/sync/polling/updaters/mySegmentsUpdater.js +0 -2
  12. package/cjs/sync/polling/updaters/splitChangesUpdater.js +0 -2
  13. package/cjs/sync/syncManagerOnline.js +24 -28
  14. package/cjs/utils/env/isLocalStorageAvailable.js +5 -28
  15. package/cjs/utils/settingsValidation/storage/storageCS.js +1 -1
  16. package/esm/storages/dataLoader.js +99 -41
  17. package/esm/storages/inLocalStorage/MySegmentsCacheInLocal.js +16 -16
  18. package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +21 -17
  19. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +37 -33
  20. package/esm/storages/inLocalStorage/index.js +14 -32
  21. package/esm/storages/inLocalStorage/validateCache.js +23 -28
  22. package/esm/storages/inMemory/InMemoryStorageCS.js +32 -14
  23. package/esm/storages/inMemory/RBSegmentsCacheInMemory.js +4 -0
  24. package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +3 -2
  25. package/esm/sync/polling/updaters/mySegmentsUpdater.js +0 -2
  26. package/esm/sync/polling/updaters/splitChangesUpdater.js +0 -2
  27. package/esm/sync/syncManagerOnline.js +24 -28
  28. package/esm/utils/env/isLocalStorageAvailable.js +3 -24
  29. package/esm/utils/settingsValidation/storage/storageCS.js +1 -1
  30. package/package.json +1 -1
  31. package/src/sdkFactory/index.ts +3 -2
  32. package/src/storages/dataLoader.ts +107 -49
  33. package/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +17 -18
  34. package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +22 -19
  35. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +37 -34
  36. package/src/storages/inLocalStorage/index.ts +16 -37
  37. package/src/storages/inLocalStorage/validateCache.ts +23 -29
  38. package/src/storages/inMemory/InMemoryStorageCS.ts +37 -14
  39. package/src/storages/inMemory/RBSegmentsCacheInMemory.ts +4 -0
  40. package/src/storages/types.ts +6 -22
  41. package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +2 -1
  42. package/src/sync/polling/updaters/mySegmentsUpdater.ts +0 -2
  43. package/src/sync/polling/updaters/splitChangesUpdater.ts +1 -3
  44. package/src/sync/syncManagerOnline.ts +22 -27
  45. package/src/types.ts +0 -35
  46. package/src/utils/env/isLocalStorageAvailable.ts +3 -24
  47. package/src/utils/settingsValidation/storage/storageCS.ts +1 -1
  48. package/types/splitio.d.ts +46 -42
  49. package/cjs/storages/inLocalStorage/storageAdapter.js +0 -54
  50. package/esm/storages/inLocalStorage/storageAdapter.js +0 -50
  51. package/src/storages/inLocalStorage/storageAdapter.ts +0 -62
@@ -6,28 +6,29 @@ 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';
10
9
 
10
+ /**
11
+ * ISplitsCacheSync implementation that stores split definitions in browser LocalStorage.
12
+ */
11
13
  export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
12
14
 
13
15
  private readonly keys: KeyBuilderCS;
14
16
  private readonly log: ILogger;
15
17
  private readonly flagSetsFilter: string[];
16
18
  private hasSync?: boolean;
17
- private readonly storage: StorageAdapter;
18
19
 
19
- constructor(settings: ISettings, keys: KeyBuilderCS, storage: StorageAdapter) {
20
+ constructor(settings: ISettings, keys: KeyBuilderCS) {
20
21
  super();
21
22
  this.keys = keys;
22
23
  this.log = settings.log;
23
24
  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(this.storage.getItem(key)) - 1;
29
- if (count > 0) this.storage.setItem(key, count + '');
30
- else this.storage.removeItem(key);
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);
31
32
  }
32
33
 
33
34
  private _decrementCounts(split: ISplit) {
@@ -47,11 +48,13 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
47
48
  private _incrementCounts(split: ISplit) {
48
49
  try {
49
50
  const ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName);
50
- this.storage.setItem(ttKey, (toNumber(this.storage.getItem(ttKey)) + 1) + '');
51
+ // @ts-expect-error
52
+ localStorage.setItem(ttKey, toNumber(localStorage.getItem(ttKey)) + 1);
51
53
 
52
54
  if (usesSegments(split)) {
53
55
  const segmentsCountKey = this.keys.buildSplitsWithSegmentCountKey();
54
- this.storage.setItem(segmentsCountKey, (toNumber(this.storage.getItem(segmentsCountKey)) + 1) + '');
56
+ // @ts-expect-error
57
+ localStorage.setItem(segmentsCountKey, toNumber(localStorage.getItem(segmentsCountKey)) + 1);
55
58
  }
56
59
  } catch (e) {
57
60
  this.log.error(LOG_PREFIX + e);
@@ -65,15 +68,15 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
65
68
  */
66
69
  clear() {
67
70
  // collect item keys
68
- const len = this.storage.length;
71
+ const len = localStorage.length;
69
72
  const accum = [];
70
73
  for (let cur = 0; cur < len; cur++) {
71
- const key = this.storage.key(cur);
74
+ const key = localStorage.key(cur);
72
75
  if (key != null && this.keys.isSplitsCacheKey(key)) accum.push(key);
73
76
  }
74
77
  // remove items
75
78
  accum.forEach(key => {
76
- this.storage.removeItem(key);
79
+ localStorage.removeItem(key);
77
80
  });
78
81
 
79
82
  this.hasSync = false;
@@ -83,15 +86,15 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
83
86
  try {
84
87
  const name = split.name;
85
88
  const splitKey = this.keys.buildSplitKey(name);
86
- const splitFromStorage = this.storage.getItem(splitKey);
87
- const previousSplit = splitFromStorage ? JSON.parse(splitFromStorage) : null;
89
+ const splitFromLocalStorage = localStorage.getItem(splitKey);
90
+ const previousSplit = splitFromLocalStorage ? JSON.parse(splitFromLocalStorage) : null;
88
91
 
89
92
  if (previousSplit) {
90
93
  this._decrementCounts(previousSplit);
91
94
  this.removeFromFlagSets(previousSplit.name, previousSplit.sets);
92
95
  }
93
96
 
94
- this.storage.setItem(splitKey, JSON.stringify(split));
97
+ localStorage.setItem(splitKey, JSON.stringify(split));
95
98
 
96
99
  this._incrementCounts(split);
97
100
  this.addToFlagSets(split);
@@ -108,7 +111,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
108
111
  const split = this.getSplit(name);
109
112
  if (!split) return false;
110
113
 
111
- this.storage.removeItem(this.keys.buildSplitKey(name));
114
+ localStorage.removeItem(this.keys.buildSplitKey(name));
112
115
 
113
116
  this._decrementCounts(split);
114
117
  this.removeFromFlagSets(split.name, split.sets);
@@ -121,15 +124,15 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
121
124
  }
122
125
 
123
126
  getSplit(name: string): ISplit | null {
124
- const item = this.storage.getItem(this.keys.buildSplitKey(name));
127
+ const item = localStorage.getItem(this.keys.buildSplitKey(name));
125
128
  return item && JSON.parse(item);
126
129
  }
127
130
 
128
131
  setChangeNumber(changeNumber: number): boolean {
129
132
  try {
130
- this.storage.setItem(this.keys.buildSplitsTillKey(), changeNumber + '');
133
+ localStorage.setItem(this.keys.buildSplitsTillKey(), changeNumber + '');
131
134
  // update "last updated" timestamp with current time
132
- this.storage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
135
+ localStorage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
133
136
  this.hasSync = true;
134
137
  return true;
135
138
  } catch (e) {
@@ -140,7 +143,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
140
143
 
141
144
  getChangeNumber(): number {
142
145
  const n = -1;
143
- let value: string | number | null = this.storage.getItem(this.keys.buildSplitsTillKey());
146
+ let value: string | number | null = localStorage.getItem(this.keys.buildSplitsTillKey());
144
147
 
145
148
  if (value !== null) {
146
149
  value = parseInt(value, 10);
@@ -152,13 +155,13 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
152
155
  }
153
156
 
154
157
  getSplitNames(): string[] {
155
- const len = this.storage.length;
158
+ const len = localStorage.length;
156
159
  const accum = [];
157
160
 
158
161
  let cur = 0;
159
162
 
160
163
  while (cur < len) {
161
- const key = this.storage.key(cur);
164
+ const key = localStorage.key(cur);
162
165
 
163
166
  if (key != null && this.keys.isSplitKey(key)) accum.push(this.keys.extractKey(key));
164
167
 
@@ -169,7 +172,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
169
172
  }
170
173
 
171
174
  trafficTypeExists(trafficType: string): boolean {
172
- const ttCount = toNumber(this.storage.getItem(this.keys.buildTrafficTypeKey(trafficType)));
175
+ const ttCount = toNumber(localStorage.getItem(this.keys.buildTrafficTypeKey(trafficType)));
173
176
  return isFiniteNumber(ttCount) && ttCount > 0;
174
177
  }
175
178
 
@@ -177,7 +180,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
177
180
  // If cache hasn't been synchronized with the cloud, assume we need them.
178
181
  if (!this.hasSync) return true;
179
182
 
180
- const storedCount = this.storage.getItem(this.keys.buildSplitsWithSegmentCountKey());
183
+ const storedCount = localStorage.getItem(this.keys.buildSplitsWithSegmentCountKey());
181
184
  const splitsWithSegmentsCount = storedCount === null ? 0 : toNumber(storedCount);
182
185
 
183
186
  return isFiniteNumber(splitsWithSegmentsCount) ?
@@ -188,9 +191,9 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
188
191
  getNamesByFlagSets(flagSets: string[]): Set<string>[] {
189
192
  return flagSets.map(flagSet => {
190
193
  const flagSetKey = this.keys.buildFlagSetKey(flagSet);
191
- const flagSetFromStorage = this.storage.getItem(flagSetKey);
194
+ const flagSetFromLocalStorage = localStorage.getItem(flagSetKey);
192
195
 
193
- return new Set(flagSetFromStorage ? JSON.parse(flagSetFromStorage) : []);
196
+ return new Set(flagSetFromLocalStorage ? JSON.parse(flagSetFromLocalStorage) : []);
194
197
  });
195
198
  }
196
199
 
@@ -203,12 +206,12 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
203
206
 
204
207
  const flagSetKey = this.keys.buildFlagSetKey(featureFlagSet);
205
208
 
206
- const flagSetFromStorage = this.storage.getItem(flagSetKey);
209
+ const flagSetFromLocalStorage = localStorage.getItem(flagSetKey);
207
210
 
208
- const flagSetCache = new Set(flagSetFromStorage ? JSON.parse(flagSetFromStorage) : []);
211
+ const flagSetCache = new Set(flagSetFromLocalStorage ? JSON.parse(flagSetFromLocalStorage) : []);
209
212
  flagSetCache.add(featureFlag.name);
210
213
 
211
- this.storage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache)));
214
+ localStorage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache)));
212
215
  });
213
216
  }
214
217
 
@@ -223,19 +226,19 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
223
226
  private removeNames(flagSetName: string, featureFlagName: string) {
224
227
  const flagSetKey = this.keys.buildFlagSetKey(flagSetName);
225
228
 
226
- const flagSetFromStorage = this.storage.getItem(flagSetKey);
229
+ const flagSetFromLocalStorage = localStorage.getItem(flagSetKey);
227
230
 
228
- if (!flagSetFromStorage) return;
231
+ if (!flagSetFromLocalStorage) return;
229
232
 
230
- const flagSetCache = new Set(JSON.parse(flagSetFromStorage));
233
+ const flagSetCache = new Set(JSON.parse(flagSetFromLocalStorage));
231
234
  flagSetCache.delete(featureFlagName);
232
235
 
233
236
  if (flagSetCache.size === 0) {
234
- this.storage.removeItem(flagSetKey);
237
+ localStorage.removeItem(flagSetKey);
235
238
  return;
236
239
  }
237
240
 
238
- this.storage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache)));
241
+ localStorage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache)));
239
242
  }
240
243
 
241
244
  }
@@ -1,10 +1,10 @@
1
1
  import { ImpressionsCacheInMemory } from '../inMemory/ImpressionsCacheInMemory';
2
2
  import { ImpressionCountsCacheInMemory } from '../inMemory/ImpressionCountsCacheInMemory';
3
3
  import { EventsCacheInMemory } from '../inMemory/EventsCacheInMemory';
4
- import { IStorageFactoryParams, IStorageSync, IStorageSyncFactory, StorageAdapter } from '../types';
4
+ import { IStorageFactoryParams, IStorageSync, IStorageSyncFactory } from '../types';
5
5
  import { validatePrefix } from '../KeyBuilder';
6
6
  import { KeyBuilderCS, myLargeSegmentsKeyBuilder } from '../KeyBuilderCS';
7
- import { isLocalStorageAvailable, isValidStorageWrapper, isWebStorage } from '../../utils/env/isLocalStorageAvailable';
7
+ import { isLocalStorageAvailable } from '../../utils/env/isLocalStorageAvailable';
8
8
  import { SplitsCacheInLocal } from './SplitsCacheInLocal';
9
9
  import { RBSegmentsCacheInLocal } from './RBSegmentsCacheInLocal';
10
10
  import { MySegmentsCacheInLocal } from './MySegmentsCacheInLocal';
@@ -15,24 +15,7 @@ import { shouldRecordTelemetry, TelemetryCacheInMemory } from '../inMemory/Telem
15
15
  import { UniqueKeysCacheInMemoryCS } from '../inMemory/UniqueKeysCacheInMemoryCS';
16
16
  import { getMatching } from '../../utils/key';
17
17
  import { validateCache } from './validateCache';
18
- import { ILogger } from '../../logger/types';
19
18
  import SplitIO from '../../../types/splitio';
20
- import { storageAdapter } from './storageAdapter';
21
-
22
- function validateStorage(log: ILogger, prefix: string, wrapper?: SplitIO.SyncStorageWrapper | SplitIO.AsyncStorageWrapper): StorageAdapter | undefined {
23
- if (wrapper) {
24
- if (isValidStorageWrapper(wrapper)) {
25
- return isWebStorage(wrapper) ?
26
- wrapper as StorageAdapter: // localStorage and sessionStorage don't need adapter
27
- storageAdapter(log, prefix, wrapper);
28
- }
29
- log.warn(LOG_PREFIX + 'Invalid storage provided. Falling back to LocalStorage API');
30
- }
31
-
32
- if (isLocalStorageAvailable()) return localStorage;
33
-
34
- log.warn(LOG_PREFIX + 'LocalStorage API is unavailable. Falling back to default MEMORY storage');
35
- }
36
19
 
37
20
  /**
38
21
  * InLocal storage factory for standalone client-side SplitFactory
@@ -42,19 +25,21 @@ export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): ISt
42
25
  const prefix = validatePrefix(options.prefix);
43
26
 
44
27
  function InLocalStorageCSFactory(params: IStorageFactoryParams): IStorageSync {
45
- const { settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize } } } = params;
46
28
 
47
- const storage = validateStorage(log, prefix, options.wrapper);
48
- if (!storage) return InMemoryStorageCSFactory(params);
29
+ // Fallback to InMemoryStorage if LocalStorage API is not available
30
+ if (!isLocalStorageAvailable()) {
31
+ params.settings.log.warn(LOG_PREFIX + 'LocalStorage API is unavailable. Falling back to default MEMORY storage');
32
+ return InMemoryStorageCSFactory(params);
33
+ }
49
34
 
35
+ const { settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize } } } = params;
50
36
  const matchingKey = getMatching(settings.core.key);
51
37
  const keys = new KeyBuilderCS(prefix, matchingKey);
52
38
 
53
- const splits = new SplitsCacheInLocal(settings, keys, storage);
54
- const rbSegments = new RBSegmentsCacheInLocal(settings, keys, storage);
55
- const segments = new MySegmentsCacheInLocal(log, keys, storage);
56
- const largeSegments = new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey), storage);
57
- let validateCachePromise: Promise<boolean> | undefined;
39
+ const splits = new SplitsCacheInLocal(settings, keys);
40
+ const rbSegments = new RBSegmentsCacheInLocal(settings, keys);
41
+ const segments = new MySegmentsCacheInLocal(log, keys);
42
+ const largeSegments = new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey));
58
43
 
59
44
  return {
60
45
  splits,
@@ -68,16 +53,10 @@ export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): ISt
68
53
  uniqueKeys: new UniqueKeysCacheInMemoryCS(),
69
54
 
70
55
  validateCache() {
71
- return validateCachePromise || (validateCachePromise = validateCache(options, storage, settings, keys, splits, rbSegments, segments, largeSegments));
56
+ return validateCache(options, settings, keys, splits, rbSegments, segments, largeSegments);
72
57
  },
73
58
 
74
- save() {
75
- return storage.save && storage.save();
76
- },
77
-
78
- destroy() {
79
- return storage.whenSaved && storage.whenSaved();
80
- },
59
+ destroy() { },
81
60
 
82
61
  // When using shared instantiation with MEMORY we reuse everything but segments (they are customer per key).
83
62
  shared(matchingKey: string) {
@@ -85,8 +64,8 @@ export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): ISt
85
64
  return {
86
65
  splits: this.splits,
87
66
  rbSegments: this.rbSegments,
88
- segments: new MySegmentsCacheInLocal(log, new KeyBuilderCS(prefix, matchingKey), storage),
89
- largeSegments: new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey), storage),
67
+ segments: new MySegmentsCacheInLocal(log, new KeyBuilderCS(prefix, matchingKey)),
68
+ largeSegments: new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey)),
90
69
  impressions: this.impressions,
91
70
  impressionCounts: this.impressionCounts,
92
71
  events: this.events,
@@ -7,7 +7,6 @@ import type { RBSegmentsCacheInLocal } from './RBSegmentsCacheInLocal';
7
7
  import type { MySegmentsCacheInLocal } from './MySegmentsCacheInLocal';
8
8
  import { KeyBuilderCS } from '../KeyBuilderCS';
9
9
  import SplitIO from '../../../types/splitio';
10
- import { StorageAdapter } from '../types';
11
10
 
12
11
  const DEFAULT_CACHE_EXPIRATION_IN_DAYS = 10;
13
12
  const MILLIS_IN_A_DAY = 86400000;
@@ -17,11 +16,11 @@ const MILLIS_IN_A_DAY = 86400000;
17
16
  *
18
17
  * @returns `true` if cache should be cleared, `false` otherwise
19
18
  */
20
- function validateExpiration(options: SplitIO.InLocalStorageOptions, storage: StorageAdapter, settings: ISettings, keys: KeyBuilderCS, currentTimestamp: number, isThereCache: boolean) {
19
+ function validateExpiration(options: SplitIO.InLocalStorageOptions, settings: ISettings, keys: KeyBuilderCS, currentTimestamp: number, isThereCache: boolean) {
21
20
  const { log } = settings;
22
21
 
23
22
  // Check expiration
24
- const lastUpdatedTimestamp = parseInt(storage.getItem(keys.buildLastUpdatedKey()) as string, 10);
23
+ const lastUpdatedTimestamp = parseInt(localStorage.getItem(keys.buildLastUpdatedKey()) as string, 10);
25
24
  if (!isNaNNumber(lastUpdatedTimestamp)) {
26
25
  const cacheExpirationInDays = isFiniteNumber(options.expirationDays) && options.expirationDays >= 1 ? options.expirationDays : DEFAULT_CACHE_EXPIRATION_IN_DAYS;
27
26
  const expirationTimestamp = currentTimestamp - MILLIS_IN_A_DAY * cacheExpirationInDays;
@@ -33,12 +32,12 @@ function validateExpiration(options: SplitIO.InLocalStorageOptions, storage: Sto
33
32
 
34
33
  // Check hash
35
34
  const storageHashKey = keys.buildHashKey();
36
- const storageHash = storage.getItem(storageHashKey);
35
+ const storageHash = localStorage.getItem(storageHashKey);
37
36
  const currentStorageHash = getStorageHash(settings);
38
37
 
39
38
  if (storageHash !== currentStorageHash) {
40
39
  try {
41
- storage.setItem(storageHashKey, currentStorageHash);
40
+ localStorage.setItem(storageHashKey, currentStorageHash);
42
41
  } catch (e) {
43
42
  log.error(LOG_PREFIX + e);
44
43
  }
@@ -51,7 +50,7 @@ function validateExpiration(options: SplitIO.InLocalStorageOptions, storage: Sto
51
50
 
52
51
  // Clear on init
53
52
  if (options.clearOnInit) {
54
- const lastClearTimestamp = parseInt(storage.getItem(keys.buildLastClear()) as string, 10);
53
+ const lastClearTimestamp = parseInt(localStorage.getItem(keys.buildLastClear()) as string, 10);
55
54
 
56
55
  if (isNaNNumber(lastClearTimestamp) || lastClearTimestamp < currentTimestamp - MILLIS_IN_A_DAY) {
57
56
  log.info(LOG_PREFIX + 'clearOnInit was set and cache was not cleared in the last 24 hours. Cleaning up cache');
@@ -68,32 +67,27 @@ function validateExpiration(options: SplitIO.InLocalStorageOptions, storage: Sto
68
67
  *
69
68
  * @returns `true` if cache is ready to be used, `false` otherwise (cache was cleared or there is no cache)
70
69
  */
71
- export function validateCache(options: SplitIO.InLocalStorageOptions, storage: StorageAdapter, settings: ISettings, keys: KeyBuilderCS, splits: SplitsCacheInLocal, rbSegments: RBSegmentsCacheInLocal, segments: MySegmentsCacheInLocal, largeSegments: MySegmentsCacheInLocal): Promise<boolean> {
70
+ export function validateCache(options: SplitIO.InLocalStorageOptions, settings: ISettings, keys: KeyBuilderCS, splits: SplitsCacheInLocal, rbSegments: RBSegmentsCacheInLocal, segments: MySegmentsCacheInLocal, largeSegments: MySegmentsCacheInLocal): boolean {
72
71
 
73
- return Promise.resolve(storage.load && storage.load()).then(() => {
74
- const currentTimestamp = Date.now();
75
- const isThereCache = splits.getChangeNumber() > -1;
72
+ const currentTimestamp = Date.now();
73
+ const isThereCache = splits.getChangeNumber() > -1;
76
74
 
77
- if (validateExpiration(options, storage, settings, keys, currentTimestamp, isThereCache)) {
78
- splits.clear();
79
- rbSegments.clear();
80
- segments.clear();
81
- largeSegments.clear();
75
+ if (validateExpiration(options, settings, keys, currentTimestamp, isThereCache)) {
76
+ splits.clear();
77
+ rbSegments.clear();
78
+ segments.clear();
79
+ largeSegments.clear();
82
80
 
83
- // Update last clear timestamp
84
- try {
85
- storage.setItem(keys.buildLastClear(), currentTimestamp + '');
86
- } catch (e) {
87
- settings.log.error(LOG_PREFIX + e);
88
- }
89
-
90
- // Persist clear
91
- if (storage.save) storage.save();
92
-
93
- return false;
81
+ // Update last clear timestamp
82
+ try {
83
+ localStorage.setItem(keys.buildLastClear(), currentTimestamp + '');
84
+ } catch (e) {
85
+ settings.log.error(LOG_PREFIX + e);
94
86
  }
95
87
 
96
- // Check if ready from cache
97
- return isThereCache;
98
- });
88
+ return false;
89
+ }
90
+
91
+ // Check if ready from cache
92
+ return isThereCache;
99
93
  }
@@ -7,6 +7,8 @@ import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
7
7
  import { LOCALHOST_MODE, STORAGE_MEMORY } from '../../utils/constants';
8
8
  import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
9
9
  import { UniqueKeysCacheInMemoryCS } from './UniqueKeysCacheInMemoryCS';
10
+ import { getMatching } from '../../utils/key';
11
+ import { setCache } from '../dataLoader';
10
12
  import { RBSegmentsCacheInMemory } from './RBSegmentsCacheInMemory';
11
13
 
12
14
  /**
@@ -15,7 +17,9 @@ import { RBSegmentsCacheInMemory } from './RBSegmentsCacheInMemory';
15
17
  * @param params - parameters required by EventsCacheSync
16
18
  */
17
19
  export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorageSync {
18
- const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize }, sync: { __splitFiltersValidation } } } = params;
20
+ const { settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize }, sync: { __splitFiltersValidation }, preloadedData }, onReadyFromCacheCb } = params;
21
+
22
+ const storages: Record<string, IStorageSync> = {};
19
23
 
20
24
  const splits = new SplitsCacheInMemory(__splitFiltersValidation);
21
25
  const rbSegments = new RBSegmentsCacheInMemory();
@@ -36,20 +40,31 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
36
40
  destroy() { },
37
41
 
38
42
  // When using shared instantiation with MEMORY we reuse everything but segments (they are unique per key)
39
- shared() {
40
- return {
41
- splits: this.splits,
42
- rbSegments: this.rbSegments,
43
- segments: new MySegmentsCacheInMemory(),
44
- largeSegments: new MySegmentsCacheInMemory(),
45
- impressions: this.impressions,
46
- impressionCounts: this.impressionCounts,
47
- events: this.events,
48
- telemetry: this.telemetry,
49
- uniqueKeys: this.uniqueKeys,
43
+ shared(matchingKey: string) {
44
+ if (!storages[matchingKey]) {
45
+ const segments = new MySegmentsCacheInMemory();
46
+ const largeSegments = new MySegmentsCacheInMemory();
47
+
48
+ if (preloadedData) {
49
+ setCache(log, preloadedData, { segments, largeSegments }, matchingKey);
50
+ }
51
+
52
+ storages[matchingKey] = {
53
+ splits: this.splits,
54
+ rbSegments: this.rbSegments,
55
+ segments,
56
+ largeSegments,
57
+ impressions: this.impressions,
58
+ impressionCounts: this.impressionCounts,
59
+ events: this.events,
60
+ telemetry: this.telemetry,
61
+ uniqueKeys: this.uniqueKeys,
62
+
63
+ destroy() { }
64
+ };
65
+ }
50
66
 
51
- destroy() { }
52
- };
67
+ return storages[matchingKey];
53
68
  },
54
69
  };
55
70
 
@@ -63,6 +78,14 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
63
78
  storage.uniqueKeys.track = noopTrack;
64
79
  }
65
80
 
81
+ const matchingKey = getMatching(params.settings.core.key);
82
+ storages[matchingKey] = storage;
83
+
84
+ if (preloadedData) {
85
+ setCache(log, preloadedData, storage, matchingKey);
86
+ if (splits.getChangeNumber() > -1) onReadyFromCacheCb();
87
+ }
88
+
66
89
  return storage;
67
90
  }
68
91
 
@@ -51,6 +51,10 @@ export class RBSegmentsCacheInMemory implements IRBSegmentsCacheSync {
51
51
  return this.cache[name] || null;
52
52
  }
53
53
 
54
+ getAll(): IRBSegment[] {
55
+ return this.getNames().map(key => this.get(key)!);
56
+ }
57
+
54
58
  contains(names: Set<string>): boolean {
55
59
  const namesArray = setToArray(names);
56
60
  const namesInStorage = this.getNames();
@@ -4,23 +4,6 @@ import { MySegmentsData } from '../sync/polling/types';
4
4
  import { EventDataType, HttpErrors, HttpLatencies, ImpressionDataType, LastSync, Method, MethodExceptions, MethodLatencies, MultiMethodExceptions, MultiMethodLatencies, MultiConfigs, OperationType, StoredEventWithMetadata, StoredImpressionWithMetadata, StreamingEvent, UniqueKeysPayloadCs, UniqueKeysPayloadSs, TelemetryUsageStatsPayload, UpdatesFromSSEEnum } from '../sync/submitters/types';
5
5
  import { ISettings } from '../types';
6
6
 
7
- /**
8
- * Internal interface based on a subset of the Web Storage API interface
9
- * (https://developer.mozilla.org/en-US/docs/Web/API/Storage) used by the SDK
10
- */
11
- export interface StorageAdapter {
12
- // Methods to support async storages
13
- load?: () => Promise<void>;
14
- save?: () => Promise<void>;
15
- whenSaved?: () => Promise<void>;
16
- // Methods based on https://developer.mozilla.org/en-US/docs/Web/API/Storage
17
- readonly length: number;
18
- key(index: number): string | null;
19
- getItem(key: string): string | null;
20
- removeItem(key: string): void;
21
- setItem(key: string, value: string): void;
22
- }
23
-
24
7
  /**
25
8
  * Interface of a pluggable storage wrapper.
26
9
  */
@@ -252,6 +235,7 @@ export interface IRBSegmentsCacheSync extends IRBSegmentsCacheBase {
252
235
  update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): boolean,
253
236
  get(name: string): IRBSegment | null,
254
237
  getChangeNumber(): number,
238
+ getAll(): IRBSegment[],
255
239
  clear(): void,
256
240
  contains(names: Set<string>): boolean,
257
241
  // Used only for smart pausing in client-side standalone. Returns true if the storage contains a RBSegment using segments or large segments matchers
@@ -482,8 +466,7 @@ export interface IStorageBase<
482
466
  telemetry?: TTelemetryCache,
483
467
  uniqueKeys: TUniqueKeysCache,
484
468
  destroy(): void | Promise<void>,
485
- shared?: (matchingKey: string, onReadyCb: (error?: any) => void) => this
486
- save?: () => void | Promise<void>,
469
+ shared?: (matchingKey: string, onReadyCb?: (error?: any) => void) => this
487
470
  }
488
471
 
489
472
  export interface IStorageSync extends IStorageBase<
@@ -497,7 +480,7 @@ export interface IStorageSync extends IStorageBase<
497
480
  IUniqueKeysCacheSync
498
481
  > {
499
482
  // Defined in client-side
500
- validateCache?: () => Promise<boolean>,
483
+ validateCache?: () => boolean, // @TODO support async
501
484
  largeSegments?: ISegmentsCacheSync,
502
485
  }
503
486
 
@@ -514,8 +497,6 @@ export interface IStorageAsync extends IStorageBase<
514
497
 
515
498
  /** StorageFactory */
516
499
 
517
- export type DataLoader = (storage: IStorageSync, matchingKey: string) => void
518
-
519
500
  export interface IStorageFactoryParams {
520
501
  settings: ISettings,
521
502
  /**
@@ -523,6 +504,9 @@ export interface IStorageFactoryParams {
523
504
  * It is meant for emitting SDK_READY event in consumer mode, and waiting before using the storage in the synchronizer.
524
505
  */
525
506
  onReadyCb: (error?: any) => void,
507
+ /**
508
+ * For emitting SDK_READY_FROM_CACHE event in consumer mode with Redis and standalone mode with preloaded data
509
+ */
526
510
  onReadyFromCacheCb: () => void,
527
511
  }
528
512
 
@@ -59,7 +59,8 @@ export function fromObjectUpdaterFactory(
59
59
 
60
60
  if (startingUp) {
61
61
  startingUp = false;
62
- Promise.resolve(storage.validateCache ? storage.validateCache() : false).then((isCacheLoaded) => {
62
+ const isCacheLoaded = storage.validateCache ? storage.validateCache() : false;
63
+ Promise.resolve().then(() => {
63
64
  // Emits SDK_READY_FROM_CACHE
64
65
  if (isCacheLoaded) readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
65
66
  // Emits SDK_READY
@@ -51,8 +51,6 @@ export function mySegmentsUpdaterFactory(
51
51
  shouldNotifyUpdate = largeSegments!.resetSegments((segmentsData as IMembershipsResponse).ls || {}) || shouldNotifyUpdate;
52
52
  }
53
53
 
54
- if (storage.save) storage.save();
55
-
56
54
  // Notify update if required
57
55
  if (usesSegmentsSync(storage) && (shouldNotifyUpdate || readyOnAlreadyExistentState)) {
58
56
  readyOnAlreadyExistentState = false;
@@ -117,7 +117,7 @@ export function computeMutation<T extends ISplit | IRBSegment>(rules: Array<T>,
117
117
  export function splitChangesUpdaterFactory(
118
118
  log: ILogger,
119
119
  splitChangesFetcher: ISplitChangesFetcher,
120
- storage: Pick<IStorageBase, 'splits' | 'rbSegments' | 'segments' | 'save'>,
120
+ storage: Pick<IStorageBase, 'splits' | 'rbSegments' | 'segments'>,
121
121
  splitFiltersValidation: ISplitFiltersValidation,
122
122
  splitsEventEmitter?: ISplitsEventEmitter,
123
123
  requestTimeoutBeforeReady: number = 0,
@@ -185,8 +185,6 @@ export function splitChangesUpdaterFactory(
185
185
  // @TODO if at least 1 segment fetch fails due to 404 and other segments are updated in the storage, SDK_UPDATE is not emitted
186
186
  segments.registerSegments(setToArray(usedSegments))
187
187
  ]).then(([ffChanged, rbsChanged]) => {
188
- if (storage.save) storage.save();
189
-
190
188
  if (splitsEventEmitter) {
191
189
  // To emit SDK_SPLITS_ARRIVED for server-side SDK, we must check that all registered segments have been fetched
192
190
  return Promise.resolve(!splitsEventEmitter.splitsArrived || ((ffChanged || rbsChanged) && (isClientSide || checkAllSegmentsExist(segments))))