@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.
- package/CHANGES.txt +4 -5
- package/LICENSE +1 -1
- package/cjs/evaluator/index.js +2 -0
- package/cjs/listeners/browser.js +4 -6
- package/cjs/readiness/readinessManager.js +0 -6
- package/cjs/sdkClient/client.js +14 -11
- package/cjs/sdkClient/sdkClient.js +1 -1
- package/cjs/sdkFactory/index.js +9 -14
- package/cjs/sdkManager/index.js +2 -1
- package/cjs/storages/AbstractSplitsCacheAsync.js +7 -0
- package/cjs/storages/AbstractSplitsCacheSync.js +7 -0
- package/cjs/storages/KeyBuilderCS.js +0 -3
- package/cjs/storages/dataLoader.js +2 -3
- package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +57 -1
- package/cjs/storages/inLocalStorage/index.js +7 -8
- package/cjs/storages/inMemory/InMemoryStorage.js +5 -7
- package/cjs/storages/inMemory/InMemoryStorageCS.js +6 -7
- package/cjs/storages/inRedis/constants.js +1 -1
- package/cjs/storages/inRedis/index.js +9 -13
- package/cjs/storages/pluggable/index.js +16 -21
- package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +2 -3
- package/cjs/sync/polling/updaters/splitChangesUpdater.js +10 -1
- package/cjs/sync/submitters/impressionCountsSubmitter.js +2 -4
- package/cjs/sync/submitters/submitterManager.js +3 -6
- package/cjs/sync/syncManagerOnline.js +3 -8
- package/cjs/trackers/impressionsTracker.js +17 -18
- package/cjs/trackers/strategy/strategyDebug.js +4 -11
- package/cjs/trackers/strategy/strategyNone.js +11 -16
- package/cjs/trackers/strategy/strategyOptimized.js +11 -21
- package/cjs/trackers/uniqueKeysTracker.js +1 -1
- package/cjs/utils/constants/browser.js +5 -0
- package/cjs/utils/settingsValidation/storage/storageCS.js +1 -1
- package/esm/evaluator/index.js +2 -0
- package/esm/listeners/browser.js +1 -3
- package/esm/readiness/readinessManager.js +0 -6
- package/esm/sdkClient/client.js +14 -11
- package/esm/sdkClient/sdkClient.js +1 -1
- package/esm/sdkFactory/index.js +10 -15
- package/esm/sdkManager/index.js +2 -1
- package/esm/storages/AbstractSplitsCacheAsync.js +7 -0
- package/esm/storages/AbstractSplitsCacheSync.js +7 -0
- package/esm/storages/KeyBuilderCS.js +0 -3
- package/esm/storages/dataLoader.js +1 -2
- package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +57 -1
- package/esm/storages/inLocalStorage/index.js +8 -9
- package/esm/storages/inMemory/InMemoryStorage.js +6 -8
- package/esm/storages/inMemory/InMemoryStorageCS.js +7 -8
- package/esm/storages/inRedis/constants.js +1 -1
- package/esm/storages/inRedis/index.js +10 -14
- package/esm/storages/pluggable/index.js +17 -22
- package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +2 -3
- package/esm/sync/polling/updaters/splitChangesUpdater.js +11 -2
- package/esm/sync/submitters/impressionCountsSubmitter.js +2 -4
- package/esm/sync/submitters/submitterManager.js +3 -6
- package/esm/sync/syncManagerOnline.js +3 -8
- package/esm/trackers/impressionsTracker.js +17 -18
- package/esm/trackers/strategy/strategyDebug.js +4 -11
- package/esm/trackers/strategy/strategyNone.js +11 -16
- package/esm/trackers/strategy/strategyOptimized.js +11 -21
- package/esm/trackers/uniqueKeysTracker.js +1 -1
- package/esm/utils/constants/browser.js +2 -0
- package/esm/utils/settingsValidation/storage/storageCS.js +1 -1
- package/package.json +1 -1
- package/src/dtos/types.ts +2 -1
- package/src/evaluator/index.ts +2 -0
- package/src/evaluator/types.ts +1 -1
- package/src/listeners/browser.ts +1 -3
- package/src/readiness/readinessManager.ts +0 -5
- package/src/sdkClient/client.ts +19 -15
- package/src/sdkClient/sdkClient.ts +1 -1
- package/src/sdkFactory/index.ts +11 -16
- package/src/sdkFactory/types.ts +1 -1
- package/src/sdkManager/index.ts +2 -1
- package/src/storages/AbstractSplitsCacheAsync.ts +8 -0
- package/src/storages/AbstractSplitsCacheSync.ts +8 -0
- package/src/storages/KeyBuilderCS.ts +0 -4
- package/src/storages/dataLoader.ts +1 -3
- package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +66 -1
- package/src/storages/inLocalStorage/index.ts +13 -12
- package/src/storages/inMemory/InMemoryStorage.ts +6 -6
- package/src/storages/inMemory/InMemoryStorageCS.ts +7 -6
- package/src/storages/inRedis/constants.ts +1 -1
- package/src/storages/inRedis/index.ts +10 -10
- package/src/storages/pluggable/index.ts +17 -22
- package/src/storages/types.ts +6 -3
- package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +5 -6
- package/src/sync/polling/updaters/splitChangesUpdater.ts +11 -2
- package/src/sync/submitters/impressionCountsSubmitter.ts +2 -4
- package/src/sync/submitters/submitterManager.ts +3 -4
- package/src/sync/submitters/uniqueKeysSubmitter.ts +2 -3
- package/src/sync/syncManagerOnline.ts +3 -9
- package/src/trackers/impressionsTracker.ts +18 -19
- package/src/trackers/strategy/strategyDebug.ts +4 -11
- package/src/trackers/strategy/strategyNone.ts +11 -17
- package/src/trackers/strategy/strategyOptimized.ts +10 -20
- package/src/trackers/types.ts +13 -8
- package/src/trackers/uniqueKeysTracker.ts +1 -1
- package/src/utils/constants/browser.ts +2 -0
- package/src/utils/lang/index.ts +1 -1
- package/src/utils/settingsValidation/storage/storageCS.ts +1 -1
- package/types/splitio.d.ts +5 -25
- package/cjs/storages/inLocalStorage/validateCache.js +0 -79
- package/esm/storages/inLocalStorage/validateCache.js +0 -75
- 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
|
|
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
|
|
6
|
+
* @returns None strategy
|
|
7
7
|
*/
|
|
8
|
-
export function strategyNoneFactory(
|
|
8
|
+
export function strategyNoneFactory(impressionCounts, uniqueKeysTracker) {
|
|
9
9
|
return {
|
|
10
|
-
process: function (
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
7
|
-
* @returns
|
|
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,
|
|
9
|
+
export function strategyOptimizedFactory(impressionsObserver, impressionCounts) {
|
|
10
10
|
return {
|
|
11
|
-
process: function (
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
*
|
|
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
|
*
|
|
@@ -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.
|
|
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
package/src/dtos/types.ts
CHANGED
package/src/evaluator/index.ts
CHANGED
|
@@ -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
|
|
package/src/evaluator/types.ts
CHANGED
|
@@ -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
|
|
package/src/listeners/browser.ts
CHANGED
|
@@ -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:
|
|
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
|
package/src/sdkClient/client.ts
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
|
64
|
+
uniqueKeysTracker.stop();
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
// Stop background jobs
|
package/src/sdkFactory/index.ts
CHANGED
|
@@ -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 {
|
|
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 =
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
97
|
+
uniqueKeysTracker.start();
|
|
103
98
|
syncManager && syncManager.start();
|
|
104
99
|
signalListener && signalListener.start();
|
|
105
100
|
|
package/src/sdkFactory/types.ts
CHANGED
|
@@ -46,7 +46,7 @@ export interface ISdkFactoryContext {
|
|
|
46
46
|
eventTracker: IEventTracker,
|
|
47
47
|
telemetryTracker: ITelemetryTracker,
|
|
48
48
|
storage: IStorageSync | IStorageAsync,
|
|
49
|
-
uniqueKeysTracker
|
|
49
|
+
uniqueKeysTracker: IUniqueKeysTracker,
|
|
50
50
|
signalListener?: ISignalListener
|
|
51
51
|
splitApi?: ISplitApi
|
|
52
52
|
syncManager?: ISyncManager,
|
package/src/sdkManager/index.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
17
|
-
|
|
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:
|
|
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
|
|
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:
|
|
51
|
+
impressionCounts: new ImpressionCountsCacheInMemory(),
|
|
48
52
|
events: new EventsCacheInMemory(eventsQueueSize),
|
|
49
53
|
telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
|
|
50
|
-
uniqueKeys:
|
|
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 {
|
|
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: {
|
|
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:
|
|
26
|
+
impressionCounts: new ImpressionCountsCacheInMemory(),
|
|
27
27
|
events: new EventsCacheInMemory(eventsQueueSize),
|
|
28
28
|
telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
|
|
29
|
-
uniqueKeys:
|
|
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
|
-
|
|
41
|
-
|
|
40
|
+
storage.impressionCounts.track = noopTrack;
|
|
41
|
+
storage.uniqueKeys.track = noopTrack;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
return storage;
|