@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
|
@@ -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 {
|
|
7
|
+
import { LOCALHOST_MODE, 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
|
|
17
|
+
const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize }, sync: { __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:
|
|
28
|
+
impressionCounts: new ImpressionCountsCacheInMemory(),
|
|
29
29
|
events: new EventsCacheInMemory(eventsQueueSize),
|
|
30
30
|
telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
|
|
31
|
-
uniqueKeys:
|
|
31
|
+
uniqueKeys: new UniqueKeysCacheInMemoryCS(),
|
|
32
32
|
|
|
33
33
|
destroy() { },
|
|
34
34
|
|
|
@@ -42,6 +42,7 @@ 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,
|
|
45
46
|
|
|
46
47
|
destroy() { }
|
|
47
48
|
};
|
|
@@ -54,8 +55,8 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
|
|
|
54
55
|
const noopTrack = () => true;
|
|
55
56
|
storage.impressions.track = noopTrack;
|
|
56
57
|
storage.events.track = noopTrack;
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
storage.impressionCounts.track = noopTrack;
|
|
59
|
+
storage.uniqueKeys.track = noopTrack;
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
return storage;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export const LOG_PREFIX = 'storage:redis: ';
|
|
2
2
|
export const DEFAULT_CACHE_SIZE = 30000;
|
|
3
|
-
export const REFRESH_RATE = 300000; //
|
|
3
|
+
export const REFRESH_RATE = 300000; // 300000 ms = start after 5 mins
|
|
4
4
|
export const TTL_REFRESH = 3600; // 1hr
|
|
@@ -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 {
|
|
9
|
+
import { 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
|
|
33
|
+
const { onReadyCb, settings, settings: { log } } = 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 =
|
|
39
|
-
const uniqueKeysCache =
|
|
38
|
+
const impressionCountsCache = new ImpressionCountsCacheInRedis(log, keys.buildImpressionsCountKey(), redisClient);
|
|
39
|
+
const uniqueKeysCache = new UniqueKeysCacheInRedis(log, keys.buildUniqueKeysKey(), redisClient);
|
|
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
|
-
|
|
45
|
-
|
|
44
|
+
impressionCountsCache.start();
|
|
45
|
+
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
return Promise.all([
|
|
64
|
+
impressionCountsCache.stop(),
|
|
65
|
+
uniqueKeysCache.stop()
|
|
66
|
+
]).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,
|
|
11
|
+
import { CONSUMER_PARTIAL_MODE, 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,37 +63,32 @@ 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,
|
|
66
|
+
const { onReadyCb, settings, settings: { log, mode, 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
|
|
71
|
+
const isSynchronizer = 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) ||
|
|
74
|
+
const telemetry = shouldRecordTelemetry(params) || isSynchronizer ?
|
|
75
75
|
isPartialConsumer ?
|
|
76
76
|
new TelemetryCacheInMemory() :
|
|
77
77
|
new TelemetryCachePluggable(log, keys, wrapper) :
|
|
78
78
|
undefined;
|
|
79
79
|
|
|
80
|
-
const impressionCountsCache =
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
new ImpressionCountsCachePluggable(log, keys.buildImpressionsCountKey(), wrapper) :
|
|
84
|
-
undefined;
|
|
80
|
+
const impressionCountsCache = isPartialConsumer ?
|
|
81
|
+
new ImpressionCountsCacheInMemory() :
|
|
82
|
+
new ImpressionCountsCachePluggable(log, keys.buildImpressionsCountKey(), wrapper);
|
|
85
83
|
|
|
86
|
-
const uniqueKeysCache =
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
new UniqueKeysCachePluggable(log, keys.buildUniqueKeysKey(), wrapper) :
|
|
90
|
-
undefined;
|
|
84
|
+
const uniqueKeysCache = isPartialConsumer ?
|
|
85
|
+
settings.core.key === undefined ? new UniqueKeysCacheInMemory() : new UniqueKeysCacheInMemoryCS() :
|
|
86
|
+
new UniqueKeysCachePluggable(log, keys.buildUniqueKeysKey(), wrapper);
|
|
91
87
|
|
|
92
88
|
// Connects to wrapper and emits SDK_READY event on main client
|
|
93
89
|
const connectPromise = wrapper.connect().then(() => {
|
|
94
|
-
if (
|
|
95
|
-
//
|
|
96
|
-
// In standalone or producer mode, clear storage if SDK key, flags filter criteria or flags spec version was modified
|
|
90
|
+
if (isSynchronizer) {
|
|
91
|
+
// In standalone or producer mode, clear storage if SDK key or feature flag filter has changed
|
|
97
92
|
return wrapper.get(keys.buildHashKey()).then((hash) => {
|
|
98
93
|
const currentHash = getStorageHash(settings);
|
|
99
94
|
if (hash !== currentHash) {
|
|
@@ -107,8 +102,8 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
|
|
|
107
102
|
});
|
|
108
103
|
} else {
|
|
109
104
|
// Start periodic flush of async storages if not running synchronizer (producer mode)
|
|
110
|
-
if (
|
|
111
|
-
if (
|
|
105
|
+
if ((impressionCountsCache as ImpressionCountsCachePluggable).start) (impressionCountsCache as ImpressionCountsCachePluggable).start();
|
|
106
|
+
if ((uniqueKeysCache as UniqueKeysCachePluggable).start) (uniqueKeysCache as UniqueKeysCachePluggable).start();
|
|
112
107
|
if (telemetry && (telemetry as ITelemetryCacheAsync).recordConfig) (telemetry as ITelemetryCacheAsync).recordConfig();
|
|
113
108
|
|
|
114
109
|
onReadyCb();
|
|
@@ -130,9 +125,9 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
|
|
|
130
125
|
|
|
131
126
|
// Stop periodic flush and disconnect the underlying storage
|
|
132
127
|
destroy() {
|
|
133
|
-
return Promise.all(
|
|
134
|
-
|
|
135
|
-
|
|
128
|
+
return Promise.all(isSynchronizer ? [] : [
|
|
129
|
+
(impressionCountsCache as ImpressionCountsCachePluggable).stop && (impressionCountsCache as ImpressionCountsCachePluggable).stop(),
|
|
130
|
+
(uniqueKeysCache as UniqueKeysCachePluggable).stop && (uniqueKeysCache as UniqueKeysCachePluggable).stop(),
|
|
136
131
|
]).then(() => wrapper.disconnect());
|
|
137
132
|
},
|
|
138
133
|
|
package/src/storages/types.ts
CHANGED
|
@@ -191,6 +191,8 @@ 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>,
|
|
194
196
|
killLocally(name: string, defaultTreatment: string, changeNumber: number): MaybeThenable<boolean>,
|
|
195
197
|
getNamesByFlagSets(flagSets: string[]): MaybeThenable<Set<string>[]>
|
|
196
198
|
}
|
|
@@ -207,6 +209,7 @@ export interface ISplitsCacheSync extends ISplitsCacheBase {
|
|
|
207
209
|
trafficTypeExists(trafficType: string): boolean,
|
|
208
210
|
usesSegments(): boolean,
|
|
209
211
|
clear(): void,
|
|
212
|
+
checkCache(): boolean,
|
|
210
213
|
killLocally(name: string, defaultTreatment: string, changeNumber: number): boolean,
|
|
211
214
|
getNamesByFlagSets(flagSets: string[]): Set<string>[]
|
|
212
215
|
}
|
|
@@ -223,6 +226,7 @@ export interface ISplitsCacheAsync extends ISplitsCacheBase {
|
|
|
223
226
|
trafficTypeExists(trafficType: string): Promise<boolean>,
|
|
224
227
|
usesSegments(): Promise<boolean>,
|
|
225
228
|
clear(): Promise<boolean | void>,
|
|
229
|
+
checkCache(): Promise<boolean>,
|
|
226
230
|
killLocally(name: string, defaultTreatment: string, changeNumber: number): Promise<boolean>,
|
|
227
231
|
getNamesByFlagSets(flagSets: string[]): Promise<Set<string>[]>
|
|
228
232
|
}
|
|
@@ -435,10 +439,10 @@ export interface IStorageBase<
|
|
|
435
439
|
splits: TSplitsCache,
|
|
436
440
|
segments: TSegmentsCache,
|
|
437
441
|
impressions: TImpressionsCache,
|
|
438
|
-
impressionCounts
|
|
442
|
+
impressionCounts: TImpressionsCountCache,
|
|
439
443
|
events: TEventsCache,
|
|
440
444
|
telemetry?: TTelemetryCache,
|
|
441
|
-
uniqueKeys
|
|
445
|
+
uniqueKeys: TUniqueKeysCache,
|
|
442
446
|
destroy(): void | Promise<void>,
|
|
443
447
|
shared?: (matchingKey: string, onReadyCb: (error?: any) => void) => this
|
|
444
448
|
}
|
|
@@ -453,7 +457,6 @@ export interface IStorageSync extends IStorageBase<
|
|
|
453
457
|
IUniqueKeysCacheSync
|
|
454
458
|
> {
|
|
455
459
|
// Defined in client-side
|
|
456
|
-
validateCache?: () => boolean, // @TODO support async
|
|
457
460
|
largeSegments?: ISegmentsCacheSync,
|
|
458
461
|
}
|
|
459
462
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { forOwn } from '../../../utils/lang';
|
|
2
2
|
import { IReadinessManager } from '../../../readiness/types';
|
|
3
|
-
import {
|
|
3
|
+
import { ISplitsCacheSync } from '../../../storages/types';
|
|
4
4
|
import { ISplitsParser } from '../splitsParser/types';
|
|
5
5
|
import { ISplit, ISplitPartial } from '../../../dtos/types';
|
|
6
6
|
import { syncTaskFactory } from '../../syncTask';
|
|
@@ -15,7 +15,7 @@ import { SYNC_OFFLINE_DATA, ERROR_SYNC_OFFLINE_LOADING } from '../../../logger/c
|
|
|
15
15
|
*/
|
|
16
16
|
export function fromObjectUpdaterFactory(
|
|
17
17
|
splitsParser: ISplitsParser,
|
|
18
|
-
storage:
|
|
18
|
+
storage: { splits: ISplitsCacheSync },
|
|
19
19
|
readiness: IReadinessManager,
|
|
20
20
|
settings: ISettings,
|
|
21
21
|
): () => Promise<boolean> {
|
|
@@ -60,10 +60,9 @@ export function fromObjectUpdaterFactory(
|
|
|
60
60
|
|
|
61
61
|
if (startingUp) {
|
|
62
62
|
startingUp = false;
|
|
63
|
-
|
|
64
|
-
Promise.resolve().then(() => {
|
|
63
|
+
Promise.resolve(splitsCache.checkCache()).then(cacheReady => {
|
|
65
64
|
// Emits SDK_READY_FROM_CACHE
|
|
66
|
-
if (
|
|
65
|
+
if (cacheReady) readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
|
|
67
66
|
// Emits SDK_READY
|
|
68
67
|
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
|
|
69
68
|
});
|
|
@@ -81,7 +80,7 @@ export function fromObjectUpdaterFactory(
|
|
|
81
80
|
*/
|
|
82
81
|
export function fromObjectSyncTaskFactory(
|
|
83
82
|
splitsParser: ISplitsParser,
|
|
84
|
-
storage:
|
|
83
|
+
storage: { splits: ISplitsCacheSync },
|
|
85
84
|
readiness: IReadinessManager,
|
|
86
85
|
settings: ISettings
|
|
87
86
|
): ISyncTask<[], boolean> {
|
|
@@ -3,7 +3,7 @@ import { ISplitChangesFetcher } from '../fetchers/types';
|
|
|
3
3
|
import { ISplit, ISplitChangesResponse, ISplitFiltersValidation } from '../../../dtos/types';
|
|
4
4
|
import { ISplitsEventEmitter } from '../../../readiness/types';
|
|
5
5
|
import { timeout } from '../../../utils/promise/timeout';
|
|
6
|
-
import { SDK_SPLITS_ARRIVED } from '../../../readiness/constants';
|
|
6
|
+
import { SDK_SPLITS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../../../readiness/constants';
|
|
7
7
|
import { ILogger } from '../../../logger/types';
|
|
8
8
|
import { SYNC_SPLITS_FETCH, SYNC_SPLITS_NEW, SYNC_SPLITS_REMOVED, SYNC_SPLITS_SEGMENTS, SYNC_SPLITS_FETCH_FAILS, SYNC_SPLITS_FETCH_RETRY } from '../../../logger/constants';
|
|
9
9
|
import { startsWith } from '../../../utils/lang';
|
|
@@ -153,7 +153,7 @@ export function splitChangesUpdaterFactory(
|
|
|
153
153
|
*/
|
|
154
154
|
function _splitChangesUpdater(since: number, retry = 0): Promise<boolean> {
|
|
155
155
|
log.debug(SYNC_SPLITS_FETCH, [since]);
|
|
156
|
-
|
|
156
|
+
const fetcherPromise = Promise.resolve(splitUpdateNotification ?
|
|
157
157
|
{ splits: [splitUpdateNotification.payload], till: splitUpdateNotification.changeNumber } :
|
|
158
158
|
splitChangesFetcher(since, noCache, till, _promiseDecorator)
|
|
159
159
|
)
|
|
@@ -200,6 +200,15 @@ export function splitChangesUpdaterFactory(
|
|
|
200
200
|
}
|
|
201
201
|
return false;
|
|
202
202
|
});
|
|
203
|
+
|
|
204
|
+
// After triggering the requests, if we have cached splits information let's notify that to emit SDK_READY_FROM_CACHE.
|
|
205
|
+
// Wrapping in a promise since checkCache can be async.
|
|
206
|
+
if (splitsEventEmitter && startingUp) {
|
|
207
|
+
Promise.resolve(splits.checkCache()).then(isCacheReady => {
|
|
208
|
+
if (isCacheReady) splitsEventEmitter.emit(SDK_SPLITS_CACHE_LOADED);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
return fetcherPromise;
|
|
203
212
|
}
|
|
204
213
|
|
|
205
214
|
let sincePromise = Promise.resolve(splits.getChangeNumber()); // `getChangeNumber` never rejects or throws error
|
|
@@ -39,8 +39,6 @@ export function impressionCountsSubmitterFactory(params: ISdkFactoryContextSync)
|
|
|
39
39
|
storage: { impressionCounts }
|
|
40
40
|
} = params;
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return submitterFactory(log, postTestImpressionsCount, impressionCounts, IMPRESSIONS_COUNT_RATE, 'impression counts', fromImpressionCountsCollector, 1);
|
|
45
|
-
}
|
|
42
|
+
// retry impressions counts only once.
|
|
43
|
+
return submitterFactory(log, postTestImpressionsCount, impressionCounts, IMPRESSIONS_COUNT_RATE, 'impression counts', fromImpressionCountsCollector, 1);
|
|
46
44
|
}
|
|
@@ -10,13 +10,12 @@ export function submitterManagerFactory(params: ISdkFactoryContextSync): ISubmit
|
|
|
10
10
|
|
|
11
11
|
const submitters = [
|
|
12
12
|
impressionsSubmitterFactory(params),
|
|
13
|
-
eventsSubmitterFactory(params)
|
|
13
|
+
eventsSubmitterFactory(params),
|
|
14
|
+
impressionCountsSubmitterFactory(params),
|
|
15
|
+
uniqueKeysSubmitterFactory(params)
|
|
14
16
|
];
|
|
15
17
|
|
|
16
|
-
const impressionCountsSubmitter = impressionCountsSubmitterFactory(params);
|
|
17
|
-
if (impressionCountsSubmitter) submitters.push(impressionCountsSubmitter);
|
|
18
18
|
const telemetrySubmitter = telemetrySubmitterFactory(params);
|
|
19
|
-
if (params.storage.uniqueKeys) submitters.push(uniqueKeysSubmitterFactory(params));
|
|
20
19
|
|
|
21
20
|
return {
|
|
22
21
|
// `onlyTelemetry` true if SDK is created with userConsent not GRANTED
|
|
@@ -19,10 +19,10 @@ export function uniqueKeysSubmitterFactory(params: ISdkFactoryContextSync) {
|
|
|
19
19
|
const isClientSide = key !== undefined;
|
|
20
20
|
const postUniqueKeysBulk = isClientSide ? postUniqueKeysBulkCs : postUniqueKeysBulkSs;
|
|
21
21
|
|
|
22
|
-
const syncTask = submitterFactory(log, postUniqueKeysBulk, uniqueKeys
|
|
22
|
+
const syncTask = submitterFactory(log, postUniqueKeysBulk, uniqueKeys, UNIQUE_KEYS_RATE, DATA_NAME);
|
|
23
23
|
|
|
24
24
|
// register unique keys submitter to be executed when uniqueKeys cache is full
|
|
25
|
-
uniqueKeys
|
|
25
|
+
uniqueKeys.setOnFullQueueCb(() => {
|
|
26
26
|
if (syncTask.isRunning()) {
|
|
27
27
|
log.info(SUBMITTERS_PUSH_FULL_QUEUE, [DATA_NAME]);
|
|
28
28
|
syncTask.execute();
|
|
@@ -33,4 +33,3 @@ export function uniqueKeysSubmitterFactory(params: ISdkFactoryContextSync) {
|
|
|
33
33
|
|
|
34
34
|
return syncTask;
|
|
35
35
|
}
|
|
36
|
-
|
|
@@ -9,7 +9,6 @@ import { SYNC_START_POLLING, SYNC_CONTINUE_POLLING, SYNC_STOP_POLLING } from '..
|
|
|
9
9
|
import { isConsentGranted } from '../consent';
|
|
10
10
|
import { POLLING, STREAMING, SYNC_MODE_UPDATE } from '../utils/constants';
|
|
11
11
|
import { ISdkFactoryContextSync } from '../sdkFactory/types';
|
|
12
|
-
import { SDK_SPLITS_CACHE_LOADED } from '../readiness/constants';
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
14
|
* Online SyncManager factory.
|
|
@@ -29,7 +28,7 @@ export function syncManagerOnlineFactory(
|
|
|
29
28
|
*/
|
|
30
29
|
return function (params: ISdkFactoryContextSync): ISyncManagerCS {
|
|
31
30
|
|
|
32
|
-
const { settings, settings: { log, streamingEnabled, sync: { enabled: syncEnabled } },
|
|
31
|
+
const { settings, settings: { log, streamingEnabled, sync: { enabled: syncEnabled } }, telemetryTracker } = params;
|
|
33
32
|
|
|
34
33
|
/** Polling Manager */
|
|
35
34
|
const pollingManager = pollingManagerFactory && pollingManagerFactory(params);
|
|
@@ -88,11 +87,6 @@ export function syncManagerOnlineFactory(
|
|
|
88
87
|
start() {
|
|
89
88
|
running = true;
|
|
90
89
|
|
|
91
|
-
if (startFirstTime) {
|
|
92
|
-
const isCacheLoaded = storage.validateCache ? storage.validateCache() : false;
|
|
93
|
-
if (isCacheLoaded) Promise.resolve().then(() => { readiness.splits.emit(SDK_SPLITS_CACHE_LOADED); });
|
|
94
|
-
}
|
|
95
|
-
|
|
96
90
|
// start syncing splits and segments
|
|
97
91
|
if (pollingManager) {
|
|
98
92
|
|
|
@@ -102,6 +96,7 @@ export function syncManagerOnlineFactory(
|
|
|
102
96
|
// Doesn't call `syncAll` when the syncManager is resuming
|
|
103
97
|
if (startFirstTime) {
|
|
104
98
|
pollingManager.syncAll();
|
|
99
|
+
startFirstTime = false;
|
|
105
100
|
}
|
|
106
101
|
pushManager.start();
|
|
107
102
|
} else {
|
|
@@ -110,14 +105,13 @@ export function syncManagerOnlineFactory(
|
|
|
110
105
|
} else {
|
|
111
106
|
if (startFirstTime) {
|
|
112
107
|
pollingManager.syncAll();
|
|
108
|
+
startFirstTime = false;
|
|
113
109
|
}
|
|
114
110
|
}
|
|
115
111
|
}
|
|
116
112
|
|
|
117
113
|
// start periodic data recording (events, impressions, telemetry).
|
|
118
114
|
submitterManager.start(!isConsentGranted(settings));
|
|
119
|
-
|
|
120
|
-
startFirstTime = false;
|
|
121
115
|
},
|
|
122
116
|
|
|
123
117
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
2
2
|
import { thenable } from '../utils/promise/thenable';
|
|
3
3
|
import { IImpressionsCacheBase, ITelemetryCacheSync, ITelemetryCacheAsync } from '../storages/types';
|
|
4
|
-
import { IImpressionsHandler, IImpressionsTracker, IStrategy } from './types';
|
|
4
|
+
import { IImpressionsHandler, IImpressionsTracker, ImpressionDecorated, IStrategy } from './types';
|
|
5
5
|
import { ISettings } from '../types';
|
|
6
6
|
import { IMPRESSIONS_TRACKER_SUCCESS, ERROR_IMPRESSIONS_TRACKER, ERROR_IMPRESSIONS_LISTENER } from '../logger/constants';
|
|
7
7
|
import { CONSENT_DECLINED, DEDUPED, QUEUED } from '../utils/constants';
|
|
@@ -9,16 +9,11 @@ import SplitIO from '../../types/splitio';
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Impressions tracker stores impressions in cache and pass them to the listener and integrations manager if provided.
|
|
12
|
-
*
|
|
13
|
-
* @param impressionsCache - cache to save impressions
|
|
14
|
-
* @param metadata - runtime metadata (ip, hostname and version)
|
|
15
|
-
* @param impressionListener - optional impression listener
|
|
16
|
-
* @param integrationsManager - optional integrations manager
|
|
17
|
-
* @param strategy - strategy for impressions tracking.
|
|
18
12
|
*/
|
|
19
13
|
export function impressionsTrackerFactory(
|
|
20
14
|
settings: ISettings,
|
|
21
15
|
impressionsCache: IImpressionsCacheBase,
|
|
16
|
+
noneStrategy: IStrategy,
|
|
22
17
|
strategy: IStrategy,
|
|
23
18
|
whenInit: (cb: () => void) => void,
|
|
24
19
|
integrationsManager?: IImpressionsHandler,
|
|
@@ -28,40 +23,44 @@ export function impressionsTrackerFactory(
|
|
|
28
23
|
const { log, impressionListener, runtime: { ip, hostname }, version } = settings;
|
|
29
24
|
|
|
30
25
|
return {
|
|
31
|
-
track(impressions:
|
|
26
|
+
track(impressions: ImpressionDecorated[], attributes?: SplitIO.Attributes) {
|
|
32
27
|
if (settings.userConsent === CONSENT_DECLINED) return;
|
|
33
28
|
|
|
34
|
-
const
|
|
35
|
-
|
|
29
|
+
const impressionsToStore = impressions.filter(({ imp, disabled }) => {
|
|
30
|
+
return disabled ?
|
|
31
|
+
noneStrategy.process(imp) :
|
|
32
|
+
strategy.process(imp);
|
|
33
|
+
});
|
|
36
34
|
|
|
37
|
-
const
|
|
35
|
+
const impressionsLength = impressions.length;
|
|
36
|
+
const impressionsToStoreLength = impressionsToStore.length;
|
|
38
37
|
|
|
39
|
-
if (
|
|
40
|
-
const res = impressionsCache.track(impressionsToStore);
|
|
38
|
+
if (impressionsToStoreLength) {
|
|
39
|
+
const res = impressionsCache.track(impressionsToStore.map((item) => item.imp));
|
|
41
40
|
|
|
42
41
|
// If we're on an async storage, handle error and log it.
|
|
43
42
|
if (thenable(res)) {
|
|
44
43
|
res.then(() => {
|
|
45
|
-
log.info(IMPRESSIONS_TRACKER_SUCCESS, [
|
|
44
|
+
log.info(IMPRESSIONS_TRACKER_SUCCESS, [impressionsLength]);
|
|
46
45
|
}).catch(err => {
|
|
47
|
-
log.error(ERROR_IMPRESSIONS_TRACKER, [
|
|
46
|
+
log.error(ERROR_IMPRESSIONS_TRACKER, [impressionsLength, err]);
|
|
48
47
|
});
|
|
49
48
|
} else {
|
|
50
49
|
// Record when impressionsCache is sync only (standalone mode)
|
|
51
50
|
// @TODO we are not dropping impressions on full queue yet, so DROPPED stats are not recorded
|
|
52
51
|
if (telemetryCache) {
|
|
53
|
-
(telemetryCache as ITelemetryCacheSync).recordImpressionStats(QUEUED,
|
|
54
|
-
(telemetryCache as ITelemetryCacheSync).recordImpressionStats(DEDUPED,
|
|
52
|
+
(telemetryCache as ITelemetryCacheSync).recordImpressionStats(QUEUED, impressionsToStoreLength);
|
|
53
|
+
(telemetryCache as ITelemetryCacheSync).recordImpressionStats(DEDUPED, impressionsLength - impressionsToStoreLength);
|
|
55
54
|
}
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
// @TODO next block might be handled by the integration manager. In that case, the metadata object doesn't need to be passed in the constructor
|
|
60
59
|
if (impressionListener || integrationsManager) {
|
|
61
|
-
for (let i = 0; i <
|
|
60
|
+
for (let i = 0; i < impressionsLength; i++) {
|
|
62
61
|
const impressionData: SplitIO.ImpressionData = {
|
|
63
62
|
// copy of impression, to avoid unexpected behavior if modified by integrations or impressionListener
|
|
64
|
-
impression: objectAssign({},
|
|
63
|
+
impression: objectAssign({}, impressions[i].imp),
|
|
65
64
|
attributes,
|
|
66
65
|
ip,
|
|
67
66
|
hostname,
|
|
@@ -6,23 +6,16 @@ import { IStrategy } from '../types';
|
|
|
6
6
|
* Debug strategy for impressions tracker. Wraps impressions to store and adds previousTime if it corresponds
|
|
7
7
|
*
|
|
8
8
|
* @param impressionsObserver - impression observer. Previous time (pt property) is included in impression instances
|
|
9
|
-
* @returns
|
|
9
|
+
* @returns Debug strategy
|
|
10
10
|
*/
|
|
11
11
|
export function strategyDebugFactory(
|
|
12
12
|
impressionsObserver: IImpressionObserver
|
|
13
13
|
): IStrategy {
|
|
14
14
|
|
|
15
15
|
return {
|
|
16
|
-
process(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
impression.pt = impressionsObserver.testAndSet(impression);
|
|
20
|
-
});
|
|
21
|
-
return {
|
|
22
|
-
impressionsToStore: impressions,
|
|
23
|
-
impressionsToListener: impressions,
|
|
24
|
-
deduped: 0
|
|
25
|
-
};
|
|
16
|
+
process(impression: SplitIO.ImpressionDTO) {
|
|
17
|
+
impression.pt = impressionsObserver.testAndSet(impression);
|
|
18
|
+
return true;
|
|
26
19
|
}
|
|
27
20
|
};
|
|
28
21
|
}
|
|
@@ -5,30 +5,24 @@ import { IStrategy, IUniqueKeysTracker } from '../types';
|
|
|
5
5
|
/**
|
|
6
6
|
* None strategy for impressions tracker.
|
|
7
7
|
*
|
|
8
|
-
* @param
|
|
8
|
+
* @param impressionCounts - cache to save impressions count. impressions will be deduped (OPTIMIZED mode)
|
|
9
9
|
* @param uniqueKeysTracker - unique keys tracker in charge of tracking the unique keys per split.
|
|
10
|
-
* @returns
|
|
10
|
+
* @returns None strategy
|
|
11
11
|
*/
|
|
12
12
|
export function strategyNoneFactory(
|
|
13
|
-
|
|
13
|
+
impressionCounts: IImpressionCountsCacheBase,
|
|
14
14
|
uniqueKeysTracker: IUniqueKeysTracker
|
|
15
15
|
): IStrategy {
|
|
16
16
|
|
|
17
17
|
return {
|
|
18
|
-
process(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return {
|
|
28
|
-
impressionsToStore: [],
|
|
29
|
-
impressionsToListener: impressions,
|
|
30
|
-
deduped: 0
|
|
31
|
-
};
|
|
18
|
+
process(impression: SplitIO.ImpressionDTO) {
|
|
19
|
+
const now = Date.now();
|
|
20
|
+
// Increments impression counter per featureName
|
|
21
|
+
impressionCounts.track(impression.feature, now, 1);
|
|
22
|
+
// Keep track by unique key
|
|
23
|
+
uniqueKeysTracker.track(impression.keyName, impression.feature);
|
|
24
|
+
// Do not store impressions
|
|
25
|
+
return false;
|
|
32
26
|
}
|
|
33
27
|
};
|
|
34
28
|
}
|
|
@@ -8,35 +8,25 @@ import { IStrategy } from '../types';
|
|
|
8
8
|
* Optimized strategy for impressions tracker. Wraps impressions to store and adds previousTime if it corresponds
|
|
9
9
|
*
|
|
10
10
|
* @param impressionsObserver - impression observer. previous time (pt property) is included in impression instances
|
|
11
|
-
* @param
|
|
12
|
-
* @returns
|
|
11
|
+
* @param impressionCounts - cache to save impressions count. impressions will be deduped (OPTIMIZED mode)
|
|
12
|
+
* @returns Optimized strategy
|
|
13
13
|
*/
|
|
14
14
|
export function strategyOptimizedFactory(
|
|
15
15
|
impressionsObserver: IImpressionObserver,
|
|
16
|
-
|
|
16
|
+
impressionCounts: IImpressionCountsCacheBase,
|
|
17
17
|
): IStrategy {
|
|
18
18
|
|
|
19
19
|
return {
|
|
20
|
-
process(
|
|
21
|
-
|
|
22
|
-
impressions.forEach((impression) => {
|
|
23
|
-
impression.pt = impressionsObserver.testAndSet(impression);
|
|
20
|
+
process(impression: SplitIO.ImpressionDTO) {
|
|
21
|
+
impression.pt = impressionsObserver.testAndSet(impression);
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
const now = Date.now();
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
// Increments impression counter per featureName
|
|
26
|
+
if (impression.pt) impressionCounts.track(impression.feature, now, 1);
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
impressionsToStore.push(impression);
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
return {
|
|
36
|
-
impressionsToStore: impressionsToStore,
|
|
37
|
-
impressionsToListener: impressions,
|
|
38
|
-
deduped: impressions.length - impressionsToStore.length
|
|
39
|
-
};
|
|
28
|
+
// Checks if the impression should be added in queue to be sent
|
|
29
|
+
return (!impression.pt || impression.pt < truncateTimeFrame(now)) ? true : false;
|
|
40
30
|
}
|
|
41
31
|
};
|
|
42
32
|
}
|
package/src/trackers/types.ts
CHANGED
|
@@ -17,8 +17,19 @@ export interface IImpressionsHandler {
|
|
|
17
17
|
handleImpression(impressionData: SplitIO.ImpressionData): any
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
export type ImpressionDecorated = {
|
|
21
|
+
/**
|
|
22
|
+
* Impression DTO
|
|
23
|
+
*/
|
|
24
|
+
imp: SplitIO.ImpressionDTO,
|
|
25
|
+
/**
|
|
26
|
+
* Whether the impression should be tracked or not
|
|
27
|
+
*/
|
|
28
|
+
disabled?: boolean
|
|
29
|
+
};
|
|
30
|
+
|
|
20
31
|
export interface IImpressionsTracker {
|
|
21
|
-
track(impressions:
|
|
32
|
+
track(impressions: ImpressionDecorated[], attributes?: SplitIO.Attributes): void
|
|
22
33
|
}
|
|
23
34
|
|
|
24
35
|
/** Telemetry tracker */
|
|
@@ -70,12 +81,6 @@ export interface IUniqueKeysTracker {
|
|
|
70
81
|
track(key: string, featureName: string): void;
|
|
71
82
|
}
|
|
72
83
|
|
|
73
|
-
export interface IStrategyResult {
|
|
74
|
-
impressionsToStore: SplitIO.ImpressionDTO[],
|
|
75
|
-
impressionsToListener: SplitIO.ImpressionDTO[],
|
|
76
|
-
deduped: number
|
|
77
|
-
}
|
|
78
|
-
|
|
79
84
|
export interface IStrategy {
|
|
80
|
-
process(
|
|
85
|
+
process(impression: SplitIO.ImpressionDTO): boolean
|
|
81
86
|
}
|