@splitsoftware/splitio-commons 2.1.0-rc.1 → 2.1.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 (104) hide show
  1. package/CHANGES.txt +4 -5
  2. package/LICENSE +1 -1
  3. package/cjs/evaluator/index.js +2 -0
  4. package/cjs/listeners/browser.js +4 -6
  5. package/cjs/readiness/readinessManager.js +0 -6
  6. package/cjs/sdkClient/client.js +14 -11
  7. package/cjs/sdkClient/sdkClient.js +1 -1
  8. package/cjs/sdkFactory/index.js +9 -14
  9. package/cjs/sdkManager/index.js +2 -1
  10. package/cjs/storages/AbstractSplitsCacheAsync.js +7 -0
  11. package/cjs/storages/AbstractSplitsCacheSync.js +7 -0
  12. package/cjs/storages/KeyBuilderCS.js +0 -3
  13. package/cjs/storages/dataLoader.js +2 -3
  14. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +57 -1
  15. package/cjs/storages/inLocalStorage/index.js +7 -8
  16. package/cjs/storages/inMemory/InMemoryStorage.js +5 -7
  17. package/cjs/storages/inMemory/InMemoryStorageCS.js +6 -7
  18. package/cjs/storages/inRedis/constants.js +1 -1
  19. package/cjs/storages/inRedis/index.js +9 -13
  20. package/cjs/storages/pluggable/index.js +16 -21
  21. package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +2 -3
  22. package/cjs/sync/polling/updaters/splitChangesUpdater.js +10 -1
  23. package/cjs/sync/submitters/impressionCountsSubmitter.js +2 -4
  24. package/cjs/sync/submitters/submitterManager.js +3 -6
  25. package/cjs/sync/syncManagerOnline.js +3 -8
  26. package/cjs/trackers/impressionsTracker.js +17 -18
  27. package/cjs/trackers/strategy/strategyDebug.js +4 -11
  28. package/cjs/trackers/strategy/strategyNone.js +11 -16
  29. package/cjs/trackers/strategy/strategyOptimized.js +11 -21
  30. package/cjs/trackers/uniqueKeysTracker.js +1 -1
  31. package/cjs/utils/constants/browser.js +5 -0
  32. package/cjs/utils/settingsValidation/storage/storageCS.js +1 -1
  33. package/esm/evaluator/index.js +2 -0
  34. package/esm/listeners/browser.js +1 -3
  35. package/esm/readiness/readinessManager.js +0 -6
  36. package/esm/sdkClient/client.js +14 -11
  37. package/esm/sdkClient/sdkClient.js +1 -1
  38. package/esm/sdkFactory/index.js +10 -15
  39. package/esm/sdkManager/index.js +2 -1
  40. package/esm/storages/AbstractSplitsCacheAsync.js +7 -0
  41. package/esm/storages/AbstractSplitsCacheSync.js +7 -0
  42. package/esm/storages/KeyBuilderCS.js +0 -3
  43. package/esm/storages/dataLoader.js +1 -2
  44. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +57 -1
  45. package/esm/storages/inLocalStorage/index.js +8 -9
  46. package/esm/storages/inMemory/InMemoryStorage.js +6 -8
  47. package/esm/storages/inMemory/InMemoryStorageCS.js +7 -8
  48. package/esm/storages/inRedis/constants.js +1 -1
  49. package/esm/storages/inRedis/index.js +10 -14
  50. package/esm/storages/pluggable/index.js +17 -22
  51. package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +2 -3
  52. package/esm/sync/polling/updaters/splitChangesUpdater.js +11 -2
  53. package/esm/sync/submitters/impressionCountsSubmitter.js +2 -4
  54. package/esm/sync/submitters/submitterManager.js +3 -6
  55. package/esm/sync/syncManagerOnline.js +3 -8
  56. package/esm/trackers/impressionsTracker.js +17 -18
  57. package/esm/trackers/strategy/strategyDebug.js +4 -11
  58. package/esm/trackers/strategy/strategyNone.js +11 -16
  59. package/esm/trackers/strategy/strategyOptimized.js +11 -21
  60. package/esm/trackers/uniqueKeysTracker.js +1 -1
  61. package/esm/utils/constants/browser.js +2 -0
  62. package/esm/utils/settingsValidation/storage/storageCS.js +1 -1
  63. package/package.json +1 -1
  64. package/src/dtos/types.ts +2 -1
  65. package/src/evaluator/index.ts +2 -0
  66. package/src/evaluator/types.ts +1 -1
  67. package/src/listeners/browser.ts +1 -3
  68. package/src/readiness/readinessManager.ts +0 -5
  69. package/src/sdkClient/client.ts +19 -15
  70. package/src/sdkClient/sdkClient.ts +1 -1
  71. package/src/sdkFactory/index.ts +11 -16
  72. package/src/sdkFactory/types.ts +1 -1
  73. package/src/sdkManager/index.ts +2 -1
  74. package/src/storages/AbstractSplitsCacheAsync.ts +8 -0
  75. package/src/storages/AbstractSplitsCacheSync.ts +8 -0
  76. package/src/storages/KeyBuilderCS.ts +0 -4
  77. package/src/storages/dataLoader.ts +1 -3
  78. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +66 -1
  79. package/src/storages/inLocalStorage/index.ts +13 -12
  80. package/src/storages/inMemory/InMemoryStorage.ts +6 -6
  81. package/src/storages/inMemory/InMemoryStorageCS.ts +7 -6
  82. package/src/storages/inRedis/constants.ts +1 -1
  83. package/src/storages/inRedis/index.ts +10 -10
  84. package/src/storages/pluggable/index.ts +17 -22
  85. package/src/storages/types.ts +6 -3
  86. package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +5 -6
  87. package/src/sync/polling/updaters/splitChangesUpdater.ts +11 -2
  88. package/src/sync/submitters/impressionCountsSubmitter.ts +2 -4
  89. package/src/sync/submitters/submitterManager.ts +3 -4
  90. package/src/sync/submitters/uniqueKeysSubmitter.ts +2 -3
  91. package/src/sync/syncManagerOnline.ts +3 -9
  92. package/src/trackers/impressionsTracker.ts +18 -19
  93. package/src/trackers/strategy/strategyDebug.ts +4 -11
  94. package/src/trackers/strategy/strategyNone.ts +11 -17
  95. package/src/trackers/strategy/strategyOptimized.ts +10 -20
  96. package/src/trackers/types.ts +13 -8
  97. package/src/trackers/uniqueKeysTracker.ts +1 -1
  98. package/src/utils/constants/browser.ts +2 -0
  99. package/src/utils/lang/index.ts +1 -1
  100. package/src/utils/settingsValidation/storage/storageCS.ts +1 -1
  101. package/types/splitio.d.ts +5 -25
  102. package/cjs/storages/inLocalStorage/validateCache.js +0 -79
  103. package/esm/storages/inLocalStorage/validateCache.js +0 -75
  104. package/src/storages/inLocalStorage/validateCache.ts +0 -91
@@ -1,25 +1,20 @@
1
1
  /**
2
2
  * None strategy for impressions tracker.
3
3
  *
4
- * @param impressionsCounter - cache to save impressions count. impressions will be deduped (OPTIMIZED mode)
4
+ * @param impressionCounts - cache to save impressions count. impressions will be deduped (OPTIMIZED mode)
5
5
  * @param uniqueKeysTracker - unique keys tracker in charge of tracking the unique keys per split.
6
- * @returns IStrategyResult
6
+ * @returns None strategy
7
7
  */
8
- export function strategyNoneFactory(impressionsCounter, uniqueKeysTracker) {
8
+ export function strategyNoneFactory(impressionCounts, uniqueKeysTracker) {
9
9
  return {
10
- process: function (impressions) {
11
- impressions.forEach(function (impression) {
12
- var now = Date.now();
13
- // Increments impression counter per featureName
14
- impressionsCounter.track(impression.feature, now, 1);
15
- // Keep track by unique key
16
- uniqueKeysTracker.track(impression.keyName, impression.feature);
17
- });
18
- return {
19
- impressionsToStore: [],
20
- impressionsToListener: impressions,
21
- deduped: 0
22
- };
10
+ process: function (impression) {
11
+ var now = Date.now();
12
+ // Increments impression counter per featureName
13
+ impressionCounts.track(impression.feature, now, 1);
14
+ // Keep track by unique key
15
+ uniqueKeysTracker.track(impression.keyName, impression.feature);
16
+ // Do not store impressions
17
+ return false;
23
18
  }
24
19
  };
25
20
  }
@@ -3,29 +3,19 @@ import { truncateTimeFrame } from '../../utils/time';
3
3
  * Optimized strategy for impressions tracker. Wraps impressions to store and adds previousTime if it corresponds
4
4
  *
5
5
  * @param impressionsObserver - impression observer. previous time (pt property) is included in impression instances
6
- * @param impressionsCounter - cache to save impressions count. impressions will be deduped (OPTIMIZED mode)
7
- * @returns IStrategyResult
6
+ * @param impressionCounts - cache to save impressions count. impressions will be deduped (OPTIMIZED mode)
7
+ * @returns Optimized strategy
8
8
  */
9
- export function strategyOptimizedFactory(impressionsObserver, impressionsCounter) {
9
+ export function strategyOptimizedFactory(impressionsObserver, impressionCounts) {
10
10
  return {
11
- process: function (impressions) {
12
- var impressionsToStore = [];
13
- impressions.forEach(function (impression) {
14
- impression.pt = impressionsObserver.testAndSet(impression);
15
- var now = Date.now();
16
- // Increments impression counter per featureName
17
- if (impression.pt)
18
- impressionsCounter.track(impression.feature, now, 1);
19
- // Checks if the impression should be added in queue to be sent
20
- if (!impression.pt || impression.pt < truncateTimeFrame(now)) {
21
- impressionsToStore.push(impression);
22
- }
23
- });
24
- return {
25
- impressionsToStore: impressionsToStore,
26
- impressionsToListener: impressions,
27
- deduped: impressions.length - impressionsToStore.length
28
- };
11
+ process: function (impression) {
12
+ impression.pt = impressionsObserver.testAndSet(impression);
13
+ var now = Date.now();
14
+ // Increments impression counter per featureName
15
+ if (impression.pt)
16
+ impressionCounts.track(impression.feature, now, 1);
17
+ // Checks if the impression should be added in queue to be sent
18
+ return (!impression.pt || impression.pt < truncateTimeFrame(now)) ? true : false;
29
19
  }
30
20
  };
31
21
  }
@@ -5,7 +5,7 @@ var noopFilterAdapter = {
5
5
  clear: function () { }
6
6
  };
7
7
  /**
8
- * Trackes uniques keys
8
+ * Tracks uniques keys
9
9
  * Unique Keys Tracker will be in charge of checking if the MTK was already sent to the BE in the last period
10
10
  * or schedule to be sent; if not it will be added in an internal cache and sent in the next post.
11
11
  *
@@ -0,0 +1,2 @@
1
+ // This value might be eventually set via a config parameter
2
+ export var DEFAULT_CACHE_EXPIRATION_IN_MILLIS = 864000000; // 10 days
@@ -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.splits.checkCache = function () { return 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.1.0-rc.1",
3
+ "version": "2.1.0",
4
4
  "description": "Split JavaScript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
package/src/dtos/types.ts CHANGED
@@ -208,7 +208,8 @@ export interface ISplit {
208
208
  configurations?: {
209
209
  [treatmentName: string]: string
210
210
  },
211
- sets?: string[]
211
+ sets?: string[],
212
+ impressionsDisabled?: boolean
212
213
  }
213
214
 
214
215
  // Split definition used in offline mode
@@ -156,12 +156,14 @@ function getEvaluation(
156
156
  return evaluation.then(result => {
157
157
  result.changeNumber = split.getChangeNumber();
158
158
  result.config = splitJSON.configurations && splitJSON.configurations[result.treatment] || null;
159
+ result.impressionsDisabled = splitJSON.impressionsDisabled;
159
160
 
160
161
  return result;
161
162
  });
162
163
  } else {
163
164
  evaluation.changeNumber = split.getChangeNumber(); // Always sync and optional
164
165
  evaluation.config = splitJSON.configurations && splitJSON.configurations[evaluation.treatment] || null;
166
+ evaluation.impressionsDisabled = splitJSON.impressionsDisabled;
165
167
  }
166
168
  }
167
169
 
@@ -25,7 +25,7 @@ export interface IEvaluation {
25
25
  config?: string | null
26
26
  }
27
27
 
28
- export type IEvaluationResult = IEvaluation & { treatment: string }
28
+ export type IEvaluationResult = IEvaluation & { treatment: string; impressionsDisabled?: boolean }
29
29
 
30
30
  export type ISplitEvaluator = (log: ILogger, key: SplitIO.SplitKey, splitName: string, attributes: SplitIO.Attributes | undefined, storage: IStorageSync | IStorageAsync) => MaybeThenable<IEvaluation>
31
31
 
@@ -8,7 +8,6 @@ import { IResponse, ISplitApi } from '../services/types';
8
8
  import { ISettings } from '../types';
9
9
  import SplitIO from '../../types/splitio';
10
10
  import { ImpressionsPayload } from '../sync/submitters/types';
11
- import { OPTIMIZED, DEBUG, NONE } from '../utils/constants';
12
11
  import { objectAssign } from '../utils/lang/objectAssign';
13
12
  import { CLEANUP_REGISTERING, CLEANUP_DEREGISTERING } from '../logger/constants';
14
13
  import { ISyncManager } from '../sync/types';
@@ -78,10 +77,9 @@ export class BrowserSignalListener implements ISignalListener {
78
77
 
79
78
  // Flush impressions & events data if there is user consent
80
79
  if (isConsentGranted(this.settings)) {
81
- const sim = this.settings.sync.impressionsMode;
82
80
  const extraMetadata = {
83
81
  // sim stands for Sync/Split Impressions Mode
84
- sim: sim === OPTIMIZED ? OPTIMIZED : sim === DEBUG ? DEBUG : NONE
82
+ sim: this.settings.sync.impressionsMode
85
83
  };
86
84
 
87
85
  this._flushData(events + '/testImpressions/beacon', this.storage.impressions, this.serviceApi.postTestImpressionsBulk, this.fromImpressionsCollector, extraMetadata);
@@ -3,7 +3,6 @@ import { ISettings } from '../types';
3
3
  import SplitIO from '../../types/splitio';
4
4
  import { SDK_SPLITS_ARRIVED, SDK_SPLITS_CACHE_LOADED, SDK_SEGMENTS_ARRIVED, SDK_READY_TIMED_OUT, SDK_READY_FROM_CACHE, SDK_UPDATE, SDK_READY } from './constants';
5
5
  import { IReadinessEventEmitter, IReadinessManager, ISegmentsEventEmitter, ISplitsEventEmitter } from './types';
6
- import { STORAGE_LOCALSTORAGE } from '../utils/constants';
7
6
 
8
7
  function splitsEventEmitterFactory(EventEmitter: new () => SplitIO.IEventEmitter): ISplitsEventEmitter {
9
8
  const splitsEventEmitter = objectAssign(new EventEmitter(), {
@@ -115,10 +114,6 @@ export function readinessManagerFactory(
115
114
  isReady = true;
116
115
  try {
117
116
  syncLastUpdate();
118
- if (!isReadyFromCache && settings.storage?.type === STORAGE_LOCALSTORAGE) {
119
- isReadyFromCache = true;
120
- gate.emit(SDK_READY_FROM_CACHE);
121
- }
122
117
  gate.emit(SDK_READY);
123
118
  } catch (e) {
124
119
  // throws user callback exceptions in next tick
@@ -11,6 +11,7 @@ import { IMPRESSION, IMPRESSION_QUEUEING } from '../logger/constants';
11
11
  import { ISdkFactoryContext } from '../sdkFactory/types';
12
12
  import { isConsumerMode } from '../utils/settingsValidation/mode';
13
13
  import { Method } from '../sync/submitters/types';
14
+ import { ImpressionDecorated } from '../trackers/types';
14
15
 
15
16
  const treatmentNotReady = { treatment: CONTROL, label: SDK_NOT_READY };
16
17
 
@@ -34,11 +35,11 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
34
35
  const stopTelemetryTracker = telemetryTracker.trackEval(withConfig ? TREATMENT_WITH_CONFIG : TREATMENT);
35
36
 
36
37
  const wrapUp = (evaluationResult: IEvaluationResult) => {
37
- const queue: SplitIO.ImpressionDTO[] = [];
38
+ const queue: ImpressionDecorated[] = [];
38
39
  const treatment = processEvaluation(evaluationResult, featureFlagName, key, attributes, withConfig, methodName, queue);
39
40
  impressionsTracker.track(queue, attributes);
40
41
 
41
- stopTelemetryTracker(queue[0] && queue[0].label);
42
+ stopTelemetryTracker(queue[0] && queue[0].imp.label);
42
43
  return treatment;
43
44
  };
44
45
 
@@ -59,14 +60,14 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
59
60
  const stopTelemetryTracker = telemetryTracker.trackEval(withConfig ? TREATMENTS_WITH_CONFIG : TREATMENTS);
60
61
 
61
62
  const wrapUp = (evaluationResults: Record<string, IEvaluationResult>) => {
62
- const queue: SplitIO.ImpressionDTO[] = [];
63
+ const queue: ImpressionDecorated[] = [];
63
64
  const treatments: Record<string, SplitIO.Treatment | SplitIO.TreatmentWithConfig> = {};
64
65
  Object.keys(evaluationResults).forEach(featureFlagName => {
65
66
  treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key, attributes, withConfig, methodName, queue);
66
67
  });
67
68
  impressionsTracker.track(queue, attributes);
68
69
 
69
- stopTelemetryTracker(queue[0] && queue[0].label);
70
+ stopTelemetryTracker(queue[0] && queue[0].imp.label);
70
71
  return treatments;
71
72
  };
72
73
 
@@ -87,7 +88,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
87
88
  const stopTelemetryTracker = telemetryTracker.trackEval(method);
88
89
 
89
90
  const wrapUp = (evaluationResults: Record<string, IEvaluationResult>) => {
90
- const queue: SplitIO.ImpressionDTO[] = [];
91
+ const queue: ImpressionDecorated[] = [];
91
92
  const treatments: Record<string, SplitIO.Treatment | SplitIO.TreatmentWithConfig> = {};
92
93
  const evaluations = evaluationResults;
93
94
  Object.keys(evaluations).forEach(featureFlagName => {
@@ -95,7 +96,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
95
96
  });
96
97
  impressionsTracker.track(queue, attributes);
97
98
 
98
- stopTelemetryTracker(queue[0] && queue[0].label);
99
+ stopTelemetryTracker(queue[0] && queue[0].imp.label);
99
100
  return treatments;
100
101
  };
101
102
 
@@ -128,24 +129,27 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
128
129
  attributes: SplitIO.Attributes | undefined,
129
130
  withConfig: boolean,
130
131
  invokingMethodName: string,
131
- queue: SplitIO.ImpressionDTO[]
132
+ queue: ImpressionDecorated[]
132
133
  ): SplitIO.Treatment | SplitIO.TreatmentWithConfig {
133
134
  const matchingKey = getMatching(key);
134
135
  const bucketingKey = getBucketing(key);
135
136
 
136
- const { treatment, label, changeNumber, config = null } = evaluation;
137
+ const { treatment, label, changeNumber, config = null, impressionsDisabled } = evaluation;
137
138
  log.info(IMPRESSION, [featureFlagName, matchingKey, treatment, label]);
138
139
 
139
140
  if (validateSplitExistence(log, readinessManager, featureFlagName, label, invokingMethodName)) {
140
141
  log.info(IMPRESSION_QUEUEING);
141
142
  queue.push({
142
- feature: featureFlagName,
143
- keyName: matchingKey,
144
- treatment,
145
- time: Date.now(),
146
- bucketingKey,
147
- label,
148
- changeNumber: changeNumber as number
143
+ imp: {
144
+ feature: featureFlagName,
145
+ keyName: matchingKey,
146
+ treatment,
147
+ time: Date.now(),
148
+ bucketingKey,
149
+ label,
150
+ changeNumber: changeNumber as number,
151
+ },
152
+ disabled: impressionsDisabled
149
153
  });
150
154
  }
151
155
 
@@ -61,7 +61,7 @@ export function sdkClientFactory(params: ISdkFactoryContext, isSharedClient?: bo
61
61
  releaseApiKey(settings.core.authorizationKey);
62
62
  telemetryTracker.sessionLength();
63
63
  signalListener && signalListener.stop();
64
- uniqueKeysTracker && uniqueKeysTracker.stop();
64
+ uniqueKeysTracker.stop();
65
65
  }
66
66
 
67
67
  // Stop background jobs
@@ -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 { NONE, OPTIMIZED } from '../utils/constants';
16
+ import { DEBUG, OPTIMIZED } from '../utils/constants';
17
17
 
18
18
  /**
19
19
  * Modular SDK factory
@@ -59,21 +59,16 @@ 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 = 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
- }
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;
75
70
 
76
- const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, strategy, whenInit, integrationsManager, storage.telemetry);
71
+ const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, noneStrategy, strategy, whenInit, integrationsManager, storage.telemetry);
77
72
  const eventTracker = eventTrackerFactory(settings, storage.events, whenInit, integrationsManager, storage.telemetry);
78
73
 
79
74
  // splitApi is used by SyncManager and Browser signal listener
@@ -99,7 +94,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
99
94
  // We will just log and allow for the SDK to end up throwing an SDK_TIMEOUT event for devs to handle.
100
95
  validateAndTrackApiKey(log, settings.core.authorizationKey);
101
96
  readiness.init();
102
- uniqueKeysTracker && uniqueKeysTracker.start();
97
+ uniqueKeysTracker.start();
103
98
  syncManager && syncManager.start();
104
99
  signalListener && signalListener.start();
105
100
 
@@ -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,7 +31,8 @@ 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
34
+ defaultTreatment: splitObject.defaultTreatment,
35
+ impressionsDisabled: splitObject.impressionsDisabled === true
35
36
  };
36
37
  }
37
38
 
@@ -27,6 +27,14 @@ 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
+
30
38
  /**
31
39
  * Kill `name` split and set `defaultTreatment` and `changeNumber`.
32
40
  * Used for SPLIT_KILL push notifications.
@@ -47,6 +47,14 @@ 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
+
50
58
  /**
51
59
  * Kill `name` split and set `defaultTreatment` and `changeNumber`.
52
60
  * Used for SPLIT_KILL push notifications.
@@ -43,10 +43,6 @@ 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
- }
50
46
  }
51
47
 
52
48
  export function myLargeSegmentsKeyBuilder(prefix: string, matchingKey: string): MySegmentsKeyBuilder {
@@ -1,9 +1,7 @@
1
1
  import { PreloadedData } from '../types';
2
+ import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../utils/constants/browser';
2
3
  import { DataLoader, ISegmentsCacheSync, ISplitsCacheSync } from './types';
3
4
 
4
- // This value might be eventually set via a config parameter
5
- const DEFAULT_CACHE_EXPIRATION_IN_MILLIS = 864000000; // 10 days
6
-
7
5
  /**
8
6
  * Factory of client-side storage loader
9
7
  *
@@ -5,6 +5,7 @@ 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';
8
9
  import { setToArray } from '../../utils/lang/sets';
9
10
 
10
11
  /**
@@ -14,14 +15,21 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
14
15
 
15
16
  private readonly keys: KeyBuilderCS;
16
17
  private readonly log: ILogger;
18
+ private readonly storageHash: string;
17
19
  private readonly flagSetsFilter: string[];
18
20
  private hasSync?: boolean;
21
+ private updateNewFilter?: boolean;
19
22
 
20
- constructor(settings: ISettings, keys: KeyBuilderCS) {
23
+ constructor(settings: ISettings, keys: KeyBuilderCS, expirationTimestamp?: number) {
21
24
  super();
22
25
  this.keys = keys;
23
26
  this.log = settings.log;
27
+ this.storageHash = getStorageHash(settings);
24
28
  this.flagSetsFilter = settings.sync.__splitFiltersValidation.groupedFilters.bySet;
29
+
30
+ this._checkExpiration(expirationTimestamp);
31
+
32
+ this._checkFilterQuery();
25
33
  }
26
34
 
27
35
  private _decrementCount(key: string) {
@@ -71,6 +79,8 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
71
79
  * We cannot simply call `localStorage.clear()` since that implies removing user items from the storage.
72
80
  */
73
81
  clear() {
82
+ this.log.info(LOG_PREFIX + 'Flushing Splits data from localStorage');
83
+
74
84
  // collect item keys
75
85
  const len = localStorage.length;
76
86
  const accum = [];
@@ -128,6 +138,19 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
128
138
  }
129
139
 
130
140
  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
+
131
154
  try {
132
155
  localStorage.setItem(this.keys.buildSplitsTillKey(), changeNumber + '');
133
156
  // update "last updated" timestamp with current time
@@ -189,6 +212,48 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
189
212
  }
190
213
  }
191
214
 
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
+
192
257
  getNamesByFlagSets(flagSets: string[]): Set<string>[] {
193
258
  return flagSets.map(flagSet => {
194
259
  const flagSetKey = this.keys.buildFlagSetKey(flagSet);
@@ -7,19 +7,22 @@ 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';
10
11
  import { InMemoryStorageCSFactory } from '../inMemory/InMemoryStorageCS';
11
12
  import { LOG_PREFIX } from './constants';
12
- import { DEBUG, NONE, STORAGE_LOCALSTORAGE } from '../../utils/constants';
13
+ import { STORAGE_LOCALSTORAGE } from '../../utils/constants';
13
14
  import { shouldRecordTelemetry, TelemetryCacheInMemory } from '../inMemory/TelemetryCacheInMemory';
14
15
  import { UniqueKeysCacheInMemoryCS } from '../inMemory/UniqueKeysCacheInMemoryCS';
15
16
  import { getMatching } from '../../utils/key';
16
- import { validateCache } from './validateCache';
17
- import SplitIO from '../../../types/splitio';
17
+
18
+ export interface InLocalStorageOptions {
19
+ prefix?: string
20
+ }
18
21
 
19
22
  /**
20
23
  * InLocal storage factory for standalone client-side SplitFactory
21
24
  */
22
- export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): IStorageSyncFactory {
25
+ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyncFactory {
23
26
 
24
27
  const prefix = validatePrefix(options.prefix);
25
28
 
@@ -31,11 +34,12 @@ export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): ISt
31
34
  return InMemoryStorageCSFactory(params);
32
35
  }
33
36
 
34
- const { settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode } } } = params;
37
+ const { settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize } } } = params;
35
38
  const matchingKey = getMatching(settings.core.key);
36
39
  const keys = new KeyBuilderCS(prefix, matchingKey);
40
+ const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
37
41
 
38
- const splits = new SplitsCacheInLocal(settings, keys);
42
+ const splits = new SplitsCacheInLocal(settings, keys, expirationTimestamp);
39
43
  const segments = new MySegmentsCacheInLocal(log, keys);
40
44
  const largeSegments = new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey));
41
45
 
@@ -44,14 +48,10 @@ export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): ISt
44
48
  segments,
45
49
  largeSegments,
46
50
  impressions: new ImpressionsCacheInMemory(impressionsQueueSize),
47
- impressionCounts: impressionsMode !== DEBUG ? new ImpressionCountsCacheInMemory() : undefined,
51
+ impressionCounts: new ImpressionCountsCacheInMemory(),
48
52
  events: new EventsCacheInMemory(eventsQueueSize),
49
53
  telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
50
- uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS() : undefined,
51
-
52
- validateCache() {
53
- return validateCache(options, settings, keys, splits, segments, largeSegments);
54
- },
54
+ uniqueKeys: new UniqueKeysCacheInMemoryCS(),
55
55
 
56
56
  destroy() { },
57
57
 
@@ -66,6 +66,7 @@ export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): ISt
66
66
  impressionCounts: this.impressionCounts,
67
67
  events: this.events,
68
68
  telemetry: this.telemetry,
69
+ uniqueKeys: this.uniqueKeys,
69
70
 
70
71
  destroy() { }
71
72
  };
@@ -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 { DEBUG, LOCALHOST_MODE, NONE, STORAGE_MEMORY } from '../../utils/constants';
7
+ import { LOCALHOST_MODE, 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: { impressionsMode, __splitFiltersValidation } } } = params;
17
+ const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { __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: impressionsMode !== DEBUG ? new ImpressionCountsCacheInMemory() : undefined,
26
+ impressionCounts: new ImpressionCountsCacheInMemory(),
27
27
  events: new EventsCacheInMemory(eventsQueueSize),
28
28
  telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
29
- uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemory() : undefined,
29
+ uniqueKeys: new UniqueKeysCacheInMemory(),
30
30
 
31
31
  destroy() { }
32
32
  };
@@ -37,8 +37,8 @@ export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageS
37
37
  const noopTrack = () => true;
38
38
  storage.impressions.track = noopTrack;
39
39
  storage.events.track = noopTrack;
40
- if (storage.impressionCounts) storage.impressionCounts.track = noopTrack;
41
- if (storage.uniqueKeys) storage.uniqueKeys.track = noopTrack;
40
+ storage.impressionCounts.track = noopTrack;
41
+ storage.uniqueKeys.track = noopTrack;
42
42
  }
43
43
 
44
44
  return storage;