@splitsoftware/splitio-commons 2.0.1 → 2.0.3-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES.txt +8 -0
- package/cjs/evaluator/index.js +2 -0
- package/cjs/listeners/browser.js +4 -6
- package/cjs/readiness/readinessManager.js +16 -18
- package/cjs/sdkClient/client.js +13 -13
- package/cjs/sdkClient/sdkClient.js +1 -1
- package/cjs/sdkFactory/index.js +10 -14
- package/cjs/sdkManager/index.js +2 -1
- package/cjs/services/decorateHeaders.js +6 -1
- package/cjs/services/splitHttpClient.js +1 -1
- package/cjs/storages/inLocalStorage/index.js +6 -20
- package/cjs/storages/inMemory/InMemoryStorage.js +4 -12
- package/cjs/storages/inMemory/InMemoryStorageCS.js +6 -19
- package/cjs/storages/inRedis/index.js +9 -13
- package/cjs/storages/pluggable/index.js +15 -19
- package/cjs/sync/submitters/impressionCountsSubmitter.js +2 -4
- package/cjs/sync/submitters/submitterManager.js +3 -6
- 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/esm/evaluator/index.js +2 -0
- package/esm/listeners/browser.js +1 -3
- package/esm/readiness/readinessManager.js +16 -18
- package/esm/sdkClient/client.js +13 -13
- package/esm/sdkClient/sdkClient.js +1 -1
- package/esm/sdkFactory/index.js +11 -15
- package/esm/sdkManager/index.js +2 -1
- package/esm/services/decorateHeaders.js +4 -0
- package/esm/services/splitHttpClient.js +2 -2
- package/esm/storages/inLocalStorage/index.js +7 -21
- package/esm/storages/inMemory/InMemoryStorage.js +5 -13
- package/esm/storages/inMemory/InMemoryStorageCS.js +7 -20
- package/esm/storages/inRedis/index.js +10 -14
- package/esm/storages/pluggable/index.js +16 -20
- package/esm/sync/submitters/impressionCountsSubmitter.js +2 -4
- package/esm/sync/submitters/submitterManager.js +3 -6
- 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/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 +15 -16
- package/src/sdkClient/client.ts +11 -11
- package/src/sdkClient/sdkClient.ts +1 -1
- package/src/sdkFactory/index.ts +12 -16
- package/src/sdkFactory/types.ts +1 -1
- package/src/sdkManager/index.ts +2 -1
- package/src/services/decorateHeaders.ts +5 -0
- package/src/services/splitHttpClient.ts +2 -2
- package/src/storages/inLocalStorage/index.ts +7 -20
- package/src/storages/inMemory/InMemoryStorage.ts +5 -13
- package/src/storages/inMemory/InMemoryStorageCS.ts +7 -20
- package/src/storages/inRedis/index.ts +10 -10
- package/src/storages/pluggable/index.ts +16 -20
- package/src/storages/types.ts +2 -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/trackers/impressionsTracker.ts +17 -18
- 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 +2 -8
- package/types/splitio.d.ts +4 -0
|
@@ -4,7 +4,7 @@ import { SplitsCacheInRedis } from './SplitsCacheInRedis';
|
|
|
4
4
|
import { SegmentsCacheInRedis } from './SegmentsCacheInRedis';
|
|
5
5
|
import { ImpressionsCacheInRedis } from './ImpressionsCacheInRedis';
|
|
6
6
|
import { EventsCacheInRedis } from './EventsCacheInRedis';
|
|
7
|
-
import {
|
|
7
|
+
import { STORAGE_REDIS } from '../../utils/constants';
|
|
8
8
|
import { TelemetryCacheInRedis } from './TelemetryCacheInRedis';
|
|
9
9
|
import { UniqueKeysCacheInRedis } from './UniqueKeysCacheInRedis';
|
|
10
10
|
import { ImpressionCountsCacheInRedis } from './ImpressionCountsCacheInRedis';
|
|
@@ -20,20 +20,18 @@ export function InRedisStorage(options) {
|
|
|
20
20
|
var RD = require('./RedisAdapter').RedisAdapter;
|
|
21
21
|
var prefix = validatePrefix(options.prefix);
|
|
22
22
|
function InRedisStorageFactory(params) {
|
|
23
|
-
var onReadyCb = params.onReadyCb, settings = params.settings,
|
|
23
|
+
var onReadyCb = params.onReadyCb, settings = params.settings, log = params.settings.log;
|
|
24
24
|
var metadata = metadataBuilder(settings);
|
|
25
25
|
var keys = new KeyBuilderSS(prefix, metadata);
|
|
26
26
|
var redisClient = new RD(log, options.options || {});
|
|
27
27
|
var telemetry = new TelemetryCacheInRedis(log, keys, redisClient);
|
|
28
|
-
var impressionCountsCache =
|
|
29
|
-
var uniqueKeysCache =
|
|
28
|
+
var impressionCountsCache = new ImpressionCountsCacheInRedis(log, keys.buildImpressionsCountKey(), redisClient);
|
|
29
|
+
var uniqueKeysCache = new UniqueKeysCacheInRedis(log, keys.buildUniqueKeysKey(), redisClient);
|
|
30
30
|
// subscription to Redis connect event in order to emit SDK_READY event on consumer mode
|
|
31
31
|
redisClient.on('connect', function () {
|
|
32
32
|
onReadyCb();
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (uniqueKeysCache)
|
|
36
|
-
uniqueKeysCache.start();
|
|
33
|
+
impressionCountsCache.start();
|
|
34
|
+
uniqueKeysCache.start();
|
|
37
35
|
// Synchronize config
|
|
38
36
|
telemetry.recordConfig();
|
|
39
37
|
});
|
|
@@ -48,12 +46,10 @@ export function InRedisStorage(options) {
|
|
|
48
46
|
// When using REDIS we should:
|
|
49
47
|
// 1- Disconnect from the storage
|
|
50
48
|
destroy: function () {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
promises.push(uniqueKeysCache.stop());
|
|
56
|
-
return Promise.all(promises).then(function () { redisClient.disconnect(); });
|
|
49
|
+
return Promise.all([
|
|
50
|
+
impressionCountsCache.stop(),
|
|
51
|
+
uniqueKeysCache.stop()
|
|
52
|
+
]).then(function () { redisClient.disconnect(); });
|
|
57
53
|
// @TODO check that caches works as expected when redisClient is disconnected
|
|
58
54
|
}
|
|
59
55
|
};
|
|
@@ -7,7 +7,7 @@ import { EventsCachePluggable } from './EventsCachePluggable';
|
|
|
7
7
|
import { wrapperAdapter, METHODS_TO_PROMISE_WRAP } from './wrapperAdapter';
|
|
8
8
|
import { isObject } from '../../utils/lang';
|
|
9
9
|
import { getStorageHash, validatePrefix } from '../KeyBuilder';
|
|
10
|
-
import { CONSUMER_PARTIAL_MODE,
|
|
10
|
+
import { CONSUMER_PARTIAL_MODE, STORAGE_PLUGGABLE } from '../../utils/constants';
|
|
11
11
|
import { ImpressionsCacheInMemory } from '../inMemory/ImpressionsCacheInMemory';
|
|
12
12
|
import { EventsCacheInMemory } from '../inMemory/EventsCacheInMemory';
|
|
13
13
|
import { ImpressionCountsCacheInMemory } from '../inMemory/ImpressionCountsCacheInMemory';
|
|
@@ -51,30 +51,26 @@ export function PluggableStorage(options) {
|
|
|
51
51
|
validatePluggableStorageOptions(options);
|
|
52
52
|
var prefix = validatePrefix(options.prefix);
|
|
53
53
|
function PluggableStorageFactory(params) {
|
|
54
|
-
var onReadyCb = params.onReadyCb, settings = params.settings, _a = params.settings, log = _a.log, mode = _a.mode,
|
|
54
|
+
var onReadyCb = params.onReadyCb, settings = params.settings, _a = params.settings, log = _a.log, mode = _a.mode, _b = _a.scheduler, impressionsQueueSize = _b.impressionsQueueSize, eventsQueueSize = _b.eventsQueueSize;
|
|
55
55
|
var metadata = metadataBuilder(settings);
|
|
56
56
|
var keys = new KeyBuilderSS(prefix, metadata);
|
|
57
57
|
var wrapper = wrapperAdapter(log, options.wrapper);
|
|
58
|
-
var
|
|
58
|
+
var isSynchronizer = mode === undefined; // If mode is not defined, the synchronizer is running
|
|
59
59
|
var isPartialConsumer = mode === CONSUMER_PARTIAL_MODE;
|
|
60
|
-
var telemetry = shouldRecordTelemetry(params) ||
|
|
60
|
+
var telemetry = shouldRecordTelemetry(params) || isSynchronizer ?
|
|
61
61
|
isPartialConsumer ?
|
|
62
62
|
new TelemetryCacheInMemory() :
|
|
63
63
|
new TelemetryCachePluggable(log, keys, wrapper) :
|
|
64
64
|
undefined;
|
|
65
|
-
var impressionCountsCache =
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
undefined
|
|
70
|
-
|
|
71
|
-
isPartialConsumer ?
|
|
72
|
-
settings.core.key === undefined ? new UniqueKeysCacheInMemory() : new UniqueKeysCacheInMemoryCS() :
|
|
73
|
-
new UniqueKeysCachePluggable(log, keys.buildUniqueKeysKey(), wrapper) :
|
|
74
|
-
undefined;
|
|
65
|
+
var impressionCountsCache = isPartialConsumer ?
|
|
66
|
+
new ImpressionCountsCacheInMemory() :
|
|
67
|
+
new ImpressionCountsCachePluggable(log, keys.buildImpressionsCountKey(), wrapper);
|
|
68
|
+
var uniqueKeysCache = isPartialConsumer ?
|
|
69
|
+
settings.core.key === undefined ? new UniqueKeysCacheInMemory() : new UniqueKeysCacheInMemoryCS() :
|
|
70
|
+
new UniqueKeysCachePluggable(log, keys.buildUniqueKeysKey(), wrapper);
|
|
75
71
|
// Connects to wrapper and emits SDK_READY event on main client
|
|
76
72
|
var connectPromise = wrapper.connect().then(function () {
|
|
77
|
-
if (
|
|
73
|
+
if (isSynchronizer) {
|
|
78
74
|
// In standalone or producer mode, clear storage if SDK key or feature flag filter has changed
|
|
79
75
|
return wrapper.get(keys.buildHashKey()).then(function (hash) {
|
|
80
76
|
var currentHash = getStorageHash(settings);
|
|
@@ -90,9 +86,9 @@ export function PluggableStorage(options) {
|
|
|
90
86
|
}
|
|
91
87
|
else {
|
|
92
88
|
// Start periodic flush of async storages if not running synchronizer (producer mode)
|
|
93
|
-
if (impressionCountsCache
|
|
89
|
+
if (impressionCountsCache.start)
|
|
94
90
|
impressionCountsCache.start();
|
|
95
|
-
if (uniqueKeysCache
|
|
91
|
+
if (uniqueKeysCache.start)
|
|
96
92
|
uniqueKeysCache.start();
|
|
97
93
|
if (telemetry && telemetry.recordConfig)
|
|
98
94
|
telemetry.recordConfig();
|
|
@@ -113,9 +109,9 @@ export function PluggableStorage(options) {
|
|
|
113
109
|
uniqueKeys: uniqueKeysCache,
|
|
114
110
|
// Stop periodic flush and disconnect the underlying storage
|
|
115
111
|
destroy: function () {
|
|
116
|
-
return Promise.all(
|
|
117
|
-
impressionCountsCache
|
|
118
|
-
uniqueKeysCache
|
|
112
|
+
return Promise.all(isSynchronizer ? [] : [
|
|
113
|
+
impressionCountsCache.stop && impressionCountsCache.stop(),
|
|
114
|
+
uniqueKeysCache.stop && uniqueKeysCache.stop(),
|
|
119
115
|
]).then(function () { return wrapper.disconnect(); });
|
|
120
116
|
},
|
|
121
117
|
// emits SDK_READY event on shared clients and returns a reference to the storage
|
|
@@ -26,8 +26,6 @@ var IMPRESSIONS_COUNT_RATE = 1800000; // 30 minutes
|
|
|
26
26
|
*/
|
|
27
27
|
export function impressionCountsSubmitterFactory(params) {
|
|
28
28
|
var log = params.settings.log, postTestImpressionsCount = params.splitApi.postTestImpressionsCount, impressionCounts = params.storage.impressionCounts;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return submitterFactory(log, postTestImpressionsCount, impressionCounts, IMPRESSIONS_COUNT_RATE, 'impression counts', fromImpressionCountsCollector, 1);
|
|
32
|
-
}
|
|
29
|
+
// retry impressions counts only once.
|
|
30
|
+
return submitterFactory(log, postTestImpressionsCount, impressionCounts, IMPRESSIONS_COUNT_RATE, 'impression counts', fromImpressionCountsCollector, 1);
|
|
33
31
|
}
|
|
@@ -6,14 +6,11 @@ import { uniqueKeysSubmitterFactory } from './uniqueKeysSubmitter';
|
|
|
6
6
|
export function submitterManagerFactory(params) {
|
|
7
7
|
var submitters = [
|
|
8
8
|
impressionsSubmitterFactory(params),
|
|
9
|
-
eventsSubmitterFactory(params)
|
|
9
|
+
eventsSubmitterFactory(params),
|
|
10
|
+
impressionCountsSubmitterFactory(params),
|
|
11
|
+
uniqueKeysSubmitterFactory(params)
|
|
10
12
|
];
|
|
11
|
-
var impressionCountsSubmitter = impressionCountsSubmitterFactory(params);
|
|
12
|
-
if (impressionCountsSubmitter)
|
|
13
|
-
submitters.push(impressionCountsSubmitter);
|
|
14
13
|
var telemetrySubmitter = telemetrySubmitterFactory(params);
|
|
15
|
-
if (params.storage.uniqueKeys)
|
|
16
|
-
submitters.push(uniqueKeysSubmitterFactory(params));
|
|
17
14
|
return {
|
|
18
15
|
// `onlyTelemetry` true if SDK is created with userConsent not GRANTED
|
|
19
16
|
start: function (onlyTelemetry) {
|
|
@@ -4,38 +4,37 @@ import { IMPRESSIONS_TRACKER_SUCCESS, ERROR_IMPRESSIONS_TRACKER, ERROR_IMPRESSIO
|
|
|
4
4
|
import { CONSENT_DECLINED, DEDUPED, QUEUED } from '../utils/constants';
|
|
5
5
|
/**
|
|
6
6
|
* Impressions tracker stores impressions in cache and pass them to the listener and integrations manager if provided.
|
|
7
|
-
*
|
|
8
|
-
* @param impressionsCache - cache to save impressions
|
|
9
|
-
* @param metadata - runtime metadata (ip, hostname and version)
|
|
10
|
-
* @param impressionListener - optional impression listener
|
|
11
|
-
* @param integrationsManager - optional integrations manager
|
|
12
|
-
* @param strategy - strategy for impressions tracking.
|
|
13
7
|
*/
|
|
14
|
-
export function impressionsTrackerFactory(settings, impressionsCache, strategy, whenInit, integrationsManager, telemetryCache) {
|
|
8
|
+
export function impressionsTrackerFactory(settings, impressionsCache, noneStrategy, strategy, whenInit, integrationsManager, telemetryCache) {
|
|
15
9
|
var log = settings.log, impressionListener = settings.impressionListener, _a = settings.runtime, ip = _a.ip, hostname = _a.hostname, version = settings.version;
|
|
16
10
|
return {
|
|
17
11
|
track: function (impressions, attributes) {
|
|
18
12
|
if (settings.userConsent === CONSENT_DECLINED)
|
|
19
13
|
return;
|
|
20
|
-
var
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
14
|
+
var impressionsToStore = impressions.filter(function (_a) {
|
|
15
|
+
var impression = _a[0], track = _a[1];
|
|
16
|
+
return track === false ?
|
|
17
|
+
noneStrategy.process(impression) :
|
|
18
|
+
strategy.process(impression);
|
|
19
|
+
});
|
|
20
|
+
var impressionsLength = impressions.length;
|
|
21
|
+
var impressionsToStoreLength = impressionsToStore.length;
|
|
22
|
+
if (impressionsToStoreLength) {
|
|
23
|
+
var res = impressionsCache.track(impressionsToStore.map(function (item) { return item[0]; }));
|
|
25
24
|
// If we're on an async storage, handle error and log it.
|
|
26
25
|
if (thenable(res)) {
|
|
27
26
|
res.then(function () {
|
|
28
|
-
log.info(IMPRESSIONS_TRACKER_SUCCESS, [
|
|
27
|
+
log.info(IMPRESSIONS_TRACKER_SUCCESS, [impressionsLength]);
|
|
29
28
|
}).catch(function (err) {
|
|
30
|
-
log.error(ERROR_IMPRESSIONS_TRACKER, [
|
|
29
|
+
log.error(ERROR_IMPRESSIONS_TRACKER, [impressionsLength, err]);
|
|
31
30
|
});
|
|
32
31
|
}
|
|
33
32
|
else {
|
|
34
33
|
// Record when impressionsCache is sync only (standalone mode)
|
|
35
34
|
// @TODO we are not dropping impressions on full queue yet, so DROPPED stats are not recorded
|
|
36
35
|
if (telemetryCache) {
|
|
37
|
-
telemetryCache.recordImpressionStats(QUEUED,
|
|
38
|
-
telemetryCache.recordImpressionStats(DEDUPED,
|
|
36
|
+
telemetryCache.recordImpressionStats(QUEUED, impressionsToStoreLength);
|
|
37
|
+
telemetryCache.recordImpressionStats(DEDUPED, impressionsLength - impressionsToStoreLength);
|
|
39
38
|
}
|
|
40
39
|
}
|
|
41
40
|
}
|
|
@@ -44,7 +43,7 @@ export function impressionsTrackerFactory(settings, impressionsCache, strategy,
|
|
|
44
43
|
var _loop_1 = function (i) {
|
|
45
44
|
var impressionData = {
|
|
46
45
|
// copy of impression, to avoid unexpected behaviour if modified by integrations or impressionListener
|
|
47
|
-
impression: objectAssign({},
|
|
46
|
+
impression: objectAssign({}, impressions[i][0]),
|
|
48
47
|
attributes: attributes,
|
|
49
48
|
ip: ip,
|
|
50
49
|
hostname: hostname,
|
|
@@ -66,7 +65,7 @@ export function impressionsTrackerFactory(settings, impressionsCache, strategy,
|
|
|
66
65
|
});
|
|
67
66
|
});
|
|
68
67
|
};
|
|
69
|
-
for (var i = 0; i <
|
|
68
|
+
for (var i = 0; i < impressionsLength; i++) {
|
|
70
69
|
_loop_1(i);
|
|
71
70
|
}
|
|
72
71
|
}
|
|
@@ -2,20 +2,13 @@
|
|
|
2
2
|
* Debug strategy for impressions tracker. Wraps impressions to store and adds previousTime if it corresponds
|
|
3
3
|
*
|
|
4
4
|
* @param impressionsObserver - impression observer. Previous time (pt property) is included in impression instances
|
|
5
|
-
* @returns
|
|
5
|
+
* @returns Debug strategy
|
|
6
6
|
*/
|
|
7
7
|
export function strategyDebugFactory(impressionsObserver) {
|
|
8
8
|
return {
|
|
9
|
-
process: function (
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
impression.pt = impressionsObserver.testAndSet(impression);
|
|
13
|
-
});
|
|
14
|
-
return {
|
|
15
|
-
impressionsToStore: impressions,
|
|
16
|
-
impressionsToListener: impressions,
|
|
17
|
-
deduped: 0
|
|
18
|
-
};
|
|
9
|
+
process: function (impression) {
|
|
10
|
+
impression.pt = impressionsObserver.testAndSet(impression);
|
|
11
|
+
return true;
|
|
19
12
|
}
|
|
20
13
|
};
|
|
21
14
|
}
|
|
@@ -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
|
}
|
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.track = splitJSON.trackImpressions;
|
|
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.track = splitJSON.trackImpressions;
|
|
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; track?: 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);
|
|
@@ -37,7 +37,9 @@ function segmentsEventEmitterFactory(EventEmitter: new () => SplitIO.IEventEmitt
|
|
|
37
37
|
export function readinessManagerFactory(
|
|
38
38
|
EventEmitter: new () => SplitIO.IEventEmitter,
|
|
39
39
|
settings: ISettings,
|
|
40
|
-
splits: ISplitsEventEmitter = splitsEventEmitterFactory(EventEmitter)
|
|
40
|
+
splits: ISplitsEventEmitter = splitsEventEmitterFactory(EventEmitter),
|
|
41
|
+
isShared?: boolean
|
|
42
|
+
): IReadinessManager {
|
|
41
43
|
|
|
42
44
|
const readyTimeout = settings.startup.readyTimeout;
|
|
43
45
|
|
|
@@ -66,11 +68,6 @@ export function readinessManagerFactory(
|
|
|
66
68
|
gate.emit(SDK_READY_TIMED_OUT, 'Split SDK emitted SDK_READY_TIMED_OUT event.');
|
|
67
69
|
}
|
|
68
70
|
|
|
69
|
-
let readyTimeoutId: ReturnType<typeof setTimeout>;
|
|
70
|
-
if (readyTimeout > 0) {
|
|
71
|
-
if (splits.hasInit) readyTimeoutId = setTimeout(timeout, readyTimeout);
|
|
72
|
-
else splits.initCallbacks.push(() => { readyTimeoutId = setTimeout(timeout, readyTimeout); });
|
|
73
|
-
}
|
|
74
71
|
|
|
75
72
|
// emit SDK_READY and SDK_UPDATE
|
|
76
73
|
let isReady = false;
|
|
@@ -78,11 +75,19 @@ export function readinessManagerFactory(
|
|
|
78
75
|
segments.on(SDK_SEGMENTS_ARRIVED, checkIsReadyOrUpdate);
|
|
79
76
|
|
|
80
77
|
let isDestroyed = false;
|
|
78
|
+
let readyTimeoutId: ReturnType<typeof setTimeout>;
|
|
79
|
+
function __init() {
|
|
80
|
+
isDestroyed = false;
|
|
81
|
+
if (readyTimeout > 0 && !isReady) readyTimeoutId = setTimeout(timeout, readyTimeout);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
splits.initCallbacks.push(__init);
|
|
85
|
+
if (splits.hasInit) __init();
|
|
81
86
|
|
|
82
87
|
function checkIsReadyFromCache() {
|
|
83
88
|
isReadyFromCache = true;
|
|
84
89
|
// Don't emit SDK_READY_FROM_CACHE if SDK_READY has been emitted
|
|
85
|
-
if (!isReady) {
|
|
90
|
+
if (!isReady && !isDestroyed) {
|
|
86
91
|
try {
|
|
87
92
|
syncLastUpdate();
|
|
88
93
|
gate.emit(SDK_READY_FROM_CACHE);
|
|
@@ -94,6 +99,7 @@ export function readinessManagerFactory(
|
|
|
94
99
|
}
|
|
95
100
|
|
|
96
101
|
function checkIsReadyOrUpdate(diff: any) {
|
|
102
|
+
if (isDestroyed) return;
|
|
97
103
|
if (isReady) {
|
|
98
104
|
try {
|
|
99
105
|
syncLastUpdate();
|
|
@@ -117,16 +123,13 @@ export function readinessManagerFactory(
|
|
|
117
123
|
}
|
|
118
124
|
}
|
|
119
125
|
|
|
120
|
-
let refCount = 1;
|
|
121
|
-
|
|
122
126
|
return {
|
|
123
127
|
splits,
|
|
124
128
|
segments,
|
|
125
129
|
gate,
|
|
126
130
|
|
|
127
131
|
shared() {
|
|
128
|
-
|
|
129
|
-
return readinessManagerFactory(EventEmitter, settings, splits);
|
|
132
|
+
return readinessManagerFactory(EventEmitter, settings, splits, true);
|
|
130
133
|
},
|
|
131
134
|
|
|
132
135
|
// @TODO review/remove next methods when non-recoverable errors are reworked
|
|
@@ -145,13 +148,9 @@ export function readinessManagerFactory(
|
|
|
145
148
|
destroy() {
|
|
146
149
|
isDestroyed = true;
|
|
147
150
|
syncLastUpdate();
|
|
148
|
-
|
|
149
|
-
segments.removeAllListeners();
|
|
150
|
-
gate.removeAllListeners();
|
|
151
151
|
clearTimeout(readyTimeoutId);
|
|
152
152
|
|
|
153
|
-
if (
|
|
154
|
-
if (refCount === 0) splits.removeAllListeners();
|
|
153
|
+
if (!isShared) splits.hasInit = false;
|
|
155
154
|
},
|
|
156
155
|
|
|
157
156
|
isReady() { return isReady; },
|
package/src/sdkClient/client.ts
CHANGED
|
@@ -34,11 +34,11 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
34
34
|
const stopTelemetryTracker = telemetryTracker.trackEval(withConfig ? TREATMENT_WITH_CONFIG : TREATMENT);
|
|
35
35
|
|
|
36
36
|
const wrapUp = (evaluationResult: IEvaluationResult) => {
|
|
37
|
-
const queue: SplitIO.ImpressionDTO[] = [];
|
|
37
|
+
const queue: [impression: SplitIO.ImpressionDTO, track?: boolean][] = [];
|
|
38
38
|
const treatment = processEvaluation(evaluationResult, featureFlagName, key, attributes, withConfig, methodName, queue);
|
|
39
39
|
impressionsTracker.track(queue, attributes);
|
|
40
40
|
|
|
41
|
-
stopTelemetryTracker(queue[0] && queue[0].label);
|
|
41
|
+
stopTelemetryTracker(queue[0] && queue[0][0].label);
|
|
42
42
|
return treatment;
|
|
43
43
|
};
|
|
44
44
|
|
|
@@ -59,14 +59,14 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
59
59
|
const stopTelemetryTracker = telemetryTracker.trackEval(withConfig ? TREATMENTS_WITH_CONFIG : TREATMENTS);
|
|
60
60
|
|
|
61
61
|
const wrapUp = (evaluationResults: Record<string, IEvaluationResult>) => {
|
|
62
|
-
const queue: SplitIO.ImpressionDTO[] = [];
|
|
62
|
+
const queue: [impression: SplitIO.ImpressionDTO, track?: boolean][] = [];
|
|
63
63
|
const treatments: Record<string, SplitIO.Treatment | SplitIO.TreatmentWithConfig> = {};
|
|
64
64
|
Object.keys(evaluationResults).forEach(featureFlagName => {
|
|
65
65
|
treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key, attributes, withConfig, methodName, queue);
|
|
66
66
|
});
|
|
67
67
|
impressionsTracker.track(queue, attributes);
|
|
68
68
|
|
|
69
|
-
stopTelemetryTracker(queue[0] && queue[0].label);
|
|
69
|
+
stopTelemetryTracker(queue[0] && queue[0][0].label);
|
|
70
70
|
return treatments;
|
|
71
71
|
};
|
|
72
72
|
|
|
@@ -87,7 +87,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
87
87
|
const stopTelemetryTracker = telemetryTracker.trackEval(method);
|
|
88
88
|
|
|
89
89
|
const wrapUp = (evaluationResults: Record<string, IEvaluationResult>) => {
|
|
90
|
-
const queue: SplitIO.ImpressionDTO[] = [];
|
|
90
|
+
const queue: [impression: SplitIO.ImpressionDTO, track?: boolean][] = [];
|
|
91
91
|
const treatments: Record<string, SplitIO.Treatment | SplitIO.TreatmentWithConfig> = {};
|
|
92
92
|
const evaluations = evaluationResults;
|
|
93
93
|
Object.keys(evaluations).forEach(featureFlagName => {
|
|
@@ -95,7 +95,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
95
95
|
});
|
|
96
96
|
impressionsTracker.track(queue, attributes);
|
|
97
97
|
|
|
98
|
-
stopTelemetryTracker(queue[0] && queue[0].label);
|
|
98
|
+
stopTelemetryTracker(queue[0] && queue[0][0].label);
|
|
99
99
|
return treatments;
|
|
100
100
|
};
|
|
101
101
|
|
|
@@ -128,25 +128,25 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
128
128
|
attributes: SplitIO.Attributes | undefined,
|
|
129
129
|
withConfig: boolean,
|
|
130
130
|
invokingMethodName: string,
|
|
131
|
-
queue: SplitIO.ImpressionDTO[]
|
|
131
|
+
queue: [impression: SplitIO.ImpressionDTO, track?: boolean][]
|
|
132
132
|
): SplitIO.Treatment | SplitIO.TreatmentWithConfig {
|
|
133
133
|
const matchingKey = getMatching(key);
|
|
134
134
|
const bucketingKey = getBucketing(key);
|
|
135
135
|
|
|
136
|
-
const { treatment, label, changeNumber, config = null } = evaluation;
|
|
136
|
+
const { treatment, label, changeNumber, config = null, track } = evaluation;
|
|
137
137
|
log.info(IMPRESSION, [featureFlagName, matchingKey, treatment, label]);
|
|
138
138
|
|
|
139
139
|
if (validateSplitExistence(log, readinessManager, featureFlagName, label, invokingMethodName)) {
|
|
140
140
|
log.info(IMPRESSION_QUEUEING);
|
|
141
|
-
queue.push({
|
|
141
|
+
queue.push([{
|
|
142
142
|
feature: featureFlagName,
|
|
143
143
|
keyName: matchingKey,
|
|
144
144
|
treatment,
|
|
145
145
|
time: Date.now(),
|
|
146
146
|
bucketingKey,
|
|
147
147
|
label,
|
|
148
|
-
changeNumber: changeNumber as number
|
|
149
|
-
});
|
|
148
|
+
changeNumber: changeNumber as number,
|
|
149
|
+
}, track]);
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
if (withConfig) {
|
|
@@ -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
|
|
|
@@ -126,6 +121,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
|
|
|
126
121
|
settings,
|
|
127
122
|
|
|
128
123
|
destroy() {
|
|
124
|
+
hasInit = false;
|
|
129
125
|
return Promise.all(Object.keys(clients).map(key => clients[key].destroy())).then(() => { });
|
|
130
126
|
}
|
|
131
127
|
}, extraProps && extraProps(ctx), lazyInit ? { init } : init());
|
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,
|