@splitsoftware/splitio-commons 1.6.1 → 1.6.2-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/cjs/listeners/browser.js +2 -1
  2. package/cjs/logger/constants.js +2 -1
  3. package/cjs/sdkFactory/index.js +14 -5
  4. package/cjs/services/splitApi.js +20 -0
  5. package/cjs/storages/inLocalStorage/index.js +4 -0
  6. package/cjs/storages/inMemory/InMemoryStorage.js +5 -1
  7. package/cjs/storages/inMemory/InMemoryStorageCS.js +5 -1
  8. package/cjs/storages/inMemory/uniqueKeysCacheInMemory.js +73 -0
  9. package/cjs/storages/inMemory/uniqueKeysCacheInMemoryCS.js +78 -0
  10. package/cjs/sync/submitters/submitterManager.js +3 -0
  11. package/cjs/sync/submitters/telemetrySubmitter.js +1 -0
  12. package/cjs/sync/submitters/uniqueKeysSubmitter.js +26 -0
  13. package/cjs/trackers/impressionsTracker.js +7 -28
  14. package/cjs/trackers/strategy/strategyDebug.js +25 -0
  15. package/cjs/trackers/strategy/strategyNone.js +29 -0
  16. package/cjs/trackers/strategy/strategyOptimized.js +34 -0
  17. package/cjs/trackers/uniqueKeysTracker.js +31 -0
  18. package/cjs/utils/constants/index.js +4 -2
  19. package/cjs/utils/settingsValidation/impressionsMode.js +2 -2
  20. package/cjs/utils/settingsValidation/index.js +3 -0
  21. package/esm/listeners/browser.js +3 -2
  22. package/esm/logger/constants.js +1 -0
  23. package/esm/sdkFactory/index.js +14 -5
  24. package/esm/services/splitApi.js +20 -0
  25. package/esm/storages/inLocalStorage/index.js +5 -1
  26. package/esm/storages/inMemory/InMemoryStorage.js +6 -2
  27. package/esm/storages/inMemory/InMemoryStorageCS.js +6 -2
  28. package/esm/storages/inMemory/uniqueKeysCacheInMemory.js +70 -0
  29. package/esm/storages/inMemory/uniqueKeysCacheInMemoryCS.js +75 -0
  30. package/esm/sync/submitters/submitterManager.js +3 -0
  31. package/esm/sync/submitters/telemetrySubmitter.js +2 -1
  32. package/esm/sync/submitters/uniqueKeysSubmitter.js +22 -0
  33. package/esm/trackers/impressionsTracker.js +7 -28
  34. package/esm/trackers/strategy/strategyDebug.js +21 -0
  35. package/esm/trackers/strategy/strategyNone.js +25 -0
  36. package/esm/trackers/strategy/strategyOptimized.js +30 -0
  37. package/esm/trackers/uniqueKeysTracker.js +27 -0
  38. package/esm/utils/constants/index.js +2 -0
  39. package/esm/utils/settingsValidation/impressionsMode.js +3 -3
  40. package/esm/utils/settingsValidation/index.js +3 -0
  41. package/package.json +1 -1
  42. package/src/listeners/browser.ts +3 -2
  43. package/src/logger/constants.ts +1 -0
  44. package/src/sdkFactory/index.ts +16 -5
  45. package/src/sdkFactory/types.ts +7 -4
  46. package/src/services/splitApi.ts +22 -0
  47. package/src/services/types.ts +6 -0
  48. package/src/storages/inLocalStorage/index.ts +4 -1
  49. package/src/storages/inMemory/InMemoryStorage.ts +5 -2
  50. package/src/storages/inMemory/InMemoryStorageCS.ts +6 -2
  51. package/src/storages/inMemory/uniqueKeysCacheInMemory.ts +83 -0
  52. package/src/storages/inMemory/uniqueKeysCacheInMemoryCS.ts +89 -0
  53. package/src/storages/types.ts +22 -6
  54. package/src/sync/submitters/submitterManager.ts +2 -0
  55. package/src/sync/submitters/telemetrySubmitter.ts +4 -3
  56. package/src/sync/submitters/types.ts +20 -1
  57. package/src/sync/submitters/uniqueKeysSubmitter.ts +35 -0
  58. package/src/trackers/impressionsTracker.ts +12 -35
  59. package/src/trackers/strategy/strategyDebug.ts +28 -0
  60. package/src/trackers/strategy/strategyNone.ts +34 -0
  61. package/src/trackers/strategy/strategyOptimized.ts +42 -0
  62. package/src/trackers/types.ts +26 -0
  63. package/src/trackers/uniqueKeysTracker.ts +37 -0
  64. package/src/types.ts +3 -1
  65. package/src/utils/constants/index.ts +2 -0
  66. package/src/utils/settingsValidation/impressionsMode.ts +3 -3
  67. package/src/utils/settingsValidation/index.ts +4 -0
  68. package/types/logger/browser/{debugLogger.d.ts → DebugLogger.d.ts} +0 -0
  69. package/types/logger/browser/{errorLogger.d.ts → ErrorLogger.d.ts} +0 -0
  70. package/types/logger/browser/{infoLogger.d.ts → InfoLogger.d.ts} +0 -0
  71. package/types/logger/browser/{warnLogger.d.ts → WarnLogger.d.ts} +0 -0
  72. package/types/logger/constants.d.ts +1 -0
  73. package/types/sdkFactory/types.d.ts +4 -2
  74. package/types/services/types.d.ts +4 -0
  75. package/types/storages/inMemory/uniqueKeysCacheInMemory.d.ts +32 -0
  76. package/types/storages/inMemory/uniqueKeysCacheInMemoryCS.d.ts +37 -0
  77. package/types/storages/types.d.ts +14 -4
  78. package/types/sync/submitters/types.d.ts +18 -1
  79. package/types/sync/submitters/uniqueKeysSubmitter.d.ts +5 -0
  80. package/types/trackers/filter/bloomFilter.d.ts +10 -0
  81. package/types/trackers/filter/dictionaryFilter.d.ts +8 -0
  82. package/types/trackers/filter/types.d.ts +5 -0
  83. package/types/trackers/impressionsTracker.d.ts +4 -6
  84. package/types/trackers/strategy/strategyDebug.d.ts +9 -0
  85. package/types/trackers/strategy/strategyNone.d.ts +10 -0
  86. package/types/trackers/strategy/strategyOptimized.d.ts +11 -0
  87. package/types/trackers/types.d.ts +21 -0
  88. package/types/trackers/uniqueKeysTracker.d.ts +13 -0
  89. package/types/types.d.ts +3 -1
  90. package/types/utils/constants/index.d.ts +2 -0
  91. package/types/utils/settingsValidation/index.d.ts +1 -0
  92. package/types/utils/timeTracker/index.d.ts +70 -1
  93. package/src/logger/.DS_Store +0 -0
  94. package/types/integrations/ga/GaToSplitPlugin.d.ts +0 -3
  95. package/types/integrations/ga/SplitToGaPlugin.d.ts +0 -4
  96. package/types/integrations/ga/autoRequire.d.ts +0 -4
  97. package/types/logger/codes.d.ts +0 -2
  98. package/types/logger/codesConstants.d.ts +0 -117
  99. package/types/logger/codesConstantsBrowser.d.ts +0 -2
  100. package/types/logger/codesConstantsNode.d.ts +0 -14
  101. package/types/logger/codesDebug.d.ts +0 -1
  102. package/types/logger/codesDebugBrowser.d.ts +0 -1
  103. package/types/logger/codesDebugNode.d.ts +0 -1
  104. package/types/logger/codesError.d.ts +0 -1
  105. package/types/logger/codesErrorNode.d.ts +0 -1
  106. package/types/logger/codesInfo.d.ts +0 -1
  107. package/types/logger/codesWarn.d.ts +0 -1
  108. package/types/logger/codesWarnNode.d.ts +0 -1
  109. package/types/logger/debugLogger.d.ts +0 -2
  110. package/types/logger/errorLogger.d.ts +0 -2
  111. package/types/logger/infoLogger.d.ts +0 -2
  112. package/types/logger/messages/debugBrowser.d.ts +0 -1
  113. package/types/logger/messages/debugNode.d.ts +0 -1
  114. package/types/logger/messages/errorNode.d.ts +0 -1
  115. package/types/logger/messages/warnNode.d.ts +0 -1
  116. package/types/logger/noopLogger.d.ts +0 -2
  117. package/types/logger/warnLogger.d.ts +0 -2
  118. package/types/sdkFactory/userConsentProps.d.ts +0 -6
  119. package/types/sdkManager/sdkManagerMethod.d.ts +0 -6
  120. package/types/storages/getRegisteredSegments.d.ts +0 -10
  121. package/types/storages/inMemory/index.d.ts +0 -10
  122. package/types/storages/parseSegments.d.ts +0 -6
  123. package/types/sync/polling/syncTasks/splitsSyncTask.copy.d.ts +0 -35
  124. package/types/sync/polling/syncTasks/splitsSyncTask.morelikeoriginal.d.ts +0 -35
  125. package/types/sync/streaming/AuthClient/indexV1.d.ts +0 -12
  126. package/types/sync/streaming/AuthClient/indexV2.d.ts +0 -8
  127. package/types/sync/streaming/pushManagerCS.d.ts +0 -1
  128. package/types/sync/streaming/pushManagerNoUsers.d.ts +0 -13
  129. package/types/sync/streaming/pushManagerSS.d.ts +0 -1
  130. package/types/sync/submitters/telemetrySyncTask.d.ts +0 -0
  131. package/types/sync/syncManagerFromFile.d.ts +0 -2
  132. package/types/sync/syncManagerFromObject.d.ts +0 -2
  133. package/types/sync/syncManagerOffline.d.ts +0 -9
  134. package/types/trackers/telemetryRecorder.d.ts +0 -0
  135. package/types/utils/EventEmitter.d.ts +0 -4
  136. package/types/utils/consent.d.ts +0 -2
  137. package/types/utils/lang/errors.d.ts +0 -10
  138. package/types/utils/murmur3/commons.d.ts +0 -12
  139. package/types/utils/settingsValidation/buildMetadata.d.ts +0 -3
  140. package/types/utils/settingsValidation/localhost/index.d.ts +0 -9
  141. package/types/utils/settingsValidation/logger.d.ts +0 -11
  142. package/types/utils/settingsValidation/runtime/browser.d.ts +0 -2
  143. package/types/utils/settingsValidation/runtime/node.d.ts +0 -2
  144. package/types/utils/settingsValidation/userConsent.d.ts +0 -5
@@ -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.1",
3
+ "version": "1.6.2-rc.2",
4
4
  "description": "Split Javascript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
@@ -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,7 +26,8 @@ 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;
26
32
 
27
33
  // @TODO handle non-recoverable errors, such as, global `fetch` not available, invalid API Key, etc.
@@ -37,6 +43,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
37
43
  const storageFactoryParams: IStorageFactoryParams = {
38
44
  impressionsQueueSize: settings.scheduler.impressionsQueueSize,
39
45
  eventsQueueSize: settings.scheduler.eventsQueueSize,
46
+ uniqueKeysCacheSize: settings.scheduler.uniqueKeysCacheSize,
40
47
  optimize: shouldBeOptimized(settings),
41
48
 
42
49
  // ATM, only used by InLocalStorage
@@ -45,6 +52,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
45
52
 
46
53
  // ATM, only used by PluggableStorage
47
54
  mode: settings.mode,
55
+ impressionsMode: settings.sync.impressionsMode,
48
56
 
49
57
  // Callback used to emit SDK_READY in consumer mode, where `syncManagerFactory` is undefined,
50
58
  // or partial consumer mode, where it only has submitters, and therefore it doesn't emit readiness events.
@@ -62,16 +70,19 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
62
70
 
63
71
  const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage });
64
72
 
65
- // trackers
66
- const observer = impressionsObserverFactory && impressionsObserverFactory();
67
- const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, integrationsManager, observer, storage.impressionCounts, storage.telemetry);
73
+ const observer = impressionsObserverFactory();
74
+ const uniqueKeysTracker = storageFactoryParams.impressionsMode === NONE ? uniqueKeysTrackerFactory(log, storage.uniqueKeys!, filterAdapterFactory && filterAdapterFactory()) : undefined;
75
+ const strategy = (storageFactoryParams.optimize) ? strategyOptimizedFactory(observer, storage.impressionCounts!) :
76
+ (storageFactoryParams.impressionsMode === NONE) ? strategyNoneFactory(storage.impressionCounts!, uniqueKeysTracker!) : strategyDebugFactory(observer);
77
+
78
+ const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, strategy, integrationsManager, storage.telemetry);
68
79
  const eventTracker = eventTrackerFactory(settings, storage.events, integrationsManager, storage.telemetry);
69
80
  const telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now);
70
81
 
71
82
  // splitApi is used by SyncManager and Browser signal listener
72
83
  const splitApi = splitApiFactory && splitApiFactory(settings, platform, telemetryTracker);
73
84
 
74
- const ctx: ISdkFactoryContext = { splitApi, eventTracker, impressionsTracker, telemetryTracker, sdkReadinessManager, readiness, settings, storage, platform };
85
+ const ctx: ISdkFactoryContext = { splitApi, eventTracker, impressionsTracker, telemetryTracker, uniqueKeysTracker, sdkReadinessManager, readiness, settings, storage, platform };
75
86
 
76
87
  const syncManager = syncManagerFactory && syncManagerFactory(ctx as ISdkFactoryContextSync);
77
88
  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}/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}/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
@@ -12,8 +12,9 @@ import { SplitsCacheInMemory } from '../inMemory/SplitsCacheInMemory';
12
12
  import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../../utils/constants/browser';
13
13
  import { InMemoryStorageCSFactory } from '../inMemory/InMemoryStorageCS';
14
14
  import { LOG_PREFIX } from './constants';
15
- import { LOCALHOST_MODE, STORAGE_LOCALSTORAGE } from '../../utils/constants';
15
+ import { LOCALHOST_MODE, NONE, STORAGE_LOCALSTORAGE } from '../../utils/constants';
16
16
  import { shouldRecordTelemetry, TelemetryCacheInMemory } from '../inMemory/TelemetryCacheInMemory';
17
+ import { UniqueKeysCacheInMemoryCS } from '../inMemory/uniqueKeysCacheInMemoryCS';
17
18
 
18
19
  export interface InLocalStorageOptions {
19
20
  prefix?: string
@@ -45,6 +46,7 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
45
46
  impressionCounts: params.optimize ? new ImpressionCountsCacheInMemory() : undefined,
46
47
  events: new EventsCacheInMemory(params.eventsQueueSize),
47
48
  telemetry: params.mode !== LOCALHOST_MODE && shouldRecordTelemetry() ? new TelemetryCacheInMemory() : undefined,
49
+ uniqueKeys: params.impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS() : undefined,
48
50
 
49
51
  destroy() {
50
52
  this.splits = new SplitsCacheInMemory();
@@ -52,6 +54,7 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
52
54
  this.impressions.clear();
53
55
  this.impressionCounts && this.impressionCounts.clear();
54
56
  this.events.clear();
57
+ this.uniqueKeys?.clear();
55
58
  },
56
59
 
57
60
  // When using shared instanciation with MEMORY we reuse everything but segments (they are customer per key).
@@ -4,8 +4,9 @@ import { ImpressionsCacheInMemory } from './ImpressionsCacheInMemory';
4
4
  import { EventsCacheInMemory } from './EventsCacheInMemory';
5
5
  import { IStorageFactoryParams, IStorageSync } from '../types';
6
6
  import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
7
- import { LOCALHOST_MODE, STORAGE_MEMORY } from '../../utils/constants';
7
+ import { DEBUG, LOCALHOST_MODE, NONE, STORAGE_MEMORY } from '../../utils/constants';
8
8
  import { TelemetryCacheInMemory } from './TelemetryCacheInMemory';
9
+ import { UniqueKeysCacheInMemory } from './uniqueKeysCacheInMemory';
9
10
 
10
11
  /**
11
12
  * InMemory storage factory for standalone server-side SplitFactory
@@ -18,9 +19,10 @@ export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageS
18
19
  splits: new SplitsCacheInMemory(),
19
20
  segments: new SegmentsCacheInMemory(),
20
21
  impressions: new ImpressionsCacheInMemory(params.impressionsQueueSize),
21
- impressionCounts: params.optimize ? new ImpressionCountsCacheInMemory() : undefined,
22
+ impressionCounts: params.impressionsMode !== DEBUG ? new ImpressionCountsCacheInMemory() : undefined,
22
23
  events: new EventsCacheInMemory(params.eventsQueueSize),
23
24
  telemetry: params.mode !== LOCALHOST_MODE ? new TelemetryCacheInMemory() : undefined, // Always track telemetry in standalone mode on server-side
25
+ uniqueKeys: params.impressionsMode === NONE ? new UniqueKeysCacheInMemory(params.uniqueKeysCacheSize) : undefined,
24
26
 
25
27
  // When using MEMORY we should clean all the caches to leave them empty
26
28
  destroy() {
@@ -29,6 +31,7 @@ export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageS
29
31
  this.impressions.clear();
30
32
  this.impressionCounts && this.impressionCounts.clear();
31
33
  this.events.clear();
34
+ this.uniqueKeys?.clear();
32
35
  }
33
36
  };
34
37
  }
@@ -4,8 +4,9 @@ import { ImpressionsCacheInMemory } from './ImpressionsCacheInMemory';
4
4
  import { EventsCacheInMemory } from './EventsCacheInMemory';
5
5
  import { IStorageSync, IStorageFactoryParams } from '../types';
6
6
  import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
7
- import { LOCALHOST_MODE, STORAGE_MEMORY } from '../../utils/constants';
7
+ import { DEBUG, LOCALHOST_MODE, NONE, STORAGE_MEMORY } from '../../utils/constants';
8
8
  import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
9
+ import { UniqueKeysCacheInMemoryCS } from './uniqueKeysCacheInMemoryCS';
9
10
 
10
11
  /**
11
12
  * InMemory storage factory for standalone client-side SplitFactory
@@ -18,9 +19,11 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
18
19
  splits: new SplitsCacheInMemory(),
19
20
  segments: new MySegmentsCacheInMemory(),
20
21
  impressions: new ImpressionsCacheInMemory(params.impressionsQueueSize),
21
- impressionCounts: params.optimize ? new ImpressionCountsCacheInMemory() : undefined,
22
+ impressionCounts: params.impressionsMode !== DEBUG ? new ImpressionCountsCacheInMemory() : undefined,
22
23
  events: new EventsCacheInMemory(params.eventsQueueSize),
23
24
  telemetry: params.mode !== LOCALHOST_MODE && shouldRecordTelemetry() ? new TelemetryCacheInMemory() : undefined,
25
+ uniqueKeys: params.impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS(params.uniqueKeysCacheSize) : undefined,
26
+
24
27
 
25
28
  // When using MEMORY we should clean all the caches to leave them empty
26
29
  destroy() {
@@ -29,6 +32,7 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
29
32
  this.impressions.clear();
30
33
  this.impressionCounts && this.impressionCounts.clear();
31
34
  this.events.clear();
35
+ this.uniqueKeys?.clear();
32
36
  },
33
37
 
34
38
  // When using shared instanciation with MEMORY we reuse everything but segments (they are unique per key)
@@ -0,0 +1,83 @@
1
+ import { IUniqueKeysCacheBase } from '../types';
2
+ import { ISet, setToArray, _Set } from '../../utils/lang/sets';
3
+ import { UniqueKeysPayloadSs } from '../../sync/submitters/types';
4
+
5
+ const DEFAULT_CACHE_SIZE = 30000;
6
+
7
+ export class UniqueKeysCacheInMemory implements IUniqueKeysCacheBase {
8
+
9
+ private onFullQueue?: () => void;
10
+ private readonly maxStorage: number;
11
+ private uniqueTrackerSize = 0;
12
+ private uniqueKeysTracker: { [keys: string]: ISet<string> };
13
+
14
+ constructor(uniqueKeysQueueSize: number = DEFAULT_CACHE_SIZE) {
15
+ this.maxStorage = uniqueKeysQueueSize;
16
+ this.uniqueKeysTracker = {};
17
+ }
18
+
19
+ setOnFullQueueCb(cb: () => void) {
20
+ this.onFullQueue = cb;
21
+ }
22
+
23
+ /**
24
+ * Store unique keys in sequential order
25
+ * key: string = feature name.
26
+ * value: Set<string> = set of unique keys.
27
+ */
28
+ track(key: string, featureName: string) {
29
+ if (!this.uniqueKeysTracker[featureName]) this.uniqueKeysTracker[featureName] = new _Set();
30
+ const tracker = this.uniqueKeysTracker[featureName];
31
+ if (!tracker.has(key)) {
32
+ tracker.add(key);
33
+ this.uniqueTrackerSize++;
34
+ }
35
+ if (this.uniqueTrackerSize >= this.maxStorage && this.onFullQueue) {
36
+ this.uniqueTrackerSize = 0;
37
+ this.onFullQueue();
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Clear the data stored on the cache.
43
+ */
44
+ clear() {
45
+ this.uniqueKeysTracker = {};
46
+ }
47
+
48
+ /**
49
+ * Pop the collected data, used as payload for posting.
50
+ */
51
+ pop() {
52
+ const data = this.uniqueKeysTracker;
53
+ this.uniqueKeysTracker = {};
54
+ return this.fromUniqueKeysCollector(data);
55
+ }
56
+
57
+ /**
58
+ * Check if the cache is empty.
59
+ */
60
+ isEmpty() {
61
+ return Object.keys(this.uniqueKeysTracker).length === 0;
62
+ }
63
+
64
+ /**
65
+ * Converts `uniqueKeys` data from cache into request payload for SS.
66
+ */
67
+ private fromUniqueKeysCollector(uniqueKeys: { [featureName: string]: ISet<string> }): UniqueKeysPayloadSs {
68
+ const payload = [];
69
+ const featureNames = Object.keys(uniqueKeys);
70
+ for (let i = 0; i < featureNames.length; i++) {
71
+ const featureName = featureNames[i];
72
+ const featureKeys = setToArray(uniqueKeys[featureName]);
73
+ const uniqueKeysPayload = {
74
+ f: featureName,
75
+ ks: featureKeys
76
+ };
77
+
78
+ payload.push(uniqueKeysPayload);
79
+ }
80
+ return { keys: payload };
81
+ }
82
+
83
+ }
@@ -0,0 +1,89 @@
1
+ import { IUniqueKeysCacheBase } from '../types';
2
+ import { ISet, setToArray, _Set } from '../../utils/lang/sets';
3
+ import { UniqueKeysPayloadCs } from '../../sync/submitters/types';
4
+
5
+ const DEFAULT_CACHE_SIZE = 30000;
6
+
7
+ export class UniqueKeysCacheInMemoryCS implements IUniqueKeysCacheBase {
8
+
9
+ private onFullQueue?: () => void;
10
+ private readonly maxStorage: number;
11
+ private uniqueTrackerSize = 0;
12
+ private uniqueKeysTracker: { [keys: string]: ISet<string> };
13
+
14
+ /**
15
+ *
16
+ * @param impressionsQueueSize number of queued impressions to call onFullQueueCb.
17
+ * Default value is 0, that means no maximum value, in case we want to avoid this being triggered.
18
+ */
19
+ constructor(uniqueKeysQueueSize: number = DEFAULT_CACHE_SIZE) {
20
+ this.maxStorage = uniqueKeysQueueSize;
21
+ this.uniqueKeysTracker = {};
22
+ }
23
+
24
+ setOnFullQueueCb(cb: () => void) {
25
+ this.onFullQueue = cb;
26
+ }
27
+
28
+ /**
29
+ * Store unique keys in sequential order
30
+ * key: string = key.
31
+ * value: HashSet<string> = set of split names.
32
+ */
33
+ track(key: string, featureName: string) {
34
+
35
+ if (!this.uniqueKeysTracker[key]) this.uniqueKeysTracker[key] = new _Set();
36
+ const tracker = this.uniqueKeysTracker[key];
37
+ if (!tracker.has(featureName)) {
38
+ tracker.add(featureName);
39
+ this.uniqueTrackerSize++;
40
+ }
41
+ if (this.uniqueTrackerSize >= this.maxStorage && this.onFullQueue) {
42
+ this.uniqueTrackerSize = 0;
43
+ this.onFullQueue();
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Clear the data stored on the cache.
49
+ */
50
+ clear() {
51
+ this.uniqueKeysTracker = {};
52
+ }
53
+
54
+ /**
55
+ * Pop the collected data, used as payload for posting.
56
+ */
57
+ pop() {
58
+ const data = this.uniqueKeysTracker;
59
+ this.uniqueKeysTracker = {};
60
+ return this.fromUniqueKeysCollector(data);
61
+ }
62
+
63
+ /**
64
+ * Check if the cache is empty.
65
+ */
66
+ isEmpty() {
67
+ return Object.keys(this.uniqueKeysTracker).length === 0;
68
+ }
69
+
70
+ /**
71
+ * Converts `uniqueKeys` data from cache into request payload.
72
+ */
73
+ private fromUniqueKeysCollector(uniqueKeys: { [featureName: string]: ISet<string> }): UniqueKeysPayloadCs {
74
+ const payload = [];
75
+ const featureKeys = Object.keys(uniqueKeys);
76
+ for (let k = 0; k < featureKeys.length; k++) {
77
+ const featureKey = featureKeys[k];
78
+ const featureNames = setToArray(uniqueKeys[featureKey]);
79
+ const uniqueKeysPayload = {
80
+ k: featureKey,
81
+ fs: featureNames
82
+ };
83
+
84
+ payload.push(uniqueKeysPayload);
85
+ }
86
+ return { keys: payload };
87
+ }
88
+
89
+ }
@@ -1,6 +1,6 @@
1
1
  import { MaybeThenable, IMetadata, ISplitFiltersValidation } from '../dtos/types';
2
2
  import { ILogger } from '../logger/types';
3
- import { EventDataType, HttpErrors, HttpLatencies, ImpressionDataType, LastSync, Method, MethodExceptions, MethodLatencies, OperationType, StoredEventWithMetadata, StoredImpressionWithMetadata, StreamingEvent } from '../sync/submitters/types';
3
+ import { EventDataType, HttpErrors, HttpLatencies, ImpressionDataType, LastSync, Method, MethodExceptions, MethodLatencies, OperationType, StoredEventWithMetadata, StoredImpressionWithMetadata, StreamingEvent, UniqueKeysPayloadCs, UniqueKeysPayloadSs } from '../sync/submitters/types';
4
4
  import { SplitIO, ImpressionDTO, SDKMode } from '../types';
5
5
 
6
6
  /**
@@ -355,6 +355,17 @@ export interface IImpressionCountsCacheSync extends IRecorderCacheProducerSync<R
355
355
  pop(toMerge?: Record<string, number> ): Record<string, number> // pop cache data
356
356
  }
357
357
 
358
+ export interface IUniqueKeysCacheBase {
359
+ // Used by unique Keys tracker
360
+ track(key: string, value: string): void
361
+
362
+ // Used by unique keys submitter in standalone and producer mode
363
+ isEmpty(): boolean // check if cache is empty. Return true if the cache was just created or cleared.
364
+ pop(): UniqueKeysPayloadSs | UniqueKeysPayloadCs // pop cache data
365
+ /* Registers callback for full queue */
366
+ setOnFullQueueCb(cb: () => void): void,
367
+ clear(): void
368
+ }
358
369
 
359
370
  /**
360
371
  * Telemetry storage interface for standalone and partial consumer modes.
@@ -445,14 +456,16 @@ export interface IStorageBase<
445
456
  TSegmentsCache extends ISegmentsCacheBase,
446
457
  TImpressionsCache extends IImpressionsCacheBase,
447
458
  TEventsCache extends IEventsCacheBase,
448
- TTelemetryCache extends ITelemetryCacheSync | ITelemetryCacheAsync
459
+ TTelemetryCache extends ITelemetryCacheSync | ITelemetryCacheAsync,
460
+ TUniqueKeysCache extends IUniqueKeysCacheBase
449
461
  > {
450
462
  splits: TSplitsCache,
451
463
  segments: TSegmentsCache,
452
464
  impressions: TImpressionsCache,
453
465
  impressionCounts?: IImpressionCountsCacheSync,
454
466
  events: TEventsCache,
455
- telemetry?: TTelemetryCache
467
+ telemetry?: TTelemetryCache,
468
+ uniqueKeys?: TUniqueKeysCache,
456
469
  destroy(): void | Promise<void>,
457
470
  shared?: (matchingKey: string, onReadyCb: (error?: any) => void) => this
458
471
  }
@@ -462,7 +475,8 @@ export interface IStorageSync extends IStorageBase<
462
475
  ISegmentsCacheSync,
463
476
  IImpressionsCacheSync,
464
477
  IEventsCacheSync,
465
- ITelemetryCacheSync
478
+ ITelemetryCacheSync,
479
+ IUniqueKeysCacheBase
466
480
  > { }
467
481
 
468
482
  export interface IStorageAsync extends IStorageBase<
@@ -470,7 +484,8 @@ export interface IStorageAsync extends IStorageBase<
470
484
  ISegmentsCacheAsync,
471
485
  IImpressionsCacheAsync | IImpressionsCacheSync,
472
486
  IEventsCacheAsync | IEventsCacheSync,
473
- ITelemetryCacheAsync
487
+ ITelemetryCacheAsync,
488
+ IUniqueKeysCacheBase
474
489
  > { }
475
490
 
476
491
  /** StorageFactory */
@@ -480,10 +495,11 @@ export type DataLoader = (storage: IStorageSync, matchingKey: string) => void
480
495
  export interface IStorageFactoryParams {
481
496
  log: ILogger,
482
497
  impressionsQueueSize?: number,
498
+ uniqueKeysCacheSize?: number;
483
499
  eventsQueueSize?: number,
484
500
  optimize?: boolean /* whether create the `impressionCounts` cache (OPTIMIZED impression mode) or not (DEBUG impression mode) */,
485
501
  mode: SDKMode,
486
-
502
+ impressionsMode?: string,
487
503
  // ATM, only used by InLocalStorage
488
504
  matchingKey?: string, /* undefined on server-side SDKs */
489
505
  splitFiltersValidation?: ISplitFiltersValidation,
@@ -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
@@ -1,7 +1,7 @@
1
1
  import { ISegmentsCacheSync, ISplitsCacheSync, ITelemetryCacheSync } from '../../storages/types';
2
2
  import { submitterFactory, firstPushWindowDecorator } from './submitter';
3
3
  import { TelemetryUsageStatsPayload, TelemetryConfigStatsPayload, TelemetryConfigStats } from './types';
4
- import { QUEUED, DEDUPED, DROPPED, CONSUMER_MODE, CONSUMER_ENUM, STANDALONE_MODE, CONSUMER_PARTIAL_MODE, STANDALONE_ENUM, CONSUMER_PARTIAL_ENUM, OPTIMIZED, DEBUG, DEBUG_ENUM, OPTIMIZED_ENUM, CONSENT_GRANTED, CONSENT_DECLINED, CONSENT_UNKNOWN } from '../../utils/constants';
4
+ import { QUEUED, DEDUPED, DROPPED, CONSUMER_MODE, CONSUMER_ENUM, STANDALONE_MODE, CONSUMER_PARTIAL_MODE, STANDALONE_ENUM, CONSUMER_PARTIAL_ENUM, OPTIMIZED, DEBUG, NONE, DEBUG_ENUM, OPTIMIZED_ENUM, NONE_ENUM, CONSENT_GRANTED, CONSENT_DECLINED, CONSENT_UNKNOWN } from '../../utils/constants';
5
5
  import { SDK_READY, SDK_READY_FROM_CACHE } from '../../readiness/constants';
6
6
  import { ConsentStatus, ISettings, SDKMode } from '../../types';
7
7
  import { base } from '../../utils/settingsValidation';
@@ -52,8 +52,9 @@ const OPERATION_MODE_MAP = {
52
52
 
53
53
  const IMPRESSIONS_MODE_MAP = {
54
54
  [OPTIMIZED]: OPTIMIZED_ENUM,
55
- [DEBUG]: DEBUG_ENUM
56
- } as Record<ISettings['sync']['impressionsMode'], (0 | 1)>;
55
+ [DEBUG]: DEBUG_ENUM,
56
+ [NONE]: NONE_ENUM
57
+ } as Record<ISettings['sync']['impressionsMode'], (0 | 1 | 2)>;
57
58
 
58
59
  const USER_CONSENT_MAP = {
59
60
  [CONSENT_UNKNOWN]: 1,
@@ -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