@splitsoftware/splitio-commons 1.6.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 (132) hide show
  1. package/CHANGES.txt +3 -0
  2. package/cjs/integrations/ga/GaToSplit.js +4 -2
  3. package/cjs/integrations/ga/GoogleAnalyticsToSplit.js +1 -0
  4. package/cjs/listeners/browser.js +2 -1
  5. package/cjs/logger/constants.js +2 -1
  6. package/cjs/sdkFactory/index.js +13 -5
  7. package/cjs/services/splitApi.js +20 -0
  8. package/cjs/sync/submitters/submitterManager.js +3 -0
  9. package/cjs/sync/submitters/uniqueKeysSubmitter.js +70 -0
  10. package/cjs/trackers/impressionsTracker.js +7 -28
  11. package/cjs/trackers/strategy/strategyDebug.js +25 -0
  12. package/cjs/trackers/strategy/strategyNone.js +29 -0
  13. package/cjs/trackers/strategy/strategyOptimized.js +34 -0
  14. package/cjs/trackers/uniqueKeysTracker.js +69 -0
  15. package/cjs/utils/constants/index.js +3 -2
  16. package/cjs/utils/settingsValidation/impressionsMode.js +2 -2
  17. package/cjs/utils/settingsValidation/index.js +3 -0
  18. package/esm/integrations/ga/GaToSplit.js +4 -2
  19. package/esm/integrations/ga/GoogleAnalyticsToSplit.js +1 -0
  20. package/esm/listeners/browser.js +3 -2
  21. package/esm/logger/constants.js +1 -0
  22. package/esm/sdkFactory/index.js +13 -5
  23. package/esm/services/splitApi.js +20 -0
  24. package/esm/sync/submitters/submitterManager.js +3 -0
  25. package/esm/sync/submitters/uniqueKeysSubmitter.js +64 -0
  26. package/esm/trackers/impressionsTracker.js +7 -28
  27. package/esm/trackers/strategy/strategyDebug.js +21 -0
  28. package/esm/trackers/strategy/strategyNone.js +25 -0
  29. package/esm/trackers/strategy/strategyOptimized.js +30 -0
  30. package/esm/trackers/uniqueKeysTracker.js +65 -0
  31. package/esm/utils/constants/index.js +1 -0
  32. package/esm/utils/settingsValidation/impressionsMode.js +3 -3
  33. package/esm/utils/settingsValidation/index.js +3 -0
  34. package/package.json +1 -1
  35. package/src/integrations/ga/GaToSplit.ts +3 -2
  36. package/src/integrations/ga/GoogleAnalyticsToSplit.ts +1 -1
  37. package/src/listeners/browser.ts +3 -2
  38. package/src/logger/constants.ts +1 -0
  39. package/src/sdkFactory/index.ts +15 -5
  40. package/src/sdkFactory/types.ts +7 -4
  41. package/src/services/splitApi.ts +22 -0
  42. package/src/services/types.ts +6 -0
  43. package/src/storages/types.ts +17 -4
  44. package/src/sync/submitters/submitterManager.ts +2 -0
  45. package/src/sync/submitters/types.ts +20 -1
  46. package/src/sync/submitters/uniqueKeysSubmitter.ts +79 -0
  47. package/src/trackers/impressionsTracker.ts +12 -34
  48. package/src/trackers/strategy/strategyDebug.ts +28 -0
  49. package/src/trackers/strategy/strategyNone.ts +34 -0
  50. package/src/trackers/strategy/strategyOptimized.ts +42 -0
  51. package/src/trackers/types.ts +30 -0
  52. package/src/trackers/uniqueKeysTracker.ts +80 -0
  53. package/src/types.ts +2 -1
  54. package/src/utils/constants/index.ts +1 -0
  55. package/src/utils/settingsValidation/impressionsMode.ts +3 -3
  56. package/src/utils/settingsValidation/index.ts +4 -0
  57. package/types/integrations/ga/GoogleAnalyticsToSplit.d.ts +1 -1
  58. package/types/logger/browser/{debugLogger.d.ts → DebugLogger.d.ts} +0 -0
  59. package/types/logger/browser/{errorLogger.d.ts → ErrorLogger.d.ts} +0 -0
  60. package/types/logger/browser/{infoLogger.d.ts → InfoLogger.d.ts} +0 -0
  61. package/types/logger/browser/{warnLogger.d.ts → WarnLogger.d.ts} +0 -0
  62. package/types/logger/constants.d.ts +1 -0
  63. package/types/sdkFactory/types.d.ts +4 -2
  64. package/types/services/types.d.ts +4 -0
  65. package/types/storages/types.d.ts +16 -3
  66. package/types/sync/submitters/types.d.ts +18 -1
  67. package/types/sync/submitters/uniqueKeysSubmitter.d.ts +19 -0
  68. package/types/trackers/filter/bloomFilter.d.ts +10 -0
  69. package/types/trackers/filter/dictionaryFilter.d.ts +8 -0
  70. package/types/trackers/filter/types.d.ts +5 -0
  71. package/types/trackers/impressionsTracker.d.ts +4 -6
  72. package/types/trackers/strategy/strategyDebug.d.ts +9 -0
  73. package/types/trackers/strategy/strategyNone.d.ts +10 -0
  74. package/types/trackers/strategy/strategyOptimized.d.ts +11 -0
  75. package/types/trackers/types.d.ts +29 -0
  76. package/types/trackers/uniqueKeysTracker.d.ts +13 -0
  77. package/types/types.d.ts +2 -1
  78. package/types/utils/constants/index.d.ts +1 -0
  79. package/types/utils/settingsValidation/index.d.ts +1 -0
  80. package/types/utils/timeTracker/index.d.ts +70 -1
  81. package/src/logger/.DS_Store +0 -0
  82. package/types/integrations/ga/GaToSplitPlugin.d.ts +0 -3
  83. package/types/integrations/ga/SplitToGaPlugin.d.ts +0 -4
  84. package/types/integrations/ga/autoRequire.d.ts +0 -4
  85. package/types/logger/codes.d.ts +0 -2
  86. package/types/logger/codesConstants.d.ts +0 -117
  87. package/types/logger/codesConstantsBrowser.d.ts +0 -2
  88. package/types/logger/codesConstantsNode.d.ts +0 -14
  89. package/types/logger/codesDebug.d.ts +0 -1
  90. package/types/logger/codesDebugBrowser.d.ts +0 -1
  91. package/types/logger/codesDebugNode.d.ts +0 -1
  92. package/types/logger/codesError.d.ts +0 -1
  93. package/types/logger/codesErrorNode.d.ts +0 -1
  94. package/types/logger/codesInfo.d.ts +0 -1
  95. package/types/logger/codesWarn.d.ts +0 -1
  96. package/types/logger/codesWarnNode.d.ts +0 -1
  97. package/types/logger/debugLogger.d.ts +0 -2
  98. package/types/logger/errorLogger.d.ts +0 -2
  99. package/types/logger/infoLogger.d.ts +0 -2
  100. package/types/logger/messages/debugBrowser.d.ts +0 -1
  101. package/types/logger/messages/debugNode.d.ts +0 -1
  102. package/types/logger/messages/errorNode.d.ts +0 -1
  103. package/types/logger/messages/warnNode.d.ts +0 -1
  104. package/types/logger/noopLogger.d.ts +0 -2
  105. package/types/logger/warnLogger.d.ts +0 -2
  106. package/types/sdkFactory/userConsentProps.d.ts +0 -6
  107. package/types/sdkManager/sdkManagerMethod.d.ts +0 -6
  108. package/types/storages/getRegisteredSegments.d.ts +0 -10
  109. package/types/storages/inMemory/index.d.ts +0 -10
  110. package/types/storages/parseSegments.d.ts +0 -6
  111. package/types/sync/polling/syncTasks/splitsSyncTask.copy.d.ts +0 -35
  112. package/types/sync/polling/syncTasks/splitsSyncTask.morelikeoriginal.d.ts +0 -35
  113. package/types/sync/streaming/AuthClient/indexV1.d.ts +0 -12
  114. package/types/sync/streaming/AuthClient/indexV2.d.ts +0 -8
  115. package/types/sync/streaming/pushManagerCS.d.ts +0 -1
  116. package/types/sync/streaming/pushManagerNoUsers.d.ts +0 -13
  117. package/types/sync/streaming/pushManagerSS.d.ts +0 -1
  118. package/types/sync/submitters/telemetrySyncTask.d.ts +0 -0
  119. package/types/sync/syncManagerFromFile.d.ts +0 -2
  120. package/types/sync/syncManagerFromObject.d.ts +0 -2
  121. package/types/sync/syncManagerOffline.d.ts +0 -9
  122. package/types/trackers/telemetryRecorder.d.ts +0 -0
  123. package/types/utils/EventEmitter.d.ts +0 -4
  124. package/types/utils/consent.d.ts +0 -2
  125. package/types/utils/lang/errors.d.ts +0 -10
  126. package/types/utils/murmur3/commons.d.ts +0 -12
  127. package/types/utils/settingsValidation/buildMetadata.d.ts +0 -3
  128. package/types/utils/settingsValidation/localhost/index.d.ts +0 -9
  129. package/types/utils/settingsValidation/logger.d.ts +0 -11
  130. package/types/utils/settingsValidation/runtime/browser.d.ts +0 -2
  131. package/types/utils/settingsValidation/runtime/node.d.ts +0 -2
  132. package/types/utils/settingsValidation/userConsent.d.ts +0 -5
@@ -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
@@ -0,0 +1,79 @@
1
+ import { ISdkFactoryContextSync } from '../../sdkFactory/types';
2
+ import { ISet, setToArray } from '../../utils/lang/sets';
3
+ import { submitterFactory } from './submitter';
4
+ import { UniqueKeysPayloadCs, UniqueKeysPayloadSs } from './types';
5
+
6
+ /**
7
+ * Invert keys for feature to features for key
8
+ */
9
+ function invertUniqueKeys(uniqueKeys: { [featureName: string]: ISet<string> }): { [key: string]: string[] } {
10
+ const featureNames = Object.keys(uniqueKeys);
11
+ const inverted: { [key: string]: string[] } = {};
12
+ for (let i = 0; i < featureNames.length; i++) {
13
+ const featureName = featureNames[i];
14
+ const featureKeys = setToArray(uniqueKeys[featureName]);
15
+ for (let j = 0; j< featureKeys.length; j++) {
16
+ const featureKey = featureKeys[j];
17
+ if (!inverted[featureKey]) inverted[featureKey] = [];
18
+ inverted[featureKey].push(featureName);
19
+ }
20
+ }
21
+ return inverted;
22
+ }
23
+
24
+ /**
25
+ * Converts `uniqueKeys` data from cache into request payload for CS.
26
+ */
27
+ export function fromUniqueKeysCollectorCs(uniqueKeys: { [featureName: string]: ISet<string> }): UniqueKeysPayloadCs {
28
+ const payload = [];
29
+ const featuresPerKey = invertUniqueKeys(uniqueKeys);
30
+ const keys = Object.keys(featuresPerKey);
31
+ for (let k = 0; k < keys.length; k++) {
32
+ const key = keys[k];
33
+ const uniqueKeysPayload = {
34
+ k: key,
35
+ fs: featuresPerKey[key]
36
+ };
37
+
38
+ payload.push(uniqueKeysPayload);
39
+ }
40
+ return { keys: payload };
41
+ }
42
+
43
+ /**
44
+ * Converts `uniqueKeys` data from cache into request payload for SS.
45
+ */
46
+ export function fromUniqueKeysCollectorSs(uniqueKeys: { [featureName: string]: ISet<string> }): UniqueKeysPayloadSs {
47
+ const payload = [];
48
+ const featureNames = Object.keys(uniqueKeys);
49
+ for (let i = 0; i < featureNames.length; i++) {
50
+ const featureName = featureNames[i];
51
+ const featureKeys = setToArray(uniqueKeys[featureName]);
52
+ const uniqueKeysPayload = {
53
+ f: featureName,
54
+ ks: featureKeys
55
+ };
56
+
57
+ payload.push(uniqueKeysPayload);
58
+ }
59
+ return { keys: payload };
60
+ }
61
+
62
+ /**
63
+ * Submitter that periodically posts impression counts
64
+ */
65
+ export function uniqueKeysSubmitterFactory(params: ISdkFactoryContextSync) {
66
+
67
+ const {
68
+ settings: { log, scheduler: { uniqueKeysRefreshRate }, core: {key}},
69
+ splitApi: { postUniqueKeysBulkCs, postUniqueKeysBulkSs },
70
+ storage: { uniqueKeys }
71
+ } = params;
72
+
73
+ const isClientSide = key !== undefined;
74
+ const postUniqueKeysBulk = isClientSide ? postUniqueKeysBulkCs : postUniqueKeysBulkSs;
75
+ const fromUniqueKeysCollector = isClientSide ? fromUniqueKeysCollectorCs : fromUniqueKeysCollectorSs;
76
+
77
+ return submitterFactory(log, postUniqueKeysBulk, uniqueKeys!, uniqueKeysRefreshRate, 'unique keys', fromUniqueKeysCollector);
78
+ }
79
+
@@ -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;
@@ -37,26 +31,10 @@ export function impressionsTrackerFactory(
37
31
 
38
32
  const impressionsCount = impressions.length;
39
33
 
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
- });
59
-
34
+ const { impressionsToStore, impressionsToListener, deduped } = strategy.process(impressions);
35
+
36
+ const impressionsToListenerCount = impressionsToListener.length;
37
+
60
38
  const res = impressionsCache.track(impressionsToStore);
61
39
 
62
40
  // If we're on an async storage, handle error and log it.
@@ -71,16 +49,16 @@ export function impressionsTrackerFactory(
71
49
  // @TODO we are not dropping impressions on full queue yet, so DROPPED stats are not recorded
72
50
  if (telemetryCache) {
73
51
  (telemetryCache as ITelemetryCacheSync).recordImpressionStats(QUEUED, impressionsToStore.length);
74
- (telemetryCache as ITelemetryCacheSync).recordImpressionStats(DEDUPED, impressions.length - impressionsToStore.length);
52
+ (telemetryCache as ITelemetryCacheSync).recordImpressionStats(DEDUPED, deduped);
75
53
  }
76
54
  }
77
55
 
78
56
  // @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
57
  if (impressionListener || integrationsManager) {
80
- for (let i = 0; i < impressionsCount; i++) {
58
+ for (let i = 0; i < impressionsToListenerCount; i++) {
81
59
  const impressionData: SplitIO.ImpressionData = {
82
60
  // copy of impression, to avoid unexpected behaviour if modified by integrations or impressionListener
83
- impression: objectAssign({}, impressions[i]),
61
+ impression: objectAssign({}, impressionsToListener[i]),
84
62
  attributes,
85
63
  ip,
86
64
  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 { IImpressionCountsCacheSync } 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: IImpressionCountsCacheSync,
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.feature, impression.keyName);
25
+ });
26
+
27
+ return {
28
+ impressionsToStore: [],
29
+ impressionsToListener: impressions,
30
+ deduped: 0
31
+ };
32
+ }
33
+ };
34
+ }
@@ -0,0 +1,42 @@
1
+ import { IImpressionCountsCacheSync } 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: IImpressionCountsCacheSync,
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
+ 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
+ }
@@ -2,6 +2,7 @@ import { SplitIO, ImpressionDTO } from '../types';
2
2
  import { StreamingEventType, Method, OperationType } from '../sync/submitters/types';
3
3
  import { IEventsCacheBase } from '../storages/types';
4
4
  import { NetworkError } from '../services/types';
5
+ import { ISet } from '../utils/lang/sets';
5
6
 
6
7
  /** Events tracker */
7
8
 
@@ -42,3 +43,32 @@ export interface ITelemetryTracker {
42
43
  */
43
44
  streamingEvent(e: StreamingEventType | AUTH_REJECTION, d?: number): void
44
45
  }
46
+
47
+ export interface IFilterAdapter {
48
+ add(featureName: string, key: string): boolean;
49
+ contains(featureName: string, key: string): boolean;
50
+ clear(): void;
51
+ }
52
+
53
+ export interface IImpressionSenderAdapter {
54
+ recordUniqueKeys(data: Object): void;
55
+ recordImpressionCounts(data: Object): void
56
+ }
57
+
58
+ /** Unique keys tracker */
59
+ export interface IUniqueKeysTracker {
60
+ track(featureName: string, key: string): void;
61
+ pop(toMerge?: { [featureName: string]: ISet<string> }): { [featureName: string]: ISet<string>; };
62
+ clear(): void;
63
+ isEmpty(): boolean;
64
+ }
65
+
66
+ export interface IStrategyResult {
67
+ impressionsToStore: ImpressionDTO[],
68
+ impressionsToListener: ImpressionDTO[],
69
+ deduped: number
70
+ }
71
+
72
+ export interface IStrategy {
73
+ process(impressions: ImpressionDTO[]): IStrategyResult
74
+ }
@@ -0,0 +1,80 @@
1
+ import { LOG_PREFIX_UNIQUE_KEYS_TRACKER } from '../logger/constants';
2
+ import { ILogger } from '../logger/types';
3
+ import { ISet, _Set } from '../utils/lang/sets';
4
+ import { IFilterAdapter, IUniqueKeysTracker } from './types';
5
+
6
+ const noopFilterAdapter = {
7
+ add() {return true;},
8
+ contains() {return true;},
9
+ clear() {}
10
+ };
11
+
12
+ const DEFAULT_CACHE_SIZE = 30000;
13
+ /**
14
+ * Trackes uniques keys
15
+ * Unique Keys Tracker will be in charge of checking if the MTK was already sent to the BE in the last period
16
+ * or schedule to be sent; if not it will be added in an internal cache and sent in the next post.
17
+ *
18
+ * @param log Logger instance
19
+ * @param filterAdapter filter adapter
20
+ * @param cacheSize optional internal cache size
21
+ * @param maxBulkSize optional max MTKs bulk size
22
+ */
23
+ export function uniqueKeysTrackerFactory(
24
+ log: ILogger,
25
+ filterAdapter: IFilterAdapter = noopFilterAdapter,
26
+ cacheSize = DEFAULT_CACHE_SIZE,
27
+ // @TODO
28
+ // maxBulkSize: number = 5000,
29
+ ): IUniqueKeysTracker {
30
+
31
+ let uniqueKeysTracker: { [featureName: string]: ISet<string> } = {};
32
+ let uniqueTrackerSize = 0;
33
+
34
+ return {
35
+ track(featureName: string, key: string): void {
36
+ if (!filterAdapter.add(featureName, key)) {
37
+ log.debug(`${LOG_PREFIX_UNIQUE_KEYS_TRACKER}The feature ${featureName} and key ${key} exist in the filter`);
38
+ return;
39
+ }
40
+ if (!uniqueKeysTracker[featureName]) uniqueKeysTracker[featureName] = new _Set();
41
+ const tracker = uniqueKeysTracker[featureName];
42
+ if (!tracker.has(key)) {
43
+ tracker.add(key);
44
+ log.debug(`${LOG_PREFIX_UNIQUE_KEYS_TRACKER}Key ${key} added to feature ${featureName}`);
45
+ uniqueTrackerSize++;
46
+ }
47
+
48
+ if (uniqueTrackerSize >= cacheSize) {
49
+ log.warn(`${LOG_PREFIX_UNIQUE_KEYS_TRACKER}The UniqueKeysTracker size reached the maximum limit`);
50
+ // @TODO trigger event to submitter to send mtk
51
+ uniqueTrackerSize = 0;
52
+ }
53
+ },
54
+
55
+ /**
56
+ * Pop the collected data, used as payload for posting.
57
+ */
58
+ pop() {
59
+ const data = uniqueKeysTracker;
60
+ uniqueKeysTracker = {};
61
+ return data;
62
+ },
63
+
64
+ /**
65
+ * Clear the data stored on the cache.
66
+ */
67
+ clear() {
68
+ uniqueKeysTracker = {};
69
+ },
70
+
71
+ /**
72
+ * Check if the cache is empty.
73
+ */
74
+ isEmpty() {
75
+ return Object.keys(uniqueKeysTracker).length === 0;
76
+ }
77
+
78
+ };
79
+
80
+ }
package/src/types.ts CHANGED
@@ -80,6 +80,7 @@ export interface ISettings {
80
80
  featuresRefreshRate: number,
81
81
  impressionsRefreshRate: number,
82
82
  impressionsQueueSize: number,
83
+ uniqueKeysRefreshRate: number,
83
84
  /**
84
85
  * @deprecated
85
86
  */
@@ -718,7 +719,7 @@ export namespace SplitIO {
718
719
  * ImpressionsMode type
719
720
  * @typedef {string} ImpressionsMode
720
721
  */
721
- export type ImpressionsMode = 'OPTIMIZED' | 'DEBUG'
722
+ export type ImpressionsMode = 'OPTIMIZED' | 'DEBUG' | 'NONE'
722
723
  /**
723
724
  * Defines the format of Split data to preload on the factory storage (cache).
724
725
  */
@@ -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';
@@ -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
  }
@@ -36,6 +36,8 @@ export const base = {
36
36
  telemetryRefreshRate: 3600,
37
37
  // publish evaluations each 300 sec (default value for OPTIMIZED impressions mode)
38
38
  impressionsRefreshRate: 300,
39
+ // publish unique Keys each 900 sec (15 min)
40
+ uniqueKeysRefreshRate: 900,
39
41
  // fetch offline changes each 15 sec
40
42
  offlineRefreshRate: 15,
41
43
  // publish events every 60 seconds after the first flush
@@ -130,11 +132,13 @@ export function settingsValidation(config: unknown, validationParams: ISettingsV
130
132
  scheduler.segmentsRefreshRate = fromSecondsToMillis(scheduler.segmentsRefreshRate);
131
133
  scheduler.offlineRefreshRate = fromSecondsToMillis(scheduler.offlineRefreshRate);
132
134
  scheduler.eventsPushRate = fromSecondsToMillis(scheduler.eventsPushRate);
135
+ scheduler.uniqueKeysRefreshRate = fromSecondsToMillis(scheduler.uniqueKeysRefreshRate);
133
136
  scheduler.telemetryRefreshRate = fromSecondsToMillis(validateMinValue('telemetryRefreshRate', scheduler.telemetryRefreshRate, 60));
134
137
 
135
138
  // Default impressionsRefreshRate for DEBUG mode is 60 secs
136
139
  if (get(config, 'scheduler.impressionsRefreshRate') === undefined && withDefaults.sync.impressionsMode === DEBUG) scheduler.impressionsRefreshRate = 60;
137
140
  scheduler.impressionsRefreshRate = fromSecondsToMillis(scheduler.impressionsRefreshRate);
141
+
138
142
 
139
143
  // Log deprecation for old telemetry param
140
144
  if (scheduler.metricsRefreshRate) log.warn('`metricsRefreshRate` will be deprecated soon. For configuring telemetry rates, update `telemetryRefreshRate` value in configs');
@@ -1,3 +1,3 @@
1
1
  import { IntegrationFactory } from '../types';
2
2
  import { GoogleAnalyticsToSplitOptions } from './types';
3
- export declare function GoogleAnalyticsToSplit(options: GoogleAnalyticsToSplitOptions): IntegrationFactory;
3
+ export declare function GoogleAnalyticsToSplit(options?: GoogleAnalyticsToSplitOptions): IntegrationFactory;
@@ -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) => 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;
@@ -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
  * Interface of a pluggable storage wrapper.
7
8
  */
@@ -297,6 +298,17 @@ export interface IImpressionCountsCacheSync extends IRecorderCacheProducerSync<R
297
298
  isEmpty(): boolean;
298
299
  pop(toMerge?: Record<string, number>): Record<string, number>;
299
300
  }
301
+ export interface IUniqueKeysCacheBase extends IRecorderCacheProducerSync<{
302
+ [featureName: string]: ISet<string>;
303
+ }> {
304
+ track(featureName: string, timeFrame: number, amount: number): void;
305
+ isEmpty(): boolean;
306
+ pop(toMerge?: {
307
+ [featureName: string]: ISet<string>;
308
+ }): {
309
+ [featureName: string]: ISet<string>;
310
+ };
311
+ }
300
312
  /**
301
313
  * Telemetry storage interface for standalone and partial consumer modes.
302
314
  * Methods are sync because data is stored in memory.
@@ -366,19 +378,20 @@ export interface ITelemetryCacheAsync extends ITelemetryEvaluationProducerAsync
366
378
  /**
367
379
  * Storages
368
380
  */
369
- export interface IStorageBase<TSplitsCache extends ISplitsCacheBase, TSegmentsCache extends ISegmentsCacheBase, TImpressionsCache extends IImpressionsCacheBase, TEventsCache extends IEventsCacheBase, TTelemetryCache extends ITelemetryCacheSync | ITelemetryCacheAsync> {
381
+ export interface IStorageBase<TSplitsCache extends ISplitsCacheBase, TSegmentsCache extends ISegmentsCacheBase, TImpressionsCache extends IImpressionsCacheBase, TEventsCache extends IEventsCacheBase, TTelemetryCache extends ITelemetryCacheSync | ITelemetryCacheAsync, TUniqueKeysCache extends IUniqueKeysCacheBase> {
370
382
  splits: TSplitsCache;
371
383
  segments: TSegmentsCache;
372
384
  impressions: TImpressionsCache;
373
385
  impressionCounts?: IImpressionCountsCacheSync;
374
386
  events: TEventsCache;
375
387
  telemetry?: TTelemetryCache;
388
+ uniqueKeys?: TUniqueKeysCache;
376
389
  destroy(): void | Promise<void>;
377
390
  shared?: (matchingKey: string, onReadyCb: (error?: any) => void) => this;
378
391
  }
379
- export interface IStorageSync extends IStorageBase<ISplitsCacheSync, ISegmentsCacheSync, IImpressionsCacheSync, IEventsCacheSync, ITelemetryCacheSync> {
392
+ export interface IStorageSync extends IStorageBase<ISplitsCacheSync, ISegmentsCacheSync, IImpressionsCacheSync, IEventsCacheSync, ITelemetryCacheSync, IUniqueKeysCacheBase> {
380
393
  }
381
- export interface IStorageAsync extends IStorageBase<ISplitsCacheAsync, ISegmentsCacheAsync, IImpressionsCacheAsync | IImpressionsCacheSync, IEventsCacheAsync | IEventsCacheSync, ITelemetryCacheAsync> {
394
+ export interface IStorageAsync extends IStorageBase<ISplitsCacheAsync, ISegmentsCacheAsync, IImpressionsCacheAsync | IImpressionsCacheSync, IEventsCacheAsync | IEventsCacheSync, ITelemetryCacheAsync, IUniqueKeysCacheBase> {
382
395
  }
383
396
  /** StorageFactory */
384
397
  export declare type DataLoader = (storage: IStorageSync, matchingKey: string) => void;