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

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 (197) hide show
  1. package/CHANGES.txt +4 -0
  2. package/cjs/consent/sdkUserConsent.js +2 -2
  3. package/cjs/evaluator/index.js +15 -16
  4. package/cjs/integrations/ga/GaToSplit.js +8 -5
  5. package/cjs/sdkClient/client.js +19 -7
  6. package/cjs/sdkClient/sdkClient.js +3 -1
  7. package/cjs/sdkFactory/index.js +15 -6
  8. package/cjs/sdkManager/index.js +3 -11
  9. package/cjs/services/splitApi.js +6 -6
  10. package/cjs/storages/AbstractSplitsCacheAsync.js +8 -10
  11. package/cjs/storages/AbstractSplitsCacheSync.js +8 -10
  12. package/cjs/storages/KeyBuilderSS.js +54 -9
  13. package/cjs/storages/dataLoader.js +1 -1
  14. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +5 -7
  15. package/cjs/storages/inLocalStorage/index.js +5 -1
  16. package/cjs/storages/inMemory/ImpressionCountsCacheInMemory.js +12 -1
  17. package/cjs/storages/inMemory/InMemoryStorage.js +6 -2
  18. package/cjs/storages/inMemory/InMemoryStorageCS.js +6 -2
  19. package/cjs/storages/inMemory/SplitsCacheInMemory.js +7 -10
  20. package/cjs/storages/inMemory/TelemetryCacheInMemory.js +10 -5
  21. package/cjs/storages/inMemory/UniqueKeysCacheInMemory.js +72 -0
  22. package/cjs/storages/inMemory/UniqueKeysCacheInMemoryCS.js +76 -0
  23. package/cjs/storages/inRedis/EventsCacheInRedis.js +1 -1
  24. package/cjs/storages/inRedis/ImpressionCountsCacheInRedis.js +85 -0
  25. package/cjs/storages/inRedis/SplitsCacheInRedis.js +15 -9
  26. package/cjs/storages/inRedis/TelemetryCacheInRedis.js +100 -0
  27. package/cjs/storages/inRedis/UniqueKeysCacheInRedis.js +71 -0
  28. package/cjs/storages/inRedis/constants.js +4 -1
  29. package/cjs/storages/inRedis/index.js +17 -2
  30. package/cjs/storages/pluggable/ImpressionCountsCachePluggable.js +81 -0
  31. package/cjs/storages/pluggable/SplitsCachePluggable.js +14 -9
  32. package/cjs/storages/pluggable/TelemetryCachePluggable.js +126 -0
  33. package/cjs/storages/pluggable/UniqueKeysCachePluggable.js +61 -0
  34. package/cjs/storages/pluggable/index.js +46 -17
  35. package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +2 -3
  36. package/cjs/sync/polling/updaters/splitChangesUpdater.js +1 -1
  37. package/cjs/sync/submitters/telemetrySubmitter.js +8 -4
  38. package/cjs/sync/submitters/uniqueKeysSubmitter.js +16 -59
  39. package/cjs/trackers/impressionsTracker.js +17 -15
  40. package/cjs/trackers/strategy/strategyNone.js +1 -1
  41. package/cjs/trackers/strategy/strategyOptimized.js +2 -1
  42. package/cjs/trackers/telemetryTracker.js +6 -0
  43. package/cjs/trackers/uniqueKeysTracker.js +11 -42
  44. package/cjs/utils/constants/index.js +3 -2
  45. package/cjs/utils/lang/maps.js +15 -7
  46. package/cjs/utils/redis/RedisMock.js +31 -0
  47. package/cjs/utils/settingsValidation/index.js +0 -3
  48. package/esm/consent/sdkUserConsent.js +2 -2
  49. package/esm/evaluator/index.js +15 -16
  50. package/esm/integrations/ga/GaToSplit.js +8 -5
  51. package/esm/sdkClient/client.js +19 -7
  52. package/esm/sdkClient/sdkClient.js +3 -1
  53. package/esm/sdkFactory/index.js +16 -7
  54. package/esm/sdkManager/index.js +3 -11
  55. package/esm/services/splitApi.js +6 -6
  56. package/esm/storages/AbstractSplitsCacheAsync.js +8 -10
  57. package/esm/storages/AbstractSplitsCacheSync.js +8 -10
  58. package/esm/storages/KeyBuilderSS.js +50 -8
  59. package/esm/storages/dataLoader.js +1 -1
  60. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +5 -7
  61. package/esm/storages/inLocalStorage/index.js +6 -2
  62. package/esm/storages/inMemory/ImpressionCountsCacheInMemory.js +12 -1
  63. package/esm/storages/inMemory/InMemoryStorage.js +8 -4
  64. package/esm/storages/inMemory/InMemoryStorageCS.js +7 -3
  65. package/esm/storages/inMemory/SplitsCacheInMemory.js +7 -10
  66. package/esm/storages/inMemory/TelemetryCacheInMemory.js +9 -5
  67. package/esm/storages/inMemory/UniqueKeysCacheInMemory.js +68 -0
  68. package/esm/storages/inMemory/UniqueKeysCacheInMemoryCS.js +73 -0
  69. package/esm/storages/inRedis/EventsCacheInRedis.js +1 -1
  70. package/esm/storages/inRedis/ImpressionCountsCacheInRedis.js +82 -0
  71. package/esm/storages/inRedis/SplitsCacheInRedis.js +15 -9
  72. package/esm/storages/inRedis/TelemetryCacheInRedis.js +100 -0
  73. package/esm/storages/inRedis/UniqueKeysCacheInRedis.js +68 -0
  74. package/esm/storages/inRedis/constants.js +3 -0
  75. package/esm/storages/inRedis/index.js +18 -3
  76. package/esm/storages/pluggable/ImpressionCountsCachePluggable.js +78 -0
  77. package/esm/storages/pluggable/SplitsCachePluggable.js +14 -9
  78. package/esm/storages/pluggable/TelemetryCachePluggable.js +126 -0
  79. package/esm/storages/pluggable/UniqueKeysCachePluggable.js +58 -0
  80. package/esm/storages/pluggable/index.js +47 -18
  81. package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +2 -3
  82. package/esm/sync/polling/updaters/splitChangesUpdater.js +1 -1
  83. package/esm/sync/submitters/telemetrySubmitter.js +9 -5
  84. package/esm/sync/submitters/uniqueKeysSubmitter.js +15 -56
  85. package/esm/trackers/impressionsTracker.js +17 -15
  86. package/esm/trackers/strategy/strategyNone.js +1 -1
  87. package/esm/trackers/strategy/strategyOptimized.js +2 -1
  88. package/esm/trackers/telemetryTracker.js +6 -0
  89. package/esm/trackers/uniqueKeysTracker.js +11 -42
  90. package/esm/utils/constants/index.js +1 -0
  91. package/esm/utils/lang/maps.js +15 -7
  92. package/esm/utils/redis/RedisMock.js +28 -0
  93. package/esm/utils/settingsValidation/index.js +0 -3
  94. package/package.json +1 -2
  95. package/src/consent/sdkUserConsent.ts +2 -2
  96. package/src/evaluator/index.ts +14 -15
  97. package/src/integrations/ga/GaToSplit.ts +9 -5
  98. package/src/integrations/types.ts +2 -1
  99. package/src/logger/.DS_Store +0 -0
  100. package/src/sdkClient/client.ts +21 -8
  101. package/src/sdkClient/sdkClient.ts +3 -1
  102. package/src/sdkFactory/index.ts +17 -7
  103. package/src/sdkManager/index.ts +3 -12
  104. package/src/services/splitApi.ts +6 -6
  105. package/src/services/types.ts +2 -2
  106. package/src/storages/AbstractSplitsCacheAsync.ts +13 -15
  107. package/src/storages/AbstractSplitsCacheSync.ts +15 -17
  108. package/src/storages/KeyBuilderSS.ts +61 -9
  109. package/src/storages/dataLoader.ts +1 -1
  110. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +8 -11
  111. package/src/storages/inLocalStorage/index.ts +5 -2
  112. package/src/storages/inMemory/ImpressionCountsCacheInMemory.ts +16 -1
  113. package/src/storages/inMemory/InMemoryStorage.ts +7 -4
  114. package/src/storages/inMemory/InMemoryStorageCS.ts +6 -3
  115. package/src/storages/inMemory/SplitsCacheInMemory.ts +10 -14
  116. package/src/storages/inMemory/TelemetryCacheInMemory.ts +10 -6
  117. package/src/storages/inMemory/UniqueKeysCacheInMemory.ts +80 -0
  118. package/src/storages/inMemory/UniqueKeysCacheInMemoryCS.ts +86 -0
  119. package/src/storages/inRedis/EventsCacheInRedis.ts +1 -1
  120. package/src/storages/inRedis/ImpressionCountsCacheInRedis.ts +95 -0
  121. package/src/storages/inRedis/SplitsCacheInRedis.ts +21 -17
  122. package/src/storages/inRedis/TelemetryCacheInRedis.ts +122 -2
  123. package/src/storages/inRedis/UniqueKeysCacheInRedis.ts +77 -0
  124. package/src/storages/inRedis/constants.ts +3 -0
  125. package/src/storages/inRedis/index.ts +15 -5
  126. package/src/storages/pluggable/ImpressionCountsCachePluggable.ts +92 -0
  127. package/src/storages/pluggable/SplitsCachePluggable.ts +20 -17
  128. package/src/storages/pluggable/TelemetryCachePluggable.ts +147 -2
  129. package/src/storages/pluggable/UniqueKeysCachePluggable.ts +67 -0
  130. package/src/storages/pluggable/index.ts +51 -19
  131. package/src/storages/types.ts +38 -30
  132. package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +5 -6
  133. package/src/sync/polling/updaters/splitChangesUpdater.ts +2 -2
  134. package/src/sync/submitters/telemetrySubmitter.ts +15 -8
  135. package/src/sync/submitters/types.ts +26 -12
  136. package/src/sync/submitters/uniqueKeysSubmitter.ts +18 -61
  137. package/src/trackers/impressionsTracker.ts +16 -15
  138. package/src/trackers/strategy/strategyNone.ts +1 -1
  139. package/src/trackers/strategy/strategyOptimized.ts +1 -1
  140. package/src/trackers/telemetryTracker.ts +7 -2
  141. package/src/trackers/types.ts +9 -7
  142. package/src/trackers/uniqueKeysTracker.ts +15 -47
  143. package/src/types.ts +0 -1
  144. package/src/utils/constants/index.ts +1 -0
  145. package/src/utils/lang/maps.ts +20 -8
  146. package/src/utils/redis/RedisMock.ts +33 -0
  147. package/src/utils/settingsValidation/index.ts +1 -4
  148. package/types/integrations/types.d.ts +2 -1
  149. package/types/services/types.d.ts +2 -2
  150. package/types/storages/AbstractSplitsCacheAsync.d.ts +7 -6
  151. package/types/storages/AbstractSplitsCacheSync.d.ts +6 -6
  152. package/types/storages/KeyBuilderSS.d.ts +9 -2
  153. package/types/storages/inLocalStorage/SplitsCacheInLocal.d.ts +3 -4
  154. package/types/storages/inMemory/ImpressionCountsCacheInMemory.d.ts +5 -1
  155. package/types/storages/inMemory/SplitsCacheInMemory.d.ts +3 -2
  156. package/types/storages/inMemory/TelemetryCacheInMemory.d.ts +6 -3
  157. package/types/storages/inMemory/uniqueKeysCacheInMemory.d.ts +35 -0
  158. package/types/storages/inMemory/uniqueKeysCacheInMemoryCS.d.ts +35 -0
  159. package/types/storages/inRedis/EventsCacheInRedis.d.ts +1 -1
  160. package/types/storages/inRedis/ImpressionCountsCacheInRedis.d.ts +16 -0
  161. package/types/storages/inRedis/SplitsCacheInRedis.d.ts +6 -5
  162. package/types/storages/inRedis/TelemetryCacheInRedis.d.ts +16 -1
  163. package/types/storages/inRedis/constants.d.ts +3 -0
  164. package/types/storages/inRedis/uniqueKeysCacheInRedis.d.ts +21 -0
  165. package/types/storages/pluggable/ImpressionCountsCachePluggable.d.ts +16 -0
  166. package/types/storages/pluggable/SplitsCachePluggable.d.ts +6 -5
  167. package/types/storages/pluggable/TelemetryCachePluggable.d.ts +17 -1
  168. package/types/storages/pluggable/UniqueKeysCachePluggable.d.ts +20 -0
  169. package/types/storages/types.d.ts +35 -35
  170. package/types/sync/polling/updaters/splitChangesUpdater.d.ts +1 -1
  171. package/types/sync/submitters/telemetrySubmitter.d.ts +1 -1
  172. package/types/sync/submitters/types.d.ts +19 -12
  173. package/types/sync/submitters/uniqueKeysSubmitter.d.ts +0 -14
  174. package/types/trackers/types.d.ts +9 -11
  175. package/types/trackers/uniqueKeysTracker.d.ts +3 -3
  176. package/types/types.d.ts +0 -1
  177. package/types/utils/constants/index.d.ts +1 -0
  178. package/types/utils/lang/maps.d.ts +6 -2
  179. package/types/utils/redis/RedisMock.d.ts +4 -0
  180. package/types/utils/settingsValidation/index.d.ts +0 -1
  181. package/types/sdkClient/types.d.ts +0 -18
  182. package/types/storages/inMemory/CountsCacheInMemory.d.ts +0 -20
  183. package/types/storages/inMemory/LatenciesCacheInMemory.d.ts +0 -20
  184. package/types/storages/inRedis/CountsCacheInRedis.d.ts +0 -9
  185. package/types/storages/inRedis/LatenciesCacheInRedis.d.ts +0 -9
  186. package/types/sync/offline/LocalhostFromFile.d.ts +0 -2
  187. package/types/sync/offline/splitsParser/splitsParserFromFile.d.ts +0 -2
  188. package/types/sync/submitters/eventsSyncTask.d.ts +0 -8
  189. package/types/sync/submitters/impressionCountsSyncTask.d.ts +0 -13
  190. package/types/sync/submitters/impressionsSyncTask.d.ts +0 -14
  191. package/types/sync/submitters/metricsSyncTask.d.ts +0 -12
  192. package/types/sync/submitters/submitterSyncTask.d.ts +0 -10
  193. package/types/sync/syncTaskComposite.d.ts +0 -5
  194. package/types/trackers/filter/bloomFilter.d.ts +0 -10
  195. package/types/trackers/filter/dictionaryFilter.d.ts +0 -8
  196. package/types/trackers/filter/types.d.ts +0 -5
  197. package/types/utils/timeTracker/index.d.ts +0 -70
@@ -0,0 +1,67 @@
1
+ import { IPluggableStorageWrapper, IUniqueKeysCacheBase } from '../types';
2
+ import { UniqueKeysCacheInMemory } from '../inMemory/UniqueKeysCacheInMemory';
3
+ import { setToArray } from '../../utils/lang/sets';
4
+ import { DEFAULT_CACHE_SIZE, REFRESH_RATE } from '../inRedis/constants';
5
+ import { LOG_PREFIX } from './constants';
6
+ import { ILogger } from '../../logger/types';
7
+ import { UniqueKeysItemSs } from '../../sync/submitters/types';
8
+
9
+ export class UniqueKeysCachePluggable extends UniqueKeysCacheInMemory implements IUniqueKeysCacheBase {
10
+
11
+ private readonly log: ILogger;
12
+ private readonly key: string;
13
+ private readonly wrapper: IPluggableStorageWrapper;
14
+ private readonly refreshRate: number;
15
+ private intervalId: any;
16
+
17
+ constructor(log: ILogger, key: string, wrapper: IPluggableStorageWrapper, uniqueKeysQueueSize = DEFAULT_CACHE_SIZE, refreshRate = REFRESH_RATE) {
18
+ super(uniqueKeysQueueSize);
19
+ this.log = log;
20
+ this.key = key;
21
+ this.wrapper = wrapper;
22
+ this.refreshRate = refreshRate;
23
+ this.onFullQueue = () => { this.storeUniqueKeys(); };
24
+ }
25
+
26
+ storeUniqueKeys() {
27
+ const featureNames = Object.keys(this.uniqueKeysTracker);
28
+ if (!featureNames.length) return Promise.resolve(false);
29
+
30
+ const pipeline = featureNames.reduce<Promise<any>>((pipeline, featureName) => {
31
+ const featureKeys = setToArray(this.uniqueKeysTracker[featureName]);
32
+ const uniqueKeysPayload = {
33
+ f: featureName,
34
+ ks: featureKeys
35
+ };
36
+
37
+ return pipeline.then(() => this.wrapper.pushItems(this.key, [JSON.stringify(uniqueKeysPayload)]));
38
+ }, Promise.resolve());
39
+
40
+ this.clear();
41
+ return pipeline.catch(err => {
42
+ this.log.error(`${LOG_PREFIX}Error in uniqueKeys pipeline: ${err}.`);
43
+ return false;
44
+ });
45
+ }
46
+
47
+
48
+ start() {
49
+ this.intervalId = setInterval(this.storeUniqueKeys.bind(this), this.refreshRate);
50
+ }
51
+
52
+ stop() {
53
+ clearInterval(this.intervalId);
54
+ return this.storeUniqueKeys();
55
+ }
56
+
57
+ /**
58
+ * Async consumer API, used by synchronizer.
59
+ * @param count number of items to pop from the queue. If not provided or equal 0, all items will be popped.
60
+ */
61
+ popNRaw(count = 0): Promise<UniqueKeysItemSs[]> {
62
+ return Promise.resolve(count || this.wrapper.getItemsCount(this.key))
63
+ .then(count => this.wrapper.popItems(this.key, count))
64
+ .then((uniqueKeyItems) => uniqueKeyItems.map(uniqueKeyItem => JSON.parse(uniqueKeyItem)));
65
+ }
66
+
67
+ }
@@ -1,4 +1,4 @@
1
- import { IPluggableStorageWrapper, IStorageAsync, IStorageAsyncFactory, IStorageFactoryParams } from '../types';
1
+ import { IPluggableStorageWrapper, IStorageAsync, IStorageAsyncFactory, IStorageFactoryParams, ITelemetryCacheAsync } from '../types';
2
2
 
3
3
  import { KeyBuilderSS } from '../KeyBuilderSS';
4
4
  import { SplitsCachePluggable } from './SplitsCachePluggable';
@@ -8,10 +8,16 @@ import { EventsCachePluggable } from './EventsCachePluggable';
8
8
  import { wrapperAdapter, METHODS_TO_PROMISE_WRAP } from './wrapperAdapter';
9
9
  import { isObject } from '../../utils/lang';
10
10
  import { validatePrefix } from '../KeyBuilder';
11
- import { CONSUMER_PARTIAL_MODE, STORAGE_PLUGGABLE } from '../../utils/constants';
11
+ import { CONSUMER_PARTIAL_MODE, DEBUG, NONE, STORAGE_PLUGGABLE } from '../../utils/constants';
12
12
  import { ImpressionsCacheInMemory } from '../inMemory/ImpressionsCacheInMemory';
13
13
  import { EventsCacheInMemory } from '../inMemory/EventsCacheInMemory';
14
14
  import { ImpressionCountsCacheInMemory } from '../inMemory/ImpressionCountsCacheInMemory';
15
+ import { shouldRecordTelemetry, TelemetryCacheInMemory } from '../inMemory/TelemetryCacheInMemory';
16
+ import { TelemetryCachePluggable } from './TelemetryCachePluggable';
17
+ import { ImpressionCountsCachePluggable } from './ImpressionCountsCachePluggable';
18
+ import { UniqueKeysCachePluggable } from './UniqueKeysCachePluggable';
19
+ import { UniqueKeysCacheInMemory } from '../inMemory/UniqueKeysCacheInMemory';
20
+ import { UniqueKeysCacheInMemoryCS } from '../inMemory/UniqueKeysCacheInMemoryCS';
15
21
 
16
22
  const NO_VALID_WRAPPER = 'Expecting pluggable storage `wrapper` in options, but no valid wrapper instance was provided.';
17
23
  const NO_VALID_WRAPPER_INTERFACE = 'The provided wrapper instance doesn’t follow the expected interface. Check our docs.';
@@ -35,16 +41,6 @@ function validatePluggableStorageOptions(options: any) {
35
41
  if (missingMethods.length) throw new Error(`${NO_VALID_WRAPPER_INTERFACE} The following methods are missing or invalid: ${missingMethods}`);
36
42
  }
37
43
 
38
- // subscription to wrapper connect event in order to emit SDK_READY event
39
- function wrapperConnect(wrapper: IPluggableStorageWrapper, onReadyCb: (error?: any) => void) {
40
- wrapper.connect().then(() => {
41
- onReadyCb();
42
- // At the moment, we don't synchronize config with pluggable storage
43
- }).catch((e) => {
44
- onReadyCb(e || new Error('Error connecting wrapper'));
45
- });
46
- }
47
-
48
44
  // Async return type in `client.track` method on consumer partial mode
49
45
  // No need to promisify impressions cache
50
46
  function promisifyEventsTrack(events: any) {
@@ -64,31 +60,67 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
64
60
 
65
61
  const prefix = validatePrefix(options.prefix);
66
62
 
67
- function PluggableStorageFactory({ log, metadata, onReadyCb, mode, eventsQueueSize, impressionsQueueSize, optimize }: IStorageFactoryParams): IStorageAsync {
63
+ function PluggableStorageFactory(params: IStorageFactoryParams): IStorageAsync {
64
+ const { log, metadata, onReadyCb, mode, eventsQueueSize, impressionsQueueSize, impressionsMode, matchingKey } = params;
68
65
  const keys = new KeyBuilderSS(prefix, metadata);
69
66
  const wrapper = wrapperAdapter(log, options.wrapper);
67
+
68
+ const isSyncronizer = mode === undefined; // If mode is not defined, the synchronizer is running
70
69
  const isPartialConsumer = mode === CONSUMER_PARTIAL_MODE;
71
70
 
71
+ const telemetry = shouldRecordTelemetry(params) || isSyncronizer ?
72
+ isPartialConsumer ?
73
+ new TelemetryCacheInMemory() :
74
+ new TelemetryCachePluggable(log, keys, wrapper) :
75
+ undefined;
76
+
77
+ const impressionCountsCache = impressionsMode !== DEBUG || isSyncronizer ?
78
+ isPartialConsumer ?
79
+ new ImpressionCountsCacheInMemory() :
80
+ new ImpressionCountsCachePluggable(log, keys.buildImpressionsCountKey(), wrapper) :
81
+ undefined;
82
+
83
+ const uniqueKeysCache = impressionsMode === NONE || isSyncronizer ?
84
+ isPartialConsumer ?
85
+ matchingKey === undefined ? new UniqueKeysCacheInMemory() : new UniqueKeysCacheInMemoryCS() :
86
+ new UniqueKeysCachePluggable(log, keys.buildUniqueKeysKey(), wrapper) :
87
+ undefined;
88
+
72
89
  // Connects to wrapper and emits SDK_READY event on main client
73
- wrapperConnect(wrapper, onReadyCb);
90
+ const connectPromise = wrapper.connect().then(() => {
91
+ onReadyCb();
92
+
93
+ // Start periodic flush of async storages
94
+ if (impressionCountsCache && (impressionCountsCache as ImpressionCountsCachePluggable).start) (impressionCountsCache as ImpressionCountsCachePluggable).start();
95
+ if (uniqueKeysCache && (uniqueKeysCache as UniqueKeysCachePluggable).start) (uniqueKeysCache as UniqueKeysCachePluggable).start();
96
+ if (telemetry && (telemetry as ITelemetryCacheAsync).recordConfig && !isSyncronizer) (telemetry as ITelemetryCacheAsync).recordConfig();
97
+ }).catch((e) => {
98
+ e = e || new Error('Error connecting wrapper');
99
+ onReadyCb(e);
100
+ return e;
101
+ });
74
102
 
75
103
  return {
76
104
  splits: new SplitsCachePluggable(log, keys, wrapper),
77
105
  segments: new SegmentsCachePluggable(log, keys, wrapper),
78
106
  impressions: isPartialConsumer ? new ImpressionsCacheInMemory(impressionsQueueSize) : new ImpressionsCachePluggable(log, keys.buildImpressionsKey(), wrapper, metadata),
79
- impressionCounts: optimize ? new ImpressionCountsCacheInMemory() : undefined,
107
+ impressionCounts: impressionCountsCache,
80
108
  events: isPartialConsumer ? promisifyEventsTrack(new EventsCacheInMemory(eventsQueueSize)) : new EventsCachePluggable(log, keys.buildEventsKey(), wrapper, metadata),
81
- // @TODO Not using TelemetryCachePluggable yet because it's not supported by the Split Synchronizer, and needs to drop or queue operations while the wrapper is not ready
82
- // telemetry: isPartialConsumer ? new TelemetryCacheInMemory() : new TelemetryCachePluggable(log, keys, wrapper),
109
+ telemetry,
110
+ uniqueKeys: uniqueKeysCache,
83
111
 
84
112
  // Disconnect the underlying storage
85
113
  destroy() {
86
- return wrapper.disconnect();
114
+ return Promise.all([
115
+ impressionCountsCache && (impressionCountsCache as ImpressionCountsCachePluggable).stop && (impressionCountsCache as ImpressionCountsCachePluggable).stop(),
116
+ uniqueKeysCache && (uniqueKeysCache as UniqueKeysCachePluggable).stop && (uniqueKeysCache as UniqueKeysCachePluggable).stop(),
117
+ ]).then(() => wrapper.disconnect());
87
118
  },
88
119
 
89
120
  // emits SDK_READY event on shared clients and returns a reference to the storage
90
121
  shared(_, onReadyCb) {
91
- wrapperConnect(wrapper, onReadyCb);
122
+ connectPromise.then(onReadyCb);
123
+
92
124
  return {
93
125
  ...this,
94
126
  // no-op destroy, to disconnect the wrapper only when the main client is destroyed
@@ -1,8 +1,7 @@
1
- import { MaybeThenable, IMetadata, ISplitFiltersValidation } from '../dtos/types';
1
+ import { MaybeThenable, IMetadata, ISplitFiltersValidation, ISplit } 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, MultiMethodExceptions, MultiMethodLatencies, MultiConfigs, OperationType, StoredEventWithMetadata, StoredImpressionWithMetadata, StreamingEvent, UniqueKeysPayloadCs, UniqueKeysPayloadSs } from '../sync/submitters/types';
4
4
  import { SplitIO, ImpressionDTO, SDKMode } from '../types';
5
- import { ISet } from '../utils/lang/sets';
6
5
 
7
6
  /**
8
7
  * Interface of a pluggable storage wrapper.
@@ -71,23 +70,25 @@ export interface IPluggableStorageWrapper {
71
70
  /** Integer operations */
72
71
 
73
72
  /**
74
- * Increments in 1 the given `key` value or set it to 1 if the value doesn't exist.
73
+ * Increments the number stored at `key` by `increment` (or 1 if `increment` is not provided), or set it to `increment` (or 1) if the value doesn't exist.
75
74
  *
76
75
  * @function incr
77
76
  * @param {string} key Key to increment
77
+ * @param {number} increment Value to increment by
78
78
  * @returns {Promise<number>} A promise that resolves with the value of key after the increment. The promise rejects if the operation fails,
79
79
  * for example, if there is a connection error or the key contains a string that can not be represented as integer.
80
80
  */
81
- incr: (key: string) => Promise<number>
81
+ incr: (key: string, increment?: number) => Promise<number>
82
82
  /**
83
- * Decrements in 1 the given `key` value or set it to -1 if the value doesn't exist.
83
+ * Decrements the number stored at `key` by `decrement` (or 1 if `decrement` is not provided), or set it to minus `decrement` (or minus 1) if the value doesn't exist.
84
84
  *
85
85
  * @function decr
86
86
  * @param {string} key Key to decrement
87
+ * @param {number} decrement Value to decrement by
87
88
  * @returns {Promise<number>} A promise that resolves with the value of key after the decrement. The promise rejects if the operation fails,
88
89
  * for example, if there is a connection error or the key contains a string that can not be represented as integer.
89
90
  */
90
- decr: (key: string) => Promise<number>
91
+ decr: (key: string, decrement?: number) => Promise<number>
91
92
 
92
93
  /** Queue operations */
93
94
 
@@ -192,14 +193,14 @@ export interface IPluggableStorageWrapper {
192
193
  /** Splits cache */
193
194
 
194
195
  export interface ISplitsCacheBase {
195
- addSplits(entries: [string, string][]): MaybeThenable<boolean[] | void>,
196
+ addSplits(entries: [string, ISplit][]): MaybeThenable<boolean[] | void>,
196
197
  removeSplits(names: string[]): MaybeThenable<boolean[] | void>,
197
- getSplit(name: string): MaybeThenable<string | null>,
198
- getSplits(names: string[]): MaybeThenable<Record<string, string | null>>, // `fetchMany` in spec
198
+ getSplit(name: string): MaybeThenable<ISplit | null>,
199
+ getSplits(names: string[]): MaybeThenable<Record<string, ISplit | null>>, // `fetchMany` in spec
199
200
  setChangeNumber(changeNumber: number): MaybeThenable<boolean | void>,
200
201
  // should never reject or throw an exception. Instead return -1 by default, assuming no splits are present in the storage.
201
202
  getChangeNumber(): MaybeThenable<number>,
202
- getAll(): MaybeThenable<string[]>,
203
+ getAll(): MaybeThenable<ISplit[]>,
203
204
  getSplitNames(): MaybeThenable<string[]>,
204
205
  // should never reject or throw an exception. Instead return true by default, asssuming the TT might exist.
205
206
  trafficTypeExists(trafficType: string): MaybeThenable<boolean>,
@@ -212,13 +213,13 @@ export interface ISplitsCacheBase {
212
213
  }
213
214
 
214
215
  export interface ISplitsCacheSync extends ISplitsCacheBase {
215
- addSplits(entries: [string, string][]): boolean[],
216
+ addSplits(entries: [string, ISplit][]): boolean[],
216
217
  removeSplits(names: string[]): boolean[],
217
- getSplit(name: string): string | null,
218
- getSplits(names: string[]): Record<string, string | null>,
218
+ getSplit(name: string): ISplit | null,
219
+ getSplits(names: string[]): Record<string, ISplit | null>,
219
220
  setChangeNumber(changeNumber: number): boolean,
220
221
  getChangeNumber(): number,
221
- getAll(): string[],
222
+ getAll(): ISplit[],
222
223
  getSplitNames(): string[],
223
224
  trafficTypeExists(trafficType: string): boolean,
224
225
  usesSegments(): boolean,
@@ -228,13 +229,13 @@ export interface ISplitsCacheSync extends ISplitsCacheBase {
228
229
  }
229
230
 
230
231
  export interface ISplitsCacheAsync extends ISplitsCacheBase {
231
- addSplits(entries: [string, string][]): Promise<boolean[] | void>,
232
+ addSplits(entries: [string, ISplit][]): Promise<boolean[] | void>,
232
233
  removeSplits(names: string[]): Promise<boolean[] | void>,
233
- getSplit(name: string): Promise<string | null>,
234
- getSplits(names: string[]): Promise<Record<string, string | null>>,
234
+ getSplit(name: string): Promise<ISplit | null>,
235
+ getSplits(names: string[]): Promise<Record<string, ISplit | null>>,
235
236
  setChangeNumber(changeNumber: number): Promise<boolean | void>,
236
237
  getChangeNumber(): Promise<number>,
237
- getAll(): Promise<string[]>,
238
+ getAll(): Promise<ISplit[]>,
238
239
  getSplitNames(): Promise<string[]>,
239
240
  trafficTypeExists(trafficType: string): Promise<boolean>,
240
241
  usesSegments(): Promise<boolean>,
@@ -353,16 +354,19 @@ export interface IImpressionCountsCacheSync extends IRecorderCacheProducerSync<R
353
354
 
354
355
  // Used by impressions count submitter in standalone and producer mode
355
356
  isEmpty(): boolean // check if cache is empty. Return true if the cache was just created or cleared.
356
- pop(toMerge?: Record<string, number> ): Record<string, number> // pop cache data
357
+ pop(toMerge?: Record<string, number>): Record<string, number> // pop cache data
357
358
  }
358
359
 
359
- export interface IUniqueKeysCacheBase extends IRecorderCacheProducerSync<{ [featureName: string]: ISet<string> }>{
360
+ export interface IUniqueKeysCacheBase {
360
361
  // Used by unique Keys tracker
361
- track(featureName: string, timeFrame: number, amount: number): void
362
+ track(key: string, value: string): void
362
363
 
363
364
  // Used by unique keys submitter in standalone and producer mode
364
365
  isEmpty(): boolean // check if cache is empty. Return true if the cache was just created or cleared.
365
- pop(toMerge?: { [featureName: string]: ISet<string> } ): { [featureName: string]: ISet<string> } // pop cache data
366
+ pop(): UniqueKeysPayloadSs | UniqueKeysPayloadCs // pop cache data
367
+ /* Registers callback for full queue */
368
+ setOnFullQueueCb(cb: () => void): void,
369
+ clear(): void
366
370
  }
367
371
 
368
372
  /**
@@ -432,18 +436,19 @@ export interface ITelemetryCacheSync extends ITelemetryStorageConsumerSync, ITel
432
436
  */
433
437
 
434
438
  export interface ITelemetryEvaluationConsumerAsync {
435
- popExceptions(): Promise<MethodExceptions>;
436
- popLatencies(): Promise<MethodLatencies>;
439
+ popLatencies(): Promise<MultiMethodLatencies>;
440
+ popExceptions(): Promise<MultiMethodExceptions>;
441
+ popConfigs(): Promise<MultiConfigs>;
437
442
  }
438
443
 
439
444
  export interface ITelemetryEvaluationProducerAsync {
440
445
  recordLatency(method: Method, latencyMs: number): Promise<any>;
441
446
  recordException(method: Method): Promise<any>;
447
+ recordConfig(): Promise<any>;
442
448
  }
443
449
 
444
450
  // ATM it only implements the producer API, used by the SDK in consumer mode.
445
- // @TODO implement consumer API for JS Synchronizer.
446
- export interface ITelemetryCacheAsync extends ITelemetryEvaluationProducerAsync { }
451
+ export interface ITelemetryCacheAsync extends ITelemetryEvaluationProducerAsync, ITelemetryEvaluationConsumerAsync { }
447
452
 
448
453
  /**
449
454
  * Storages
@@ -453,6 +458,7 @@ export interface IStorageBase<
453
458
  TSplitsCache extends ISplitsCacheBase,
454
459
  TSegmentsCache extends ISegmentsCacheBase,
455
460
  TImpressionsCache extends IImpressionsCacheBase,
461
+ TImpressionsCountCache extends IImpressionCountsCacheSync,
456
462
  TEventsCache extends IEventsCacheBase,
457
463
  TTelemetryCache extends ITelemetryCacheSync | ITelemetryCacheAsync,
458
464
  TUniqueKeysCache extends IUniqueKeysCacheBase
@@ -460,7 +466,7 @@ export interface IStorageBase<
460
466
  splits: TSplitsCache,
461
467
  segments: TSegmentsCache,
462
468
  impressions: TImpressionsCache,
463
- impressionCounts?: IImpressionCountsCacheSync,
469
+ impressionCounts?: TImpressionsCountCache,
464
470
  events: TEventsCache,
465
471
  telemetry?: TTelemetryCache,
466
472
  uniqueKeys?: TUniqueKeysCache,
@@ -472,6 +478,7 @@ export interface IStorageSync extends IStorageBase<
472
478
  ISplitsCacheSync,
473
479
  ISegmentsCacheSync,
474
480
  IImpressionsCacheSync,
481
+ IImpressionCountsCacheSync,
475
482
  IEventsCacheSync,
476
483
  ITelemetryCacheSync,
477
484
  IUniqueKeysCacheBase
@@ -481,8 +488,9 @@ export interface IStorageAsync extends IStorageBase<
481
488
  ISplitsCacheAsync,
482
489
  ISegmentsCacheAsync,
483
490
  IImpressionsCacheAsync | IImpressionsCacheSync,
491
+ IImpressionCountsCacheSync,
484
492
  IEventsCacheAsync | IEventsCacheSync,
485
- ITelemetryCacheAsync,
493
+ ITelemetryCacheAsync | ITelemetryCacheSync,
486
494
  IUniqueKeysCacheBase
487
495
  > { }
488
496
 
@@ -496,7 +504,7 @@ export interface IStorageFactoryParams {
496
504
  eventsQueueSize?: number,
497
505
  optimize?: boolean /* whether create the `impressionCounts` cache (OPTIMIZED impression mode) or not (DEBUG impression mode) */,
498
506
  mode: SDKMode,
499
-
507
+ impressionsMode?: string,
500
508
  // ATM, only used by InLocalStorage
501
509
  matchingKey?: string, /* undefined on server-side SDKs */
502
510
  splitFiltersValidation?: ISplitFiltersValidation,
@@ -2,7 +2,7 @@ import { forOwn } from '../../../utils/lang';
2
2
  import { IReadinessManager } from '../../../readiness/types';
3
3
  import { ISplitsCacheSync } from '../../../storages/types';
4
4
  import { ISplitsParser } from '../splitsParser/types';
5
- import { ISplitPartial } from '../../../dtos/types';
5
+ import { ISplit, ISplitPartial } from '../../../dtos/types';
6
6
  import { syncTaskFactory } from '../../syncTask';
7
7
  import { ISyncTask } from '../../types';
8
8
  import { ISettings } from '../../../types';
@@ -24,7 +24,7 @@ export function fromObjectUpdaterFactory(
24
24
  let startingUp = true;
25
25
 
26
26
  return function objectUpdater() {
27
- const splits: [string, string][] = [];
27
+ const splits: [string, ISplit][] = [];
28
28
  let loadError = null;
29
29
  let splitsMock: false | Record<string, ISplitPartial> = {};
30
30
  try {
@@ -38,9 +38,8 @@ export function fromObjectUpdaterFactory(
38
38
  log.debug(SYNC_OFFLINE_DATA, [JSON.stringify(splitsMock)]);
39
39
 
40
40
  forOwn(splitsMock, function (val, name) {
41
- splits.push([
42
- name,
43
- JSON.stringify({
41
+ splits.push([ // @ts-ignore Split changeNumber and seed is undefined in localhost mode
42
+ name, {
44
43
  name,
45
44
  status: 'ACTIVE',
46
45
  killed: false,
@@ -49,7 +48,7 @@ export function fromObjectUpdaterFactory(
49
48
  conditions: val.conditions || [],
50
49
  configurations: val.configurations,
51
50
  trafficTypeName: val.trafficTypeName
52
- })
51
+ }
53
52
  ]);
54
53
  });
55
54
 
@@ -40,7 +40,7 @@ export function parseSegments({ conditions }: ISplit): ISet<string> {
40
40
  }
41
41
 
42
42
  interface ISplitMutations {
43
- added: [string, string][],
43
+ added: [string, ISplit][],
44
44
  removed: string[],
45
45
  segments: string[]
46
46
  }
@@ -54,7 +54,7 @@ export function computeSplitsMutation(entries: ISplit[]): ISplitMutations {
54
54
  const segments = new _Set<string>();
55
55
  const computed = entries.reduce((accum, split) => {
56
56
  if (split.status === 'ACTIVE') {
57
- accum.added.push([split.name, JSON.stringify(split)]);
57
+ accum.added.push([split.name, split]);
58
58
 
59
59
  parseSegments(split).forEach((segmentName: string) => {
60
60
  segments.add(segmentName);
@@ -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';
@@ -9,11 +9,12 @@ import { usedKeysMap } from '../../utils/inputValidation/apiKey';
9
9
  import { timer } from '../../utils/timeTracker/timer';
10
10
  import { ISdkFactoryContextSync } from '../../sdkFactory/types';
11
11
  import { objectAssign } from '../../utils/lang/objectAssign';
12
+ import { isStorageSync } from '../../trackers/impressionObserver/utils';
12
13
 
13
14
  /**
14
15
  * Converts data from telemetry cache into /metrics/usage request payload.
15
16
  */
16
- export function telemetryCacheStatsAdapter(telemetry: ITelemetryCacheSync, splits: ISplitsCacheSync, segments: ISegmentsCacheSync) {
17
+ export function telemetryCacheStatsAdapter(telemetry: ITelemetryCacheSync, splits?: ISplitsCacheSync, segments?: ISegmentsCacheSync) {
17
18
  return {
18
19
  isEmpty() { return false; }, // There is always data in telemetry cache
19
20
  clear() { }, // No-op
@@ -31,9 +32,9 @@ export function telemetryCacheStatsAdapter(telemetry: ITelemetryCacheSync, split
31
32
  iQ: telemetry.getImpressionStats(QUEUED),
32
33
  iDe: telemetry.getImpressionStats(DEDUPED),
33
34
  iDr: telemetry.getImpressionStats(DROPPED),
34
- spC: splits.getSplitNames().length,
35
- seC: segments.getRegisteredSegments().length,
36
- skC: segments.getKeysCount(),
35
+ spC: splits && splits.getSplitNames().length,
36
+ seC: segments && segments.getRegisteredSegments().length,
37
+ skC: segments && segments.getKeysCount(),
37
38
  sL: telemetry.getSessionLength(),
38
39
  eQ: telemetry.getEventStats(QUEUED),
39
40
  eD: telemetry.getEventStats(DROPPED),
@@ -52,8 +53,9 @@ const OPERATION_MODE_MAP = {
52
53
 
53
54
  const IMPRESSIONS_MODE_MAP = {
54
55
  [OPTIMIZED]: OPTIMIZED_ENUM,
55
- [DEBUG]: DEBUG_ENUM
56
- } as Record<ISettings['sync']['impressionsMode'], (0 | 1)>;
56
+ [DEBUG]: DEBUG_ENUM,
57
+ [NONE]: NONE_ENUM
58
+ } as Record<ISettings['sync']['impressionsMode'], (0 | 1 | 2)>;
57
59
 
58
60
  const USER_CONSENT_MAP = {
59
61
  [CONSENT_UNKNOWN]: 1,
@@ -136,7 +138,12 @@ export function telemetrySubmitterFactory(params: ISdkFactoryContextSync) {
136
138
  const startTime = timer(now);
137
139
 
138
140
  const submitter = firstPushWindowDecorator(
139
- submitterFactory(log, splitApi.postMetricsUsage, telemetryCacheStatsAdapter(telemetry, splits, segments), telemetryRefreshRate, 'telemetry stats', undefined, 0, true),
141
+ submitterFactory(
142
+ log, splitApi.postMetricsUsage,
143
+ // @TODO cannot provide splits and segments cache if they are async, because `submitterFactory` expects a sync storage source
144
+ isStorageSync(params.settings) ? telemetryCacheStatsAdapter(telemetry, splits, segments) : telemetryCacheStatsAdapter(telemetry),
145
+ telemetryRefreshRate, 'telemetry stats', undefined, 0, true
146
+ ),
140
147
  telemetryRefreshRate
141
148
  );
142
149
 
@@ -1,5 +1,7 @@
1
+ /* eslint-disable no-use-before-define */
1
2
  import { IMetadata } from '../../dtos/types';
2
3
  import { SplitIO } from '../../types';
4
+ import { IMap } from '../../utils/lang/maps';
3
5
  import { ISyncTask } from '../types';
4
6
 
5
7
  export type ImpressionsPayload = {
@@ -35,13 +37,15 @@ export type ImpressionCountsPayload = {
35
37
  }[]
36
38
  }
37
39
 
40
+ export type UniqueKeysItemSs = {
41
+ /** Split name */
42
+ f: string
43
+ /** keyNames */
44
+ ks: string[]
45
+ }
46
+
38
47
  export type UniqueKeysPayloadSs = {
39
- keys: {
40
- /** Split name */
41
- f: string
42
- /** keyNames */
43
- ks: string[]
44
- }[]
48
+ keys: UniqueKeysItemSs[]
45
49
  }
46
50
 
47
51
  export type UniqueKeysPayloadCs = {
@@ -82,6 +86,12 @@ export type StoredEventWithMetadata = {
82
86
  e: SplitIO.EventData
83
87
  }
84
88
 
89
+ export type MultiMethodLatencies = IMap<string, MethodLatencies>
90
+
91
+ export type MultiMethodExceptions = IMap<string, MethodExceptions>
92
+
93
+ export type MultiConfigs = IMap<string, TelemetryConfigStats>
94
+
85
95
  /**
86
96
  * Telemetry usage stats
87
97
  */
@@ -133,11 +143,15 @@ export type StreamingEvent = {
133
143
  t: number, // timestamp
134
144
  }
135
145
 
146
+ // 'telemetry.latencias' and 'telemetry.exceptions' Redis/Pluggable keys
147
+ export type TelemetryUsageStats = {
148
+ mL?: MethodLatencies, // clientMethodLatencies
149
+ mE?: MethodExceptions, // methodExceptions
150
+ }
151
+
136
152
  // 'metrics/usage' JSON request body
137
- export type TelemetryUsageStatsPayload = {
153
+ export type TelemetryUsageStatsPayload = TelemetryUsageStats & {
138
154
  lS: LastSync, // lastSynchronization
139
- mL: MethodLatencies, // clientMethodLatencies
140
- mE: MethodExceptions, // methodExceptions
141
155
  hE: HttpErrors, // httpErrors
142
156
  hL: HttpLatencies, // httpLatencies
143
157
  tR: number, // tokenRefreshes
@@ -145,9 +159,9 @@ export type TelemetryUsageStatsPayload = {
145
159
  iQ: number, // impressionsQueued
146
160
  iDe: number, // impressionsDeduped
147
161
  iDr: number, // impressionsDropped
148
- spC: number, // splitCount
149
- seC: number, // segmentCount
150
- skC: number, // segmentKeyCount
162
+ spC?: number, // splitCount
163
+ seC?: number, // segmentCount
164
+ skC?: number, // segmentKeyCount
151
165
  sL?: number, // sessionLengthMs
152
166
  eQ: number, // eventsQueued
153
167
  eD: number, // eventsDropped
@@ -1,63 +1,9 @@
1
+ import { SUBMITTERS_PUSH_FULL_QUEUE } from '../../logger/constants';
1
2
  import { ISdkFactoryContextSync } from '../../sdkFactory/types';
2
- import { ISet, setToArray } from '../../utils/lang/sets';
3
3
  import { submitterFactory } from './submitter';
4
- import { UniqueKeysPayloadCs, UniqueKeysPayloadSs } from './types';
5
4
 
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
- }
5
+ const DATA_NAME = 'unique keys';
6
+ const UNIQUE_KEYS_RATE = 900000; // 15 minutes
61
7
 
62
8
  /**
63
9
  * Submitter that periodically posts impression counts
@@ -65,15 +11,26 @@ export function fromUniqueKeysCollectorSs(uniqueKeys: { [featureName: string]: I
65
11
  export function uniqueKeysSubmitterFactory(params: ISdkFactoryContextSync) {
66
12
 
67
13
  const {
68
- settings: { log, scheduler: { uniqueKeysRefreshRate }, core: {key}},
14
+ settings: { log, core: { key } },
69
15
  splitApi: { postUniqueKeysBulkCs, postUniqueKeysBulkSs },
70
16
  storage: { uniqueKeys }
71
17
  } = params;
72
-
18
+
73
19
  const isClientSide = key !== undefined;
74
20
  const postUniqueKeysBulk = isClientSide ? postUniqueKeysBulkCs : postUniqueKeysBulkSs;
75
- const fromUniqueKeysCollector = isClientSide ? fromUniqueKeysCollectorCs : fromUniqueKeysCollectorSs;
76
21
 
77
- return submitterFactory(log, postUniqueKeysBulk, uniqueKeys!, uniqueKeysRefreshRate, 'unique keys', fromUniqueKeysCollector);
22
+ const syncTask = submitterFactory(log, postUniqueKeysBulk, uniqueKeys!, UNIQUE_KEYS_RATE, DATA_NAME);
23
+
24
+ // register unique keys submitter to be executed when uniqueKeys cache is full
25
+ uniqueKeys!.setOnFullQueueCb(() => {
26
+ if (syncTask.isRunning()) {
27
+ log.info(SUBMITTERS_PUSH_FULL_QUEUE, [DATA_NAME]);
28
+ syncTask.execute();
29
+ }
30
+ // If submitter is stopped (e.g., user consent declined or unknown, or app state offline), we don't send the data.
31
+ // Data will be sent when submitter is resumed.
32
+ });
33
+
34
+ return syncTask;
78
35
  }
79
36