@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.
Files changed (49) hide show
  1. package/cjs/sdkFactory/index.js +4 -3
  2. package/cjs/services/splitApi.js +2 -2
  3. package/cjs/storages/inLocalStorage/index.js +4 -0
  4. package/cjs/storages/inMemory/InMemoryStorage.js +5 -1
  5. package/cjs/storages/inMemory/InMemoryStorageCS.js +5 -1
  6. package/cjs/storages/inMemory/uniqueKeysCacheInMemory.js +73 -0
  7. package/cjs/storages/inMemory/uniqueKeysCacheInMemoryCS.js +78 -0
  8. package/cjs/sync/submitters/telemetrySubmitter.js +1 -0
  9. package/cjs/sync/submitters/uniqueKeysSubmitter.js +14 -58
  10. package/cjs/trackers/strategy/strategyNone.js +1 -1
  11. package/cjs/trackers/uniqueKeysTracker.js +5 -43
  12. package/cjs/utils/constants/index.js +3 -2
  13. package/esm/sdkFactory/index.js +4 -3
  14. package/esm/services/splitApi.js +2 -2
  15. package/esm/storages/inLocalStorage/index.js +5 -1
  16. package/esm/storages/inMemory/InMemoryStorage.js +6 -2
  17. package/esm/storages/inMemory/InMemoryStorageCS.js +6 -2
  18. package/esm/storages/inMemory/uniqueKeysCacheInMemory.js +70 -0
  19. package/esm/storages/inMemory/uniqueKeysCacheInMemoryCS.js +75 -0
  20. package/esm/sync/submitters/telemetrySubmitter.js +2 -1
  21. package/esm/sync/submitters/uniqueKeysSubmitter.js +13 -55
  22. package/esm/trackers/strategy/strategyNone.js +1 -1
  23. package/esm/trackers/uniqueKeysTracker.js +5 -43
  24. package/esm/utils/constants/index.js +1 -0
  25. package/package.json +1 -1
  26. package/src/sdkFactory/index.ts +4 -3
  27. package/src/services/splitApi.ts +2 -2
  28. package/src/storages/inLocalStorage/index.ts +4 -1
  29. package/src/storages/inMemory/InMemoryStorage.ts +5 -2
  30. package/src/storages/inMemory/InMemoryStorageCS.ts +6 -2
  31. package/src/storages/inMemory/uniqueKeysCacheInMemory.ts +83 -0
  32. package/src/storages/inMemory/uniqueKeysCacheInMemoryCS.ts +89 -0
  33. package/src/storages/types.ts +9 -6
  34. package/src/sync/submitters/telemetrySubmitter.ts +4 -3
  35. package/src/sync/submitters/uniqueKeysSubmitter.ts +15 -59
  36. package/src/trackers/impressionsTracker.ts +0 -1
  37. package/src/trackers/strategy/strategyNone.ts +1 -1
  38. package/src/trackers/types.ts +3 -7
  39. package/src/trackers/uniqueKeysTracker.ts +6 -49
  40. package/src/types.ts +1 -0
  41. package/src/utils/constants/index.ts +1 -0
  42. package/types/storages/inMemory/uniqueKeysCacheInMemory.d.ts +32 -0
  43. package/types/storages/inMemory/uniqueKeysCacheInMemoryCS.d.ts +37 -0
  44. package/types/storages/types.d.ts +8 -11
  45. package/types/sync/submitters/uniqueKeysSubmitter.d.ts +0 -14
  46. package/types/trackers/types.d.ts +3 -11
  47. package/types/trackers/uniqueKeysTracker.d.ts +3 -3
  48. package/types/types.d.ts +1 -0
  49. 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 { setToArray } from '../../utils/lang/sets';
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 fromUniqueKeysCollector = isClientSide ? fromUniqueKeysCollectorCs : fromUniqueKeysCollectorSs;
63
- return submitterFactory(log, postUniqueKeysBulk, uniqueKeys, uniqueKeysRefreshRate, 'unique keys', fromUniqueKeysCollector);
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.feature, impression.keyName);
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 cacheSize optional internal cache size
17
- * @param maxBulkSize optional max MTKs bulk size
14
+ * @param uniqueKeysCache cache to save unique keys
18
15
  */
19
- export function uniqueKeysTrackerFactory(log, filterAdapter, cacheSize) {
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 (featureName, key) {
26
- if (!filterAdapter.add(featureName, key)) {
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
- if (!uniqueKeysTracker[featureName])
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splitsoftware/splitio-commons",
3
- "version": "1.6.2-rc.1",
3
+ "version": "1.6.2-rc.2",
4
4
  "description": "Split Javascript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
@@ -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);
@@ -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}/api/v1/keys/cs`;
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}/api/v1/keys/ss`;
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.optimize ? new ImpressionCountsCacheInMemory() : undefined,
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.optimize ? new ImpressionCountsCacheInMemory() : undefined,
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
+ }