@splitsoftware/splitio-commons 1.6.2-rc.0 → 1.6.2-rc.1

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 (121) hide show
  1. package/cjs/listeners/browser.js +2 -1
  2. package/cjs/logger/constants.js +2 -1
  3. package/cjs/sdkFactory/index.js +13 -5
  4. package/cjs/services/splitApi.js +20 -0
  5. package/cjs/sync/submitters/submitterManager.js +3 -0
  6. package/cjs/sync/submitters/uniqueKeysSubmitter.js +70 -0
  7. package/cjs/trackers/impressionsTracker.js +7 -28
  8. package/cjs/trackers/strategy/strategyDebug.js +25 -0
  9. package/cjs/trackers/strategy/strategyNone.js +29 -0
  10. package/cjs/trackers/strategy/strategyOptimized.js +34 -0
  11. package/cjs/trackers/uniqueKeysTracker.js +69 -0
  12. package/cjs/utils/constants/index.js +3 -2
  13. package/cjs/utils/settingsValidation/impressionsMode.js +2 -2
  14. package/cjs/utils/settingsValidation/index.js +3 -0
  15. package/esm/listeners/browser.js +3 -2
  16. package/esm/logger/constants.js +1 -0
  17. package/esm/sdkFactory/index.js +13 -5
  18. package/esm/services/splitApi.js +20 -0
  19. package/esm/sync/submitters/submitterManager.js +3 -0
  20. package/esm/sync/submitters/uniqueKeysSubmitter.js +64 -0
  21. package/esm/trackers/impressionsTracker.js +7 -28
  22. package/esm/trackers/strategy/strategyDebug.js +21 -0
  23. package/esm/trackers/strategy/strategyNone.js +25 -0
  24. package/esm/trackers/strategy/strategyOptimized.js +30 -0
  25. package/esm/trackers/uniqueKeysTracker.js +65 -0
  26. package/esm/utils/constants/index.js +1 -0
  27. package/esm/utils/settingsValidation/impressionsMode.js +3 -3
  28. package/esm/utils/settingsValidation/index.js +3 -0
  29. package/package.json +1 -1
  30. package/src/listeners/browser.ts +3 -2
  31. package/src/logger/constants.ts +1 -0
  32. package/src/sdkFactory/index.ts +15 -5
  33. package/src/sdkFactory/types.ts +7 -4
  34. package/src/services/splitApi.ts +22 -0
  35. package/src/services/types.ts +6 -0
  36. package/src/storages/types.ts +17 -4
  37. package/src/sync/submitters/submitterManager.ts +2 -0
  38. package/src/sync/submitters/types.ts +20 -1
  39. package/src/sync/submitters/uniqueKeysSubmitter.ts +79 -0
  40. package/src/trackers/impressionsTracker.ts +12 -34
  41. package/src/trackers/strategy/strategyDebug.ts +28 -0
  42. package/src/trackers/strategy/strategyNone.ts +34 -0
  43. package/src/trackers/strategy/strategyOptimized.ts +42 -0
  44. package/src/trackers/types.ts +30 -0
  45. package/src/trackers/uniqueKeysTracker.ts +80 -0
  46. package/src/types.ts +2 -1
  47. package/src/utils/constants/index.ts +1 -0
  48. package/src/utils/settingsValidation/impressionsMode.ts +3 -3
  49. package/src/utils/settingsValidation/index.ts +4 -0
  50. package/types/logger/browser/{debugLogger.d.ts → DebugLogger.d.ts} +0 -0
  51. package/types/logger/browser/{errorLogger.d.ts → ErrorLogger.d.ts} +0 -0
  52. package/types/logger/browser/{infoLogger.d.ts → InfoLogger.d.ts} +0 -0
  53. package/types/logger/browser/{warnLogger.d.ts → WarnLogger.d.ts} +0 -0
  54. package/types/logger/constants.d.ts +1 -0
  55. package/types/sdkFactory/types.d.ts +4 -2
  56. package/types/services/types.d.ts +4 -0
  57. package/types/storages/types.d.ts +16 -3
  58. package/types/sync/submitters/types.d.ts +18 -1
  59. package/types/sync/submitters/uniqueKeysSubmitter.d.ts +10 -4
  60. package/types/trackers/filter/bloomFilter.d.ts +10 -0
  61. package/types/trackers/filter/dictionaryFilter.d.ts +8 -0
  62. package/types/trackers/filter/types.d.ts +5 -0
  63. package/types/trackers/impressionsTracker.d.ts +4 -6
  64. package/types/trackers/types.d.ts +29 -0
  65. package/types/types.d.ts +2 -1
  66. package/types/utils/constants/index.d.ts +1 -0
  67. package/types/utils/settingsValidation/index.d.ts +1 -0
  68. package/types/utils/timeTracker/index.d.ts +70 -1
  69. package/src/logger/.DS_Store +0 -0
  70. package/src/splitio.d.ts +0 -1602
  71. package/types/integrations/ga/GaToSplitPlugin.d.ts +0 -3
  72. package/types/integrations/ga/SplitToGaPlugin.d.ts +0 -4
  73. package/types/integrations/ga/autoRequire.d.ts +0 -4
  74. package/types/logger/codes.d.ts +0 -2
  75. package/types/logger/codesConstants.d.ts +0 -117
  76. package/types/logger/codesConstantsBrowser.d.ts +0 -2
  77. package/types/logger/codesConstantsNode.d.ts +0 -14
  78. package/types/logger/codesDebug.d.ts +0 -1
  79. package/types/logger/codesDebugBrowser.d.ts +0 -1
  80. package/types/logger/codesDebugNode.d.ts +0 -1
  81. package/types/logger/codesError.d.ts +0 -1
  82. package/types/logger/codesErrorNode.d.ts +0 -1
  83. package/types/logger/codesInfo.d.ts +0 -1
  84. package/types/logger/codesWarn.d.ts +0 -1
  85. package/types/logger/codesWarnNode.d.ts +0 -1
  86. package/types/logger/debugLogger.d.ts +0 -2
  87. package/types/logger/errorLogger.d.ts +0 -2
  88. package/types/logger/infoLogger.d.ts +0 -2
  89. package/types/logger/messages/debugBrowser.d.ts +0 -1
  90. package/types/logger/messages/debugNode.d.ts +0 -1
  91. package/types/logger/messages/errorNode.d.ts +0 -1
  92. package/types/logger/messages/warnNode.d.ts +0 -1
  93. package/types/logger/noopLogger.d.ts +0 -2
  94. package/types/logger/warnLogger.d.ts +0 -2
  95. package/types/sdkFactory/userConsentProps.d.ts +0 -6
  96. package/types/sdkManager/sdkManagerMethod.d.ts +0 -6
  97. package/types/storages/getRegisteredSegments.d.ts +0 -10
  98. package/types/storages/inMemory/index.d.ts +0 -10
  99. package/types/storages/parseSegments.d.ts +0 -6
  100. package/types/sync/polling/syncTasks/splitsSyncTask.copy.d.ts +0 -35
  101. package/types/sync/polling/syncTasks/splitsSyncTask.morelikeoriginal.d.ts +0 -35
  102. package/types/sync/streaming/AuthClient/indexV1.d.ts +0 -12
  103. package/types/sync/streaming/AuthClient/indexV2.d.ts +0 -8
  104. package/types/sync/streaming/pushManagerCS.d.ts +0 -1
  105. package/types/sync/streaming/pushManagerNoUsers.d.ts +0 -13
  106. package/types/sync/streaming/pushManagerSS.d.ts +0 -1
  107. package/types/sync/submitters/telemetrySyncTask.d.ts +0 -0
  108. package/types/sync/syncManagerFromFile.d.ts +0 -2
  109. package/types/sync/syncManagerFromObject.d.ts +0 -2
  110. package/types/sync/syncManagerOffline.d.ts +0 -9
  111. package/types/trackers/telemetryRecorder.d.ts +0 -0
  112. package/types/utils/EventEmitter.d.ts +0 -4
  113. package/types/utils/consent.d.ts +0 -2
  114. package/types/utils/lang/errors.d.ts +0 -10
  115. package/types/utils/murmur3/commons.d.ts +0 -12
  116. package/types/utils/settingsValidation/buildMetadata.d.ts +0 -3
  117. package/types/utils/settingsValidation/localhost/index.d.ts +0 -9
  118. package/types/utils/settingsValidation/logger.d.ts +0 -11
  119. package/types/utils/settingsValidation/runtime/browser.d.ts +0 -2
  120. package/types/utils/settingsValidation/runtime/node.d.ts +0 -2
  121. package/types/utils/settingsValidation/userConsent.d.ts +0 -5
@@ -0,0 +1,64 @@
1
+ import { setToArray } from '../../utils/lang/sets';
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
+ }
55
+ /**
56
+ * Submitter that periodically posts impression counts
57
+ */
58
+ export function uniqueKeysSubmitterFactory(params) {
59
+ 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
+ var isClientSide = key !== undefined;
61
+ var postUniqueKeysBulk = isClientSide ? postUniqueKeysBulkCs : postUniqueKeysBulkSs;
62
+ var fromUniqueKeysCollector = isClientSide ? fromUniqueKeysCollectorCs : fromUniqueKeysCollectorSs;
63
+ return submitterFactory(log, postUniqueKeysBulk, uniqueKeys, uniqueKeysRefreshRate, 'unique keys', fromUniqueKeysCollector);
64
+ }
@@ -1,6 +1,5 @@
1
1
  import { objectAssign } from '../utils/lang/objectAssign';
2
2
  import { thenable } from '../utils/promise/thenable';
3
- import { truncateTimeFrame } from '../utils/time';
4
3
  import { IMPRESSIONS_TRACKER_SUCCESS, ERROR_IMPRESSIONS_TRACKER, ERROR_IMPRESSIONS_LISTENER } from '../logger/constants';
5
4
  import { CONSENT_DECLINED, DEDUPED, QUEUED } from '../utils/constants';
6
5
  /**
@@ -10,37 +9,17 @@ import { CONSENT_DECLINED, DEDUPED, QUEUED } from '../utils/constants';
10
9
  * @param metadata runtime metadata (ip, hostname and version)
11
10
  * @param impressionListener optional impression listener
12
11
  * @param integrationsManager optional integrations manager
13
- * @param observer optional impression observer. If provided, previous time (pt property) is included in impression instances
14
- * @param countsCache optional cache to save impressions count. If provided, impressions will be deduped (OPTIMIZED mode)
12
+ * @param strategy strategy for impressions tracking.
15
13
  */
16
- export function impressionsTrackerFactory(settings, impressionsCache, integrationsManager,
17
- // if observer is provided, it implies `shouldAddPreviousTime` flag (i.e., if impressions previous time should be added or not)
18
- observer,
19
- // if countsCache is provided, it implies `isOptimized` flag (i.e., if impressions should be deduped or not)
20
- countsCache, telemetryCache) {
14
+ export function impressionsTrackerFactory(settings, impressionsCache, strategy, integrationsManager, telemetryCache) {
21
15
  var log = settings.log, impressionListener = settings.impressionListener, _a = settings.runtime, ip = _a.ip, hostname = _a.hostname, version = settings.version;
22
16
  return {
23
17
  track: function (impressions, attributes) {
24
18
  if (settings.userConsent === CONSENT_DECLINED)
25
19
  return;
26
20
  var impressionsCount = impressions.length;
27
- var impressionsToStore = []; // Track only the impressions that are going to be stored
28
- // Wraps impressions to store and adds previousTime if it corresponds
29
- impressions.forEach(function (impression) {
30
- if (observer) {
31
- // Adds previous time if it is enabled
32
- impression.pt = observer.testAndSet(impression);
33
- }
34
- var now = Date.now();
35
- if (countsCache) {
36
- // Increments impression counter per featureName
37
- countsCache.track(impression.feature, now, 1);
38
- }
39
- // Checks if the impression should be added in queue to be sent
40
- if (!countsCache || !impression.pt || impression.pt < truncateTimeFrame(now)) {
41
- impressionsToStore.push(impression);
42
- }
43
- });
21
+ var _a = strategy.process(impressions), impressionsToStore = _a.impressionsToStore, impressionsToListener = _a.impressionsToListener, deduped = _a.deduped;
22
+ var impressionsToListenerCount = impressionsToListener.length;
44
23
  var res = impressionsCache.track(impressionsToStore);
45
24
  // If we're on an async storage, handle error and log it.
46
25
  if (thenable(res)) {
@@ -55,7 +34,7 @@ countsCache, telemetryCache) {
55
34
  // @TODO we are not dropping impressions on full queue yet, so DROPPED stats are not recorded
56
35
  if (telemetryCache) {
57
36
  telemetryCache.recordImpressionStats(QUEUED, impressionsToStore.length);
58
- telemetryCache.recordImpressionStats(DEDUPED, impressions.length - impressionsToStore.length);
37
+ telemetryCache.recordImpressionStats(DEDUPED, deduped);
59
38
  }
60
39
  }
61
40
  // @TODO next block might be handled by the integration manager. In that case, the metadata object doesn't need to be passed in the constructor
@@ -63,7 +42,7 @@ countsCache, telemetryCache) {
63
42
  var _loop_1 = function (i) {
64
43
  var impressionData = {
65
44
  // copy of impression, to avoid unexpected behaviour if modified by integrations or impressionListener
66
- impression: objectAssign({}, impressions[i]),
45
+ impression: objectAssign({}, impressionsToListener[i]),
67
46
  attributes: attributes,
68
47
  ip: ip,
69
48
  hostname: hostname,
@@ -83,7 +62,7 @@ countsCache, telemetryCache) {
83
62
  }
84
63
  }, 0);
85
64
  };
86
- for (var i = 0; i < impressionsCount; i++) {
65
+ for (var i = 0; i < impressionsToListenerCount; i++) {
87
66
  _loop_1(i);
88
67
  }
89
68
  }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Debug strategy for impressions tracker. Wraps impressions to store and adds previousTime if it corresponds
3
+ *
4
+ * @param impressionsObserver impression observer. Previous time (pt property) is included in impression instances
5
+ * @returns IStrategyResult
6
+ */
7
+ export function strategyDebugFactory(impressionsObserver) {
8
+ return {
9
+ process: function (impressions) {
10
+ impressions.forEach(function (impression) {
11
+ // Adds previous time if it is enabled
12
+ impression.pt = impressionsObserver.testAndSet(impression);
13
+ });
14
+ return {
15
+ impressionsToStore: impressions,
16
+ impressionsToListener: impressions,
17
+ deduped: 0
18
+ };
19
+ }
20
+ };
21
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * None strategy for impressions tracker.
3
+ *
4
+ * @param impressionsCounter cache to save impressions count. impressions will be deduped (OPTIMIZED mode)
5
+ * @param uniqueKeysTracker unique keys tracker in charge of tracking the unique keys per split.
6
+ * @returns IStrategyResult
7
+ */
8
+ export function strategyNoneFactory(impressionsCounter, uniqueKeysTracker) {
9
+ return {
10
+ process: function (impressions) {
11
+ impressions.forEach(function (impression) {
12
+ var now = Date.now();
13
+ // Increments impression counter per featureName
14
+ impressionsCounter.track(impression.feature, now, 1);
15
+ // Keep track by unique key
16
+ uniqueKeysTracker.track(impression.feature, impression.keyName);
17
+ });
18
+ return {
19
+ impressionsToStore: [],
20
+ impressionsToListener: impressions,
21
+ deduped: 0
22
+ };
23
+ }
24
+ };
25
+ }
@@ -0,0 +1,30 @@
1
+ import { truncateTimeFrame } from '../../utils/time';
2
+ /**
3
+ * Optimized strategy for impressions tracker. Wraps impressions to store and adds previousTime if it corresponds
4
+ *
5
+ * @param impressionsObserver impression observer. previous time (pt property) is included in impression instances
6
+ * @param impressionsCounter cache to save impressions count. impressions will be deduped (OPTIMIZED mode)
7
+ * @returns IStrategyResult
8
+ */
9
+ export function strategyOptimizedFactory(impressionsObserver, impressionsCounter) {
10
+ return {
11
+ process: function (impressions) {
12
+ var impressionsToStore = [];
13
+ impressions.forEach(function (impression) {
14
+ impression.pt = impressionsObserver.testAndSet(impression);
15
+ var now = Date.now();
16
+ // Increments impression counter per featureName
17
+ impressionsCounter.track(impression.feature, now, 1);
18
+ // Checks if the impression should be added in queue to be sent
19
+ if (!impression.pt || impression.pt < truncateTimeFrame(now)) {
20
+ impressionsToStore.push(impression);
21
+ }
22
+ });
23
+ return {
24
+ impressionsToStore: impressionsToStore,
25
+ impressionsToListener: impressions,
26
+ deduped: impressions.length - impressionsToStore.length
27
+ };
28
+ }
29
+ };
30
+ }
@@ -0,0 +1,65 @@
1
+ import { LOG_PREFIX_UNIQUE_KEYS_TRACKER } from '../logger/constants';
2
+ import { _Set } from '../utils/lang/sets';
3
+ var noopFilterAdapter = {
4
+ add: function () { return true; },
5
+ contains: function () { return true; },
6
+ clear: function () { }
7
+ };
8
+ var DEFAULT_CACHE_SIZE = 30000;
9
+ /**
10
+ * Trackes uniques keys
11
+ * Unique Keys Tracker will be in charge of checking if the MTK was already sent to the BE in the last period
12
+ * or schedule to be sent; if not it will be added in an internal cache and sent in the next post.
13
+ *
14
+ * @param log Logger instance
15
+ * @param filterAdapter filter adapter
16
+ * @param cacheSize optional internal cache size
17
+ * @param maxBulkSize optional max MTKs bulk size
18
+ */
19
+ export function uniqueKeysTrackerFactory(log, filterAdapter, cacheSize) {
20
+ if (filterAdapter === void 0) { filterAdapter = noopFilterAdapter; }
21
+ if (cacheSize === void 0) { cacheSize = DEFAULT_CACHE_SIZE; }
22
+ var uniqueKeysTracker = {};
23
+ var uniqueTrackerSize = 0;
24
+ return {
25
+ track: function (featureName, key) {
26
+ if (!filterAdapter.add(featureName, key)) {
27
+ log.debug(LOG_PREFIX_UNIQUE_KEYS_TRACKER + "The feature " + featureName + " and key " + key + " exist in the filter");
28
+ return;
29
+ }
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;
63
+ }
64
+ };
65
+ }
@@ -13,6 +13,7 @@ export var SPLIT_EVENT = 'EVENT';
13
13
  // Impression collection modes
14
14
  export var DEBUG = 'DEBUG';
15
15
  export var OPTIMIZED = 'OPTIMIZED';
16
+ export var NONE = 'NONE';
16
17
  // SDK Modes
17
18
  export var LOCALHOST_MODE = 'localhost';
18
19
  export var STANDALONE_MODE = 'standalone';
@@ -1,10 +1,10 @@
1
1
  import { ERROR_INVALID_CONFIG_PARAM } from '../../logger/constants';
2
- import { DEBUG, OPTIMIZED } from '../constants';
2
+ import { DEBUG, OPTIMIZED, NONE } from '../constants';
3
3
  import { stringToUpperCase } from '../lang';
4
4
  export function validImpressionsMode(log, impressionsMode) {
5
5
  impressionsMode = stringToUpperCase(impressionsMode);
6
- if ([DEBUG, OPTIMIZED].indexOf(impressionsMode) > -1)
6
+ if ([DEBUG, OPTIMIZED, NONE].indexOf(impressionsMode) > -1)
7
7
  return impressionsMode;
8
- log.error(ERROR_INVALID_CONFIG_PARAM, ['impressionsMode', [DEBUG, OPTIMIZED], OPTIMIZED]);
8
+ log.error(ERROR_INVALID_CONFIG_PARAM, ['impressionsMode', [DEBUG, OPTIMIZED, NONE], OPTIMIZED]);
9
9
  return OPTIMIZED;
10
10
  }
@@ -31,6 +31,8 @@ export var base = {
31
31
  telemetryRefreshRate: 3600,
32
32
  // publish evaluations each 300 sec (default value for OPTIMIZED impressions mode)
33
33
  impressionsRefreshRate: 300,
34
+ // publish unique Keys each 900 sec (15 min)
35
+ uniqueKeysRefreshRate: 900,
34
36
  // fetch offline changes each 15 sec
35
37
  offlineRefreshRate: 15,
36
38
  // publish events every 60 seconds after the first flush
@@ -109,6 +111,7 @@ export function settingsValidation(config, validationParams) {
109
111
  scheduler.segmentsRefreshRate = fromSecondsToMillis(scheduler.segmentsRefreshRate);
110
112
  scheduler.offlineRefreshRate = fromSecondsToMillis(scheduler.offlineRefreshRate);
111
113
  scheduler.eventsPushRate = fromSecondsToMillis(scheduler.eventsPushRate);
114
+ scheduler.uniqueKeysRefreshRate = fromSecondsToMillis(scheduler.uniqueKeysRefreshRate);
112
115
  scheduler.telemetryRefreshRate = fromSecondsToMillis(validateMinValue('telemetryRefreshRate', scheduler.telemetryRefreshRate, 60));
113
116
  // Default impressionsRefreshRate for DEBUG mode is 60 secs
114
117
  if (get(config, 'scheduler.impressionsRefreshRate') === undefined && withDefaults.sync.impressionsMode === DEBUG)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splitsoftware/splitio-commons",
3
- "version": "1.6.2-rc.0",
3
+ "version": "1.6.2-rc.1",
4
4
  "description": "Split Javascript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
@@ -7,7 +7,7 @@ import { fromImpressionCountsCollector } from '../sync/submitters/impressionCoun
7
7
  import { IResponse, ISplitApi } from '../services/types';
8
8
  import { ImpressionDTO, ISettings } from '../types';
9
9
  import { ImpressionsPayload } from '../sync/submitters/types';
10
- import { OPTIMIZED, DEBUG } from '../utils/constants';
10
+ import { OPTIMIZED, DEBUG, NONE } from '../utils/constants';
11
11
  import { objectAssign } from '../utils/lang/objectAssign';
12
12
  import { CLEANUP_REGISTERING, CLEANUP_DEREGISTERING } from '../logger/constants';
13
13
  import { ISyncManager } from '../sync/types';
@@ -88,9 +88,10 @@ export class BrowserSignalListener implements ISignalListener {
88
88
  // Flush impressions & events data if there is user consent
89
89
  if (isConsentGranted(this.settings)) {
90
90
  const eventsUrl = this.settings.urls.events;
91
+ const sim = this.settings.sync.impressionsMode;
91
92
  const extraMetadata = {
92
93
  // sim stands for Sync/Split Impressions Mode
93
- sim: this.settings.sync.impressionsMode === OPTIMIZED ? OPTIMIZED : DEBUG
94
+ sim: sim === OPTIMIZED ? OPTIMIZED : sim === DEBUG ? DEBUG : NONE
94
95
  };
95
96
 
96
97
  this._flushData(eventsUrl + '/testImpressions/beacon', this.storage.impressions, this.serviceApi.postTestImpressionsBulk, this.fromImpressionsCollector, extraMetadata);
@@ -143,4 +143,5 @@ export const LOG_PREFIX_SYNC_POLLING = LOG_PREFIX_SYNC + ':polling-manager: ';
143
143
  export const LOG_PREFIX_SYNC_SUBMITTERS = LOG_PREFIX_SYNC + ':submitter: ';
144
144
  export const LOG_PREFIX_IMPRESSIONS_TRACKER = 'impressions-tracker: ';
145
145
  export const LOG_PREFIX_EVENTS_TRACKER = 'events-tracker: ';
146
+ export const LOG_PREFIX_UNIQUE_KEYS_TRACKER = 'unique-keys-tracker: ';
146
147
  export const LOG_PREFIX_CLEANUP = 'cleanup: ';
@@ -13,6 +13,11 @@ import { NEW_FACTORY, RETRIEVE_MANAGER } from '../logger/constants';
13
13
  import { metadataBuilder } from '../storages/metadataBuilder';
14
14
  import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../readiness/constants';
15
15
  import { objectAssign } from '../utils/lang/objectAssign';
16
+ import { strategyDebugFactory } from '../trackers/strategy/strategyDebug';
17
+ import { strategyOptimizedFactory } from '../trackers/strategy/strategyOptimized';
18
+ import { strategyNoneFactory } from '../trackers/strategy/strategyNone';
19
+ import { uniqueKeysTrackerFactory } from '../trackers/uniqueKeysTracker';
20
+ import { NONE } from '../utils/constants';
16
21
 
17
22
  /**
18
23
  * Modular SDK factory
@@ -21,8 +26,10 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
21
26
 
22
27
  const { settings, platform, storageFactory, splitApiFactory, extraProps,
23
28
  syncManagerFactory, SignalListener, impressionsObserverFactory,
24
- integrationsManagerFactory, sdkManagerFactory, sdkClientMethodFactory } = params;
29
+ integrationsManagerFactory, sdkManagerFactory, sdkClientMethodFactory,
30
+ filterAdapterFactory } = params;
25
31
  const log = settings.log;
32
+ const impressionsMode = settings.sync.impressionsMode;
26
33
 
27
34
  // @TODO handle non-recoverable errors, such as, global `fetch` not available, invalid API Key, etc.
28
35
  // On non-recoverable errors, we should mark the SDK as destroyed and not start synchronization.
@@ -62,16 +69,19 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
62
69
 
63
70
  const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage });
64
71
 
65
- // trackers
66
- const observer = impressionsObserverFactory && impressionsObserverFactory();
67
- const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, integrationsManager, observer, storage.impressionCounts, storage.telemetry);
72
+ const observer = impressionsObserverFactory();
73
+ const uniqueKeysTracker = impressionsMode === NONE ? uniqueKeysTrackerFactory(log, filterAdapterFactory && filterAdapterFactory()) : undefined;
74
+ const strategy = (storageFactoryParams.optimize) ? strategyOptimizedFactory(observer, storage.impressionCounts!) :
75
+ (impressionsMode === NONE) ? strategyNoneFactory(storage.impressionCounts!, uniqueKeysTracker!) : strategyDebugFactory(observer);
76
+
77
+ const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, strategy, integrationsManager, storage.telemetry);
68
78
  const eventTracker = eventTrackerFactory(settings, storage.events, integrationsManager, storage.telemetry);
69
79
  const telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now);
70
80
 
71
81
  // splitApi is used by SyncManager and Browser signal listener
72
82
  const splitApi = splitApiFactory && splitApiFactory(settings, platform, telemetryTracker);
73
83
 
74
- const ctx: ISdkFactoryContext = { splitApi, eventTracker, impressionsTracker, telemetryTracker, sdkReadinessManager, readiness, settings, storage, platform };
84
+ const ctx: ISdkFactoryContext = { splitApi, eventTracker, impressionsTracker, telemetryTracker, uniqueKeysTracker, sdkReadinessManager, readiness, settings, storage, platform };
75
85
 
76
86
  const syncManager = syncManagerFactory && syncManagerFactory(ctx as ISdkFactoryContextSync);
77
87
  ctx.syncManager = syncManager;
@@ -6,7 +6,7 @@ import { IFetch, ISplitApi, IEventSourceConstructor } from '../services/types';
6
6
  import { IStorageAsync, IStorageSync, ISplitsCacheSync, ISplitsCacheAsync, IStorageFactoryParams } from '../storages/types';
7
7
  import { ISyncManager } from '../sync/types';
8
8
  import { IImpressionObserver } from '../trackers/impressionObserver/types';
9
- import { IImpressionsTracker, IEventTracker, ITelemetryTracker } from '../trackers/types';
9
+ import { IImpressionsTracker, IEventTracker, ITelemetryTracker, IFilterAdapter, IUniqueKeysTracker } from '../trackers/types';
10
10
  import { SplitIO, ISettings, IEventEmitter } from '../types';
11
11
 
12
12
  /**
@@ -44,6 +44,7 @@ export interface ISdkFactoryContext {
44
44
  eventTracker: IEventTracker,
45
45
  telemetryTracker: ITelemetryTracker,
46
46
  storage: IStorageSync | IStorageAsync,
47
+ uniqueKeysTracker?: IUniqueKeysTracker,
47
48
  signalListener?: ISignalListener
48
49
  splitApi?: ISplitApi
49
50
  syncManager?: ISyncManager,
@@ -96,6 +97,11 @@ export interface ISdkFactoryParams {
96
97
  // It Allows to distinguish SDK clients with the client-side API (`ICsSDK`) or server-side API (`ISDK` or `IAsyncSDK`).
97
98
  sdkClientMethodFactory: (params: ISdkFactoryContext) => ({ (): SplitIO.ICsClient; (key: SplitIO.SplitKey, trafficType?: string | undefined): SplitIO.ICsClient; } | (() => SplitIO.IClient) | (() => SplitIO.IAsyncClient))
98
99
 
100
+ // Impression observer factory. If provided, will be used for impressions dedupe
101
+ impressionsObserverFactory: () => IImpressionObserver
102
+
103
+ filterAdapterFactory?: () => IFilterAdapter
104
+
99
105
  // Optional signal listener constructor. Used to handle special app states, like shutdown, app paused or resumed.
100
106
  // Pass only if `syncManager` (used by Node listener) and `splitApi` (used by Browser listener) are passed.
101
107
  SignalListener?: new (
@@ -107,9 +113,6 @@ export interface ISdkFactoryParams {
107
113
  // @TODO review impressionListener and integrations interfaces. What about handling impressionListener as an integration ?
108
114
  integrationsManagerFactory?: (params: IIntegrationFactoryParams) => IIntegrationManager | undefined,
109
115
 
110
- // Impression observer factory. If provided, will be used for impressions dedupe
111
- impressionsObserverFactory?: () => IImpressionObserver
112
-
113
116
  // Optional function to assign additional properties to the factory instance
114
117
  extraProps?: (params: ISdkFactoryContext) => object
115
118
  }
@@ -106,6 +106,28 @@ export function splitApiFactory(
106
106
  const url = `${urls.events}/testImpressions/count`;
107
107
  return splitHttpClient(url, { method: 'POST', body, headers }, telemetryTracker.trackHttp(IMPRESSIONS_COUNT));
108
108
  },
109
+
110
+ /**
111
+ * Post unique keys for client side.
112
+ *
113
+ * @param body unique keys payload
114
+ * @param headers Optionals headers to overwrite default ones. For example, it is used in producer mode to overwrite metadata headers.
115
+ */
116
+ postUniqueKeysBulkCs(body: string, headers?: Record<string, string>) {
117
+ const url = `${urls.telemetry}/api/v1/keys/cs`;
118
+ return splitHttpClient(url, { method: 'POST', body, headers }, telemetryTracker.trackHttp(TELEMETRY));
119
+ },
120
+
121
+ /**
122
+ * Post unique keys for server side.
123
+ *
124
+ * @param body unique keys payload
125
+ * @param headers Optionals headers to overwrite default ones. For example, it is used in producer mode to overwrite metadata headers.
126
+ */
127
+ postUniqueKeysBulkSs(body: string, headers?: Record<string, string>) {
128
+ const url = `${urls.telemetry}/api/v1/keys/ss`;
129
+ return splitHttpClient(url, { method: 'POST', body, headers }, telemetryTracker.trackHttp(TELEMETRY));
130
+ },
109
131
 
110
132
  postMetricsConfig(body: string) {
111
133
  const url = `${urls.telemetry}/v1/metrics/config`;
@@ -43,6 +43,10 @@ export type IFetchMySegments = (userMatchingKey: string, noCache?: boolean) => P
43
43
 
44
44
  export type IPostEventsBulk = (body: string, headers?: Record<string, string>) => Promise<IResponse>
45
45
 
46
+ export type IPostUniqueKeysBulkCs = (body: string, headers?: Record<string, string>) => Promise<IResponse>
47
+
48
+ export type IPostUniqueKeysBulkSs = (body: string, headers?: Record<string, string>) => Promise<IResponse>
49
+
46
50
  export type IPostTestImpressionsBulk = (body: string, headers?: Record<string, string>) => Promise<IResponse>
47
51
 
48
52
  export type IPostTestImpressionsCount = (body: string, headers?: Record<string, string>) => Promise<IResponse>
@@ -59,6 +63,8 @@ export interface ISplitApi {
59
63
  fetchSegmentChanges: IFetchSegmentChanges
60
64
  fetchMySegments: IFetchMySegments
61
65
  postEventsBulk: IPostEventsBulk
66
+ postUniqueKeysBulkCs: IPostUniqueKeysBulkCs
67
+ postUniqueKeysBulkSs: IPostUniqueKeysBulkSs
62
68
  postTestImpressionsBulk: IPostTestImpressionsBulk
63
69
  postTestImpressionsCount: IPostTestImpressionsCount
64
70
  postMetricsConfig: IPostMetricsConfig
@@ -2,6 +2,7 @@ import { MaybeThenable, IMetadata, ISplitFiltersValidation } from '../dtos/types
2
2
  import { ILogger } from '../logger/types';
3
3
  import { EventDataType, HttpErrors, HttpLatencies, ImpressionDataType, LastSync, Method, MethodExceptions, MethodLatencies, OperationType, StoredEventWithMetadata, StoredImpressionWithMetadata, StreamingEvent } from '../sync/submitters/types';
4
4
  import { SplitIO, ImpressionDTO, SDKMode } from '../types';
5
+ import { ISet } from '../utils/lang/sets';
5
6
 
6
7
  /**
7
8
  * Interface of a pluggable storage wrapper.
@@ -355,6 +356,14 @@ export interface IImpressionCountsCacheSync extends IRecorderCacheProducerSync<R
355
356
  pop(toMerge?: Record<string, number> ): Record<string, number> // pop cache data
356
357
  }
357
358
 
359
+ export interface IUniqueKeysCacheBase extends IRecorderCacheProducerSync<{ [featureName: string]: ISet<string> }>{
360
+ // Used by unique Keys tracker
361
+ track(featureName: string, timeFrame: number, amount: number): void
362
+
363
+ // Used by unique keys submitter in standalone and producer mode
364
+ isEmpty(): boolean // check if cache is empty. Return true if the cache was just created or cleared.
365
+ pop(toMerge?: { [featureName: string]: ISet<string> } ): { [featureName: string]: ISet<string> } // pop cache data
366
+ }
358
367
 
359
368
  /**
360
369
  * Telemetry storage interface for standalone and partial consumer modes.
@@ -445,14 +454,16 @@ export interface IStorageBase<
445
454
  TSegmentsCache extends ISegmentsCacheBase,
446
455
  TImpressionsCache extends IImpressionsCacheBase,
447
456
  TEventsCache extends IEventsCacheBase,
448
- TTelemetryCache extends ITelemetryCacheSync | ITelemetryCacheAsync
457
+ TTelemetryCache extends ITelemetryCacheSync | ITelemetryCacheAsync,
458
+ TUniqueKeysCache extends IUniqueKeysCacheBase
449
459
  > {
450
460
  splits: TSplitsCache,
451
461
  segments: TSegmentsCache,
452
462
  impressions: TImpressionsCache,
453
463
  impressionCounts?: IImpressionCountsCacheSync,
454
464
  events: TEventsCache,
455
- telemetry?: TTelemetryCache
465
+ telemetry?: TTelemetryCache,
466
+ uniqueKeys?: TUniqueKeysCache,
456
467
  destroy(): void | Promise<void>,
457
468
  shared?: (matchingKey: string, onReadyCb: (error?: any) => void) => this
458
469
  }
@@ -462,7 +473,8 @@ export interface IStorageSync extends IStorageBase<
462
473
  ISegmentsCacheSync,
463
474
  IImpressionsCacheSync,
464
475
  IEventsCacheSync,
465
- ITelemetryCacheSync
476
+ ITelemetryCacheSync,
477
+ IUniqueKeysCacheBase
466
478
  > { }
467
479
 
468
480
  export interface IStorageAsync extends IStorageBase<
@@ -470,7 +482,8 @@ export interface IStorageAsync extends IStorageBase<
470
482
  ISegmentsCacheAsync,
471
483
  IImpressionsCacheAsync | IImpressionsCacheSync,
472
484
  IEventsCacheAsync | IEventsCacheSync,
473
- ITelemetryCacheAsync
485
+ ITelemetryCacheAsync,
486
+ IUniqueKeysCacheBase
474
487
  > { }
475
488
 
476
489
  /** StorageFactory */
@@ -4,6 +4,7 @@ import { impressionCountsSubmitterFactory } from './impressionCountsSubmitter';
4
4
  import { telemetrySubmitterFactory } from './telemetrySubmitter';
5
5
  import { ISdkFactoryContextSync } from '../../sdkFactory/types';
6
6
  import { ISubmitterManager } from './types';
7
+ import { uniqueKeysSubmitterFactory } from './uniqueKeysSubmitter';
7
8
 
8
9
  export function submitterManagerFactory(params: ISdkFactoryContextSync): ISubmitterManager {
9
10
 
@@ -15,6 +16,7 @@ export function submitterManagerFactory(params: ISdkFactoryContextSync): ISubmit
15
16
  const impressionCountsSubmitter = impressionCountsSubmitterFactory(params);
16
17
  if (impressionCountsSubmitter) submitters.push(impressionCountsSubmitter);
17
18
  const telemetrySubmitter = telemetrySubmitterFactory(params);
19
+ if (params.uniqueKeysTracker) submitters.push(uniqueKeysSubmitterFactory(params));
18
20
 
19
21
  return {
20
22
  // `onlyTelemetry` true if SDK is created with userConsent not GRANTED
@@ -35,6 +35,24 @@ export type ImpressionCountsPayload = {
35
35
  }[]
36
36
  }
37
37
 
38
+ export type UniqueKeysPayloadSs = {
39
+ keys: {
40
+ /** Split name */
41
+ f: string
42
+ /** keyNames */
43
+ ks: string[]
44
+ }[]
45
+ }
46
+
47
+ export type UniqueKeysPayloadCs = {
48
+ keys: {
49
+ /** keyNames */
50
+ k: string
51
+ /** Split name */
52
+ fs: string[]
53
+ }[]
54
+ }
55
+
38
56
  export type StoredImpressionWithMetadata = {
39
57
  /** Metadata */
40
58
  m: IMetadata,
@@ -148,7 +166,8 @@ export type OperationMode = STANDALONE_ENUM | CONSUMER_ENUM | CONSUMER_PARTIAL_E
148
166
 
149
167
  export type OPTIMIZED_ENUM = 0;
150
168
  export type DEBUG_ENUM = 1;
151
- export type ImpressionsMode = OPTIMIZED_ENUM | DEBUG_ENUM;
169
+ export type NONE_ENUM = 2;
170
+ export type ImpressionsMode = OPTIMIZED_ENUM | DEBUG_ENUM | NONE_ENUM;
152
171
 
153
172
  export type RefreshRates = {
154
173
  sp: number, // splits