@splitsoftware/splitio-commons 2.0.3-rc.0 → 2.1.0-rc.1

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 (120) hide show
  1. package/CHANGES.txt +5 -2
  2. package/README.md +2 -2
  3. package/cjs/evaluator/index.js +0 -2
  4. package/cjs/evaluator/matchers/large_segment.js +0 -6
  5. package/cjs/evaluator/matchers/segment.js +0 -6
  6. package/cjs/listeners/browser.js +6 -4
  7. package/cjs/listeners/node.js +2 -2
  8. package/cjs/readiness/readinessManager.js +6 -0
  9. package/cjs/sdkClient/client.js +13 -13
  10. package/cjs/sdkClient/sdkClient.js +1 -1
  11. package/cjs/sdkFactory/index.js +14 -9
  12. package/cjs/sdkManager/index.js +1 -2
  13. package/cjs/storages/AbstractSplitsCacheAsync.js +0 -7
  14. package/cjs/storages/AbstractSplitsCacheSync.js +0 -7
  15. package/cjs/storages/KeyBuilderCS.js +3 -0
  16. package/cjs/storages/dataLoader.js +3 -2
  17. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +1 -57
  18. package/cjs/storages/inLocalStorage/index.js +8 -7
  19. package/cjs/storages/inLocalStorage/validateCache.js +79 -0
  20. package/cjs/storages/inMemory/InMemoryStorage.js +3 -3
  21. package/cjs/storages/inMemory/InMemoryStorageCS.js +3 -4
  22. package/cjs/storages/inRedis/SplitsCacheInRedis.js +1 -1
  23. package/cjs/storages/inRedis/index.js +13 -9
  24. package/cjs/storages/pluggable/index.js +21 -16
  25. package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +3 -2
  26. package/cjs/sync/polling/updaters/splitChangesUpdater.js +1 -10
  27. package/cjs/sync/streaming/pushManager.js +8 -6
  28. package/cjs/sync/submitters/impressionCountsSubmitter.js +4 -2
  29. package/cjs/sync/submitters/submitterManager.js +6 -3
  30. package/cjs/sync/syncManagerOnline.js +10 -4
  31. package/cjs/trackers/eventTracker.js +1 -1
  32. package/cjs/trackers/impressionsTracker.js +19 -18
  33. package/cjs/trackers/strategy/strategyDebug.js +11 -4
  34. package/cjs/trackers/strategy/strategyNone.js +16 -11
  35. package/cjs/trackers/strategy/strategyOptimized.js +21 -11
  36. package/cjs/utils/settingsValidation/index.js +1 -1
  37. package/cjs/utils/settingsValidation/storage/storageCS.js +1 -1
  38. package/esm/evaluator/index.js +0 -2
  39. package/esm/evaluator/matchers/large_segment.js +0 -6
  40. package/esm/evaluator/matchers/segment.js +0 -6
  41. package/esm/listeners/browser.js +3 -1
  42. package/esm/listeners/node.js +2 -2
  43. package/esm/readiness/readinessManager.js +6 -0
  44. package/esm/sdkClient/client.js +13 -13
  45. package/esm/sdkClient/sdkClient.js +1 -1
  46. package/esm/sdkFactory/index.js +15 -10
  47. package/esm/sdkManager/index.js +1 -2
  48. package/esm/storages/AbstractSplitsCacheAsync.js +0 -7
  49. package/esm/storages/AbstractSplitsCacheSync.js +0 -7
  50. package/esm/storages/KeyBuilderCS.js +3 -0
  51. package/esm/storages/dataLoader.js +2 -1
  52. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +1 -57
  53. package/esm/storages/inLocalStorage/index.js +9 -8
  54. package/esm/storages/inLocalStorage/validateCache.js +75 -0
  55. package/esm/storages/inMemory/InMemoryStorage.js +4 -4
  56. package/esm/storages/inMemory/InMemoryStorageCS.js +4 -5
  57. package/esm/storages/inRedis/SplitsCacheInRedis.js +1 -1
  58. package/esm/storages/inRedis/index.js +14 -10
  59. package/esm/storages/pluggable/index.js +22 -17
  60. package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +3 -2
  61. package/esm/sync/polling/updaters/splitChangesUpdater.js +2 -11
  62. package/esm/sync/streaming/pushManager.js +8 -6
  63. package/esm/sync/submitters/impressionCountsSubmitter.js +4 -2
  64. package/esm/sync/submitters/submitterManager.js +6 -3
  65. package/esm/sync/syncManagerOnline.js +10 -4
  66. package/esm/trackers/eventTracker.js +1 -1
  67. package/esm/trackers/impressionsTracker.js +19 -18
  68. package/esm/trackers/strategy/strategyDebug.js +11 -4
  69. package/esm/trackers/strategy/strategyNone.js +16 -11
  70. package/esm/trackers/strategy/strategyOptimized.js +21 -11
  71. package/esm/utils/settingsValidation/index.js +1 -1
  72. package/esm/utils/settingsValidation/storage/storageCS.js +1 -1
  73. package/package.json +1 -1
  74. package/src/dtos/types.ts +1 -2
  75. package/src/evaluator/index.ts +0 -2
  76. package/src/evaluator/matchers/large_segment.ts +0 -7
  77. package/src/evaluator/matchers/segment.ts +0 -7
  78. package/src/evaluator/types.ts +1 -1
  79. package/src/listeners/browser.ts +3 -1
  80. package/src/listeners/node.ts +2 -2
  81. package/src/readiness/readinessManager.ts +5 -0
  82. package/src/sdkClient/client.ts +11 -11
  83. package/src/sdkClient/sdkClient.ts +1 -1
  84. package/src/sdkFactory/index.ts +16 -11
  85. package/src/sdkFactory/types.ts +1 -1
  86. package/src/sdkManager/index.ts +1 -2
  87. package/src/storages/AbstractSplitsCacheAsync.ts +0 -8
  88. package/src/storages/AbstractSplitsCacheSync.ts +0 -8
  89. package/src/storages/KeyBuilderCS.ts +4 -0
  90. package/src/storages/dataLoader.ts +3 -1
  91. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +1 -66
  92. package/src/storages/inLocalStorage/index.ts +12 -13
  93. package/src/storages/inLocalStorage/validateCache.ts +91 -0
  94. package/src/storages/inMemory/InMemoryStorage.ts +4 -4
  95. package/src/storages/inMemory/InMemoryStorageCS.ts +4 -5
  96. package/src/storages/inRedis/SplitsCacheInRedis.ts +1 -1
  97. package/src/storages/inRedis/index.ts +10 -10
  98. package/src/storages/pluggable/index.ts +22 -17
  99. package/src/storages/types.ts +3 -6
  100. package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +6 -5
  101. package/src/sync/polling/updaters/splitChangesUpdater.ts +2 -11
  102. package/src/sync/streaming/pushManager.ts +8 -6
  103. package/src/sync/submitters/impressionCountsSubmitter.ts +4 -2
  104. package/src/sync/submitters/submitterManager.ts +4 -3
  105. package/src/sync/submitters/uniqueKeysSubmitter.ts +3 -2
  106. package/src/sync/syncManagerOnline.ts +11 -5
  107. package/src/trackers/eventTracker.ts +1 -1
  108. package/src/trackers/impressionsTracker.ts +19 -18
  109. package/src/trackers/strategy/strategyDebug.ts +11 -4
  110. package/src/trackers/strategy/strategyNone.ts +17 -11
  111. package/src/trackers/strategy/strategyOptimized.ts +20 -10
  112. package/src/trackers/types.ts +8 -2
  113. package/src/utils/lang/index.ts +1 -1
  114. package/src/utils/settingsValidation/index.ts +1 -1
  115. package/src/utils/settingsValidation/storage/storageCS.ts +1 -1
  116. package/types/index.d.ts +1 -1
  117. package/types/splitio.d.ts +26 -6
  118. package/cjs/utils/constants/browser.js +0 -5
  119. package/esm/utils/constants/browser.js +0 -2
  120. package/src/utils/constants/browser.ts +0 -2
@@ -13,7 +13,7 @@ import { strategyDebugFactory } from '../trackers/strategy/strategyDebug';
13
13
  import { strategyOptimizedFactory } from '../trackers/strategy/strategyOptimized';
14
14
  import { strategyNoneFactory } from '../trackers/strategy/strategyNone';
15
15
  import { uniqueKeysTrackerFactory } from '../trackers/uniqueKeysTracker';
16
- import { DEBUG, OPTIMIZED } from '../utils/constants';
16
+ import { NONE, OPTIMIZED } from '../utils/constants';
17
17
 
18
18
  /**
19
19
  * Modular SDK factory
@@ -59,16 +59,21 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
59
59
  const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage, telemetryTracker });
60
60
 
61
61
  const observer = impressionsObserverFactory();
62
- const uniqueKeysTracker = uniqueKeysTrackerFactory(log, storage.uniqueKeys, filterAdapterFactory && filterAdapterFactory());
63
-
64
- const noneStrategy = strategyNoneFactory(storage.impressionCounts, uniqueKeysTracker);
65
- const strategy = impressionsMode === OPTIMIZED ?
66
- strategyOptimizedFactory(observer, storage.impressionCounts) :
67
- impressionsMode === DEBUG ?
68
- strategyDebugFactory(observer) :
69
- noneStrategy;
62
+ const uniqueKeysTracker = impressionsMode === NONE ? uniqueKeysTrackerFactory(log, storage.uniqueKeys!, filterAdapterFactory && filterAdapterFactory()) : undefined;
63
+
64
+ let strategy;
65
+ switch (impressionsMode) {
66
+ case OPTIMIZED:
67
+ strategy = strategyOptimizedFactory(observer, storage.impressionCounts!);
68
+ break;
69
+ case NONE:
70
+ strategy = strategyNoneFactory(storage.impressionCounts!, uniqueKeysTracker!);
71
+ break;
72
+ default:
73
+ strategy = strategyDebugFactory(observer);
74
+ }
70
75
 
71
- const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, noneStrategy, strategy, whenInit, integrationsManager, storage.telemetry);
76
+ const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, strategy, whenInit, integrationsManager, storage.telemetry);
72
77
  const eventTracker = eventTrackerFactory(settings, storage.events, whenInit, integrationsManager, storage.telemetry);
73
78
 
74
79
  // splitApi is used by SyncManager and Browser signal listener
@@ -94,7 +99,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
94
99
  // We will just log and allow for the SDK to end up throwing an SDK_TIMEOUT event for devs to handle.
95
100
  validateAndTrackApiKey(log, settings.core.authorizationKey);
96
101
  readiness.init();
97
- uniqueKeysTracker.start();
102
+ uniqueKeysTracker && uniqueKeysTracker.start();
98
103
  syncManager && syncManager.start();
99
104
  signalListener && signalListener.start();
100
105
 
@@ -46,7 +46,7 @@ export interface ISdkFactoryContext {
46
46
  eventTracker: IEventTracker,
47
47
  telemetryTracker: ITelemetryTracker,
48
48
  storage: IStorageSync | IStorageAsync,
49
- uniqueKeysTracker: IUniqueKeysTracker,
49
+ uniqueKeysTracker?: IUniqueKeysTracker,
50
50
  signalListener?: ISignalListener
51
51
  splitApi?: ISplitApi
52
52
  syncManager?: ISyncManager,
@@ -31,8 +31,7 @@ function objectToView(splitObject: ISplit | null): SplitIO.SplitView | null {
31
31
  treatments: collectTreatments(splitObject),
32
32
  configs: splitObject.configurations || {},
33
33
  sets: splitObject.sets || [],
34
- defaultTreatment: splitObject.defaultTreatment,
35
- trackImpressions: splitObject.trackImpressions !== false
34
+ defaultTreatment: splitObject.defaultTreatment
36
35
  };
37
36
  }
38
37
 
@@ -27,14 +27,6 @@ export abstract class AbstractSplitsCacheAsync implements ISplitsCacheAsync {
27
27
  return Promise.resolve(true);
28
28
  }
29
29
 
30
- /**
31
- * Check if the splits information is already stored in cache.
32
- * Noop, just keeping the interface. This is used by client-side implementations only.
33
- */
34
- checkCache(): Promise<boolean> {
35
- return Promise.resolve(false);
36
- }
37
-
38
30
  /**
39
31
  * Kill `name` split and set `defaultTreatment` and `changeNumber`.
40
32
  * Used for SPLIT_KILL push notifications.
@@ -47,14 +47,6 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
47
47
 
48
48
  abstract clear(): void
49
49
 
50
- /**
51
- * Check if the splits information is already stored in cache. This data can be preloaded.
52
- * It is used as condition to emit SDK_SPLITS_CACHE_LOADED, and then SDK_READY_FROM_CACHE.
53
- */
54
- checkCache(): boolean {
55
- return false;
56
- }
57
-
58
50
  /**
59
51
  * Kill `name` split and set `defaultTreatment` and `changeNumber`.
60
52
  * Used for SPLIT_KILL push notifications.
@@ -43,6 +43,10 @@ export class KeyBuilderCS extends KeyBuilder implements MySegmentsKeyBuilder {
43
43
  buildTillKey() {
44
44
  return `${this.prefix}.${this.matchingKey}.segments.till`;
45
45
  }
46
+
47
+ buildLastClear() {
48
+ return `${this.prefix}.lastClear`;
49
+ }
46
50
  }
47
51
 
48
52
  export function myLargeSegmentsKeyBuilder(prefix: string, matchingKey: string): MySegmentsKeyBuilder {
@@ -1,7 +1,9 @@
1
1
  import { PreloadedData } from '../types';
2
- import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../utils/constants/browser';
3
2
  import { DataLoader, ISegmentsCacheSync, ISplitsCacheSync } from './types';
4
3
 
4
+ // This value might be eventually set via a config parameter
5
+ const DEFAULT_CACHE_EXPIRATION_IN_MILLIS = 864000000; // 10 days
6
+
5
7
  /**
6
8
  * Factory of client-side storage loader
7
9
  *
@@ -5,7 +5,6 @@ import { KeyBuilderCS } from '../KeyBuilderCS';
5
5
  import { ILogger } from '../../logger/types';
6
6
  import { LOG_PREFIX } from './constants';
7
7
  import { ISettings } from '../../types';
8
- import { getStorageHash } from '../KeyBuilder';
9
8
  import { setToArray } from '../../utils/lang/sets';
10
9
 
11
10
  /**
@@ -15,21 +14,14 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
15
14
 
16
15
  private readonly keys: KeyBuilderCS;
17
16
  private readonly log: ILogger;
18
- private readonly storageHash: string;
19
17
  private readonly flagSetsFilter: string[];
20
18
  private hasSync?: boolean;
21
- private updateNewFilter?: boolean;
22
19
 
23
- constructor(settings: ISettings, keys: KeyBuilderCS, expirationTimestamp?: number) {
20
+ constructor(settings: ISettings, keys: KeyBuilderCS) {
24
21
  super();
25
22
  this.keys = keys;
26
23
  this.log = settings.log;
27
- this.storageHash = getStorageHash(settings);
28
24
  this.flagSetsFilter = settings.sync.__splitFiltersValidation.groupedFilters.bySet;
29
-
30
- this._checkExpiration(expirationTimestamp);
31
-
32
- this._checkFilterQuery();
33
25
  }
34
26
 
35
27
  private _decrementCount(key: string) {
@@ -79,8 +71,6 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
79
71
  * We cannot simply call `localStorage.clear()` since that implies removing user items from the storage.
80
72
  */
81
73
  clear() {
82
- this.log.info(LOG_PREFIX + 'Flushing Splits data from localStorage');
83
-
84
74
  // collect item keys
85
75
  const len = localStorage.length;
86
76
  const accum = [];
@@ -138,19 +128,6 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
138
128
  }
139
129
 
140
130
  setChangeNumber(changeNumber: number): boolean {
141
-
142
- // when using a new split query, we must update it at the store
143
- if (this.updateNewFilter) {
144
- this.log.info(LOG_PREFIX + 'SDK key, flags filter criteria or flags spec version was modified. Updating cache');
145
- const storageHashKey = this.keys.buildHashKey();
146
- try {
147
- localStorage.setItem(storageHashKey, this.storageHash);
148
- } catch (e) {
149
- this.log.error(LOG_PREFIX + e);
150
- }
151
- this.updateNewFilter = false;
152
- }
153
-
154
131
  try {
155
132
  localStorage.setItem(this.keys.buildSplitsTillKey(), changeNumber + '');
156
133
  // update "last updated" timestamp with current time
@@ -212,48 +189,6 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
212
189
  }
213
190
  }
214
191
 
215
- /**
216
- * Check if the splits information is already stored in browser LocalStorage.
217
- * In this function we could add more code to check if the data is valid.
218
- * @override
219
- */
220
- checkCache(): boolean {
221
- return this.getChangeNumber() > -1;
222
- }
223
-
224
- /**
225
- * Clean Splits cache if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
226
- *
227
- * @param expirationTimestamp - if the value is not a number, data will not be cleaned
228
- */
229
- private _checkExpiration(expirationTimestamp?: number) {
230
- let value: string | number | null = localStorage.getItem(this.keys.buildLastUpdatedKey());
231
- if (value !== null) {
232
- value = parseInt(value, 10);
233
- if (!isNaNNumber(value) && expirationTimestamp && value < expirationTimestamp) this.clear();
234
- }
235
- }
236
-
237
- // @TODO eventually remove `_checkFilterQuery`. Cache should be cleared at the storage level, reusing same logic than PluggableStorage
238
- private _checkFilterQuery() {
239
- const storageHashKey = this.keys.buildHashKey();
240
- const storageHash = localStorage.getItem(storageHashKey);
241
-
242
- if (storageHash !== this.storageHash) {
243
- try {
244
- // mark cache to update the new query filter on first successful splits fetch
245
- this.updateNewFilter = true;
246
-
247
- // if there is cache, clear it
248
- if (this.checkCache()) this.clear();
249
-
250
- } catch (e) {
251
- this.log.error(LOG_PREFIX + e);
252
- }
253
- }
254
- // if the filter didn't change, nothing is done
255
- }
256
-
257
192
  getNamesByFlagSets(flagSets: string[]): Set<string>[] {
258
193
  return flagSets.map(flagSet => {
259
194
  const flagSetKey = this.keys.buildFlagSetKey(flagSet);
@@ -7,22 +7,19 @@ import { KeyBuilderCS, myLargeSegmentsKeyBuilder } from '../KeyBuilderCS';
7
7
  import { isLocalStorageAvailable } from '../../utils/env/isLocalStorageAvailable';
8
8
  import { SplitsCacheInLocal } from './SplitsCacheInLocal';
9
9
  import { MySegmentsCacheInLocal } from './MySegmentsCacheInLocal';
10
- import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../../utils/constants/browser';
11
10
  import { InMemoryStorageCSFactory } from '../inMemory/InMemoryStorageCS';
12
11
  import { LOG_PREFIX } from './constants';
13
- import { STORAGE_LOCALSTORAGE } from '../../utils/constants';
12
+ import { DEBUG, NONE, STORAGE_LOCALSTORAGE } from '../../utils/constants';
14
13
  import { shouldRecordTelemetry, TelemetryCacheInMemory } from '../inMemory/TelemetryCacheInMemory';
15
14
  import { UniqueKeysCacheInMemoryCS } from '../inMemory/UniqueKeysCacheInMemoryCS';
16
15
  import { getMatching } from '../../utils/key';
17
-
18
- export interface InLocalStorageOptions {
19
- prefix?: string
20
- }
16
+ import { validateCache } from './validateCache';
17
+ import SplitIO from '../../../types/splitio';
21
18
 
22
19
  /**
23
20
  * InLocal storage factory for standalone client-side SplitFactory
24
21
  */
25
- export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyncFactory {
22
+ export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): IStorageSyncFactory {
26
23
 
27
24
  const prefix = validatePrefix(options.prefix);
28
25
 
@@ -34,12 +31,11 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
34
31
  return InMemoryStorageCSFactory(params);
35
32
  }
36
33
 
37
- const { settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize } } } = params;
34
+ const { settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode } } } = params;
38
35
  const matchingKey = getMatching(settings.core.key);
39
36
  const keys = new KeyBuilderCS(prefix, matchingKey);
40
- const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
41
37
 
42
- const splits = new SplitsCacheInLocal(settings, keys, expirationTimestamp);
38
+ const splits = new SplitsCacheInLocal(settings, keys);
43
39
  const segments = new MySegmentsCacheInLocal(log, keys);
44
40
  const largeSegments = new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey));
45
41
 
@@ -48,10 +44,14 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
48
44
  segments,
49
45
  largeSegments,
50
46
  impressions: new ImpressionsCacheInMemory(impressionsQueueSize),
51
- impressionCounts: new ImpressionCountsCacheInMemory(),
47
+ impressionCounts: impressionsMode !== DEBUG ? new ImpressionCountsCacheInMemory() : undefined,
52
48
  events: new EventsCacheInMemory(eventsQueueSize),
53
49
  telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
54
- uniqueKeys: new UniqueKeysCacheInMemoryCS(),
50
+ uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS() : undefined,
51
+
52
+ validateCache() {
53
+ return validateCache(options, settings, keys, splits, segments, largeSegments);
54
+ },
55
55
 
56
56
  destroy() { },
57
57
 
@@ -66,7 +66,6 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
66
66
  impressionCounts: this.impressionCounts,
67
67
  events: this.events,
68
68
  telemetry: this.telemetry,
69
- uniqueKeys: this.uniqueKeys,
70
69
 
71
70
  destroy() { }
72
71
  };
@@ -0,0 +1,91 @@
1
+ import { ISettings } from '../../types';
2
+ import { isFiniteNumber, isNaNNumber } from '../../utils/lang';
3
+ import { getStorageHash } from '../KeyBuilder';
4
+ import { LOG_PREFIX } from './constants';
5
+ import type { SplitsCacheInLocal } from './SplitsCacheInLocal';
6
+ import type { MySegmentsCacheInLocal } from './MySegmentsCacheInLocal';
7
+ import { KeyBuilderCS } from '../KeyBuilderCS';
8
+ import SplitIO from '../../../types/splitio';
9
+
10
+ const DEFAULT_CACHE_EXPIRATION_IN_DAYS = 10;
11
+ const MILLIS_IN_A_DAY = 86400000;
12
+
13
+ /**
14
+ * Validates if cache should be cleared and sets the cache `hash` if needed.
15
+ *
16
+ * @returns `true` if cache should be cleared, `false` otherwise
17
+ */
18
+ function validateExpiration(options: SplitIO.InLocalStorageOptions, settings: ISettings, keys: KeyBuilderCS, currentTimestamp: number, isThereCache: boolean) {
19
+ const { log } = settings;
20
+
21
+ // Check expiration
22
+ const lastUpdatedTimestamp = parseInt(localStorage.getItem(keys.buildLastUpdatedKey()) as string, 10);
23
+ if (!isNaNNumber(lastUpdatedTimestamp)) {
24
+ const cacheExpirationInDays = isFiniteNumber(options.expirationDays) && options.expirationDays >= 1 ? options.expirationDays : DEFAULT_CACHE_EXPIRATION_IN_DAYS;
25
+ const expirationTimestamp = currentTimestamp - MILLIS_IN_A_DAY * cacheExpirationInDays;
26
+ if (lastUpdatedTimestamp < expirationTimestamp) {
27
+ log.info(LOG_PREFIX + 'Cache expired more than ' + cacheExpirationInDays + ' days ago. Cleaning up cache');
28
+ return true;
29
+ }
30
+ }
31
+
32
+ // Check hash
33
+ const storageHashKey = keys.buildHashKey();
34
+ const storageHash = localStorage.getItem(storageHashKey);
35
+ const currentStorageHash = getStorageHash(settings);
36
+
37
+ if (storageHash !== currentStorageHash) {
38
+ try {
39
+ localStorage.setItem(storageHashKey, currentStorageHash);
40
+ } catch (e) {
41
+ log.error(LOG_PREFIX + e);
42
+ }
43
+ if (isThereCache) {
44
+ log.info(LOG_PREFIX + 'SDK key, flags filter criteria, or flags spec version has changed. Cleaning up cache');
45
+ return true;
46
+ }
47
+ return false; // No cache to clear
48
+ }
49
+
50
+ // Clear on init
51
+ if (options.clearOnInit) {
52
+ const lastClearTimestamp = parseInt(localStorage.getItem(keys.buildLastClear()) as string, 10);
53
+
54
+ if (isNaNNumber(lastClearTimestamp) || lastClearTimestamp < currentTimestamp - MILLIS_IN_A_DAY) {
55
+ log.info(LOG_PREFIX + 'clearOnInit was set and cache was not cleared in the last 24 hours. Cleaning up cache');
56
+ return true;
57
+ }
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Clean cache if:
63
+ * - it has expired, i.e., its `lastUpdated` timestamp is older than the given `expirationTimestamp`
64
+ * - its hash has changed, i.e., the SDK key, flags filter criteria or flags spec version was modified
65
+ * - `clearOnInit` was set and cache was not cleared in the last 24 hours
66
+ *
67
+ * @returns `true` if cache is ready to be used, `false` otherwise (cache was cleared or there is no cache)
68
+ */
69
+ export function validateCache(options: SplitIO.InLocalStorageOptions, settings: ISettings, keys: KeyBuilderCS, splits: SplitsCacheInLocal, segments: MySegmentsCacheInLocal, largeSegments: MySegmentsCacheInLocal): boolean {
70
+
71
+ const currentTimestamp = Date.now();
72
+ const isThereCache = splits.getChangeNumber() > -1;
73
+
74
+ if (validateExpiration(options, settings, keys, currentTimestamp, isThereCache)) {
75
+ splits.clear();
76
+ segments.clear();
77
+ largeSegments.clear();
78
+
79
+ // Update last clear timestamp
80
+ try {
81
+ localStorage.setItem(keys.buildLastClear(), currentTimestamp + '');
82
+ } catch (e) {
83
+ settings.log.error(LOG_PREFIX + e);
84
+ }
85
+
86
+ return false;
87
+ }
88
+
89
+ // Check if ready from cache
90
+ return isThereCache;
91
+ }
@@ -4,7 +4,7 @@ import { ImpressionsCacheInMemory } from './ImpressionsCacheInMemory';
4
4
  import { EventsCacheInMemory } from './EventsCacheInMemory';
5
5
  import { IStorageFactoryParams, IStorageSync } from '../types';
6
6
  import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
7
- import { LOCALHOST_MODE, STORAGE_MEMORY } from '../../utils/constants';
7
+ import { DEBUG, LOCALHOST_MODE, NONE, STORAGE_MEMORY } from '../../utils/constants';
8
8
  import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
9
9
  import { UniqueKeysCacheInMemory } from './UniqueKeysCacheInMemory';
10
10
 
@@ -14,7 +14,7 @@ import { UniqueKeysCacheInMemory } from './UniqueKeysCacheInMemory';
14
14
  * @param params - parameters required by EventsCacheSync
15
15
  */
16
16
  export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageSync {
17
- const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { __splitFiltersValidation } } } = params;
17
+ const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode, __splitFiltersValidation } } } = params;
18
18
 
19
19
  const splits = new SplitsCacheInMemory(__splitFiltersValidation);
20
20
  const segments = new SegmentsCacheInMemory();
@@ -23,10 +23,10 @@ export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageS
23
23
  splits,
24
24
  segments,
25
25
  impressions: new ImpressionsCacheInMemory(impressionsQueueSize),
26
- impressionCounts: new ImpressionCountsCacheInMemory(),
26
+ impressionCounts: impressionsMode !== DEBUG ? new ImpressionCountsCacheInMemory() : undefined,
27
27
  events: new EventsCacheInMemory(eventsQueueSize),
28
28
  telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
29
- uniqueKeys: new UniqueKeysCacheInMemory(),
29
+ uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemory() : undefined,
30
30
 
31
31
  destroy() { }
32
32
  };
@@ -4,7 +4,7 @@ import { ImpressionsCacheInMemory } from './ImpressionsCacheInMemory';
4
4
  import { EventsCacheInMemory } from './EventsCacheInMemory';
5
5
  import { IStorageSync, IStorageFactoryParams } from '../types';
6
6
  import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
7
- import { LOCALHOST_MODE, STORAGE_MEMORY } from '../../utils/constants';
7
+ import { DEBUG, LOCALHOST_MODE, NONE, STORAGE_MEMORY } from '../../utils/constants';
8
8
  import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
9
9
  import { UniqueKeysCacheInMemoryCS } from './UniqueKeysCacheInMemoryCS';
10
10
 
@@ -14,7 +14,7 @@ import { UniqueKeysCacheInMemoryCS } from './UniqueKeysCacheInMemoryCS';
14
14
  * @param params - parameters required by EventsCacheSync
15
15
  */
16
16
  export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorageSync {
17
- const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize }, sync: { __splitFiltersValidation } } } = params;
17
+ const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode, __splitFiltersValidation } } } = params;
18
18
 
19
19
  const splits = new SplitsCacheInMemory(__splitFiltersValidation);
20
20
  const segments = new MySegmentsCacheInMemory();
@@ -25,10 +25,10 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
25
25
  segments,
26
26
  largeSegments,
27
27
  impressions: new ImpressionsCacheInMemory(impressionsQueueSize),
28
- impressionCounts: new ImpressionCountsCacheInMemory(),
28
+ impressionCounts: impressionsMode !== DEBUG ? new ImpressionCountsCacheInMemory() : undefined,
29
29
  events: new EventsCacheInMemory(eventsQueueSize),
30
30
  telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
31
- uniqueKeys: new UniqueKeysCacheInMemoryCS(),
31
+ uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS() : undefined,
32
32
 
33
33
  destroy() { },
34
34
 
@@ -42,7 +42,6 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
42
42
  impressionCounts: this.impressionCounts,
43
43
  events: this.events,
44
44
  telemetry: this.telemetry,
45
- uniqueKeys: this.uniqueKeys,
46
45
 
47
46
  destroy() { }
48
47
  };
@@ -266,10 +266,10 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
266
266
  return Promise.reject(this.redisError);
267
267
  }
268
268
 
269
- const splits: Record<string, ISplit | null> = {};
270
269
  const keys = names.map(name => this.keys.buildSplitKey(name));
271
270
  return this.redis.mget(...keys)
272
271
  .then(splitDefinitions => {
272
+ const splits: Record<string, ISplit | null> = {};
273
273
  names.forEach((name, idx) => {
274
274
  const split = splitDefinitions[idx];
275
275
  splits[name] = split && JSON.parse(split);
@@ -6,7 +6,7 @@ import { SplitsCacheInRedis } from './SplitsCacheInRedis';
6
6
  import { SegmentsCacheInRedis } from './SegmentsCacheInRedis';
7
7
  import { ImpressionsCacheInRedis } from './ImpressionsCacheInRedis';
8
8
  import { EventsCacheInRedis } from './EventsCacheInRedis';
9
- import { STORAGE_REDIS } from '../../utils/constants';
9
+ import { DEBUG, NONE, STORAGE_REDIS } from '../../utils/constants';
10
10
  import { TelemetryCacheInRedis } from './TelemetryCacheInRedis';
11
11
  import { UniqueKeysCacheInRedis } from './UniqueKeysCacheInRedis';
12
12
  import { ImpressionCountsCacheInRedis } from './ImpressionCountsCacheInRedis';
@@ -30,19 +30,19 @@ export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsy
30
30
  const prefix = validatePrefix(options.prefix);
31
31
 
32
32
  function InRedisStorageFactory(params: IStorageFactoryParams): IStorageAsync {
33
- const { onReadyCb, settings, settings: { log } } = params;
33
+ const { onReadyCb, settings, settings: { log, sync: { impressionsMode } } } = params;
34
34
  const metadata = metadataBuilder(settings);
35
35
  const keys = new KeyBuilderSS(prefix, metadata);
36
36
  const redisClient: RedisAdapter = new RD(log, options.options || {});
37
37
  const telemetry = new TelemetryCacheInRedis(log, keys, redisClient);
38
- const impressionCountsCache = new ImpressionCountsCacheInRedis(log, keys.buildImpressionsCountKey(), redisClient);
39
- const uniqueKeysCache = new UniqueKeysCacheInRedis(log, keys.buildUniqueKeysKey(), redisClient);
38
+ const impressionCountsCache = impressionsMode !== DEBUG ? new ImpressionCountsCacheInRedis(log, keys.buildImpressionsCountKey(), redisClient) : undefined;
39
+ const uniqueKeysCache = impressionsMode === NONE ? new UniqueKeysCacheInRedis(log, keys.buildUniqueKeysKey(), redisClient) : undefined;
40
40
 
41
41
  // subscription to Redis connect event in order to emit SDK_READY event on consumer mode
42
42
  redisClient.on('connect', () => {
43
43
  onReadyCb();
44
- impressionCountsCache.start();
45
- uniqueKeysCache.start();
44
+ if (impressionCountsCache) impressionCountsCache.start();
45
+ if (uniqueKeysCache) uniqueKeysCache.start();
46
46
 
47
47
  // Synchronize config
48
48
  telemetry.recordConfig();
@@ -60,10 +60,10 @@ export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsy
60
60
  // When using REDIS we should:
61
61
  // 1- Disconnect from the storage
62
62
  destroy(): Promise<void> {
63
- return Promise.all([
64
- impressionCountsCache.stop(),
65
- uniqueKeysCache.stop()
66
- ]).then(() => { redisClient.disconnect(); });
63
+ let promises = [];
64
+ if (impressionCountsCache) promises.push(impressionCountsCache.stop());
65
+ if (uniqueKeysCache) promises.push(uniqueKeysCache.stop());
66
+ return Promise.all(promises).then(() => { redisClient.disconnect(); });
67
67
  // @TODO check that caches works as expected when redisClient is disconnected
68
68
  }
69
69
  };
@@ -8,7 +8,7 @@ import { EventsCachePluggable } from './EventsCachePluggable';
8
8
  import { wrapperAdapter, METHODS_TO_PROMISE_WRAP } from './wrapperAdapter';
9
9
  import { isObject } from '../../utils/lang';
10
10
  import { getStorageHash, validatePrefix } from '../KeyBuilder';
11
- import { CONSUMER_PARTIAL_MODE, STORAGE_PLUGGABLE } from '../../utils/constants';
11
+ import { CONSUMER_PARTIAL_MODE, DEBUG, NONE, STORAGE_PLUGGABLE } from '../../utils/constants';
12
12
  import { ImpressionsCacheInMemory } from '../inMemory/ImpressionsCacheInMemory';
13
13
  import { EventsCacheInMemory } from '../inMemory/EventsCacheInMemory';
14
14
  import { ImpressionCountsCacheInMemory } from '../inMemory/ImpressionCountsCacheInMemory';
@@ -63,32 +63,37 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
63
63
  const prefix = validatePrefix(options.prefix);
64
64
 
65
65
  function PluggableStorageFactory(params: IStorageFactoryParams): IStorageAsync {
66
- const { onReadyCb, settings, settings: { log, mode, scheduler: { impressionsQueueSize, eventsQueueSize } } } = params;
66
+ const { onReadyCb, settings, settings: { log, mode, sync: { impressionsMode }, scheduler: { impressionsQueueSize, eventsQueueSize } } } = params;
67
67
  const metadata = metadataBuilder(settings);
68
68
  const keys = new KeyBuilderSS(prefix, metadata);
69
69
  const wrapper = wrapperAdapter(log, options.wrapper);
70
70
 
71
- const isSynchronizer = mode === undefined; // If mode is not defined, the synchronizer is running
71
+ const isSyncronizer = mode === undefined; // If mode is not defined, the synchronizer is running
72
72
  const isPartialConsumer = mode === CONSUMER_PARTIAL_MODE;
73
73
 
74
- const telemetry = shouldRecordTelemetry(params) || isSynchronizer ?
74
+ const telemetry = shouldRecordTelemetry(params) || isSyncronizer ?
75
75
  isPartialConsumer ?
76
76
  new TelemetryCacheInMemory() :
77
77
  new TelemetryCachePluggable(log, keys, wrapper) :
78
78
  undefined;
79
79
 
80
- const impressionCountsCache = isPartialConsumer ?
81
- new ImpressionCountsCacheInMemory() :
82
- new ImpressionCountsCachePluggable(log, keys.buildImpressionsCountKey(), wrapper);
80
+ const impressionCountsCache = impressionsMode !== DEBUG || isSyncronizer ?
81
+ isPartialConsumer ?
82
+ new ImpressionCountsCacheInMemory() :
83
+ new ImpressionCountsCachePluggable(log, keys.buildImpressionsCountKey(), wrapper) :
84
+ undefined;
83
85
 
84
- const uniqueKeysCache = isPartialConsumer ?
85
- settings.core.key === undefined ? new UniqueKeysCacheInMemory() : new UniqueKeysCacheInMemoryCS() :
86
- new UniqueKeysCachePluggable(log, keys.buildUniqueKeysKey(), wrapper);
86
+ const uniqueKeysCache = impressionsMode === NONE || isSyncronizer ?
87
+ isPartialConsumer ?
88
+ settings.core.key === undefined ? new UniqueKeysCacheInMemory() : new UniqueKeysCacheInMemoryCS() :
89
+ new UniqueKeysCachePluggable(log, keys.buildUniqueKeysKey(), wrapper) :
90
+ undefined;
87
91
 
88
92
  // Connects to wrapper and emits SDK_READY event on main client
89
93
  const connectPromise = wrapper.connect().then(() => {
90
- if (isSynchronizer) {
91
- // In standalone or producer mode, clear storage if SDK key or feature flag filter has changed
94
+ if (isSyncronizer) {
95
+ // @TODO reuse InLocalStorage::validateCache logic
96
+ // In standalone or producer mode, clear storage if SDK key, flags filter criteria or flags spec version was modified
92
97
  return wrapper.get(keys.buildHashKey()).then((hash) => {
93
98
  const currentHash = getStorageHash(settings);
94
99
  if (hash !== currentHash) {
@@ -102,8 +107,8 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
102
107
  });
103
108
  } else {
104
109
  // Start periodic flush of async storages if not running synchronizer (producer mode)
105
- if ((impressionCountsCache as ImpressionCountsCachePluggable).start) (impressionCountsCache as ImpressionCountsCachePluggable).start();
106
- if ((uniqueKeysCache as UniqueKeysCachePluggable).start) (uniqueKeysCache as UniqueKeysCachePluggable).start();
110
+ if (impressionCountsCache && (impressionCountsCache as ImpressionCountsCachePluggable).start) (impressionCountsCache as ImpressionCountsCachePluggable).start();
111
+ if (uniqueKeysCache && (uniqueKeysCache as UniqueKeysCachePluggable).start) (uniqueKeysCache as UniqueKeysCachePluggable).start();
107
112
  if (telemetry && (telemetry as ITelemetryCacheAsync).recordConfig) (telemetry as ITelemetryCacheAsync).recordConfig();
108
113
 
109
114
  onReadyCb();
@@ -125,9 +130,9 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
125
130
 
126
131
  // Stop periodic flush and disconnect the underlying storage
127
132
  destroy() {
128
- return Promise.all(isSynchronizer ? [] : [
129
- (impressionCountsCache as ImpressionCountsCachePluggable).stop && (impressionCountsCache as ImpressionCountsCachePluggable).stop(),
130
- (uniqueKeysCache as UniqueKeysCachePluggable).stop && (uniqueKeysCache as UniqueKeysCachePluggable).stop(),
133
+ return Promise.all(isSyncronizer ? [] : [
134
+ impressionCountsCache && (impressionCountsCache as ImpressionCountsCachePluggable).stop && (impressionCountsCache as ImpressionCountsCachePluggable).stop(),
135
+ uniqueKeysCache && (uniqueKeysCache as UniqueKeysCachePluggable).stop && (uniqueKeysCache as UniqueKeysCachePluggable).stop(),
131
136
  ]).then(() => wrapper.disconnect());
132
137
  },
133
138
 
@@ -191,8 +191,6 @@ export interface ISplitsCacheBase {
191
191
  // only for Client-Side. Returns true if the storage is not synchronized yet (getChangeNumber() === -1) or contains a FF using segments or large segments
192
192
  usesSegments(): MaybeThenable<boolean>,
193
193
  clear(): MaybeThenable<boolean | void>,
194
- // should never reject or throw an exception. Instead return false by default, to avoid emitting SDK_READY_FROM_CACHE.
195
- checkCache(): MaybeThenable<boolean>,
196
194
  killLocally(name: string, defaultTreatment: string, changeNumber: number): MaybeThenable<boolean>,
197
195
  getNamesByFlagSets(flagSets: string[]): MaybeThenable<Set<string>[]>
198
196
  }
@@ -209,7 +207,6 @@ export interface ISplitsCacheSync extends ISplitsCacheBase {
209
207
  trafficTypeExists(trafficType: string): boolean,
210
208
  usesSegments(): boolean,
211
209
  clear(): void,
212
- checkCache(): boolean,
213
210
  killLocally(name: string, defaultTreatment: string, changeNumber: number): boolean,
214
211
  getNamesByFlagSets(flagSets: string[]): Set<string>[]
215
212
  }
@@ -226,7 +223,6 @@ export interface ISplitsCacheAsync extends ISplitsCacheBase {
226
223
  trafficTypeExists(trafficType: string): Promise<boolean>,
227
224
  usesSegments(): Promise<boolean>,
228
225
  clear(): Promise<boolean | void>,
229
- checkCache(): Promise<boolean>,
230
226
  killLocally(name: string, defaultTreatment: string, changeNumber: number): Promise<boolean>,
231
227
  getNamesByFlagSets(flagSets: string[]): Promise<Set<string>[]>
232
228
  }
@@ -439,10 +435,10 @@ export interface IStorageBase<
439
435
  splits: TSplitsCache,
440
436
  segments: TSegmentsCache,
441
437
  impressions: TImpressionsCache,
442
- impressionCounts: TImpressionsCountCache,
438
+ impressionCounts?: TImpressionsCountCache,
443
439
  events: TEventsCache,
444
440
  telemetry?: TTelemetryCache,
445
- uniqueKeys: TUniqueKeysCache,
441
+ uniqueKeys?: TUniqueKeysCache,
446
442
  destroy(): void | Promise<void>,
447
443
  shared?: (matchingKey: string, onReadyCb: (error?: any) => void) => this
448
444
  }
@@ -457,6 +453,7 @@ export interface IStorageSync extends IStorageBase<
457
453
  IUniqueKeysCacheSync
458
454
  > {
459
455
  // Defined in client-side
456
+ validateCache?: () => boolean, // @TODO support async
460
457
  largeSegments?: ISegmentsCacheSync,
461
458
  }
462
459