@splitsoftware/splitio-commons 2.0.1 → 2.0.3-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGES.txt +8 -0
  2. package/cjs/evaluator/index.js +2 -0
  3. package/cjs/listeners/browser.js +4 -6
  4. package/cjs/readiness/readinessManager.js +16 -18
  5. package/cjs/sdkClient/client.js +13 -13
  6. package/cjs/sdkClient/sdkClient.js +1 -1
  7. package/cjs/sdkFactory/index.js +10 -14
  8. package/cjs/sdkManager/index.js +2 -1
  9. package/cjs/services/decorateHeaders.js +6 -1
  10. package/cjs/services/splitHttpClient.js +1 -1
  11. package/cjs/storages/inLocalStorage/index.js +6 -20
  12. package/cjs/storages/inMemory/InMemoryStorage.js +4 -12
  13. package/cjs/storages/inMemory/InMemoryStorageCS.js +6 -19
  14. package/cjs/storages/inRedis/index.js +9 -13
  15. package/cjs/storages/pluggable/index.js +15 -19
  16. package/cjs/sync/submitters/impressionCountsSubmitter.js +2 -4
  17. package/cjs/sync/submitters/submitterManager.js +3 -6
  18. package/cjs/trackers/impressionsTracker.js +17 -18
  19. package/cjs/trackers/strategy/strategyDebug.js +4 -11
  20. package/cjs/trackers/strategy/strategyNone.js +11 -16
  21. package/cjs/trackers/strategy/strategyOptimized.js +11 -21
  22. package/esm/evaluator/index.js +2 -0
  23. package/esm/listeners/browser.js +1 -3
  24. package/esm/readiness/readinessManager.js +16 -18
  25. package/esm/sdkClient/client.js +13 -13
  26. package/esm/sdkClient/sdkClient.js +1 -1
  27. package/esm/sdkFactory/index.js +11 -15
  28. package/esm/sdkManager/index.js +2 -1
  29. package/esm/services/decorateHeaders.js +4 -0
  30. package/esm/services/splitHttpClient.js +2 -2
  31. package/esm/storages/inLocalStorage/index.js +7 -21
  32. package/esm/storages/inMemory/InMemoryStorage.js +5 -13
  33. package/esm/storages/inMemory/InMemoryStorageCS.js +7 -20
  34. package/esm/storages/inRedis/index.js +10 -14
  35. package/esm/storages/pluggable/index.js +16 -20
  36. package/esm/sync/submitters/impressionCountsSubmitter.js +2 -4
  37. package/esm/sync/submitters/submitterManager.js +3 -6
  38. package/esm/trackers/impressionsTracker.js +17 -18
  39. package/esm/trackers/strategy/strategyDebug.js +4 -11
  40. package/esm/trackers/strategy/strategyNone.js +11 -16
  41. package/esm/trackers/strategy/strategyOptimized.js +11 -21
  42. package/package.json +1 -1
  43. package/src/dtos/types.ts +2 -1
  44. package/src/evaluator/index.ts +2 -0
  45. package/src/evaluator/types.ts +1 -1
  46. package/src/listeners/browser.ts +1 -3
  47. package/src/readiness/readinessManager.ts +15 -16
  48. package/src/sdkClient/client.ts +11 -11
  49. package/src/sdkClient/sdkClient.ts +1 -1
  50. package/src/sdkFactory/index.ts +12 -16
  51. package/src/sdkFactory/types.ts +1 -1
  52. package/src/sdkManager/index.ts +2 -1
  53. package/src/services/decorateHeaders.ts +5 -0
  54. package/src/services/splitHttpClient.ts +2 -2
  55. package/src/storages/inLocalStorage/index.ts +7 -20
  56. package/src/storages/inMemory/InMemoryStorage.ts +5 -13
  57. package/src/storages/inMemory/InMemoryStorageCS.ts +7 -20
  58. package/src/storages/inRedis/index.ts +10 -10
  59. package/src/storages/pluggable/index.ts +16 -20
  60. package/src/storages/types.ts +2 -2
  61. package/src/sync/submitters/impressionCountsSubmitter.ts +2 -4
  62. package/src/sync/submitters/submitterManager.ts +3 -4
  63. package/src/sync/submitters/uniqueKeysSubmitter.ts +2 -3
  64. package/src/trackers/impressionsTracker.ts +17 -18
  65. package/src/trackers/strategy/strategyDebug.ts +4 -11
  66. package/src/trackers/strategy/strategyNone.ts +11 -17
  67. package/src/trackers/strategy/strategyOptimized.ts +10 -20
  68. package/src/trackers/types.ts +2 -8
  69. package/types/splitio.d.ts +4 -0
@@ -4,7 +4,7 @@ import { SplitsCacheInRedis } from './SplitsCacheInRedis';
4
4
  import { SegmentsCacheInRedis } from './SegmentsCacheInRedis';
5
5
  import { ImpressionsCacheInRedis } from './ImpressionsCacheInRedis';
6
6
  import { EventsCacheInRedis } from './EventsCacheInRedis';
7
- import { DEBUG, NONE, STORAGE_REDIS } from '../../utils/constants';
7
+ import { STORAGE_REDIS } from '../../utils/constants';
8
8
  import { TelemetryCacheInRedis } from './TelemetryCacheInRedis';
9
9
  import { UniqueKeysCacheInRedis } from './UniqueKeysCacheInRedis';
10
10
  import { ImpressionCountsCacheInRedis } from './ImpressionCountsCacheInRedis';
@@ -20,20 +20,18 @@ export function InRedisStorage(options) {
20
20
  var RD = require('./RedisAdapter').RedisAdapter;
21
21
  var prefix = validatePrefix(options.prefix);
22
22
  function InRedisStorageFactory(params) {
23
- var onReadyCb = params.onReadyCb, settings = params.settings, _a = params.settings, log = _a.log, impressionsMode = _a.sync.impressionsMode;
23
+ var onReadyCb = params.onReadyCb, settings = params.settings, log = params.settings.log;
24
24
  var metadata = metadataBuilder(settings);
25
25
  var keys = new KeyBuilderSS(prefix, metadata);
26
26
  var redisClient = new RD(log, options.options || {});
27
27
  var telemetry = new TelemetryCacheInRedis(log, keys, redisClient);
28
- var impressionCountsCache = impressionsMode !== DEBUG ? new ImpressionCountsCacheInRedis(log, keys.buildImpressionsCountKey(), redisClient) : undefined;
29
- var uniqueKeysCache = impressionsMode === NONE ? new UniqueKeysCacheInRedis(log, keys.buildUniqueKeysKey(), redisClient) : undefined;
28
+ var impressionCountsCache = new ImpressionCountsCacheInRedis(log, keys.buildImpressionsCountKey(), redisClient);
29
+ var uniqueKeysCache = new UniqueKeysCacheInRedis(log, keys.buildUniqueKeysKey(), redisClient);
30
30
  // subscription to Redis connect event in order to emit SDK_READY event on consumer mode
31
31
  redisClient.on('connect', function () {
32
32
  onReadyCb();
33
- if (impressionCountsCache)
34
- impressionCountsCache.start();
35
- if (uniqueKeysCache)
36
- uniqueKeysCache.start();
33
+ impressionCountsCache.start();
34
+ uniqueKeysCache.start();
37
35
  // Synchronize config
38
36
  telemetry.recordConfig();
39
37
  });
@@ -48,12 +46,10 @@ export function InRedisStorage(options) {
48
46
  // When using REDIS we should:
49
47
  // 1- Disconnect from the storage
50
48
  destroy: function () {
51
- var promises = [];
52
- if (impressionCountsCache)
53
- promises.push(impressionCountsCache.stop());
54
- if (uniqueKeysCache)
55
- promises.push(uniqueKeysCache.stop());
56
- return Promise.all(promises).then(function () { redisClient.disconnect(); });
49
+ return Promise.all([
50
+ impressionCountsCache.stop(),
51
+ uniqueKeysCache.stop()
52
+ ]).then(function () { redisClient.disconnect(); });
57
53
  // @TODO check that caches works as expected when redisClient is disconnected
58
54
  }
59
55
  };
@@ -7,7 +7,7 @@ import { EventsCachePluggable } from './EventsCachePluggable';
7
7
  import { wrapperAdapter, METHODS_TO_PROMISE_WRAP } from './wrapperAdapter';
8
8
  import { isObject } from '../../utils/lang';
9
9
  import { getStorageHash, validatePrefix } from '../KeyBuilder';
10
- import { CONSUMER_PARTIAL_MODE, DEBUG, NONE, STORAGE_PLUGGABLE } from '../../utils/constants';
10
+ import { CONSUMER_PARTIAL_MODE, STORAGE_PLUGGABLE } from '../../utils/constants';
11
11
  import { ImpressionsCacheInMemory } from '../inMemory/ImpressionsCacheInMemory';
12
12
  import { EventsCacheInMemory } from '../inMemory/EventsCacheInMemory';
13
13
  import { ImpressionCountsCacheInMemory } from '../inMemory/ImpressionCountsCacheInMemory';
@@ -51,30 +51,26 @@ export function PluggableStorage(options) {
51
51
  validatePluggableStorageOptions(options);
52
52
  var prefix = validatePrefix(options.prefix);
53
53
  function PluggableStorageFactory(params) {
54
- var onReadyCb = params.onReadyCb, settings = params.settings, _a = params.settings, log = _a.log, mode = _a.mode, impressionsMode = _a.sync.impressionsMode, _b = _a.scheduler, impressionsQueueSize = _b.impressionsQueueSize, eventsQueueSize = _b.eventsQueueSize;
54
+ var onReadyCb = params.onReadyCb, settings = params.settings, _a = params.settings, log = _a.log, mode = _a.mode, _b = _a.scheduler, impressionsQueueSize = _b.impressionsQueueSize, eventsQueueSize = _b.eventsQueueSize;
55
55
  var metadata = metadataBuilder(settings);
56
56
  var keys = new KeyBuilderSS(prefix, metadata);
57
57
  var wrapper = wrapperAdapter(log, options.wrapper);
58
- var isSyncronizer = mode === undefined; // If mode is not defined, the synchronizer is running
58
+ var isSynchronizer = mode === undefined; // If mode is not defined, the synchronizer is running
59
59
  var isPartialConsumer = mode === CONSUMER_PARTIAL_MODE;
60
- var telemetry = shouldRecordTelemetry(params) || isSyncronizer ?
60
+ var telemetry = shouldRecordTelemetry(params) || isSynchronizer ?
61
61
  isPartialConsumer ?
62
62
  new TelemetryCacheInMemory() :
63
63
  new TelemetryCachePluggable(log, keys, wrapper) :
64
64
  undefined;
65
- var impressionCountsCache = impressionsMode !== DEBUG || isSyncronizer ?
66
- isPartialConsumer ?
67
- new ImpressionCountsCacheInMemory() :
68
- new ImpressionCountsCachePluggable(log, keys.buildImpressionsCountKey(), wrapper) :
69
- undefined;
70
- var uniqueKeysCache = impressionsMode === NONE || isSyncronizer ?
71
- isPartialConsumer ?
72
- settings.core.key === undefined ? new UniqueKeysCacheInMemory() : new UniqueKeysCacheInMemoryCS() :
73
- new UniqueKeysCachePluggable(log, keys.buildUniqueKeysKey(), wrapper) :
74
- undefined;
65
+ var impressionCountsCache = isPartialConsumer ?
66
+ new ImpressionCountsCacheInMemory() :
67
+ new ImpressionCountsCachePluggable(log, keys.buildImpressionsCountKey(), wrapper);
68
+ var uniqueKeysCache = isPartialConsumer ?
69
+ settings.core.key === undefined ? new UniqueKeysCacheInMemory() : new UniqueKeysCacheInMemoryCS() :
70
+ new UniqueKeysCachePluggable(log, keys.buildUniqueKeysKey(), wrapper);
75
71
  // Connects to wrapper and emits SDK_READY event on main client
76
72
  var connectPromise = wrapper.connect().then(function () {
77
- if (isSyncronizer) {
73
+ if (isSynchronizer) {
78
74
  // In standalone or producer mode, clear storage if SDK key or feature flag filter has changed
79
75
  return wrapper.get(keys.buildHashKey()).then(function (hash) {
80
76
  var currentHash = getStorageHash(settings);
@@ -90,9 +86,9 @@ export function PluggableStorage(options) {
90
86
  }
91
87
  else {
92
88
  // Start periodic flush of async storages if not running synchronizer (producer mode)
93
- if (impressionCountsCache && impressionCountsCache.start)
89
+ if (impressionCountsCache.start)
94
90
  impressionCountsCache.start();
95
- if (uniqueKeysCache && uniqueKeysCache.start)
91
+ if (uniqueKeysCache.start)
96
92
  uniqueKeysCache.start();
97
93
  if (telemetry && telemetry.recordConfig)
98
94
  telemetry.recordConfig();
@@ -113,9 +109,9 @@ export function PluggableStorage(options) {
113
109
  uniqueKeys: uniqueKeysCache,
114
110
  // Stop periodic flush and disconnect the underlying storage
115
111
  destroy: function () {
116
- return Promise.all(isSyncronizer ? [] : [
117
- impressionCountsCache && impressionCountsCache.stop && impressionCountsCache.stop(),
118
- uniqueKeysCache && uniqueKeysCache.stop && uniqueKeysCache.stop(),
112
+ return Promise.all(isSynchronizer ? [] : [
113
+ impressionCountsCache.stop && impressionCountsCache.stop(),
114
+ uniqueKeysCache.stop && uniqueKeysCache.stop(),
119
115
  ]).then(function () { return wrapper.disconnect(); });
120
116
  },
121
117
  // emits SDK_READY event on shared clients and returns a reference to the storage
@@ -26,8 +26,6 @@ var IMPRESSIONS_COUNT_RATE = 1800000; // 30 minutes
26
26
  */
27
27
  export function impressionCountsSubmitterFactory(params) {
28
28
  var log = params.settings.log, postTestImpressionsCount = params.splitApi.postTestImpressionsCount, impressionCounts = params.storage.impressionCounts;
29
- if (impressionCounts) {
30
- // retry impressions counts only once.
31
- return submitterFactory(log, postTestImpressionsCount, impressionCounts, IMPRESSIONS_COUNT_RATE, 'impression counts', fromImpressionCountsCollector, 1);
32
- }
29
+ // retry impressions counts only once.
30
+ return submitterFactory(log, postTestImpressionsCount, impressionCounts, IMPRESSIONS_COUNT_RATE, 'impression counts', fromImpressionCountsCollector, 1);
33
31
  }
@@ -6,14 +6,11 @@ import { uniqueKeysSubmitterFactory } from './uniqueKeysSubmitter';
6
6
  export function submitterManagerFactory(params) {
7
7
  var submitters = [
8
8
  impressionsSubmitterFactory(params),
9
- eventsSubmitterFactory(params)
9
+ eventsSubmitterFactory(params),
10
+ impressionCountsSubmitterFactory(params),
11
+ uniqueKeysSubmitterFactory(params)
10
12
  ];
11
- var impressionCountsSubmitter = impressionCountsSubmitterFactory(params);
12
- if (impressionCountsSubmitter)
13
- submitters.push(impressionCountsSubmitter);
14
13
  var telemetrySubmitter = telemetrySubmitterFactory(params);
15
- if (params.storage.uniqueKeys)
16
- submitters.push(uniqueKeysSubmitterFactory(params));
17
14
  return {
18
15
  // `onlyTelemetry` true if SDK is created with userConsent not GRANTED
19
16
  start: function (onlyTelemetry) {
@@ -4,38 +4,37 @@ import { IMPRESSIONS_TRACKER_SUCCESS, ERROR_IMPRESSIONS_TRACKER, ERROR_IMPRESSIO
4
4
  import { CONSENT_DECLINED, DEDUPED, QUEUED } from '../utils/constants';
5
5
  /**
6
6
  * Impressions tracker stores impressions in cache and pass them to the listener and integrations manager if provided.
7
- *
8
- * @param impressionsCache - cache to save impressions
9
- * @param metadata - runtime metadata (ip, hostname and version)
10
- * @param impressionListener - optional impression listener
11
- * @param integrationsManager - optional integrations manager
12
- * @param strategy - strategy for impressions tracking.
13
7
  */
14
- export function impressionsTrackerFactory(settings, impressionsCache, strategy, whenInit, integrationsManager, telemetryCache) {
8
+ export function impressionsTrackerFactory(settings, impressionsCache, noneStrategy, strategy, whenInit, integrationsManager, telemetryCache) {
15
9
  var log = settings.log, impressionListener = settings.impressionListener, _a = settings.runtime, ip = _a.ip, hostname = _a.hostname, version = settings.version;
16
10
  return {
17
11
  track: function (impressions, attributes) {
18
12
  if (settings.userConsent === CONSENT_DECLINED)
19
13
  return;
20
- var impressionsCount = impressions.length;
21
- var _a = strategy.process(impressions), impressionsToStore = _a.impressionsToStore, impressionsToListener = _a.impressionsToListener, deduped = _a.deduped;
22
- var impressionsToListenerCount = impressionsToListener.length;
23
- if (impressionsToStore.length > 0) {
24
- var res = impressionsCache.track(impressionsToStore);
14
+ var impressionsToStore = impressions.filter(function (_a) {
15
+ var impression = _a[0], track = _a[1];
16
+ return track === false ?
17
+ noneStrategy.process(impression) :
18
+ strategy.process(impression);
19
+ });
20
+ var impressionsLength = impressions.length;
21
+ var impressionsToStoreLength = impressionsToStore.length;
22
+ if (impressionsToStoreLength) {
23
+ var res = impressionsCache.track(impressionsToStore.map(function (item) { return item[0]; }));
25
24
  // If we're on an async storage, handle error and log it.
26
25
  if (thenable(res)) {
27
26
  res.then(function () {
28
- log.info(IMPRESSIONS_TRACKER_SUCCESS, [impressionsCount]);
27
+ log.info(IMPRESSIONS_TRACKER_SUCCESS, [impressionsLength]);
29
28
  }).catch(function (err) {
30
- log.error(ERROR_IMPRESSIONS_TRACKER, [impressionsCount, err]);
29
+ log.error(ERROR_IMPRESSIONS_TRACKER, [impressionsLength, err]);
31
30
  });
32
31
  }
33
32
  else {
34
33
  // Record when impressionsCache is sync only (standalone mode)
35
34
  // @TODO we are not dropping impressions on full queue yet, so DROPPED stats are not recorded
36
35
  if (telemetryCache) {
37
- telemetryCache.recordImpressionStats(QUEUED, impressionsToStore.length);
38
- telemetryCache.recordImpressionStats(DEDUPED, deduped);
36
+ telemetryCache.recordImpressionStats(QUEUED, impressionsToStoreLength);
37
+ telemetryCache.recordImpressionStats(DEDUPED, impressionsLength - impressionsToStoreLength);
39
38
  }
40
39
  }
41
40
  }
@@ -44,7 +43,7 @@ export function impressionsTrackerFactory(settings, impressionsCache, strategy,
44
43
  var _loop_1 = function (i) {
45
44
  var impressionData = {
46
45
  // copy of impression, to avoid unexpected behaviour if modified by integrations or impressionListener
47
- impression: objectAssign({}, impressionsToListener[i]),
46
+ impression: objectAssign({}, impressions[i][0]),
48
47
  attributes: attributes,
49
48
  ip: ip,
50
49
  hostname: hostname,
@@ -66,7 +65,7 @@ export function impressionsTrackerFactory(settings, impressionsCache, strategy,
66
65
  });
67
66
  });
68
67
  };
69
- for (var i = 0; i < impressionsToListenerCount; i++) {
68
+ for (var i = 0; i < impressionsLength; i++) {
70
69
  _loop_1(i);
71
70
  }
72
71
  }
@@ -2,20 +2,13 @@
2
2
  * Debug strategy for impressions tracker. Wraps impressions to store and adds previousTime if it corresponds
3
3
  *
4
4
  * @param impressionsObserver - impression observer. Previous time (pt property) is included in impression instances
5
- * @returns IStrategyResult
5
+ * @returns Debug strategy
6
6
  */
7
7
  export function strategyDebugFactory(impressionsObserver) {
8
8
  return {
9
- process: function (impressions) {
10
- impressions.forEach(function (impression) {
11
- // Adds previous time if it is enabled
12
- impression.pt = impressionsObserver.testAndSet(impression);
13
- });
14
- return {
15
- impressionsToStore: impressions,
16
- impressionsToListener: impressions,
17
- deduped: 0
18
- };
9
+ process: function (impression) {
10
+ impression.pt = impressionsObserver.testAndSet(impression);
11
+ return true;
19
12
  }
20
13
  };
21
14
  }
@@ -1,25 +1,20 @@
1
1
  /**
2
2
  * None strategy for impressions tracker.
3
3
  *
4
- * @param impressionsCounter - cache to save impressions count. impressions will be deduped (OPTIMIZED mode)
4
+ * @param impressionCounts - cache to save impressions count. impressions will be deduped (OPTIMIZED mode)
5
5
  * @param uniqueKeysTracker - unique keys tracker in charge of tracking the unique keys per split.
6
- * @returns IStrategyResult
6
+ * @returns None strategy
7
7
  */
8
- export function strategyNoneFactory(impressionsCounter, uniqueKeysTracker) {
8
+ export function strategyNoneFactory(impressionCounts, uniqueKeysTracker) {
9
9
  return {
10
- process: function (impressions) {
11
- impressions.forEach(function (impression) {
12
- var now = Date.now();
13
- // Increments impression counter per featureName
14
- impressionsCounter.track(impression.feature, now, 1);
15
- // Keep track by unique key
16
- uniqueKeysTracker.track(impression.keyName, impression.feature);
17
- });
18
- return {
19
- impressionsToStore: [],
20
- impressionsToListener: impressions,
21
- deduped: 0
22
- };
10
+ process: function (impression) {
11
+ var now = Date.now();
12
+ // Increments impression counter per featureName
13
+ impressionCounts.track(impression.feature, now, 1);
14
+ // Keep track by unique key
15
+ uniqueKeysTracker.track(impression.keyName, impression.feature);
16
+ // Do not store impressions
17
+ return false;
23
18
  }
24
19
  };
25
20
  }
@@ -3,29 +3,19 @@ import { truncateTimeFrame } from '../../utils/time';
3
3
  * Optimized strategy for impressions tracker. Wraps impressions to store and adds previousTime if it corresponds
4
4
  *
5
5
  * @param impressionsObserver - impression observer. previous time (pt property) is included in impression instances
6
- * @param impressionsCounter - cache to save impressions count. impressions will be deduped (OPTIMIZED mode)
7
- * @returns IStrategyResult
6
+ * @param impressionCounts - cache to save impressions count. impressions will be deduped (OPTIMIZED mode)
7
+ * @returns Optimized strategy
8
8
  */
9
- export function strategyOptimizedFactory(impressionsObserver, impressionsCounter) {
9
+ export function strategyOptimizedFactory(impressionsObserver, impressionCounts) {
10
10
  return {
11
- process: function (impressions) {
12
- var impressionsToStore = [];
13
- impressions.forEach(function (impression) {
14
- impression.pt = impressionsObserver.testAndSet(impression);
15
- var now = Date.now();
16
- // Increments impression counter per featureName
17
- if (impression.pt)
18
- impressionsCounter.track(impression.feature, now, 1);
19
- // Checks if the impression should be added in queue to be sent
20
- if (!impression.pt || impression.pt < truncateTimeFrame(now)) {
21
- impressionsToStore.push(impression);
22
- }
23
- });
24
- return {
25
- impressionsToStore: impressionsToStore,
26
- impressionsToListener: impressions,
27
- deduped: impressions.length - impressionsToStore.length
28
- };
11
+ process: function (impression) {
12
+ impression.pt = impressionsObserver.testAndSet(impression);
13
+ var now = Date.now();
14
+ // Increments impression counter per featureName
15
+ if (impression.pt)
16
+ impressionCounts.track(impression.feature, now, 1);
17
+ // Checks if the impression should be added in queue to be sent
18
+ return (!impression.pt || impression.pt < truncateTimeFrame(now)) ? true : false;
29
19
  }
30
20
  };
31
21
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splitsoftware/splitio-commons",
3
- "version": "2.0.1",
3
+ "version": "2.0.3-rc.0",
4
4
  "description": "Split JavaScript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
package/src/dtos/types.ts CHANGED
@@ -208,7 +208,8 @@ export interface ISplit {
208
208
  configurations?: {
209
209
  [treatmentName: string]: string
210
210
  },
211
- sets?: string[]
211
+ sets?: string[],
212
+ trackImpressions?: boolean
212
213
  }
213
214
 
214
215
  // Split definition used in offline mode
@@ -156,12 +156,14 @@ function getEvaluation(
156
156
  return evaluation.then(result => {
157
157
  result.changeNumber = split.getChangeNumber();
158
158
  result.config = splitJSON.configurations && splitJSON.configurations[result.treatment] || null;
159
+ result.track = splitJSON.trackImpressions;
159
160
 
160
161
  return result;
161
162
  });
162
163
  } else {
163
164
  evaluation.changeNumber = split.getChangeNumber(); // Always sync and optional
164
165
  evaluation.config = splitJSON.configurations && splitJSON.configurations[evaluation.treatment] || null;
166
+ evaluation.track = splitJSON.trackImpressions;
165
167
  }
166
168
  }
167
169
 
@@ -25,7 +25,7 @@ export interface IEvaluation {
25
25
  config?: string | null
26
26
  }
27
27
 
28
- export type IEvaluationResult = IEvaluation & { treatment: string }
28
+ export type IEvaluationResult = IEvaluation & { treatment: string; track?: boolean }
29
29
 
30
30
  export type ISplitEvaluator = (log: ILogger, key: SplitIO.SplitKey, splitName: string, attributes: SplitIO.Attributes | undefined, storage: IStorageSync | IStorageAsync) => MaybeThenable<IEvaluation>
31
31
 
@@ -8,7 +8,6 @@ import { IResponse, ISplitApi } from '../services/types';
8
8
  import { ISettings } from '../types';
9
9
  import SplitIO from '../../types/splitio';
10
10
  import { ImpressionsPayload } from '../sync/submitters/types';
11
- import { OPTIMIZED, DEBUG, NONE } from '../utils/constants';
12
11
  import { objectAssign } from '../utils/lang/objectAssign';
13
12
  import { CLEANUP_REGISTERING, CLEANUP_DEREGISTERING } from '../logger/constants';
14
13
  import { ISyncManager } from '../sync/types';
@@ -78,10 +77,9 @@ export class BrowserSignalListener implements ISignalListener {
78
77
 
79
78
  // Flush impressions & events data if there is user consent
80
79
  if (isConsentGranted(this.settings)) {
81
- const sim = this.settings.sync.impressionsMode;
82
80
  const extraMetadata = {
83
81
  // sim stands for Sync/Split Impressions Mode
84
- sim: sim === OPTIMIZED ? OPTIMIZED : sim === DEBUG ? DEBUG : NONE
82
+ sim: this.settings.sync.impressionsMode
85
83
  };
86
84
 
87
85
  this._flushData(events + '/testImpressions/beacon', this.storage.impressions, this.serviceApi.postTestImpressionsBulk, this.fromImpressionsCollector, extraMetadata);
@@ -37,7 +37,9 @@ function segmentsEventEmitterFactory(EventEmitter: new () => SplitIO.IEventEmitt
37
37
  export function readinessManagerFactory(
38
38
  EventEmitter: new () => SplitIO.IEventEmitter,
39
39
  settings: ISettings,
40
- splits: ISplitsEventEmitter = splitsEventEmitterFactory(EventEmitter)): IReadinessManager {
40
+ splits: ISplitsEventEmitter = splitsEventEmitterFactory(EventEmitter),
41
+ isShared?: boolean
42
+ ): IReadinessManager {
41
43
 
42
44
  const readyTimeout = settings.startup.readyTimeout;
43
45
 
@@ -66,11 +68,6 @@ export function readinessManagerFactory(
66
68
  gate.emit(SDK_READY_TIMED_OUT, 'Split SDK emitted SDK_READY_TIMED_OUT event.');
67
69
  }
68
70
 
69
- let readyTimeoutId: ReturnType<typeof setTimeout>;
70
- if (readyTimeout > 0) {
71
- if (splits.hasInit) readyTimeoutId = setTimeout(timeout, readyTimeout);
72
- else splits.initCallbacks.push(() => { readyTimeoutId = setTimeout(timeout, readyTimeout); });
73
- }
74
71
 
75
72
  // emit SDK_READY and SDK_UPDATE
76
73
  let isReady = false;
@@ -78,11 +75,19 @@ export function readinessManagerFactory(
78
75
  segments.on(SDK_SEGMENTS_ARRIVED, checkIsReadyOrUpdate);
79
76
 
80
77
  let isDestroyed = false;
78
+ let readyTimeoutId: ReturnType<typeof setTimeout>;
79
+ function __init() {
80
+ isDestroyed = false;
81
+ if (readyTimeout > 0 && !isReady) readyTimeoutId = setTimeout(timeout, readyTimeout);
82
+ }
83
+
84
+ splits.initCallbacks.push(__init);
85
+ if (splits.hasInit) __init();
81
86
 
82
87
  function checkIsReadyFromCache() {
83
88
  isReadyFromCache = true;
84
89
  // Don't emit SDK_READY_FROM_CACHE if SDK_READY has been emitted
85
- if (!isReady) {
90
+ if (!isReady && !isDestroyed) {
86
91
  try {
87
92
  syncLastUpdate();
88
93
  gate.emit(SDK_READY_FROM_CACHE);
@@ -94,6 +99,7 @@ export function readinessManagerFactory(
94
99
  }
95
100
 
96
101
  function checkIsReadyOrUpdate(diff: any) {
102
+ if (isDestroyed) return;
97
103
  if (isReady) {
98
104
  try {
99
105
  syncLastUpdate();
@@ -117,16 +123,13 @@ export function readinessManagerFactory(
117
123
  }
118
124
  }
119
125
 
120
- let refCount = 1;
121
-
122
126
  return {
123
127
  splits,
124
128
  segments,
125
129
  gate,
126
130
 
127
131
  shared() {
128
- refCount++;
129
- return readinessManagerFactory(EventEmitter, settings, splits);
132
+ return readinessManagerFactory(EventEmitter, settings, splits, true);
130
133
  },
131
134
 
132
135
  // @TODO review/remove next methods when non-recoverable errors are reworked
@@ -145,13 +148,9 @@ export function readinessManagerFactory(
145
148
  destroy() {
146
149
  isDestroyed = true;
147
150
  syncLastUpdate();
148
-
149
- segments.removeAllListeners();
150
- gate.removeAllListeners();
151
151
  clearTimeout(readyTimeoutId);
152
152
 
153
- if (refCount > 0) refCount--;
154
- if (refCount === 0) splits.removeAllListeners();
153
+ if (!isShared) splits.hasInit = false;
155
154
  },
156
155
 
157
156
  isReady() { return isReady; },
@@ -34,11 +34,11 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
34
34
  const stopTelemetryTracker = telemetryTracker.trackEval(withConfig ? TREATMENT_WITH_CONFIG : TREATMENT);
35
35
 
36
36
  const wrapUp = (evaluationResult: IEvaluationResult) => {
37
- const queue: SplitIO.ImpressionDTO[] = [];
37
+ const queue: [impression: SplitIO.ImpressionDTO, track?: boolean][] = [];
38
38
  const treatment = processEvaluation(evaluationResult, featureFlagName, key, attributes, withConfig, methodName, queue);
39
39
  impressionsTracker.track(queue, attributes);
40
40
 
41
- stopTelemetryTracker(queue[0] && queue[0].label);
41
+ stopTelemetryTracker(queue[0] && queue[0][0].label);
42
42
  return treatment;
43
43
  };
44
44
 
@@ -59,14 +59,14 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
59
59
  const stopTelemetryTracker = telemetryTracker.trackEval(withConfig ? TREATMENTS_WITH_CONFIG : TREATMENTS);
60
60
 
61
61
  const wrapUp = (evaluationResults: Record<string, IEvaluationResult>) => {
62
- const queue: SplitIO.ImpressionDTO[] = [];
62
+ const queue: [impression: SplitIO.ImpressionDTO, track?: boolean][] = [];
63
63
  const treatments: Record<string, SplitIO.Treatment | SplitIO.TreatmentWithConfig> = {};
64
64
  Object.keys(evaluationResults).forEach(featureFlagName => {
65
65
  treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key, attributes, withConfig, methodName, queue);
66
66
  });
67
67
  impressionsTracker.track(queue, attributes);
68
68
 
69
- stopTelemetryTracker(queue[0] && queue[0].label);
69
+ stopTelemetryTracker(queue[0] && queue[0][0].label);
70
70
  return treatments;
71
71
  };
72
72
 
@@ -87,7 +87,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
87
87
  const stopTelemetryTracker = telemetryTracker.trackEval(method);
88
88
 
89
89
  const wrapUp = (evaluationResults: Record<string, IEvaluationResult>) => {
90
- const queue: SplitIO.ImpressionDTO[] = [];
90
+ const queue: [impression: SplitIO.ImpressionDTO, track?: boolean][] = [];
91
91
  const treatments: Record<string, SplitIO.Treatment | SplitIO.TreatmentWithConfig> = {};
92
92
  const evaluations = evaluationResults;
93
93
  Object.keys(evaluations).forEach(featureFlagName => {
@@ -95,7 +95,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
95
95
  });
96
96
  impressionsTracker.track(queue, attributes);
97
97
 
98
- stopTelemetryTracker(queue[0] && queue[0].label);
98
+ stopTelemetryTracker(queue[0] && queue[0][0].label);
99
99
  return treatments;
100
100
  };
101
101
 
@@ -128,25 +128,25 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
128
128
  attributes: SplitIO.Attributes | undefined,
129
129
  withConfig: boolean,
130
130
  invokingMethodName: string,
131
- queue: SplitIO.ImpressionDTO[]
131
+ queue: [impression: SplitIO.ImpressionDTO, track?: boolean][]
132
132
  ): SplitIO.Treatment | SplitIO.TreatmentWithConfig {
133
133
  const matchingKey = getMatching(key);
134
134
  const bucketingKey = getBucketing(key);
135
135
 
136
- const { treatment, label, changeNumber, config = null } = evaluation;
136
+ const { treatment, label, changeNumber, config = null, track } = evaluation;
137
137
  log.info(IMPRESSION, [featureFlagName, matchingKey, treatment, label]);
138
138
 
139
139
  if (validateSplitExistence(log, readinessManager, featureFlagName, label, invokingMethodName)) {
140
140
  log.info(IMPRESSION_QUEUEING);
141
- queue.push({
141
+ queue.push([{
142
142
  feature: featureFlagName,
143
143
  keyName: matchingKey,
144
144
  treatment,
145
145
  time: Date.now(),
146
146
  bucketingKey,
147
147
  label,
148
- changeNumber: changeNumber as number
149
- });
148
+ changeNumber: changeNumber as number,
149
+ }, track]);
150
150
  }
151
151
 
152
152
  if (withConfig) {
@@ -61,7 +61,7 @@ export function sdkClientFactory(params: ISdkFactoryContext, isSharedClient?: bo
61
61
  releaseApiKey(settings.core.authorizationKey);
62
62
  telemetryTracker.sessionLength();
63
63
  signalListener && signalListener.stop();
64
- uniqueKeysTracker && uniqueKeysTracker.stop();
64
+ uniqueKeysTracker.stop();
65
65
  }
66
66
 
67
67
  // Stop background jobs
@@ -13,7 +13,7 @@ import { strategyDebugFactory } from '../trackers/strategy/strategyDebug';
13
13
  import { strategyOptimizedFactory } from '../trackers/strategy/strategyOptimized';
14
14
  import { strategyNoneFactory } from '../trackers/strategy/strategyNone';
15
15
  import { uniqueKeysTrackerFactory } from '../trackers/uniqueKeysTracker';
16
- import { NONE, OPTIMIZED } from '../utils/constants';
16
+ import { DEBUG, OPTIMIZED } from '../utils/constants';
17
17
 
18
18
  /**
19
19
  * Modular SDK factory
@@ -59,21 +59,16 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
59
59
  const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage, telemetryTracker });
60
60
 
61
61
  const observer = impressionsObserverFactory();
62
- const uniqueKeysTracker = impressionsMode === NONE ? uniqueKeysTrackerFactory(log, storage.uniqueKeys!, filterAdapterFactory && filterAdapterFactory()) : undefined;
63
-
64
- let strategy;
65
- switch (impressionsMode) {
66
- case OPTIMIZED:
67
- strategy = strategyOptimizedFactory(observer, storage.impressionCounts!);
68
- break;
69
- case NONE:
70
- strategy = strategyNoneFactory(storage.impressionCounts!, uniqueKeysTracker!);
71
- break;
72
- default:
73
- strategy = strategyDebugFactory(observer);
74
- }
62
+ const uniqueKeysTracker = uniqueKeysTrackerFactory(log, storage.uniqueKeys, filterAdapterFactory && filterAdapterFactory());
63
+
64
+ const noneStrategy = strategyNoneFactory(storage.impressionCounts, uniqueKeysTracker);
65
+ const strategy = impressionsMode === OPTIMIZED ?
66
+ strategyOptimizedFactory(observer, storage.impressionCounts) :
67
+ impressionsMode === DEBUG ?
68
+ strategyDebugFactory(observer) :
69
+ noneStrategy;
75
70
 
76
- const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, strategy, whenInit, integrationsManager, storage.telemetry);
71
+ const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, noneStrategy, strategy, whenInit, integrationsManager, storage.telemetry);
77
72
  const eventTracker = eventTrackerFactory(settings, storage.events, whenInit, integrationsManager, storage.telemetry);
78
73
 
79
74
  // splitApi is used by SyncManager and Browser signal listener
@@ -99,7 +94,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
99
94
  // We will just log and allow for the SDK to end up throwing an SDK_TIMEOUT event for devs to handle.
100
95
  validateAndTrackApiKey(log, settings.core.authorizationKey);
101
96
  readiness.init();
102
- uniqueKeysTracker && uniqueKeysTracker.start();
97
+ uniqueKeysTracker.start();
103
98
  syncManager && syncManager.start();
104
99
  signalListener && signalListener.start();
105
100
 
@@ -126,6 +121,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
126
121
  settings,
127
122
 
128
123
  destroy() {
124
+ hasInit = false;
129
125
  return Promise.all(Object.keys(clients).map(key => clients[key].destroy())).then(() => { });
130
126
  }
131
127
  }, extraProps && extraProps(ctx), lazyInit ? { init } : init());
@@ -46,7 +46,7 @@ export interface ISdkFactoryContext {
46
46
  eventTracker: IEventTracker,
47
47
  telemetryTracker: ITelemetryTracker,
48
48
  storage: IStorageSync | IStorageAsync,
49
- uniqueKeysTracker?: IUniqueKeysTracker,
49
+ uniqueKeysTracker: IUniqueKeysTracker,
50
50
  signalListener?: ISignalListener
51
51
  splitApi?: ISplitApi
52
52
  syncManager?: ISyncManager,