@splitsoftware/splitio-commons 1.6.2-rc.9 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/CHANGES.txt +3 -1
  2. package/cjs/consent/sdkUserConsent.js +2 -2
  3. package/cjs/listeners/browser.js +11 -12
  4. package/cjs/logger/constants.js +2 -1
  5. package/cjs/sdkClient/sdkClient.js +3 -1
  6. package/cjs/sdkFactory/index.js +26 -26
  7. package/cjs/services/splitApi.js +20 -0
  8. package/cjs/storages/AbstractSplitsCacheAsync.js +1 -1
  9. package/cjs/storages/AbstractSplitsCacheSync.js +1 -1
  10. package/cjs/storages/KeyBuilderSS.js +10 -43
  11. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +0 -1
  12. package/cjs/storages/inLocalStorage/index.js +17 -9
  13. package/cjs/storages/inMemory/ImpressionCountsCacheInMemory.js +12 -1
  14. package/cjs/storages/inMemory/InMemoryStorage.js +13 -6
  15. package/cjs/storages/inMemory/InMemoryStorageCS.js +13 -6
  16. package/cjs/storages/inMemory/TelemetryCacheInMemory.js +60 -35
  17. package/cjs/storages/inMemory/UniqueKeysCacheInMemory.js +72 -0
  18. package/cjs/storages/inMemory/UniqueKeysCacheInMemoryCS.js +76 -0
  19. package/cjs/storages/inRedis/ImpressionCountsCacheInRedis.js +85 -0
  20. package/cjs/storages/inRedis/ImpressionsCacheInRedis.js +2 -19
  21. package/cjs/storages/inRedis/TelemetryCacheInRedis.js +4 -4
  22. package/cjs/storages/inRedis/UniqueKeysCacheInRedis.js +71 -0
  23. package/cjs/storages/inRedis/constants.js +4 -1
  24. package/cjs/storages/inRedis/index.js +20 -3
  25. package/cjs/storages/pluggable/ImpressionCountsCachePluggable.js +81 -0
  26. package/cjs/storages/pluggable/ImpressionsCachePluggable.js +2 -19
  27. package/cjs/storages/pluggable/TelemetryCachePluggable.js +4 -4
  28. package/cjs/storages/pluggable/UniqueKeysCachePluggable.js +61 -0
  29. package/cjs/storages/pluggable/inMemoryWrapper.js +8 -6
  30. package/cjs/storages/pluggable/index.js +38 -9
  31. package/cjs/storages/utils.js +73 -0
  32. package/cjs/sync/submitters/submitterManager.js +3 -0
  33. package/cjs/sync/submitters/telemetrySubmitter.js +5 -40
  34. package/cjs/sync/submitters/uniqueKeysSubmitter.js +27 -0
  35. package/cjs/trackers/impressionObserver/utils.js +1 -17
  36. package/cjs/trackers/impressionsTracker.js +22 -41
  37. package/cjs/trackers/strategy/strategyDebug.js +25 -0
  38. package/cjs/trackers/strategy/strategyNone.js +29 -0
  39. package/cjs/trackers/strategy/strategyOptimized.js +35 -0
  40. package/cjs/trackers/uniqueKeysTracker.js +38 -0
  41. package/cjs/utils/constants/index.js +4 -2
  42. package/cjs/utils/redis/RedisMock.js +31 -0
  43. package/cjs/utils/settingsValidation/impressionsMode.js +2 -2
  44. package/cjs/utils/settingsValidation/index.js +7 -1
  45. package/esm/consent/sdkUserConsent.js +2 -2
  46. package/esm/listeners/browser.js +12 -13
  47. package/esm/logger/constants.js +1 -0
  48. package/esm/sdkClient/sdkClient.js +3 -1
  49. package/esm/sdkFactory/index.js +26 -26
  50. package/esm/services/splitApi.js +20 -0
  51. package/esm/storages/AbstractSplitsCacheAsync.js +1 -1
  52. package/esm/storages/AbstractSplitsCacheSync.js +1 -1
  53. package/esm/storages/KeyBuilderSS.js +7 -37
  54. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +0 -1
  55. package/esm/storages/inLocalStorage/index.js +18 -10
  56. package/esm/storages/inMemory/ImpressionCountsCacheInMemory.js +12 -1
  57. package/esm/storages/inMemory/InMemoryStorage.js +14 -7
  58. package/esm/storages/inMemory/InMemoryStorageCS.js +14 -7
  59. package/esm/storages/inMemory/TelemetryCacheInMemory.js +61 -36
  60. package/esm/storages/inMemory/UniqueKeysCacheInMemory.js +68 -0
  61. package/esm/storages/inMemory/UniqueKeysCacheInMemoryCS.js +73 -0
  62. package/esm/storages/inRedis/ImpressionCountsCacheInRedis.js +82 -0
  63. package/esm/storages/inRedis/ImpressionsCacheInRedis.js +2 -19
  64. package/esm/storages/inRedis/TelemetryCacheInRedis.js +1 -1
  65. package/esm/storages/inRedis/UniqueKeysCacheInRedis.js +68 -0
  66. package/esm/storages/inRedis/constants.js +3 -0
  67. package/esm/storages/inRedis/index.js +21 -4
  68. package/esm/storages/pluggable/ImpressionCountsCachePluggable.js +78 -0
  69. package/esm/storages/pluggable/ImpressionsCachePluggable.js +2 -19
  70. package/esm/storages/pluggable/TelemetryCachePluggable.js +1 -1
  71. package/esm/storages/pluggable/UniqueKeysCachePluggable.js +58 -0
  72. package/esm/storages/pluggable/inMemoryWrapper.js +8 -6
  73. package/esm/storages/pluggable/index.js +39 -10
  74. package/esm/storages/utils.js +65 -0
  75. package/esm/sync/submitters/submitterManager.js +3 -0
  76. package/esm/sync/submitters/telemetrySubmitter.js +5 -39
  77. package/esm/sync/submitters/uniqueKeysSubmitter.js +23 -0
  78. package/esm/trackers/impressionObserver/utils.js +1 -15
  79. package/esm/trackers/impressionsTracker.js +22 -41
  80. package/esm/trackers/strategy/strategyDebug.js +21 -0
  81. package/esm/trackers/strategy/strategyNone.js +25 -0
  82. package/esm/trackers/strategy/strategyOptimized.js +31 -0
  83. package/esm/trackers/uniqueKeysTracker.js +34 -0
  84. package/esm/utils/constants/index.js +2 -0
  85. package/esm/utils/redis/RedisMock.js +28 -0
  86. package/esm/utils/settingsValidation/impressionsMode.js +3 -3
  87. package/esm/utils/settingsValidation/index.js +7 -1
  88. package/package.json +1 -2
  89. package/src/consent/sdkUserConsent.ts +2 -2
  90. package/src/listeners/browser.ts +12 -15
  91. package/src/logger/constants.ts +1 -0
  92. package/src/sdkClient/sdkClient.ts +3 -1
  93. package/src/sdkFactory/index.ts +29 -31
  94. package/src/sdkFactory/types.ts +7 -4
  95. package/src/services/splitApi.ts +22 -0
  96. package/src/services/types.ts +6 -0
  97. package/src/storages/AbstractSplitsCacheAsync.ts +1 -1
  98. package/src/storages/AbstractSplitsCacheSync.ts +1 -1
  99. package/src/storages/KeyBuilderSS.ts +9 -43
  100. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +0 -1
  101. package/src/storages/inLocalStorage/index.ts +18 -10
  102. package/src/storages/inMemory/AttributesCacheInMemory.ts +7 -7
  103. package/src/storages/inMemory/ImpressionCountsCacheInMemory.ts +16 -1
  104. package/src/storages/inMemory/InMemoryStorage.ts +14 -7
  105. package/src/storages/inMemory/InMemoryStorageCS.ts +14 -7
  106. package/src/storages/inMemory/TelemetryCacheInMemory.ts +69 -34
  107. package/src/storages/inMemory/UniqueKeysCacheInMemory.ts +80 -0
  108. package/src/storages/inMemory/UniqueKeysCacheInMemoryCS.ts +86 -0
  109. package/src/storages/inRedis/ImpressionCountsCacheInRedis.ts +95 -0
  110. package/src/storages/inRedis/ImpressionsCacheInRedis.ts +2 -22
  111. package/src/storages/inRedis/TelemetryCacheInRedis.ts +3 -2
  112. package/src/storages/inRedis/UniqueKeysCacheInRedis.ts +77 -0
  113. package/src/storages/inRedis/constants.ts +3 -0
  114. package/src/storages/inRedis/index.ts +18 -5
  115. package/src/storages/pluggable/ImpressionCountsCachePluggable.ts +92 -0
  116. package/src/storages/pluggable/ImpressionsCachePluggable.ts +3 -23
  117. package/src/storages/pluggable/TelemetryCachePluggable.ts +3 -2
  118. package/src/storages/pluggable/UniqueKeysCachePluggable.ts +67 -0
  119. package/src/storages/pluggable/inMemoryWrapper.ts +6 -6
  120. package/src/storages/pluggable/index.ts +41 -9
  121. package/src/storages/types.ts +56 -55
  122. package/src/storages/utils.ts +78 -0
  123. package/src/sync/submitters/submitter.ts +2 -2
  124. package/src/sync/submitters/submitterManager.ts +2 -0
  125. package/src/sync/submitters/telemetrySubmitter.ts +8 -43
  126. package/src/sync/submitters/types.ts +29 -27
  127. package/src/sync/submitters/uniqueKeysSubmitter.ts +36 -0
  128. package/src/trackers/impressionObserver/utils.ts +1 -16
  129. package/src/trackers/impressionsTracker.ts +25 -46
  130. package/src/trackers/strategy/strategyDebug.ts +28 -0
  131. package/src/trackers/strategy/strategyNone.ts +34 -0
  132. package/src/trackers/strategy/strategyOptimized.ts +42 -0
  133. package/src/trackers/types.ts +28 -0
  134. package/src/trackers/uniqueKeysTracker.ts +48 -0
  135. package/src/types.ts +1 -1
  136. package/src/utils/constants/index.ts +2 -0
  137. package/src/utils/redis/RedisMock.ts +33 -0
  138. package/src/utils/settingsValidation/impressionsMode.ts +3 -3
  139. package/src/utils/settingsValidation/index.ts +5 -1
  140. package/types/logger/constants.d.ts +1 -0
  141. package/types/sdkFactory/types.d.ts +4 -2
  142. package/types/services/types.d.ts +4 -0
  143. package/types/storages/AbstractSplitsCacheAsync.d.ts +1 -1
  144. package/types/storages/AbstractSplitsCacheSync.d.ts +1 -1
  145. package/types/storages/KeyBuilderSS.d.ts +3 -3
  146. package/types/storages/inLocalStorage/SplitsCacheInLocal.d.ts +0 -1
  147. package/types/storages/inMemory/ImpressionCountsCacheInMemory.d.ts +5 -1
  148. package/types/storages/inMemory/TelemetryCacheInMemory.d.ts +20 -9
  149. package/types/storages/inMemory/uniqueKeysCacheInMemory.d.ts +35 -0
  150. package/types/storages/inMemory/uniqueKeysCacheInMemoryCS.d.ts +35 -0
  151. package/types/storages/inRedis/ImpressionCountsCacheInRedis.d.ts +16 -0
  152. package/types/storages/inRedis/ImpressionsCacheInRedis.d.ts +0 -1
  153. package/types/storages/inRedis/constants.d.ts +3 -0
  154. package/types/storages/inRedis/uniqueKeysCacheInRedis.d.ts +21 -0
  155. package/types/storages/pluggable/ImpressionCountsCachePluggable.d.ts +16 -0
  156. package/types/storages/pluggable/ImpressionsCachePluggable.d.ts +1 -2
  157. package/types/storages/pluggable/UniqueKeysCachePluggable.d.ts +20 -0
  158. package/types/storages/types.d.ts +39 -38
  159. package/types/storages/utils.d.ts +8 -0
  160. package/types/sync/submitters/submitter.d.ts +2 -2
  161. package/types/sync/submitters/telemetrySubmitter.d.ts +2 -10
  162. package/types/sync/submitters/types.d.ts +27 -7
  163. package/types/sync/submitters/uniqueKeysSubmitter.d.ts +5 -0
  164. package/types/trackers/impressionObserver/utils.d.ts +0 -8
  165. package/types/trackers/impressionsTracker.d.ts +4 -6
  166. package/types/trackers/strategy/strategyDebug.d.ts +9 -0
  167. package/types/trackers/strategy/strategyNone.d.ts +10 -0
  168. package/types/trackers/strategy/strategyOptimized.d.ts +11 -0
  169. package/types/trackers/types.d.ts +23 -0
  170. package/types/trackers/uniqueKeysTracker.d.ts +13 -0
  171. package/types/types.d.ts +1 -1
  172. package/types/utils/constants/index.d.ts +2 -0
  173. package/types/utils/redis/RedisMock.d.ts +4 -0
  174. package/cjs/storages/metadataBuilder.js +0 -12
  175. package/esm/storages/metadataBuilder.js +0 -8
  176. package/src/storages/metadataBuilder.ts +0 -11
@@ -1,21 +1,6 @@
1
- import { CONSUMER_MODE, CONSUMER_PARTIAL_MODE, OPTIMIZED, PRODUCER_MODE, STANDALONE_MODE } from '../../utils/constants';
1
+ import { CONSUMER_MODE, CONSUMER_PARTIAL_MODE } from '../../utils/constants';
2
2
  import { ISettings } from '../../types';
3
3
 
4
- /**
5
- * Checks if impressions previous time should be added or not.
6
- */
7
- export function shouldAddPt(settings: ISettings) {
8
- return [PRODUCER_MODE, STANDALONE_MODE, CONSUMER_PARTIAL_MODE].indexOf(settings.mode) > -1 ? true : false;
9
- }
10
-
11
- /**
12
- * Checks if it should dedupe impressions or not.
13
- */
14
- export function shouldBeOptimized(settings: ISettings) {
15
- if (!shouldAddPt(settings)) return false;
16
- return settings.sync.impressionsMode === OPTIMIZED ? true : false;
17
- }
18
-
19
4
  /**
20
5
  * Storage is async if mode is consumer or partial consumer
21
6
  */
@@ -1,10 +1,8 @@
1
1
  import { objectAssign } from '../utils/lang/objectAssign';
2
2
  import { thenable } from '../utils/promise/thenable';
3
- import { truncateTimeFrame } from '../utils/time';
4
- import { IImpressionCountsCacheSync, IImpressionsCacheBase, ITelemetryCacheSync, ITelemetryCacheAsync } from '../storages/types';
5
- import { IImpressionsHandler, IImpressionsTracker } from './types';
3
+ import { IImpressionsCacheBase, ITelemetryCacheSync, ITelemetryCacheAsync } from '../storages/types';
4
+ import { IImpressionsHandler, IImpressionsTracker, IStrategy } from './types';
6
5
  import { SplitIO, ImpressionDTO, ISettings } from '../types';
7
- import { IImpressionObserver } from './impressionObserver/types';
8
6
  import { IMPRESSIONS_TRACKER_SUCCESS, ERROR_IMPRESSIONS_TRACKER, ERROR_IMPRESSIONS_LISTENER } from '../logger/constants';
9
7
  import { CONSENT_DECLINED, DEDUPED, QUEUED } from '../utils/constants';
10
8
 
@@ -15,18 +13,14 @@ import { CONSENT_DECLINED, DEDUPED, QUEUED } from '../utils/constants';
15
13
  * @param metadata runtime metadata (ip, hostname and version)
16
14
  * @param impressionListener optional impression listener
17
15
  * @param integrationsManager optional integrations manager
18
- * @param observer optional impression observer. If provided, previous time (pt property) is included in impression instances
19
- * @param countsCache optional cache to save impressions count. If provided, impressions will be deduped (OPTIMIZED mode)
16
+ * @param strategy strategy for impressions tracking.
20
17
  */
21
18
  export function impressionsTrackerFactory(
22
19
  settings: ISettings,
23
20
  impressionsCache: IImpressionsCacheBase,
21
+ strategy: IStrategy,
24
22
  integrationsManager?: IImpressionsHandler,
25
- // if observer is provided, it implies `shouldAddPreviousTime` flag (i.e., if impressions previous time should be added or not)
26
- observer?: IImpressionObserver,
27
- // if countsCache is provided, it implies `isOptimized` flag (i.e., if impressions should be deduped or not)
28
- countsCache?: IImpressionCountsCacheSync,
29
- telemetryCache?: ITelemetryCacheSync | ITelemetryCacheAsync
23
+ telemetryCache?: ITelemetryCacheSync | ITelemetryCacheAsync,
30
24
  ): IImpressionsTracker {
31
25
 
32
26
  const { log, impressionListener, runtime: { ip, hostname }, version } = settings;
@@ -36,51 +30,36 @@ export function impressionsTrackerFactory(
36
30
  if (settings.userConsent === CONSENT_DECLINED) return;
37
31
 
38
32
  const impressionsCount = impressions.length;
33
+ const { impressionsToStore, impressionsToListener, deduped } = strategy.process(impressions);
39
34
 
40
- const impressionsToStore: ImpressionDTO[] = []; // Track only the impressions that are going to be stored
41
- // Wraps impressions to store and adds previousTime if it corresponds
42
- impressions.forEach((impression) => {
43
- if (observer) {
44
- // Adds previous time if it is enabled
45
- impression.pt = observer.testAndSet(impression);
46
- }
47
-
48
- const now = Date.now();
49
- if (countsCache) {
50
- // Increments impression counter per featureName
51
- countsCache.track(impression.feature, now, 1);
52
- }
53
-
54
- // Checks if the impression should be added in queue to be sent
55
- if (!countsCache || !impression.pt || impression.pt < truncateTimeFrame(now)) {
56
- impressionsToStore.push(impression);
57
- }
58
- });
35
+ const impressionsToListenerCount = impressionsToListener.length;
59
36
 
60
- const res = impressionsCache.track(impressionsToStore);
37
+ if ( impressionsToStore.length>0 ){
38
+ const res = impressionsCache.track(impressionsToStore);
61
39
 
62
- // If we're on an async storage, handle error and log it.
63
- if (thenable(res)) {
64
- res.then(() => {
65
- log.info(IMPRESSIONS_TRACKER_SUCCESS, [impressionsCount]);
66
- }).catch(err => {
67
- log.error(ERROR_IMPRESSIONS_TRACKER, [impressionsCount, err]);
68
- });
69
- } else {
70
- // Record when impressionsCache is sync only (standalone mode)
71
- // @TODO we are not dropping impressions on full queue yet, so DROPPED stats are not recorded
72
- if (telemetryCache) {
73
- (telemetryCache as ITelemetryCacheSync).recordImpressionStats(QUEUED, impressionsToStore.length);
74
- (telemetryCache as ITelemetryCacheSync).recordImpressionStats(DEDUPED, impressions.length - impressionsToStore.length);
40
+ // If we're on an async storage, handle error and log it.
41
+ if (thenable(res)) {
42
+ res.then(() => {
43
+ log.info(IMPRESSIONS_TRACKER_SUCCESS, [impressionsCount]);
44
+ }).catch(err => {
45
+ log.error(ERROR_IMPRESSIONS_TRACKER, [impressionsCount, err]);
46
+ });
47
+ } else {
48
+ // Record when impressionsCache is sync only (standalone mode)
49
+ // @TODO we are not dropping impressions on full queue yet, so DROPPED stats are not recorded
50
+ if (telemetryCache) {
51
+ (telemetryCache as ITelemetryCacheSync).recordImpressionStats(QUEUED, impressionsToStore.length);
52
+ (telemetryCache as ITelemetryCacheSync).recordImpressionStats(DEDUPED, deduped);
53
+ }
75
54
  }
76
55
  }
77
56
 
78
57
  // @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
79
58
  if (impressionListener || integrationsManager) {
80
- for (let i = 0; i < impressionsCount; i++) {
59
+ for (let i = 0; i < impressionsToListenerCount; i++) {
81
60
  const impressionData: SplitIO.ImpressionData = {
82
61
  // copy of impression, to avoid unexpected behaviour if modified by integrations or impressionListener
83
- impression: objectAssign({}, impressions[i]),
62
+ impression: objectAssign({}, impressionsToListener[i]),
84
63
  attributes,
85
64
  ip,
86
65
  hostname,
@@ -0,0 +1,28 @@
1
+ import { ImpressionDTO } from '../../types';
2
+ import { IImpressionObserver } from '../impressionObserver/types';
3
+ import { IStrategy } from '../types';
4
+
5
+ /**
6
+ * Debug strategy for impressions tracker. Wraps impressions to store and adds previousTime if it corresponds
7
+ *
8
+ * @param impressionsObserver impression observer. Previous time (pt property) is included in impression instances
9
+ * @returns IStrategyResult
10
+ */
11
+ export function strategyDebugFactory(
12
+ impressionsObserver: IImpressionObserver
13
+ ): IStrategy {
14
+
15
+ return {
16
+ process(impressions: ImpressionDTO[]) {
17
+ impressions.forEach((impression) => {
18
+ // Adds previous time if it is enabled
19
+ impression.pt = impressionsObserver.testAndSet(impression);
20
+ });
21
+ return {
22
+ impressionsToStore: impressions,
23
+ impressionsToListener: impressions,
24
+ deduped: 0
25
+ };
26
+ }
27
+ };
28
+ }
@@ -0,0 +1,34 @@
1
+ import { IImpressionCountsCacheBase } from '../../storages/types';
2
+ import { ImpressionDTO } from '../../types';
3
+ import { IStrategy, IUniqueKeysTracker } from '../types';
4
+
5
+ /**
6
+ * None strategy for impressions tracker.
7
+ *
8
+ * @param impressionsCounter cache to save impressions count. impressions will be deduped (OPTIMIZED mode)
9
+ * @param uniqueKeysTracker unique keys tracker in charge of tracking the unique keys per split.
10
+ * @returns IStrategyResult
11
+ */
12
+ export function strategyNoneFactory(
13
+ impressionsCounter: IImpressionCountsCacheBase,
14
+ uniqueKeysTracker: IUniqueKeysTracker
15
+ ): IStrategy {
16
+
17
+ return {
18
+ process(impressions: ImpressionDTO[]) {
19
+ impressions.forEach((impression) => {
20
+ const now = Date.now();
21
+ // Increments impression counter per featureName
22
+ impressionsCounter.track(impression.feature, now, 1);
23
+ // Keep track by unique key
24
+ uniqueKeysTracker.track(impression.keyName, impression.feature);
25
+ });
26
+
27
+ return {
28
+ impressionsToStore: [],
29
+ impressionsToListener: impressions,
30
+ deduped: 0
31
+ };
32
+ }
33
+ };
34
+ }
@@ -0,0 +1,42 @@
1
+ import { IImpressionCountsCacheBase } from '../../storages/types';
2
+ import { ImpressionDTO } from '../../types';
3
+ import { truncateTimeFrame } from '../../utils/time';
4
+ import { IImpressionObserver } from '../impressionObserver/types';
5
+ import { IStrategy } from '../types';
6
+
7
+ /**
8
+ * Optimized strategy for impressions tracker. Wraps impressions to store and adds previousTime if it corresponds
9
+ *
10
+ * @param impressionsObserver impression observer. previous time (pt property) is included in impression instances
11
+ * @param impressionsCounter cache to save impressions count. impressions will be deduped (OPTIMIZED mode)
12
+ * @returns IStrategyResult
13
+ */
14
+ export function strategyOptimizedFactory(
15
+ impressionsObserver: IImpressionObserver,
16
+ impressionsCounter: IImpressionCountsCacheBase,
17
+ ): IStrategy {
18
+
19
+ return {
20
+ process(impressions: ImpressionDTO[]) {
21
+ const impressionsToStore: ImpressionDTO[] = [];
22
+ impressions.forEach((impression) => {
23
+ impression.pt = impressionsObserver.testAndSet(impression);
24
+
25
+ const now = Date.now();
26
+
27
+ // Increments impression counter per featureName
28
+ if (impression.pt) impressionsCounter.track(impression.feature, now, 1);
29
+
30
+ // Checks if the impression should be added in queue to be sent
31
+ if (!impression.pt || impression.pt < truncateTimeFrame(now)) {
32
+ impressionsToStore.push(impression);
33
+ }
34
+ });
35
+ return {
36
+ impressionsToStore: impressionsToStore,
37
+ impressionsToListener: impressions,
38
+ deduped: impressions.length - impressionsToStore.length
39
+ };
40
+ }
41
+ };
42
+ }
@@ -46,3 +46,31 @@ export interface ITelemetryTracker {
46
46
  */
47
47
  addTag(tag: string): void
48
48
  }
49
+
50
+ export interface IFilterAdapter {
51
+ add(key: string, featureName: string): boolean;
52
+ contains(key: string, featureName: string): boolean;
53
+ clear(): void;
54
+ refreshRate?: number;
55
+ }
56
+
57
+ export interface IImpressionSenderAdapter {
58
+ recordUniqueKeys(data: Object): void;
59
+ recordImpressionCounts(data: Object): void
60
+ }
61
+
62
+ /** Unique keys tracker */
63
+ export interface IUniqueKeysTracker {
64
+ stop(): void;
65
+ track(key: string, featureName: string): void;
66
+ }
67
+
68
+ export interface IStrategyResult {
69
+ impressionsToStore: ImpressionDTO[],
70
+ impressionsToListener: ImpressionDTO[],
71
+ deduped: number
72
+ }
73
+
74
+ export interface IStrategy {
75
+ process(impressions: ImpressionDTO[]): IStrategyResult
76
+ }
@@ -0,0 +1,48 @@
1
+ import { LOG_PREFIX_UNIQUE_KEYS_TRACKER } from '../logger/constants';
2
+ import { ILogger } from '../logger/types';
3
+ import { IUniqueKeysCacheBase } from '../storages/types';
4
+ import { IFilterAdapter, IUniqueKeysTracker } from './types';
5
+
6
+ const noopFilterAdapter = {
7
+ add() { return true; },
8
+ contains() { return true; },
9
+ clear() { }
10
+ };
11
+
12
+ /**
13
+ * Trackes uniques keys
14
+ * Unique Keys Tracker will be in charge of checking if the MTK was already sent to the BE in the last period
15
+ * or schedule to be sent; if not it will be added in an internal cache and sent in the next post.
16
+ *
17
+ * @param log Logger instance
18
+ * @param uniqueKeysCache cache to save unique keys
19
+ * @param filterAdapter filter adapter
20
+ */
21
+ export function uniqueKeysTrackerFactory(
22
+ log: ILogger,
23
+ uniqueKeysCache: IUniqueKeysCacheBase,
24
+ filterAdapter: IFilterAdapter = noopFilterAdapter,
25
+ ): IUniqueKeysTracker {
26
+ let intervalId: any;
27
+
28
+ if (filterAdapter.refreshRate) {
29
+ intervalId = setInterval(filterAdapter.clear, filterAdapter.refreshRate);
30
+ }
31
+
32
+ return {
33
+
34
+ track(key: string, featureName: string): void {
35
+ if (!filterAdapter.add(key, featureName)) {
36
+ log.debug(`${LOG_PREFIX_UNIQUE_KEYS_TRACKER}The feature ${featureName} and key ${key} exist in the filter`);
37
+ return;
38
+ }
39
+ uniqueKeysCache.track(key, featureName);
40
+ },
41
+
42
+ stop(): void {
43
+ clearInterval(intervalId);
44
+ }
45
+
46
+ };
47
+
48
+ }
package/src/types.ts CHANGED
@@ -718,7 +718,7 @@ export namespace SplitIO {
718
718
  * ImpressionsMode type
719
719
  * @typedef {string} ImpressionsMode
720
720
  */
721
- export type ImpressionsMode = 'OPTIMIZED' | 'DEBUG'
721
+ export type ImpressionsMode = 'OPTIMIZED' | 'DEBUG' | 'NONE'
722
722
  /**
723
723
  * Defines the format of Split data to preload on the factory storage (cache).
724
724
  */
@@ -19,6 +19,7 @@ export const SPLIT_EVENT = 'EVENT';
19
19
  // Impression collection modes
20
20
  export const DEBUG = 'DEBUG';
21
21
  export const OPTIMIZED = 'OPTIMIZED';
22
+ export const NONE = 'NONE';
22
23
 
23
24
  // SDK Modes
24
25
  export const LOCALHOST_MODE: SDKMode = 'localhost';
@@ -49,6 +50,7 @@ export const CONSUMER_PARTIAL_ENUM = 2;
49
50
 
50
51
  export const OPTIMIZED_ENUM = 0;
51
52
  export const DEBUG_ENUM = 1;
53
+ export const NONE_ENUM = 2;
52
54
 
53
55
  export const SPLITS = 'sp';
54
56
  export const IMPRESSIONS = 'im';
@@ -0,0 +1,33 @@
1
+ //@ts-nocheck
2
+ function identityFunction(data: any): any {
3
+ return data;
4
+ }
5
+
6
+ function asyncFunction(data: any): Promise<any> {
7
+ return Promise.resolve(data);
8
+ }
9
+
10
+ const IDENTITY_METHODS: string[] = [];
11
+ const ASYNC_METHODS = ['rpush', 'hincrby'];
12
+ const PIPELINE_METHODS = ['rpush', 'hincrby'];
13
+
14
+ export class RedisMock {
15
+
16
+ private pipelineMethods: any = { exec: jest.fn(asyncFunction) }
17
+
18
+ constructor() {
19
+ IDENTITY_METHODS.forEach(method => {
20
+ this[method] = jest.fn(identityFunction);
21
+ });
22
+ ASYNC_METHODS.forEach(method => {
23
+ this[method] = jest.fn(asyncFunction);
24
+ });
25
+ PIPELINE_METHODS.forEach(method => {
26
+ this.pipelineMethods[method] = this[method];
27
+ });
28
+
29
+ this.pipeline = jest.fn(() => {return this.pipelineMethods;});
30
+ }
31
+
32
+
33
+ }
@@ -1,14 +1,14 @@
1
1
  import { ERROR_INVALID_CONFIG_PARAM } from '../../logger/constants';
2
2
  import { ILogger } from '../../logger/types';
3
3
  import { SplitIO } from '../../types';
4
- import { DEBUG, OPTIMIZED } from '../constants';
4
+ import { DEBUG, OPTIMIZED, NONE } from '../constants';
5
5
  import { stringToUpperCase } from '../lang';
6
6
 
7
7
  export function validImpressionsMode(log: ILogger, impressionsMode: any): SplitIO.ImpressionsMode {
8
8
  impressionsMode = stringToUpperCase(impressionsMode);
9
9
 
10
- if ([DEBUG, OPTIMIZED].indexOf(impressionsMode) > -1) return impressionsMode;
10
+ if ([DEBUG, OPTIMIZED, NONE].indexOf(impressionsMode) > -1) return impressionsMode;
11
11
 
12
- log.error(ERROR_INVALID_CONFIG_PARAM, ['impressionsMode', [DEBUG, OPTIMIZED], OPTIMIZED]);
12
+ log.error(ERROR_INVALID_CONFIG_PARAM, ['impressionsMode', [DEBUG, OPTIMIZED, NONE], OPTIMIZED]);
13
13
  return OPTIMIZED;
14
14
  }
@@ -153,8 +153,8 @@ export function settingsValidation(config: unknown, validationParams: ISettingsV
153
153
  if (storage) withDefaults.storage = storage(withDefaults);
154
154
 
155
155
  // Validate key and TT (for client-side)
156
+ const maybeKey = withDefaults.core.key;
156
157
  if (validationParams.acceptKey) {
157
- const maybeKey = withDefaults.core.key;
158
158
  // Although `key` is required in client-side, it can be omitted in LOCALHOST mode. In that case, the value `localhost_key` is used.
159
159
  if (withDefaults.mode === LOCALHOST_MODE && maybeKey === undefined) {
160
160
  withDefaults.core.key = 'localhost_key';
@@ -171,6 +171,10 @@ export function settingsValidation(config: unknown, validationParams: ISettingsV
171
171
  withDefaults.core.trafficType = validateTrafficType(log, maybeTT, 'Client instantiation');
172
172
  }
173
173
  }
174
+ } else {
175
+ // On server-side, key is undefined and used to distinguish from client-side
176
+ if (maybeKey !== undefined) log.warn('Provided `key` is ignored in server-side SDK.'); // @ts-ignore
177
+ withDefaults.core.key = undefined;
174
178
  }
175
179
 
176
180
  // Current ip/hostname information
@@ -138,4 +138,5 @@ export declare const LOG_PREFIX_SYNC_POLLING: string;
138
138
  export declare const LOG_PREFIX_SYNC_SUBMITTERS: string;
139
139
  export declare const LOG_PREFIX_IMPRESSIONS_TRACKER = "impressions-tracker: ";
140
140
  export declare const LOG_PREFIX_EVENTS_TRACKER = "events-tracker: ";
141
+ export declare const LOG_PREFIX_UNIQUE_KEYS_TRACKER = "unique-keys-tracker: ";
141
142
  export declare const LOG_PREFIX_CLEANUP = "cleanup: ";
@@ -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
  * Environment related dependencies.
@@ -42,6 +42,7 @@ export interface ISdkFactoryContext {
42
42
  eventTracker: IEventTracker;
43
43
  telemetryTracker: ITelemetryTracker;
44
44
  storage: IStorageSync | IStorageAsync;
45
+ uniqueKeysTracker?: IUniqueKeysTracker;
45
46
  signalListener?: ISignalListener;
46
47
  splitApi?: ISplitApi;
47
48
  syncManager?: ISyncManager;
@@ -70,11 +71,12 @@ export interface ISdkFactoryParams {
70
71
  (): SplitIO.ICsClient;
71
72
  (key: SplitIO.SplitKey, trafficType?: string | undefined): SplitIO.ICsClient;
72
73
  } | (() => SplitIO.IClient) | (() => SplitIO.IAsyncClient));
74
+ impressionsObserverFactory: () => IImpressionObserver;
75
+ filterAdapterFactory?: () => IFilterAdapter;
73
76
  SignalListener?: new (syncManager: ISyncManager | undefined, // Used by NodeSignalListener to flush data, and by BrowserSignalListener to close streaming connection.
74
77
  settings: ISettings, // Used by BrowserSignalListener
75
78
  storage: IStorageSync | IStorageAsync, // Used by BrowserSignalListener
76
79
  serviceApi: ISplitApi | undefined) => ISignalListener;
77
80
  integrationsManagerFactory?: (params: IIntegrationFactoryParams) => IIntegrationManager | undefined;
78
- impressionsObserverFactory?: () => IImpressionObserver;
79
81
  extraProps?: (params: ISdkFactoryContext) => object;
80
82
  }
@@ -20,6 +20,8 @@ export declare type IFetchSplitChanges = (since: number, noCache?: boolean, till
20
20
  export declare type IFetchSegmentChanges = (since: number, segmentName: string, noCache?: boolean, till?: number) => Promise<IResponse>;
21
21
  export declare type IFetchMySegments = (userMatchingKey: string, noCache?: boolean) => Promise<IResponse>;
22
22
  export declare type IPostEventsBulk = (body: string, headers?: Record<string, string>) => Promise<IResponse>;
23
+ export declare type IPostUniqueKeysBulkCs = (body: string, headers?: Record<string, string>) => Promise<IResponse>;
24
+ export declare type IPostUniqueKeysBulkSs = (body: string, headers?: Record<string, string>) => Promise<IResponse>;
23
25
  export declare type IPostTestImpressionsBulk = (body: string, headers?: Record<string, string>) => Promise<IResponse>;
24
26
  export declare type IPostTestImpressionsCount = (body: string, headers?: Record<string, string>) => Promise<IResponse>;
25
27
  export declare type IPostMetricsConfig = (body: string, headers?: Record<string, string>) => Promise<IResponse>;
@@ -32,6 +34,8 @@ export interface ISplitApi {
32
34
  fetchSegmentChanges: IFetchSegmentChanges;
33
35
  fetchMySegments: IFetchMySegments;
34
36
  postEventsBulk: IPostEventsBulk;
37
+ postUniqueKeysBulkCs: IPostUniqueKeysBulkCs;
38
+ postUniqueKeysBulkSs: IPostUniqueKeysBulkSs;
35
39
  postTestImpressionsBulk: IPostTestImpressionsBulk;
36
40
  postTestImpressionsCount: IPostTestImpressionsCount;
37
41
  postMetricsConfig: IPostMetricsConfig;
@@ -29,7 +29,7 @@ export declare abstract class AbstractSplitsCacheAsync implements ISplitsCacheAs
29
29
  * @param {string} name
30
30
  * @param {string} defaultTreatment
31
31
  * @param {number} changeNumber
32
- * @returns {Promise} a promise that is resolved once the split kill operation is performed. The fulfillment value is a boolean: `true` if the kill success updating the split or `false` if no split is updated,
32
+ * @returns {Promise} a promise that is resolved once the split kill operation is performed. The fulfillment value is a boolean: `true` if the operation successed updating the split or `false` if no split is updated,
33
33
  * for instance, if the `changeNumber` is old, or if the split is not found (e.g., `/splitchanges` hasn't been fetched yet), or if the storage fails to apply the update.
34
34
  * The promise will never be rejected.
35
35
  */
@@ -30,7 +30,7 @@ export declare abstract class AbstractSplitsCacheSync implements ISplitsCacheSyn
30
30
  * @param {string} name
31
31
  * @param {string} defaultTreatment
32
32
  * @param {number} changeNumber
33
- * @returns {Promise} a promise that is resolved once the split kill is performed. The fulfillment value is a boolean: `true` if the kill success updating the split or `false` if no split is updated,
33
+ * @returns {boolean} `true` if the operation successed updating the split, or `false` if no split is updated,
34
34
  * for instance, if the `changeNumber` is old, or if the split is not found (e.g., `/splitchanges` hasn't been fetched yet), or if the storage fails to apply the update.
35
35
  */
36
36
  killLocally(name: string, defaultTreatment: string, changeNumber: number): boolean;
@@ -1,6 +1,7 @@
1
1
  import { KeyBuilder } from './KeyBuilder';
2
2
  import { IMetadata } from '../dtos/types';
3
3
  import { Method } from '../sync/submitters/types';
4
+ export declare const METHOD_NAMES: Record<Method, string>;
4
5
  export declare class KeyBuilderSS extends KeyBuilder {
5
6
  latencyPrefix: string;
6
7
  exceptionPrefix: string;
@@ -9,12 +10,11 @@ export declare class KeyBuilderSS extends KeyBuilder {
9
10
  constructor(prefix: string, metadata: IMetadata);
10
11
  buildRegisteredSegmentsKey(): string;
11
12
  buildImpressionsKey(): string;
13
+ buildImpressionsCountKey(): string;
14
+ buildUniqueKeysKey(): string;
12
15
  buildEventsKey(): string;
13
16
  searchPatternForSplitKeys(): string;
14
17
  buildLatencyKey(method: Method, bucket: number): string;
15
18
  buildExceptionKey(method: Method): string;
16
19
  buildInitKey(): string;
17
20
  }
18
- export declare function parseMetadata(field: string): [metadata: string] | string;
19
- export declare function parseExceptionField(field: string): [metadata: string, method: Method] | string;
20
- export declare function parseLatencyField(field: string): [metadata: string, method: Method, bucket: number] | string;
@@ -42,7 +42,6 @@ export declare class SplitsCacheInLocal extends AbstractSplitsCacheSync {
42
42
  checkCache(): boolean;
43
43
  /**
44
44
  * Clean Splits cache if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
45
- * Clean operation (clear) also updates `lastUpdated` timestamp with current time.
46
45
  *
47
46
  * @param {number | undefined} expirationTimestamp if the value is not a number, data will not be cleaned
48
47
  */
@@ -1,6 +1,10 @@
1
1
  import { IImpressionCountsCacheSync } from '../types';
2
2
  export declare class ImpressionCountsCacheInMemory implements IImpressionCountsCacheSync {
3
- private cache;
3
+ protected cache: Record<string, number>;
4
+ private readonly maxStorage;
5
+ protected onFullQueue?: () => void;
6
+ private cacheSize;
7
+ constructor(impressionCountsCacheSize?: number);
4
8
  /**
5
9
  * Builds key to be stored in the cache with the featureName and the timeFrame truncated.
6
10
  */
@@ -1,13 +1,21 @@
1
- import { ImpressionDataType, EventDataType, LastSync, HttpErrors, HttpLatencies, StreamingEvent, Method, OperationType, MethodExceptions, MethodLatencies } from '../../sync/submitters/types';
2
- import { IStorageFactoryParams, ITelemetryCacheSync } from '../types';
1
+ import { ImpressionDataType, EventDataType, StreamingEvent, Method, OperationType, TelemetryUsageStatsPayload } from '../../sync/submitters/types';
2
+ import { ISegmentsCacheSync, ISplitsCacheSync, IStorageFactoryParams, ITelemetryCacheSync } from '../types';
3
3
  export declare const MAX_LATENCY_BUCKET_COUNT = 23;
4
- export declare function newBuckets(): any[];
4
+ export declare function newBuckets(): number[];
5
5
  /**
6
6
  * Record telemetry if mode is not localhost.
7
7
  * All factory instances track telemetry on server-side, and 0.1% on client-side.
8
8
  */
9
- export declare function shouldRecordTelemetry(params: IStorageFactoryParams): boolean;
9
+ export declare function shouldRecordTelemetry({ settings }: IStorageFactoryParams): boolean;
10
10
  export declare class TelemetryCacheInMemory implements ITelemetryCacheSync {
11
+ private splits?;
12
+ private segments?;
13
+ constructor(splits?: ISplitsCacheSync | undefined, segments?: ISegmentsCacheSync | undefined);
14
+ private e;
15
+ isEmpty(): boolean;
16
+ clear(): void;
17
+ pop(): TelemetryUsageStatsPayload;
18
+ /** Config stats */
11
19
  private timeUntilReady?;
12
20
  getTimeUntilReady(): number | undefined;
13
21
  recordTimeUntilReady(ms: number): void;
@@ -17,6 +25,7 @@ export declare class TelemetryCacheInMemory implements ITelemetryCacheSync {
17
25
  private notReadyUsage;
18
26
  getNonReadyUsage(): number;
19
27
  recordNonReadyUsage(): void;
28
+ /** Usage stats */
20
29
  private impressionStats;
21
30
  getImpressionStats(type: ImpressionDataType): number;
22
31
  recordImpressionStats(type: ImpressionDataType, count: number): void;
@@ -24,13 +33,15 @@ export declare class TelemetryCacheInMemory implements ITelemetryCacheSync {
24
33
  getEventStats(type: EventDataType): number;
25
34
  recordEventStats(type: EventDataType, count: number): void;
26
35
  private lastSync;
27
- getLastSynchronization(): LastSync;
36
+ getLastSynchronization(): Partial<Record<OperationType, number | undefined>>;
28
37
  recordSuccessfulSync(resource: OperationType, timeMs: number): void;
29
38
  private httpErrors;
30
- popHttpErrors(): HttpErrors;
39
+ popHttpErrors(): Partial<Record<OperationType, {
40
+ [statusCode: string]: number;
41
+ }>>;
31
42
  recordHttpError(resource: OperationType, status: number): void;
32
43
  private httpLatencies;
33
- popHttpLatencies(): HttpLatencies;
44
+ popHttpLatencies(): Partial<Record<OperationType, number[]>>;
34
45
  recordHttpLatency(resource: OperationType, latencyMs: number): void;
35
46
  private authRejections;
36
47
  popAuthRejections(): number;
@@ -48,9 +59,9 @@ export declare class TelemetryCacheInMemory implements ITelemetryCacheSync {
48
59
  getSessionLength(): number | undefined;
49
60
  recordSessionLength(ms: number): void;
50
61
  private exceptions;
51
- popExceptions(): MethodExceptions;
62
+ popExceptions(): Partial<Record<Method, number>>;
52
63
  recordException(method: Method): void;
53
64
  private latencies;
54
- popLatencies(): MethodLatencies;
65
+ popLatencies(): Partial<Record<Method, number[]>>;
55
66
  recordLatency(method: Method, latencyMs: number): void;
56
67
  }
@@ -0,0 +1,35 @@
1
+ import { IUniqueKeysCacheBase } from '../types';
2
+ import { ISet } from '../../utils/lang/sets';
3
+ import { UniqueKeysPayloadSs } from '../../sync/submitters/types';
4
+ /**
5
+ * Converts `uniqueKeys` data from cache into request payload for SS.
6
+ */
7
+ export declare function fromUniqueKeysCollector(uniqueKeys: {
8
+ [featureName: string]: ISet<string>;
9
+ }): UniqueKeysPayloadSs;
10
+ export declare class UniqueKeysCacheInMemory implements IUniqueKeysCacheBase {
11
+ protected onFullQueue?: () => void;
12
+ private readonly maxStorage;
13
+ private uniqueTrackerSize;
14
+ protected uniqueKeysTracker: {
15
+ [featureName: string]: ISet<string>;
16
+ };
17
+ constructor(uniqueKeysQueueSize?: number);
18
+ setOnFullQueueCb(cb: () => void): void;
19
+ /**
20
+ * Store unique keys per feature.
21
+ */
22
+ track(userKey: string, featureName: string): void;
23
+ /**
24
+ * Clear the data stored on the cache.
25
+ */
26
+ clear(): void;
27
+ /**
28
+ * Pop the collected data, used as payload for posting.
29
+ */
30
+ pop(): UniqueKeysPayloadSs;
31
+ /**
32
+ * Check if the cache is empty.
33
+ */
34
+ isEmpty(): boolean;
35
+ }