@splitsoftware/splitio-commons 1.6.2-rc.6 → 1.6.2-rc.7

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 (135) hide show
  1. package/CHANGES.txt +3 -0
  2. package/cjs/consent/sdkUserConsent.js +2 -2
  3. package/cjs/listeners/browser.js +2 -1
  4. package/cjs/logger/constants.js +2 -1
  5. package/cjs/sdkClient/sdkClient.js +3 -1
  6. package/cjs/sdkFactory/index.js +26 -5
  7. package/cjs/services/splitApi.js +20 -0
  8. package/cjs/storages/KeyBuilderSS.js +6 -0
  9. package/cjs/storages/inLocalStorage/index.js +4 -0
  10. package/cjs/storages/inMemory/ImpressionCountsCacheInMemory.js +12 -1
  11. package/cjs/storages/inMemory/InMemoryStorage.js +5 -1
  12. package/cjs/storages/inMemory/InMemoryStorageCS.js +5 -1
  13. package/cjs/storages/inMemory/uniqueKeysCacheInMemory.js +73 -0
  14. package/cjs/storages/inMemory/uniqueKeysCacheInMemoryCS.js +78 -0
  15. package/cjs/storages/inRedis/ImpressionCountsCacheInRedis.js +49 -0
  16. package/cjs/storages/inRedis/constants.js +4 -1
  17. package/cjs/storages/inRedis/index.js +15 -1
  18. package/cjs/storages/inRedis/uniqueKeysCacheInRedis.js +56 -0
  19. package/cjs/sync/submitters/submitterManager.js +3 -0
  20. package/cjs/sync/submitters/telemetrySubmitter.js +1 -0
  21. package/cjs/sync/submitters/uniqueKeysSubmitter.js +26 -0
  22. package/cjs/trackers/impressionsTracker.js +22 -41
  23. package/cjs/trackers/strategy/strategyDebug.js +25 -0
  24. package/cjs/trackers/strategy/strategyNone.js +29 -0
  25. package/cjs/trackers/strategy/strategyOptimized.js +35 -0
  26. package/cjs/trackers/uniqueKeysTracker.js +38 -0
  27. package/cjs/utils/constants/index.js +4 -2
  28. package/cjs/utils/settingsValidation/impressionsMode.js +2 -2
  29. package/cjs/utils/settingsValidation/index.js +4 -0
  30. package/esm/consent/sdkUserConsent.js +2 -2
  31. package/esm/listeners/browser.js +3 -2
  32. package/esm/logger/constants.js +1 -0
  33. package/esm/sdkClient/sdkClient.js +3 -1
  34. package/esm/sdkFactory/index.js +26 -5
  35. package/esm/services/splitApi.js +20 -0
  36. package/esm/storages/KeyBuilderSS.js +6 -0
  37. package/esm/storages/inLocalStorage/index.js +5 -1
  38. package/esm/storages/inMemory/ImpressionCountsCacheInMemory.js +12 -1
  39. package/esm/storages/inMemory/InMemoryStorage.js +6 -2
  40. package/esm/storages/inMemory/InMemoryStorageCS.js +6 -2
  41. package/esm/storages/inMemory/uniqueKeysCacheInMemory.js +70 -0
  42. package/esm/storages/inMemory/uniqueKeysCacheInMemoryCS.js +75 -0
  43. package/esm/storages/inRedis/ImpressionCountsCacheInRedis.js +46 -0
  44. package/esm/storages/inRedis/constants.js +3 -0
  45. package/esm/storages/inRedis/index.js +16 -2
  46. package/esm/storages/inRedis/uniqueKeysCacheInRedis.js +53 -0
  47. package/esm/sync/submitters/submitterManager.js +3 -0
  48. package/esm/sync/submitters/telemetrySubmitter.js +2 -1
  49. package/esm/sync/submitters/uniqueKeysSubmitter.js +22 -0
  50. package/esm/trackers/impressionsTracker.js +22 -41
  51. package/esm/trackers/strategy/strategyDebug.js +21 -0
  52. package/esm/trackers/strategy/strategyNone.js +25 -0
  53. package/esm/trackers/strategy/strategyOptimized.js +31 -0
  54. package/esm/trackers/uniqueKeysTracker.js +34 -0
  55. package/esm/utils/constants/index.js +2 -0
  56. package/esm/utils/settingsValidation/impressionsMode.js +3 -3
  57. package/esm/utils/settingsValidation/index.js +4 -0
  58. package/package.json +1 -1
  59. package/src/consent/sdkUserConsent.ts +2 -2
  60. package/src/listeners/browser.ts +3 -2
  61. package/src/logger/constants.ts +1 -0
  62. package/src/sdkClient/sdkClient.ts +3 -1
  63. package/src/sdkFactory/index.ts +29 -5
  64. package/src/sdkFactory/types.ts +7 -4
  65. package/src/services/splitApi.ts +22 -0
  66. package/src/services/types.ts +6 -0
  67. package/src/storages/AbstractSplitsCacheAsync.ts +0 -1
  68. package/src/storages/KeyBuilderSS.ts +8 -0
  69. package/src/storages/inLocalStorage/index.ts +4 -1
  70. package/src/storages/inMemory/ImpressionCountsCacheInMemory.ts +16 -1
  71. package/src/storages/inMemory/InMemoryStorage.ts +5 -2
  72. package/src/storages/inMemory/InMemoryStorageCS.ts +6 -2
  73. package/src/storages/inMemory/uniqueKeysCacheInMemory.ts +82 -0
  74. package/src/storages/inMemory/uniqueKeysCacheInMemoryCS.ts +88 -0
  75. package/src/storages/inRedis/ImpressionCountsCacheInRedis.ts +51 -0
  76. package/src/storages/inRedis/constants.ts +3 -0
  77. package/src/storages/inRedis/index.ts +12 -3
  78. package/src/storages/inRedis/uniqueKeysCacheInRedis.ts +63 -0
  79. package/src/storages/types.ts +30 -8
  80. package/src/sync/submitters/submitterManager.ts +2 -0
  81. package/src/sync/submitters/telemetrySubmitter.ts +4 -3
  82. package/src/sync/submitters/types.ts +20 -1
  83. package/src/sync/submitters/uniqueKeysSubmitter.ts +35 -0
  84. package/src/trackers/impressionsTracker.ts +27 -48
  85. package/src/trackers/strategy/strategyDebug.ts +28 -0
  86. package/src/trackers/strategy/strategyNone.ts +34 -0
  87. package/src/trackers/strategy/strategyOptimized.ts +42 -0
  88. package/src/trackers/types.ts +28 -0
  89. package/src/trackers/uniqueKeysTracker.ts +48 -0
  90. package/src/types.ts +5 -1
  91. package/src/utils/constants/index.ts +2 -0
  92. package/src/utils/settingsValidation/impressionsMode.ts +3 -3
  93. package/src/utils/settingsValidation/index.ts +5 -0
  94. package/types/logger/constants.d.ts +1 -0
  95. package/types/sdkClient/types.d.ts +18 -0
  96. package/types/sdkFactory/types.d.ts +4 -2
  97. package/types/services/types.d.ts +4 -0
  98. package/types/storages/KeyBuilderSS.d.ts +2 -0
  99. package/types/storages/inMemory/CountsCacheInMemory.d.ts +20 -0
  100. package/types/storages/inMemory/ImpressionCountsCacheInMemory.d.ts +5 -1
  101. package/types/storages/inMemory/LatenciesCacheInMemory.d.ts +20 -0
  102. package/types/storages/inMemory/uniqueKeysCacheInMemory.d.ts +35 -0
  103. package/types/storages/inMemory/uniqueKeysCacheInMemoryCS.d.ts +37 -0
  104. package/types/storages/inRedis/CountsCacheInRedis.d.ts +9 -0
  105. package/types/storages/inRedis/ImpressionCountsCacheInRedis.d.ts +14 -0
  106. package/types/storages/inRedis/LatenciesCacheInRedis.d.ts +9 -0
  107. package/types/storages/inRedis/constants.d.ts +3 -0
  108. package/types/storages/inRedis/uniqueKeysCacheInRedis.d.ts +15 -0
  109. package/types/storages/types.d.ts +18 -5
  110. package/types/sync/offline/LocalhostFromFile.d.ts +2 -0
  111. package/types/sync/offline/splitsParser/splitsParserFromFile.d.ts +2 -0
  112. package/types/sync/submitters/eventsSyncTask.d.ts +8 -0
  113. package/types/sync/submitters/impressionCountsSubmitterInRedis.d.ts +5 -0
  114. package/types/sync/submitters/impressionCountsSyncTask.d.ts +13 -0
  115. package/types/sync/submitters/impressionsSyncTask.d.ts +14 -0
  116. package/types/sync/submitters/metricsSyncTask.d.ts +12 -0
  117. package/types/sync/submitters/submitterSyncTask.d.ts +10 -0
  118. package/types/sync/submitters/types.d.ts +18 -1
  119. package/types/sync/submitters/uniqueKeysSubmitter.d.ts +5 -0
  120. package/types/sync/submitters/uniqueKeysSubmitterInRedis.d.ts +5 -0
  121. package/types/sync/syncTaskComposite.d.ts +5 -0
  122. package/types/trackers/filter/bloomFilter.d.ts +10 -0
  123. package/types/trackers/filter/dictionaryFilter.d.ts +8 -0
  124. package/types/trackers/filter/types.d.ts +5 -0
  125. package/types/trackers/impressionsTracker.d.ts +4 -6
  126. package/types/trackers/strategy/strategyDebug.d.ts +9 -0
  127. package/types/trackers/strategy/strategyNone.d.ts +10 -0
  128. package/types/trackers/strategy/strategyOptimized.d.ts +11 -0
  129. package/types/trackers/types.d.ts +23 -0
  130. package/types/trackers/uniqueKeysTracker.d.ts +13 -0
  131. package/types/types.d.ts +5 -1
  132. package/types/utils/constants/index.d.ts +2 -0
  133. package/types/utils/settingsValidation/index.d.ts +1 -0
  134. package/types/utils/timeTracker/index.d.ts +70 -0
  135. package/src/logger/.DS_Store +0 -0
@@ -0,0 +1,63 @@
1
+ import { IUniqueKeysCacheBase } from '../types';
2
+ import { Redis } from 'ioredis';
3
+ import { UniqueKeysCacheInMemory } from '../inMemory/uniqueKeysCacheInMemory';
4
+ import { setToArray } from '../../utils/lang/sets';
5
+ import { DEFAULT_CACHE_SIZE, REFRESH_RATE, TTL_REFRESH } from './constants';
6
+ import { LOG_PREFIX } from './constants';
7
+ import { ILogger } from '../../logger/types';
8
+
9
+ export class UniqueKeysCacheInRedis extends UniqueKeysCacheInMemory implements IUniqueKeysCacheBase {
10
+
11
+ private readonly log: ILogger;
12
+ private readonly key: string;
13
+ private readonly redis: Redis;
14
+ private readonly refreshRate: number;
15
+ private intervalId: any;
16
+
17
+ constructor(log: ILogger, key: string, redis: Redis, uniqueKeysQueueSize: number = DEFAULT_CACHE_SIZE, refreshRate: number = REFRESH_RATE) {
18
+ super(uniqueKeysQueueSize);
19
+ this.log = log;
20
+ this.key = key;
21
+ this.redis = redis;
22
+ this.refreshRate = refreshRate;
23
+ this.onFullQueue = () => {this.postUniqueKeysInRedis();};
24
+ }
25
+
26
+ postUniqueKeysInRedis() {
27
+ const pipeline = this.redis.pipeline();
28
+
29
+ const featureNames = Object.keys(this.uniqueKeysTracker);
30
+ for (let i = 0; i < featureNames.length; i++) {
31
+ const featureName = featureNames[i];
32
+ const featureKeys = setToArray(this.uniqueKeysTracker[featureName]);
33
+ const uniqueKeysPayload = {
34
+ f: featureName,
35
+ ks: featureKeys
36
+ };
37
+
38
+ pipeline.rpush(this.key, JSON.stringify(uniqueKeysPayload));
39
+ }
40
+ this.clear();
41
+ return pipeline.exec()
42
+ .then(data => {
43
+ // If this is the creation of the key on Redis, set the expiration for it in 3600 seconds.
44
+ if (data.length && data.length === featureNames.length) {
45
+ return this.redis.expire(this.key, TTL_REFRESH);
46
+ }
47
+ })
48
+ .catch(err => {
49
+ this.log.error(`${LOG_PREFIX}Error in uniqueKeys pipeline: ${err}.`);
50
+ return false;
51
+ });
52
+ }
53
+
54
+
55
+ start() {
56
+ this.intervalId = setInterval(this.postUniqueKeysInRedis.bind(this), this.refreshRate);
57
+ }
58
+
59
+ stop() {
60
+ clearInterval(this.intervalId);
61
+ }
62
+
63
+ }
@@ -1,6 +1,6 @@
1
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, OperationType, StoredEventWithMetadata, StoredImpressionWithMetadata, StreamingEvent, UniqueKeysPayloadCs, UniqueKeysPayloadSs } from '../sync/submitters/types';
4
4
  import { SplitIO, ImpressionDTO, SDKMode } from '../types';
5
5
 
6
6
  /**
@@ -347,7 +347,7 @@ export interface IEventsCacheAsync extends IEventsCacheBase, IRecorderCacheProdu
347
347
  * Only in memory. Named `ImpressionsCounter` in spec.
348
348
  */
349
349
  export interface IImpressionCountsCacheSync extends IRecorderCacheProducerSync<Record<string, number>> {
350
- // Used by impressions tracker
350
+ // Used by impressions tracker
351
351
  track(featureName: string, timeFrame: number, amount: number): void
352
352
 
353
353
  // Used by impressions count submitter in standalone and producer mode
@@ -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.
@@ -444,15 +455,18 @@ export interface IStorageBase<
444
455
  TSplitsCache extends ISplitsCacheBase,
445
456
  TSegmentsCache extends ISegmentsCacheBase,
446
457
  TImpressionsCache extends IImpressionsCacheBase,
458
+ TImpressionsCountCache extends IImpressionCountsCacheSync,
447
459
  TEventsCache extends IEventsCacheBase,
448
- TTelemetryCache extends ITelemetryCacheSync | ITelemetryCacheAsync
460
+ TTelemetryCache extends ITelemetryCacheSync | ITelemetryCacheAsync,
461
+ TUniqueKeysCache extends IUniqueKeysCacheBase
449
462
  > {
450
463
  splits: TSplitsCache,
451
464
  segments: TSegmentsCache,
452
465
  impressions: TImpressionsCache,
453
- impressionCounts?: IImpressionCountsCacheSync,
466
+ impressionCounts?: TImpressionsCountCache,
454
467
  events: TEventsCache,
455
- telemetry?: TTelemetryCache
468
+ telemetry?: TTelemetryCache,
469
+ uniqueKeys?: TUniqueKeysCache,
456
470
  destroy(): void | Promise<void>,
457
471
  shared?: (matchingKey: string, onReadyCb: (error?: any) => void) => this
458
472
  }
@@ -461,16 +475,20 @@ export interface IStorageSync extends IStorageBase<
461
475
  ISplitsCacheSync,
462
476
  ISegmentsCacheSync,
463
477
  IImpressionsCacheSync,
478
+ IImpressionCountsCacheSync,
464
479
  IEventsCacheSync,
465
- ITelemetryCacheSync
480
+ ITelemetryCacheSync,
481
+ IUniqueKeysCacheBase
466
482
  > { }
467
483
 
468
484
  export interface IStorageAsync extends IStorageBase<
469
485
  ISplitsCacheAsync,
470
486
  ISegmentsCacheAsync,
471
487
  IImpressionsCacheAsync | IImpressionsCacheSync,
488
+ IImpressionCountsCacheSync,
472
489
  IEventsCacheAsync | IEventsCacheSync,
473
- ITelemetryCacheAsync
490
+ ITelemetryCacheAsync,
491
+ IUniqueKeysCacheBase
474
492
  > { }
475
493
 
476
494
  /** StorageFactory */
@@ -480,10 +498,14 @@ export type DataLoader = (storage: IStorageSync, matchingKey: string) => void
480
498
  export interface IStorageFactoryParams {
481
499
  log: ILogger,
482
500
  impressionsQueueSize?: number,
501
+ impressionCountsQueueSize?: number,
502
+ impressionCountsRefreshRate?: number,
503
+ uniqueKeysRefreshRate?: number,
504
+ uniqueKeysCacheSize?: number,
483
505
  eventsQueueSize?: number,
484
506
  optimize?: boolean /* whether create the `impressionCounts` cache (OPTIMIZED impression mode) or not (DEBUG impression mode) */,
485
507
  mode: SDKMode,
486
-
508
+ impressionsMode?: string,
487
509
  // ATM, only used by InLocalStorage
488
510
  matchingKey?: string, /* undefined on server-side SDKs */
489
511
  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
@@ -0,0 +1,35 @@
1
+ import { SUBMITTERS_PUSH_FULL_QUEUE } from '../../logger/constants';
2
+ import { ISdkFactoryContextSync } from '../../sdkFactory/types';
3
+ import { submitterFactory } from './submitter';
4
+
5
+ const DATA_NAME = 'uniqueKeys';
6
+
7
+ /**
8
+ * Submitter that periodically posts impression counts
9
+ */
10
+ export function uniqueKeysSubmitterFactory(params: ISdkFactoryContextSync) {
11
+
12
+ const {
13
+ settings: { log, scheduler: { uniqueKeysRefreshRate }, core: {key}},
14
+ splitApi: { postUniqueKeysBulkCs, postUniqueKeysBulkSs },
15
+ storage: { uniqueKeys }
16
+ } = params;
17
+
18
+ const isClientSide = key !== undefined;
19
+ const postUniqueKeysBulk = isClientSide ? postUniqueKeysBulkCs : postUniqueKeysBulkSs;
20
+
21
+ const syncTask = submitterFactory(log, postUniqueKeysBulk, uniqueKeys!, uniqueKeysRefreshRate, 'unique keys');
22
+
23
+ // register unique keys submitter to be executed when uniqueKeys cache is full
24
+ uniqueKeys!.setOnFullQueueCb(() => {
25
+ if (syncTask.isRunning()) {
26
+ log.info(SUBMITTERS_PUSH_FULL_QUEUE, [DATA_NAME]);
27
+ syncTask.execute();
28
+ }
29
+ // If submitter is stopped (e.g., user consent declined or unknown, or app state offline), we don't send the data.
30
+ // Data will be sent when submitter is resumed.
31
+ });
32
+
33
+ return syncTask;
34
+ }
35
+
@@ -1,10 +1,8 @@
1
1
  import { objectAssign } from '../utils/lang/objectAssign';
2
2
  import { thenable } from '../utils/promise/thenable';
3
- import { truncateTimeFrame } from '../utils/time';
4
- import { IImpressionCountsCacheSync, IImpressionsCacheBase, ITelemetryCacheSync, ITelemetryCacheAsync } from '../storages/types';
5
- import { IImpressionsHandler, IImpressionsTracker } from './types';
3
+ import { IImpressionsCacheBase, ITelemetryCacheSync, ITelemetryCacheAsync } from '../storages/types';
4
+ import { IImpressionsHandler, IImpressionsTracker, IStrategy } from './types';
6
5
  import { SplitIO, ImpressionDTO, ISettings } from '../types';
7
- import { IImpressionObserver } from './impressionObserver/types';
8
6
  import { IMPRESSIONS_TRACKER_SUCCESS, ERROR_IMPRESSIONS_TRACKER, ERROR_IMPRESSIONS_LISTENER } from '../logger/constants';
9
7
  import { CONSENT_DECLINED, DEDUPED, QUEUED } from '../utils/constants';
10
8
 
@@ -15,18 +13,14 @@ import { CONSENT_DECLINED, DEDUPED, QUEUED } from '../utils/constants';
15
13
  * @param metadata runtime metadata (ip, hostname and version)
16
14
  * @param impressionListener optional impression listener
17
15
  * @param integrationsManager optional integrations manager
18
- * @param observer optional impression observer. If provided, previous time (pt property) is included in impression instances
19
- * @param countsCache optional cache to save impressions count. If provided, impressions will be deduped (OPTIMIZED mode)
16
+ * @param strategy strategy for impressions tracking.
20
17
  */
21
18
  export function impressionsTrackerFactory(
22
19
  settings: ISettings,
23
20
  impressionsCache: IImpressionsCacheBase,
21
+ strategy: IStrategy,
24
22
  integrationsManager?: IImpressionsHandler,
25
- // if observer is provided, it implies `shouldAddPreviousTime` flag (i.e., if impressions previous time should be added or not)
26
- observer?: IImpressionObserver,
27
- // if countsCache is provided, it implies `isOptimized` flag (i.e., if impressions should be deduped or not)
28
- countsCache?: IImpressionCountsCacheSync,
29
- telemetryCache?: ITelemetryCacheSync | ITelemetryCacheAsync
23
+ telemetryCache?: ITelemetryCacheSync | ITelemetryCacheAsync,
30
24
  ): IImpressionsTracker {
31
25
 
32
26
  const { log, impressionListener, runtime: { ip, hostname }, version } = settings;
@@ -36,51 +30,36 @@ export function impressionsTrackerFactory(
36
30
  if (settings.userConsent === CONSENT_DECLINED) return;
37
31
 
38
32
  const impressionsCount = impressions.length;
33
+ const { impressionsToStore, impressionsToListener, deduped } = strategy.process(impressions);
34
+
35
+ const impressionsToListenerCount = impressionsToListener.length;
36
+
37
+ if ( impressionsToStore.length>0 ){
38
+ const res = impressionsCache.track(impressionsToStore);
39
39
 
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
-
60
- const res = impressionsCache.track(impressionsToStore);
61
-
62
- // If we're on an async storage, handle error and log it.
63
- if (thenable(res)) {
64
- res.then(() => {
65
- log.info(IMPRESSIONS_TRACKER_SUCCESS, [impressionsCount]);
66
- }).catch(err => {
67
- log.error(ERROR_IMPRESSIONS_TRACKER, [impressionsCount, err]);
68
- });
69
- } else {
70
- // Record when impressionsCache is sync only (standalone mode)
71
- // @TODO we are not dropping impressions on full queue yet, so DROPPED stats are not recorded
72
- if (telemetryCache) {
73
- (telemetryCache as ITelemetryCacheSync).recordImpressionStats(QUEUED, impressionsToStore.length);
74
- (telemetryCache as ITelemetryCacheSync).recordImpressionStats(DEDUPED, impressions.length - impressionsToStore.length);
40
+ // If we're on an async storage, handle error and log it.
41
+ if (thenable(res)) {
42
+ res.then(() => {
43
+ log.info(IMPRESSIONS_TRACKER_SUCCESS, [impressionsCount]);
44
+ }).catch(err => {
45
+ log.error(ERROR_IMPRESSIONS_TRACKER, [impressionsCount, err]);
46
+ });
47
+ } else {
48
+ // Record when impressionsCache is sync only (standalone mode)
49
+ // @TODO we are not dropping impressions on full queue yet, so DROPPED stats are not recorded
50
+ if (telemetryCache) {
51
+ (telemetryCache as ITelemetryCacheSync).recordImpressionStats(QUEUED, impressionsToStore.length);
52
+ (telemetryCache as ITelemetryCacheSync).recordImpressionStats(DEDUPED, deduped);
53
+ }
75
54
  }
76
55
  }
77
56
 
78
57
  // @TODO next block might be handled by the integration manager. In that case, the metadata object doesn't need to be passed in the constructor
79
58
  if (impressionListener || integrationsManager) {
80
- for (let i = 0; i < impressionsCount; i++) {
59
+ for (let i = 0; i < impressionsToListenerCount; i++) {
81
60
  const impressionData: SplitIO.ImpressionData = {
82
61
  // copy of impression, to avoid unexpected behaviour if modified by integrations or impressionListener
83
- impression: objectAssign({}, impressions[i]),
62
+ impression: objectAssign({}, impressionsToListener[i]),
84
63
  attributes,
85
64
  ip,
86
65
  hostname,
@@ -0,0 +1,28 @@
1
+ import { ImpressionDTO } from '../../types';
2
+ import { IImpressionObserver } from '../impressionObserver/types';
3
+ import { IStrategy } from '../types';
4
+
5
+ /**
6
+ * Debug strategy for impressions tracker. Wraps impressions to store and adds previousTime if it corresponds
7
+ *
8
+ * @param impressionsObserver impression observer. Previous time (pt property) is included in impression instances
9
+ * @returns IStrategyResult
10
+ */
11
+ export function strategyDebugFactory(
12
+ impressionsObserver: IImpressionObserver
13
+ ): IStrategy {
14
+
15
+ return {
16
+ process(impressions: ImpressionDTO[]) {
17
+ impressions.forEach((impression) => {
18
+ // Adds previous time if it is enabled
19
+ impression.pt = impressionsObserver.testAndSet(impression);
20
+ });
21
+ return {
22
+ impressionsToStore: impressions,
23
+ impressionsToListener: impressions,
24
+ deduped: 0
25
+ };
26
+ }
27
+ };
28
+ }
@@ -0,0 +1,34 @@
1
+ import { 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.keyName, impression.feature);
25
+ });
26
+
27
+ return {
28
+ impressionsToStore: [],
29
+ impressionsToListener: impressions,
30
+ deduped: 0
31
+ };
32
+ }
33
+ };
34
+ }
@@ -0,0 +1,42 @@
1
+ import { 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
+ if (impression.pt) impressionsCounter.track(impression.feature, now, 1);
29
+
30
+ // Checks if the impression should be added in queue to be sent
31
+ if (!impression.pt || impression.pt < truncateTimeFrame(now)) {
32
+ impressionsToStore.push(impression);
33
+ }
34
+ });
35
+ return {
36
+ impressionsToStore: impressionsToStore,
37
+ impressionsToListener: impressions,
38
+ deduped: impressions.length - impressionsToStore.length
39
+ };
40
+ }
41
+ };
42
+ }
@@ -46,3 +46,31 @@ export interface ITelemetryTracker {
46
46
  */
47
47
  addTag(tag: string): void
48
48
  }
49
+
50
+ export interface IFilterAdapter {
51
+ add(key: string, featureName: string): boolean;
52
+ contains(key: string, featureName: string): boolean;
53
+ clear(): void;
54
+ refreshRate?: number;
55
+ }
56
+
57
+ export interface IImpressionSenderAdapter {
58
+ recordUniqueKeys(data: Object): void;
59
+ recordImpressionCounts(data: Object): void
60
+ }
61
+
62
+ /** Unique keys tracker */
63
+ export interface IUniqueKeysTracker {
64
+ stop(): void;
65
+ track(key: string, featureName: string): void;
66
+ }
67
+
68
+ export interface IStrategyResult {
69
+ impressionsToStore: ImpressionDTO[],
70
+ impressionsToListener: ImpressionDTO[],
71
+ deduped: number
72
+ }
73
+
74
+ export interface IStrategy {
75
+ process(impressions: ImpressionDTO[]): IStrategyResult
76
+ }
@@ -0,0 +1,48 @@
1
+ import { LOG_PREFIX_UNIQUE_KEYS_TRACKER } from '../logger/constants';
2
+ import { ILogger } from '../logger/types';
3
+ import { IUniqueKeysCacheBase } from '../storages/types';
4
+ import { IFilterAdapter, IUniqueKeysTracker } from './types';
5
+
6
+ const noopFilterAdapter = {
7
+ add() {return true;},
8
+ contains() {return true;},
9
+ clear() {}
10
+ };
11
+
12
+ /**
13
+ * Trackes uniques keys
14
+ * Unique Keys Tracker will be in charge of checking if the MTK was already sent to the BE in the last period
15
+ * or schedule to be sent; if not it will be added in an internal cache and sent in the next post.
16
+ *
17
+ * @param log Logger instance
18
+ * @param uniqueKeysCache cache to save unique keys
19
+ * @param filterAdapter filter adapter
20
+ */
21
+ export function uniqueKeysTrackerFactory(
22
+ log: ILogger,
23
+ uniqueKeysCache: IUniqueKeysCacheBase,
24
+ filterAdapter: IFilterAdapter = noopFilterAdapter,
25
+ ): IUniqueKeysTracker {
26
+ let intervalId: any;
27
+
28
+ if (filterAdapter.refreshRate) {
29
+ intervalId = setInterval(filterAdapter.clear, filterAdapter.refreshRate);
30
+ }
31
+
32
+ return {
33
+
34
+ track(key: string, featureName: string): void {
35
+ if (!filterAdapter.add(key, featureName)) {
36
+ log.debug(`${LOG_PREFIX_UNIQUE_KEYS_TRACKER}The feature ${featureName} and key ${key} exist in the filter`);
37
+ return;
38
+ }
39
+ uniqueKeysCache.track(key, featureName);
40
+ },
41
+
42
+ stop(): void {
43
+ clearInterval(intervalId);
44
+ }
45
+
46
+ };
47
+
48
+ }
package/src/types.ts CHANGED
@@ -80,6 +80,10 @@ export interface ISettings {
80
80
  featuresRefreshRate: number,
81
81
  impressionsRefreshRate: number,
82
82
  impressionsQueueSize: number,
83
+ impressionCountsRefreshRate: number,
84
+ impressionCountsQueueSize: number,
85
+ uniqueKeysRefreshRate: number,
86
+ uniqueKeysCacheSize: number,
83
87
  /**
84
88
  * @deprecated
85
89
  */
@@ -718,7 +722,7 @@ export namespace SplitIO {
718
722
  * ImpressionsMode type
719
723
  * @typedef {string} ImpressionsMode
720
724
  */
721
- export type ImpressionsMode = 'OPTIMIZED' | 'DEBUG'
725
+ export type ImpressionsMode = 'OPTIMIZED' | 'DEBUG' | 'NONE'
722
726
  /**
723
727
  * Defines the format of Split data to preload on the factory storage (cache).
724
728
  */
@@ -19,6 +19,7 @@ export const SPLIT_EVENT = 'EVENT';
19
19
  // Impression collection modes
20
20
  export const DEBUG = 'DEBUG';
21
21
  export const OPTIMIZED = 'OPTIMIZED';
22
+ export const NONE = 'NONE';
22
23
 
23
24
  // SDK Modes
24
25
  export const LOCALHOST_MODE: SDKMode = 'localhost';
@@ -49,6 +50,7 @@ export const CONSUMER_PARTIAL_ENUM = 2;
49
50
 
50
51
  export const OPTIMIZED_ENUM = 0;
51
52
  export const DEBUG_ENUM = 1;
53
+ export const NONE_ENUM = 2;
52
54
 
53
55
  export const SPLITS = 'sp';
54
56
  export const IMPRESSIONS = 'im';
@@ -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,14 @@ 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);
136
+ scheduler.impressionCountsRefreshRate = fromSecondsToMillis(scheduler.impressionCountsRefreshRate);
133
137
  scheduler.telemetryRefreshRate = fromSecondsToMillis(validateMinValue('telemetryRefreshRate', scheduler.telemetryRefreshRate, 60));
134
138
 
135
139
  // Default impressionsRefreshRate for DEBUG mode is 60 secs
136
140
  if (get(config, 'scheduler.impressionsRefreshRate') === undefined && withDefaults.sync.impressionsMode === DEBUG) scheduler.impressionsRefreshRate = 60;
137
141
  scheduler.impressionsRefreshRate = fromSecondsToMillis(scheduler.impressionsRefreshRate);
142
+
138
143
 
139
144
  // Log deprecation for old telemetry param
140
145
  if (scheduler.metricsRefreshRate) log.warn('`metricsRefreshRate` will be deprecated soon. For configuring telemetry rates, update `telemetryRefreshRate` value in configs');
@@ -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: ";