@splitsoftware/splitio-commons 1.6.2-rc.1 → 1.6.2-rc.2
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/cjs/sdkFactory/index.js +4 -3
- package/cjs/services/splitApi.js +2 -2
- package/cjs/storages/inLocalStorage/index.js +4 -0
- package/cjs/storages/inMemory/InMemoryStorage.js +5 -1
- package/cjs/storages/inMemory/InMemoryStorageCS.js +5 -1
- package/cjs/storages/inMemory/uniqueKeysCacheInMemory.js +73 -0
- package/cjs/storages/inMemory/uniqueKeysCacheInMemoryCS.js +78 -0
- package/cjs/sync/submitters/telemetrySubmitter.js +1 -0
- package/cjs/sync/submitters/uniqueKeysSubmitter.js +14 -58
- package/cjs/trackers/strategy/strategyNone.js +1 -1
- package/cjs/trackers/uniqueKeysTracker.js +5 -43
- package/cjs/utils/constants/index.js +3 -2
- package/esm/sdkFactory/index.js +4 -3
- package/esm/services/splitApi.js +2 -2
- package/esm/storages/inLocalStorage/index.js +5 -1
- package/esm/storages/inMemory/InMemoryStorage.js +6 -2
- package/esm/storages/inMemory/InMemoryStorageCS.js +6 -2
- package/esm/storages/inMemory/uniqueKeysCacheInMemory.js +70 -0
- package/esm/storages/inMemory/uniqueKeysCacheInMemoryCS.js +75 -0
- package/esm/sync/submitters/telemetrySubmitter.js +2 -1
- package/esm/sync/submitters/uniqueKeysSubmitter.js +13 -55
- package/esm/trackers/strategy/strategyNone.js +1 -1
- package/esm/trackers/uniqueKeysTracker.js +5 -43
- package/esm/utils/constants/index.js +1 -0
- package/package.json +1 -1
- package/src/sdkFactory/index.ts +4 -3
- package/src/services/splitApi.ts +2 -2
- package/src/storages/inLocalStorage/index.ts +4 -1
- package/src/storages/inMemory/InMemoryStorage.ts +5 -2
- package/src/storages/inMemory/InMemoryStorageCS.ts +6 -2
- package/src/storages/inMemory/uniqueKeysCacheInMemory.ts +83 -0
- package/src/storages/inMemory/uniqueKeysCacheInMemoryCS.ts +89 -0
- package/src/storages/types.ts +9 -6
- package/src/sync/submitters/telemetrySubmitter.ts +4 -3
- package/src/sync/submitters/uniqueKeysSubmitter.ts +15 -59
- package/src/trackers/impressionsTracker.ts +0 -1
- package/src/trackers/strategy/strategyNone.ts +1 -1
- package/src/trackers/types.ts +3 -7
- package/src/trackers/uniqueKeysTracker.ts +6 -49
- package/src/types.ts +1 -0
- package/src/utils/constants/index.ts +1 -0
- package/types/storages/inMemory/uniqueKeysCacheInMemory.d.ts +32 -0
- package/types/storages/inMemory/uniqueKeysCacheInMemoryCS.d.ts +37 -0
- package/types/storages/types.d.ts +8 -11
- package/types/sync/submitters/uniqueKeysSubmitter.d.ts +0 -14
- package/types/trackers/types.d.ts +3 -11
- package/types/trackers/uniqueKeysTracker.d.ts +3 -3
- package/types/types.d.ts +1 -0
- package/types/utils/constants/index.d.ts +1 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { setToArray, _Set } from '../../utils/lang/sets';
|
|
2
|
+
var DEFAULT_CACHE_SIZE = 30000;
|
|
3
|
+
var UniqueKeysCacheInMemory = /** @class */ (function () {
|
|
4
|
+
function UniqueKeysCacheInMemory(uniqueKeysQueueSize) {
|
|
5
|
+
if (uniqueKeysQueueSize === void 0) { uniqueKeysQueueSize = DEFAULT_CACHE_SIZE; }
|
|
6
|
+
this.uniqueTrackerSize = 0;
|
|
7
|
+
this.maxStorage = uniqueKeysQueueSize;
|
|
8
|
+
this.uniqueKeysTracker = {};
|
|
9
|
+
}
|
|
10
|
+
UniqueKeysCacheInMemory.prototype.setOnFullQueueCb = function (cb) {
|
|
11
|
+
this.onFullQueue = cb;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Store unique keys in sequential order
|
|
15
|
+
* key: string = feature name.
|
|
16
|
+
* value: Set<string> = set of unique keys.
|
|
17
|
+
*/
|
|
18
|
+
UniqueKeysCacheInMemory.prototype.track = function (key, featureName) {
|
|
19
|
+
if (!this.uniqueKeysTracker[featureName])
|
|
20
|
+
this.uniqueKeysTracker[featureName] = new _Set();
|
|
21
|
+
var tracker = this.uniqueKeysTracker[featureName];
|
|
22
|
+
if (!tracker.has(key)) {
|
|
23
|
+
tracker.add(key);
|
|
24
|
+
this.uniqueTrackerSize++;
|
|
25
|
+
}
|
|
26
|
+
if (this.uniqueTrackerSize >= this.maxStorage && this.onFullQueue) {
|
|
27
|
+
this.uniqueTrackerSize = 0;
|
|
28
|
+
this.onFullQueue();
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Clear the data stored on the cache.
|
|
33
|
+
*/
|
|
34
|
+
UniqueKeysCacheInMemory.prototype.clear = function () {
|
|
35
|
+
this.uniqueKeysTracker = {};
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Pop the collected data, used as payload for posting.
|
|
39
|
+
*/
|
|
40
|
+
UniqueKeysCacheInMemory.prototype.pop = function () {
|
|
41
|
+
var data = this.uniqueKeysTracker;
|
|
42
|
+
this.uniqueKeysTracker = {};
|
|
43
|
+
return this.fromUniqueKeysCollector(data);
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Check if the cache is empty.
|
|
47
|
+
*/
|
|
48
|
+
UniqueKeysCacheInMemory.prototype.isEmpty = function () {
|
|
49
|
+
return Object.keys(this.uniqueKeysTracker).length === 0;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Converts `uniqueKeys` data from cache into request payload for SS.
|
|
53
|
+
*/
|
|
54
|
+
UniqueKeysCacheInMemory.prototype.fromUniqueKeysCollector = function (uniqueKeys) {
|
|
55
|
+
var payload = [];
|
|
56
|
+
var featureNames = Object.keys(uniqueKeys);
|
|
57
|
+
for (var i = 0; i < featureNames.length; i++) {
|
|
58
|
+
var featureName = featureNames[i];
|
|
59
|
+
var featureKeys = setToArray(uniqueKeys[featureName]);
|
|
60
|
+
var uniqueKeysPayload = {
|
|
61
|
+
f: featureName,
|
|
62
|
+
ks: featureKeys
|
|
63
|
+
};
|
|
64
|
+
payload.push(uniqueKeysPayload);
|
|
65
|
+
}
|
|
66
|
+
return { keys: payload };
|
|
67
|
+
};
|
|
68
|
+
return UniqueKeysCacheInMemory;
|
|
69
|
+
}());
|
|
70
|
+
export { UniqueKeysCacheInMemory };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { setToArray, _Set } from '../../utils/lang/sets';
|
|
2
|
+
var DEFAULT_CACHE_SIZE = 30000;
|
|
3
|
+
var UniqueKeysCacheInMemoryCS = /** @class */ (function () {
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
* @param impressionsQueueSize number of queued impressions to call onFullQueueCb.
|
|
7
|
+
* Default value is 0, that means no maximum value, in case we want to avoid this being triggered.
|
|
8
|
+
*/
|
|
9
|
+
function UniqueKeysCacheInMemoryCS(uniqueKeysQueueSize) {
|
|
10
|
+
if (uniqueKeysQueueSize === void 0) { uniqueKeysQueueSize = DEFAULT_CACHE_SIZE; }
|
|
11
|
+
this.uniqueTrackerSize = 0;
|
|
12
|
+
this.maxStorage = uniqueKeysQueueSize;
|
|
13
|
+
this.uniqueKeysTracker = {};
|
|
14
|
+
}
|
|
15
|
+
UniqueKeysCacheInMemoryCS.prototype.setOnFullQueueCb = function (cb) {
|
|
16
|
+
this.onFullQueue = cb;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Store unique keys in sequential order
|
|
20
|
+
* key: string = key.
|
|
21
|
+
* value: HashSet<string> = set of split names.
|
|
22
|
+
*/
|
|
23
|
+
UniqueKeysCacheInMemoryCS.prototype.track = function (key, featureName) {
|
|
24
|
+
if (!this.uniqueKeysTracker[key])
|
|
25
|
+
this.uniqueKeysTracker[key] = new _Set();
|
|
26
|
+
var tracker = this.uniqueKeysTracker[key];
|
|
27
|
+
if (!tracker.has(featureName)) {
|
|
28
|
+
tracker.add(featureName);
|
|
29
|
+
this.uniqueTrackerSize++;
|
|
30
|
+
}
|
|
31
|
+
if (this.uniqueTrackerSize >= this.maxStorage && this.onFullQueue) {
|
|
32
|
+
this.uniqueTrackerSize = 0;
|
|
33
|
+
this.onFullQueue();
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Clear the data stored on the cache.
|
|
38
|
+
*/
|
|
39
|
+
UniqueKeysCacheInMemoryCS.prototype.clear = function () {
|
|
40
|
+
this.uniqueKeysTracker = {};
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Pop the collected data, used as payload for posting.
|
|
44
|
+
*/
|
|
45
|
+
UniqueKeysCacheInMemoryCS.prototype.pop = function () {
|
|
46
|
+
var data = this.uniqueKeysTracker;
|
|
47
|
+
this.uniqueKeysTracker = {};
|
|
48
|
+
return this.fromUniqueKeysCollector(data);
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Check if the cache is empty.
|
|
52
|
+
*/
|
|
53
|
+
UniqueKeysCacheInMemoryCS.prototype.isEmpty = function () {
|
|
54
|
+
return Object.keys(this.uniqueKeysTracker).length === 0;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Converts `uniqueKeys` data from cache into request payload.
|
|
58
|
+
*/
|
|
59
|
+
UniqueKeysCacheInMemoryCS.prototype.fromUniqueKeysCollector = function (uniqueKeys) {
|
|
60
|
+
var payload = [];
|
|
61
|
+
var featureKeys = Object.keys(uniqueKeys);
|
|
62
|
+
for (var k = 0; k < featureKeys.length; k++) {
|
|
63
|
+
var featureKey = featureKeys[k];
|
|
64
|
+
var featureNames = setToArray(uniqueKeys[featureKey]);
|
|
65
|
+
var uniqueKeysPayload = {
|
|
66
|
+
k: featureKey,
|
|
67
|
+
fs: featureNames
|
|
68
|
+
};
|
|
69
|
+
payload.push(uniqueKeysPayload);
|
|
70
|
+
}
|
|
71
|
+
return { keys: payload };
|
|
72
|
+
};
|
|
73
|
+
return UniqueKeysCacheInMemoryCS;
|
|
74
|
+
}());
|
|
75
|
+
export { UniqueKeysCacheInMemoryCS };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
var _a, _b, _c;
|
|
2
2
|
import { submitterFactory, firstPushWindowDecorator } from './submitter';
|
|
3
|
-
import { QUEUED, DEDUPED, DROPPED, CONSUMER_MODE, CONSUMER_ENUM, STANDALONE_MODE, CONSUMER_PARTIAL_MODE, STANDALONE_ENUM, CONSUMER_PARTIAL_ENUM, OPTIMIZED, DEBUG, DEBUG_ENUM, OPTIMIZED_ENUM, CONSENT_GRANTED, CONSENT_DECLINED, CONSENT_UNKNOWN } from '../../utils/constants';
|
|
3
|
+
import { QUEUED, DEDUPED, DROPPED, CONSUMER_MODE, CONSUMER_ENUM, STANDALONE_MODE, CONSUMER_PARTIAL_MODE, STANDALONE_ENUM, CONSUMER_PARTIAL_ENUM, OPTIMIZED, DEBUG, NONE, DEBUG_ENUM, OPTIMIZED_ENUM, NONE_ENUM, CONSENT_GRANTED, CONSENT_DECLINED, CONSENT_UNKNOWN } from '../../utils/constants';
|
|
4
4
|
import { SDK_READY, SDK_READY_FROM_CACHE } from '../../readiness/constants';
|
|
5
5
|
import { base } from '../../utils/settingsValidation';
|
|
6
6
|
import { usedKeysMap } from '../../utils/inputValidation/apiKey';
|
|
@@ -46,6 +46,7 @@ var OPERATION_MODE_MAP = (_a = {},
|
|
|
46
46
|
var IMPRESSIONS_MODE_MAP = (_b = {},
|
|
47
47
|
_b[OPTIMIZED] = OPTIMIZED_ENUM,
|
|
48
48
|
_b[DEBUG] = DEBUG_ENUM,
|
|
49
|
+
_b[NONE] = NONE_ENUM,
|
|
49
50
|
_b);
|
|
50
51
|
var USER_CONSENT_MAP = (_c = {},
|
|
51
52
|
_c[CONSENT_UNKNOWN] = 1,
|
|
@@ -1,57 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SUBMITTERS_PUSH_FULL_QUEUE } from '../../logger/constants';
|
|
2
2
|
import { submitterFactory } from './submitter';
|
|
3
|
-
|
|
4
|
-
* Invert keys for feature to features for key
|
|
5
|
-
*/
|
|
6
|
-
function invertUniqueKeys(uniqueKeys) {
|
|
7
|
-
var featureNames = Object.keys(uniqueKeys);
|
|
8
|
-
var inverted = {};
|
|
9
|
-
for (var i = 0; i < featureNames.length; i++) {
|
|
10
|
-
var featureName = featureNames[i];
|
|
11
|
-
var featureKeys = setToArray(uniqueKeys[featureName]);
|
|
12
|
-
for (var j = 0; j < featureKeys.length; j++) {
|
|
13
|
-
var featureKey = featureKeys[j];
|
|
14
|
-
if (!inverted[featureKey])
|
|
15
|
-
inverted[featureKey] = [];
|
|
16
|
-
inverted[featureKey].push(featureName);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return inverted;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Converts `uniqueKeys` data from cache into request payload for CS.
|
|
23
|
-
*/
|
|
24
|
-
export function fromUniqueKeysCollectorCs(uniqueKeys) {
|
|
25
|
-
var payload = [];
|
|
26
|
-
var featuresPerKey = invertUniqueKeys(uniqueKeys);
|
|
27
|
-
var keys = Object.keys(featuresPerKey);
|
|
28
|
-
for (var k = 0; k < keys.length; k++) {
|
|
29
|
-
var key = keys[k];
|
|
30
|
-
var uniqueKeysPayload = {
|
|
31
|
-
k: key,
|
|
32
|
-
fs: featuresPerKey[key]
|
|
33
|
-
};
|
|
34
|
-
payload.push(uniqueKeysPayload);
|
|
35
|
-
}
|
|
36
|
-
return { keys: payload };
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Converts `uniqueKeys` data from cache into request payload for SS.
|
|
40
|
-
*/
|
|
41
|
-
export function fromUniqueKeysCollectorSs(uniqueKeys) {
|
|
42
|
-
var payload = [];
|
|
43
|
-
var featureNames = Object.keys(uniqueKeys);
|
|
44
|
-
for (var i = 0; i < featureNames.length; i++) {
|
|
45
|
-
var featureName = featureNames[i];
|
|
46
|
-
var featureKeys = setToArray(uniqueKeys[featureName]);
|
|
47
|
-
var uniqueKeysPayload = {
|
|
48
|
-
f: featureName,
|
|
49
|
-
ks: featureKeys
|
|
50
|
-
};
|
|
51
|
-
payload.push(uniqueKeysPayload);
|
|
52
|
-
}
|
|
53
|
-
return { keys: payload };
|
|
54
|
-
}
|
|
3
|
+
var DATA_NAME = 'uniqueKeys';
|
|
55
4
|
/**
|
|
56
5
|
* Submitter that periodically posts impression counts
|
|
57
6
|
*/
|
|
@@ -59,6 +8,15 @@ export function uniqueKeysSubmitterFactory(params) {
|
|
|
59
8
|
var _a = params.settings, log = _a.log, uniqueKeysRefreshRate = _a.scheduler.uniqueKeysRefreshRate, key = _a.core.key, _b = params.splitApi, postUniqueKeysBulkCs = _b.postUniqueKeysBulkCs, postUniqueKeysBulkSs = _b.postUniqueKeysBulkSs, uniqueKeys = params.storage.uniqueKeys;
|
|
60
9
|
var isClientSide = key !== undefined;
|
|
61
10
|
var postUniqueKeysBulk = isClientSide ? postUniqueKeysBulkCs : postUniqueKeysBulkSs;
|
|
62
|
-
var
|
|
63
|
-
|
|
11
|
+
var syncTask = submitterFactory(log, postUniqueKeysBulk, uniqueKeys, uniqueKeysRefreshRate, 'unique keys');
|
|
12
|
+
// register unique keys submitter to be executed when uniqueKeys cache is full
|
|
13
|
+
uniqueKeys.setOnFullQueueCb(function () {
|
|
14
|
+
if (syncTask.isRunning()) {
|
|
15
|
+
log.info(SUBMITTERS_PUSH_FULL_QUEUE, [DATA_NAME]);
|
|
16
|
+
syncTask.execute();
|
|
17
|
+
}
|
|
18
|
+
// If submitter is stopped (e.g., user consent declined or unknown, or app state offline), we don't send the data.
|
|
19
|
+
// Data will be sent when submitter is resumed.
|
|
20
|
+
});
|
|
21
|
+
return syncTask;
|
|
64
22
|
}
|
|
@@ -13,7 +13,7 @@ export function strategyNoneFactory(impressionsCounter, uniqueKeysTracker) {
|
|
|
13
13
|
// Increments impression counter per featureName
|
|
14
14
|
impressionsCounter.track(impression.feature, now, 1);
|
|
15
15
|
// Keep track by unique key
|
|
16
|
-
uniqueKeysTracker.track(impression.
|
|
16
|
+
uniqueKeysTracker.track(impression.keyName, impression.feature);
|
|
17
17
|
});
|
|
18
18
|
return {
|
|
19
19
|
impressionsToStore: [],
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { LOG_PREFIX_UNIQUE_KEYS_TRACKER } from '../logger/constants';
|
|
2
|
-
import { _Set } from '../utils/lang/sets';
|
|
3
2
|
var noopFilterAdapter = {
|
|
4
3
|
add: function () { return true; },
|
|
5
4
|
contains: function () { return true; },
|
|
6
5
|
clear: function () { }
|
|
7
6
|
};
|
|
8
|
-
var DEFAULT_CACHE_SIZE = 30000;
|
|
9
7
|
/**
|
|
10
8
|
* Trackes uniques keys
|
|
11
9
|
* Unique Keys Tracker will be in charge of checking if the MTK was already sent to the BE in the last period
|
|
@@ -13,53 +11,17 @@ var DEFAULT_CACHE_SIZE = 30000;
|
|
|
13
11
|
*
|
|
14
12
|
* @param log Logger instance
|
|
15
13
|
* @param filterAdapter filter adapter
|
|
16
|
-
* @param
|
|
17
|
-
* @param maxBulkSize optional max MTKs bulk size
|
|
14
|
+
* @param uniqueKeysCache cache to save unique keys
|
|
18
15
|
*/
|
|
19
|
-
export function uniqueKeysTrackerFactory(log,
|
|
16
|
+
export function uniqueKeysTrackerFactory(log, uniqueKeysCache, filterAdapter) {
|
|
20
17
|
if (filterAdapter === void 0) { filterAdapter = noopFilterAdapter; }
|
|
21
|
-
if (cacheSize === void 0) { cacheSize = DEFAULT_CACHE_SIZE; }
|
|
22
|
-
var uniqueKeysTracker = {};
|
|
23
|
-
var uniqueTrackerSize = 0;
|
|
24
18
|
return {
|
|
25
|
-
track: function (
|
|
26
|
-
if (!filterAdapter.add(
|
|
19
|
+
track: function (key, featureName) {
|
|
20
|
+
if (!filterAdapter.add(key, featureName)) {
|
|
27
21
|
log.debug(LOG_PREFIX_UNIQUE_KEYS_TRACKER + "The feature " + featureName + " and key " + key + " exist in the filter");
|
|
28
22
|
return;
|
|
29
23
|
}
|
|
30
|
-
|
|
31
|
-
uniqueKeysTracker[featureName] = new _Set();
|
|
32
|
-
var tracker = uniqueKeysTracker[featureName];
|
|
33
|
-
if (!tracker.has(key)) {
|
|
34
|
-
tracker.add(key);
|
|
35
|
-
log.debug(LOG_PREFIX_UNIQUE_KEYS_TRACKER + "Key " + key + " added to feature " + featureName);
|
|
36
|
-
uniqueTrackerSize++;
|
|
37
|
-
}
|
|
38
|
-
if (uniqueTrackerSize >= cacheSize) {
|
|
39
|
-
log.warn(LOG_PREFIX_UNIQUE_KEYS_TRACKER + "The UniqueKeysTracker size reached the maximum limit");
|
|
40
|
-
// @TODO trigger event to submitter to send mtk
|
|
41
|
-
uniqueTrackerSize = 0;
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
/**
|
|
45
|
-
* Pop the collected data, used as payload for posting.
|
|
46
|
-
*/
|
|
47
|
-
pop: function () {
|
|
48
|
-
var data = uniqueKeysTracker;
|
|
49
|
-
uniqueKeysTracker = {};
|
|
50
|
-
return data;
|
|
51
|
-
},
|
|
52
|
-
/**
|
|
53
|
-
* Clear the data stored on the cache.
|
|
54
|
-
*/
|
|
55
|
-
clear: function () {
|
|
56
|
-
uniqueKeysTracker = {};
|
|
57
|
-
},
|
|
58
|
-
/**
|
|
59
|
-
* Check if the cache is empty.
|
|
60
|
-
*/
|
|
61
|
-
isEmpty: function () {
|
|
62
|
-
return Object.keys(uniqueKeysTracker).length === 0;
|
|
24
|
+
uniqueKeysCache.track(key, featureName);
|
|
63
25
|
}
|
|
64
26
|
};
|
|
65
27
|
}
|
|
@@ -38,6 +38,7 @@ export var CONSUMER_ENUM = 1;
|
|
|
38
38
|
export var CONSUMER_PARTIAL_ENUM = 2;
|
|
39
39
|
export var OPTIMIZED_ENUM = 0;
|
|
40
40
|
export var DEBUG_ENUM = 1;
|
|
41
|
+
export var NONE_ENUM = 2;
|
|
41
42
|
export var SPLITS = 'sp';
|
|
42
43
|
export var IMPRESSIONS = 'im';
|
|
43
44
|
export var IMPRESSIONS_COUNT = 'ic';
|
package/package.json
CHANGED
package/src/sdkFactory/index.ts
CHANGED
|
@@ -29,7 +29,6 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
29
29
|
integrationsManagerFactory, sdkManagerFactory, sdkClientMethodFactory,
|
|
30
30
|
filterAdapterFactory } = params;
|
|
31
31
|
const log = settings.log;
|
|
32
|
-
const impressionsMode = settings.sync.impressionsMode;
|
|
33
32
|
|
|
34
33
|
// @TODO handle non-recoverable errors, such as, global `fetch` not available, invalid API Key, etc.
|
|
35
34
|
// On non-recoverable errors, we should mark the SDK as destroyed and not start synchronization.
|
|
@@ -44,6 +43,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
44
43
|
const storageFactoryParams: IStorageFactoryParams = {
|
|
45
44
|
impressionsQueueSize: settings.scheduler.impressionsQueueSize,
|
|
46
45
|
eventsQueueSize: settings.scheduler.eventsQueueSize,
|
|
46
|
+
uniqueKeysCacheSize: settings.scheduler.uniqueKeysCacheSize,
|
|
47
47
|
optimize: shouldBeOptimized(settings),
|
|
48
48
|
|
|
49
49
|
// ATM, only used by InLocalStorage
|
|
@@ -52,6 +52,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
52
52
|
|
|
53
53
|
// ATM, only used by PluggableStorage
|
|
54
54
|
mode: settings.mode,
|
|
55
|
+
impressionsMode: settings.sync.impressionsMode,
|
|
55
56
|
|
|
56
57
|
// Callback used to emit SDK_READY in consumer mode, where `syncManagerFactory` is undefined,
|
|
57
58
|
// or partial consumer mode, where it only has submitters, and therefore it doesn't emit readiness events.
|
|
@@ -70,9 +71,9 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
70
71
|
const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage });
|
|
71
72
|
|
|
72
73
|
const observer = impressionsObserverFactory();
|
|
73
|
-
const uniqueKeysTracker = impressionsMode === NONE ? uniqueKeysTrackerFactory(log, filterAdapterFactory && filterAdapterFactory()) : undefined;
|
|
74
|
+
const uniqueKeysTracker = storageFactoryParams.impressionsMode === NONE ? uniqueKeysTrackerFactory(log, storage.uniqueKeys!, filterAdapterFactory && filterAdapterFactory()) : undefined;
|
|
74
75
|
const strategy = (storageFactoryParams.optimize) ? strategyOptimizedFactory(observer, storage.impressionCounts!) :
|
|
75
|
-
(impressionsMode === NONE) ? strategyNoneFactory(storage.impressionCounts!, uniqueKeysTracker!) : strategyDebugFactory(observer);
|
|
76
|
+
(storageFactoryParams.impressionsMode === NONE) ? strategyNoneFactory(storage.impressionCounts!, uniqueKeysTracker!) : strategyDebugFactory(observer);
|
|
76
77
|
|
|
77
78
|
const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, strategy, integrationsManager, storage.telemetry);
|
|
78
79
|
const eventTracker = eventTrackerFactory(settings, storage.events, integrationsManager, storage.telemetry);
|
package/src/services/splitApi.ts
CHANGED
|
@@ -114,7 +114,7 @@ export function splitApiFactory(
|
|
|
114
114
|
* @param headers Optionals headers to overwrite default ones. For example, it is used in producer mode to overwrite metadata headers.
|
|
115
115
|
*/
|
|
116
116
|
postUniqueKeysBulkCs(body: string, headers?: Record<string, string>) {
|
|
117
|
-
const url = `${urls.telemetry}/
|
|
117
|
+
const url = `${urls.telemetry}/v1/keys/cs`;
|
|
118
118
|
return splitHttpClient(url, { method: 'POST', body, headers }, telemetryTracker.trackHttp(TELEMETRY));
|
|
119
119
|
},
|
|
120
120
|
|
|
@@ -125,7 +125,7 @@ export function splitApiFactory(
|
|
|
125
125
|
* @param headers Optionals headers to overwrite default ones. For example, it is used in producer mode to overwrite metadata headers.
|
|
126
126
|
*/
|
|
127
127
|
postUniqueKeysBulkSs(body: string, headers?: Record<string, string>) {
|
|
128
|
-
const url = `${urls.telemetry}/
|
|
128
|
+
const url = `${urls.telemetry}/v1/keys/ss`;
|
|
129
129
|
return splitHttpClient(url, { method: 'POST', body, headers }, telemetryTracker.trackHttp(TELEMETRY));
|
|
130
130
|
},
|
|
131
131
|
|
|
@@ -12,8 +12,9 @@ import { SplitsCacheInMemory } from '../inMemory/SplitsCacheInMemory';
|
|
|
12
12
|
import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../../utils/constants/browser';
|
|
13
13
|
import { InMemoryStorageCSFactory } from '../inMemory/InMemoryStorageCS';
|
|
14
14
|
import { LOG_PREFIX } from './constants';
|
|
15
|
-
import { LOCALHOST_MODE, STORAGE_LOCALSTORAGE } from '../../utils/constants';
|
|
15
|
+
import { LOCALHOST_MODE, NONE, STORAGE_LOCALSTORAGE } from '../../utils/constants';
|
|
16
16
|
import { shouldRecordTelemetry, TelemetryCacheInMemory } from '../inMemory/TelemetryCacheInMemory';
|
|
17
|
+
import { UniqueKeysCacheInMemoryCS } from '../inMemory/uniqueKeysCacheInMemoryCS';
|
|
17
18
|
|
|
18
19
|
export interface InLocalStorageOptions {
|
|
19
20
|
prefix?: string
|
|
@@ -45,6 +46,7 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
|
|
|
45
46
|
impressionCounts: params.optimize ? new ImpressionCountsCacheInMemory() : undefined,
|
|
46
47
|
events: new EventsCacheInMemory(params.eventsQueueSize),
|
|
47
48
|
telemetry: params.mode !== LOCALHOST_MODE && shouldRecordTelemetry() ? new TelemetryCacheInMemory() : undefined,
|
|
49
|
+
uniqueKeys: params.impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS() : undefined,
|
|
48
50
|
|
|
49
51
|
destroy() {
|
|
50
52
|
this.splits = new SplitsCacheInMemory();
|
|
@@ -52,6 +54,7 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
|
|
|
52
54
|
this.impressions.clear();
|
|
53
55
|
this.impressionCounts && this.impressionCounts.clear();
|
|
54
56
|
this.events.clear();
|
|
57
|
+
this.uniqueKeys?.clear();
|
|
55
58
|
},
|
|
56
59
|
|
|
57
60
|
// When using shared instanciation with MEMORY we reuse everything but segments (they are customer per key).
|
|
@@ -4,8 +4,9 @@ import { ImpressionsCacheInMemory } from './ImpressionsCacheInMemory';
|
|
|
4
4
|
import { EventsCacheInMemory } from './EventsCacheInMemory';
|
|
5
5
|
import { IStorageFactoryParams, IStorageSync } from '../types';
|
|
6
6
|
import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
|
|
7
|
-
import { LOCALHOST_MODE, STORAGE_MEMORY } from '../../utils/constants';
|
|
7
|
+
import { DEBUG, LOCALHOST_MODE, NONE, STORAGE_MEMORY } from '../../utils/constants';
|
|
8
8
|
import { TelemetryCacheInMemory } from './TelemetryCacheInMemory';
|
|
9
|
+
import { UniqueKeysCacheInMemory } from './uniqueKeysCacheInMemory';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* InMemory storage factory for standalone server-side SplitFactory
|
|
@@ -18,9 +19,10 @@ export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageS
|
|
|
18
19
|
splits: new SplitsCacheInMemory(),
|
|
19
20
|
segments: new SegmentsCacheInMemory(),
|
|
20
21
|
impressions: new ImpressionsCacheInMemory(params.impressionsQueueSize),
|
|
21
|
-
impressionCounts: params.
|
|
22
|
+
impressionCounts: params.impressionsMode !== DEBUG ? new ImpressionCountsCacheInMemory() : undefined,
|
|
22
23
|
events: new EventsCacheInMemory(params.eventsQueueSize),
|
|
23
24
|
telemetry: params.mode !== LOCALHOST_MODE ? new TelemetryCacheInMemory() : undefined, // Always track telemetry in standalone mode on server-side
|
|
25
|
+
uniqueKeys: params.impressionsMode === NONE ? new UniqueKeysCacheInMemory(params.uniqueKeysCacheSize) : undefined,
|
|
24
26
|
|
|
25
27
|
// When using MEMORY we should clean all the caches to leave them empty
|
|
26
28
|
destroy() {
|
|
@@ -29,6 +31,7 @@ export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageS
|
|
|
29
31
|
this.impressions.clear();
|
|
30
32
|
this.impressionCounts && this.impressionCounts.clear();
|
|
31
33
|
this.events.clear();
|
|
34
|
+
this.uniqueKeys?.clear();
|
|
32
35
|
}
|
|
33
36
|
};
|
|
34
37
|
}
|
|
@@ -4,8 +4,9 @@ import { ImpressionsCacheInMemory } from './ImpressionsCacheInMemory';
|
|
|
4
4
|
import { EventsCacheInMemory } from './EventsCacheInMemory';
|
|
5
5
|
import { IStorageSync, IStorageFactoryParams } from '../types';
|
|
6
6
|
import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
|
|
7
|
-
import { LOCALHOST_MODE, STORAGE_MEMORY } from '../../utils/constants';
|
|
7
|
+
import { DEBUG, LOCALHOST_MODE, NONE, STORAGE_MEMORY } from '../../utils/constants';
|
|
8
8
|
import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
|
|
9
|
+
import { UniqueKeysCacheInMemoryCS } from './uniqueKeysCacheInMemoryCS';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* InMemory storage factory for standalone client-side SplitFactory
|
|
@@ -18,9 +19,11 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
|
|
|
18
19
|
splits: new SplitsCacheInMemory(),
|
|
19
20
|
segments: new MySegmentsCacheInMemory(),
|
|
20
21
|
impressions: new ImpressionsCacheInMemory(params.impressionsQueueSize),
|
|
21
|
-
impressionCounts: params.
|
|
22
|
+
impressionCounts: params.impressionsMode !== DEBUG ? new ImpressionCountsCacheInMemory() : undefined,
|
|
22
23
|
events: new EventsCacheInMemory(params.eventsQueueSize),
|
|
23
24
|
telemetry: params.mode !== LOCALHOST_MODE && shouldRecordTelemetry() ? new TelemetryCacheInMemory() : undefined,
|
|
25
|
+
uniqueKeys: params.impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS(params.uniqueKeysCacheSize) : undefined,
|
|
26
|
+
|
|
24
27
|
|
|
25
28
|
// When using MEMORY we should clean all the caches to leave them empty
|
|
26
29
|
destroy() {
|
|
@@ -29,6 +32,7 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
|
|
|
29
32
|
this.impressions.clear();
|
|
30
33
|
this.impressionCounts && this.impressionCounts.clear();
|
|
31
34
|
this.events.clear();
|
|
35
|
+
this.uniqueKeys?.clear();
|
|
32
36
|
},
|
|
33
37
|
|
|
34
38
|
// When using shared instanciation with MEMORY we reuse everything but segments (they are unique per key)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { IUniqueKeysCacheBase } from '../types';
|
|
2
|
+
import { ISet, setToArray, _Set } from '../../utils/lang/sets';
|
|
3
|
+
import { UniqueKeysPayloadSs } from '../../sync/submitters/types';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_CACHE_SIZE = 30000;
|
|
6
|
+
|
|
7
|
+
export class UniqueKeysCacheInMemory implements IUniqueKeysCacheBase {
|
|
8
|
+
|
|
9
|
+
private onFullQueue?: () => void;
|
|
10
|
+
private readonly maxStorage: number;
|
|
11
|
+
private uniqueTrackerSize = 0;
|
|
12
|
+
private uniqueKeysTracker: { [keys: string]: ISet<string> };
|
|
13
|
+
|
|
14
|
+
constructor(uniqueKeysQueueSize: number = DEFAULT_CACHE_SIZE) {
|
|
15
|
+
this.maxStorage = uniqueKeysQueueSize;
|
|
16
|
+
this.uniqueKeysTracker = {};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
setOnFullQueueCb(cb: () => void) {
|
|
20
|
+
this.onFullQueue = cb;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Store unique keys in sequential order
|
|
25
|
+
* key: string = feature name.
|
|
26
|
+
* value: Set<string> = set of unique keys.
|
|
27
|
+
*/
|
|
28
|
+
track(key: string, featureName: string) {
|
|
29
|
+
if (!this.uniqueKeysTracker[featureName]) this.uniqueKeysTracker[featureName] = new _Set();
|
|
30
|
+
const tracker = this.uniqueKeysTracker[featureName];
|
|
31
|
+
if (!tracker.has(key)) {
|
|
32
|
+
tracker.add(key);
|
|
33
|
+
this.uniqueTrackerSize++;
|
|
34
|
+
}
|
|
35
|
+
if (this.uniqueTrackerSize >= this.maxStorage && this.onFullQueue) {
|
|
36
|
+
this.uniqueTrackerSize = 0;
|
|
37
|
+
this.onFullQueue();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Clear the data stored on the cache.
|
|
43
|
+
*/
|
|
44
|
+
clear() {
|
|
45
|
+
this.uniqueKeysTracker = {};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Pop the collected data, used as payload for posting.
|
|
50
|
+
*/
|
|
51
|
+
pop() {
|
|
52
|
+
const data = this.uniqueKeysTracker;
|
|
53
|
+
this.uniqueKeysTracker = {};
|
|
54
|
+
return this.fromUniqueKeysCollector(data);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Check if the cache is empty.
|
|
59
|
+
*/
|
|
60
|
+
isEmpty() {
|
|
61
|
+
return Object.keys(this.uniqueKeysTracker).length === 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Converts `uniqueKeys` data from cache into request payload for SS.
|
|
66
|
+
*/
|
|
67
|
+
private fromUniqueKeysCollector(uniqueKeys: { [featureName: string]: ISet<string> }): UniqueKeysPayloadSs {
|
|
68
|
+
const payload = [];
|
|
69
|
+
const featureNames = Object.keys(uniqueKeys);
|
|
70
|
+
for (let i = 0; i < featureNames.length; i++) {
|
|
71
|
+
const featureName = featureNames[i];
|
|
72
|
+
const featureKeys = setToArray(uniqueKeys[featureName]);
|
|
73
|
+
const uniqueKeysPayload = {
|
|
74
|
+
f: featureName,
|
|
75
|
+
ks: featureKeys
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
payload.push(uniqueKeysPayload);
|
|
79
|
+
}
|
|
80
|
+
return { keys: payload };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { IUniqueKeysCacheBase } from '../types';
|
|
2
|
+
import { ISet, setToArray, _Set } from '../../utils/lang/sets';
|
|
3
|
+
import { UniqueKeysPayloadCs } from '../../sync/submitters/types';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_CACHE_SIZE = 30000;
|
|
6
|
+
|
|
7
|
+
export class UniqueKeysCacheInMemoryCS implements IUniqueKeysCacheBase {
|
|
8
|
+
|
|
9
|
+
private onFullQueue?: () => void;
|
|
10
|
+
private readonly maxStorage: number;
|
|
11
|
+
private uniqueTrackerSize = 0;
|
|
12
|
+
private uniqueKeysTracker: { [keys: string]: ISet<string> };
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
*
|
|
16
|
+
* @param impressionsQueueSize number of queued impressions to call onFullQueueCb.
|
|
17
|
+
* Default value is 0, that means no maximum value, in case we want to avoid this being triggered.
|
|
18
|
+
*/
|
|
19
|
+
constructor(uniqueKeysQueueSize: number = DEFAULT_CACHE_SIZE) {
|
|
20
|
+
this.maxStorage = uniqueKeysQueueSize;
|
|
21
|
+
this.uniqueKeysTracker = {};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
setOnFullQueueCb(cb: () => void) {
|
|
25
|
+
this.onFullQueue = cb;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Store unique keys in sequential order
|
|
30
|
+
* key: string = key.
|
|
31
|
+
* value: HashSet<string> = set of split names.
|
|
32
|
+
*/
|
|
33
|
+
track(key: string, featureName: string) {
|
|
34
|
+
|
|
35
|
+
if (!this.uniqueKeysTracker[key]) this.uniqueKeysTracker[key] = new _Set();
|
|
36
|
+
const tracker = this.uniqueKeysTracker[key];
|
|
37
|
+
if (!tracker.has(featureName)) {
|
|
38
|
+
tracker.add(featureName);
|
|
39
|
+
this.uniqueTrackerSize++;
|
|
40
|
+
}
|
|
41
|
+
if (this.uniqueTrackerSize >= this.maxStorage && this.onFullQueue) {
|
|
42
|
+
this.uniqueTrackerSize = 0;
|
|
43
|
+
this.onFullQueue();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Clear the data stored on the cache.
|
|
49
|
+
*/
|
|
50
|
+
clear() {
|
|
51
|
+
this.uniqueKeysTracker = {};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Pop the collected data, used as payload for posting.
|
|
56
|
+
*/
|
|
57
|
+
pop() {
|
|
58
|
+
const data = this.uniqueKeysTracker;
|
|
59
|
+
this.uniqueKeysTracker = {};
|
|
60
|
+
return this.fromUniqueKeysCollector(data);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Check if the cache is empty.
|
|
65
|
+
*/
|
|
66
|
+
isEmpty() {
|
|
67
|
+
return Object.keys(this.uniqueKeysTracker).length === 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Converts `uniqueKeys` data from cache into request payload.
|
|
72
|
+
*/
|
|
73
|
+
private fromUniqueKeysCollector(uniqueKeys: { [featureName: string]: ISet<string> }): UniqueKeysPayloadCs {
|
|
74
|
+
const payload = [];
|
|
75
|
+
const featureKeys = Object.keys(uniqueKeys);
|
|
76
|
+
for (let k = 0; k < featureKeys.length; k++) {
|
|
77
|
+
const featureKey = featureKeys[k];
|
|
78
|
+
const featureNames = setToArray(uniqueKeys[featureKey]);
|
|
79
|
+
const uniqueKeysPayload = {
|
|
80
|
+
k: featureKey,
|
|
81
|
+
fs: featureNames
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
payload.push(uniqueKeysPayload);
|
|
85
|
+
}
|
|
86
|
+
return { keys: payload };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
}
|