@splitsoftware/splitio-commons 1.2.1-rc.7 → 1.3.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 (101) hide show
  1. package/CHANGES.txt +14 -0
  2. package/cjs/{utils/consent.js → consent/index.js} +1 -1
  3. package/cjs/consent/sdkUserConsent.js +58 -0
  4. package/cjs/listeners/browser.js +1 -1
  5. package/cjs/logger/constants.js +3 -2
  6. package/cjs/logger/messages/info.js +1 -0
  7. package/cjs/sdkClient/client.js +4 -9
  8. package/cjs/sdkClient/clientCS.js +1 -1
  9. package/cjs/sdkClient/clientInputValidation.js +6 -8
  10. package/cjs/sdkClient/sdkClient.js +4 -7
  11. package/cjs/sdkClient/sdkClientMethodCS.js +3 -9
  12. package/cjs/sdkClient/sdkClientMethodCSWithTT.js +3 -13
  13. package/cjs/sdkFactory/index.js +6 -5
  14. package/cjs/storages/inRedis/RedisAdapter.js +9 -2
  15. package/cjs/sync/syncManagerOnline.js +1 -1
  16. package/cjs/trackers/eventTracker.js +8 -1
  17. package/cjs/trackers/impressionObserver/utils.js +8 -1
  18. package/cjs/trackers/impressionsTracker.js +6 -5
  19. package/cjs/utils/inputValidation/attributes.js +1 -1
  20. package/cjs/utils/lang/index.js +4 -2
  21. package/cjs/utils/lang/maps.js +16 -2
  22. package/cjs/utils/settingsValidation/index.js +21 -3
  23. package/esm/{utils/consent.js → consent/index.js} +1 -1
  24. package/esm/consent/sdkUserConsent.js +54 -0
  25. package/esm/listeners/browser.js +1 -1
  26. package/esm/logger/constants.js +1 -0
  27. package/esm/logger/messages/info.js +1 -0
  28. package/esm/sdkClient/client.js +5 -10
  29. package/esm/sdkClient/clientCS.js +1 -1
  30. package/esm/sdkClient/clientInputValidation.js +6 -8
  31. package/esm/sdkClient/sdkClient.js +4 -7
  32. package/esm/sdkClient/sdkClientMethodCS.js +3 -9
  33. package/esm/sdkClient/sdkClientMethodCSWithTT.js +3 -13
  34. package/esm/sdkFactory/index.js +6 -5
  35. package/esm/storages/inRedis/RedisAdapter.js +9 -2
  36. package/esm/sync/syncManagerOnline.js +1 -1
  37. package/esm/trackers/eventTracker.js +8 -1
  38. package/esm/trackers/impressionObserver/utils.js +7 -1
  39. package/esm/trackers/impressionsTracker.js +6 -5
  40. package/esm/utils/inputValidation/attributes.js +1 -1
  41. package/esm/utils/lang/index.js +4 -2
  42. package/esm/utils/lang/maps.js +14 -1
  43. package/esm/utils/settingsValidation/index.js +21 -3
  44. package/package.json +1 -1
  45. package/src/{utils/consent.ts → consent/index.ts} +1 -1
  46. package/src/consent/sdkUserConsent.ts +58 -0
  47. package/src/evaluator/parser/index.ts +1 -1
  48. package/src/evaluator/types.ts +2 -2
  49. package/src/evaluator/value/index.ts +2 -2
  50. package/src/evaluator/value/sanitize.ts +2 -2
  51. package/src/integrations/pluggable.ts +2 -2
  52. package/src/listeners/browser.ts +1 -1
  53. package/src/logger/constants.ts +1 -0
  54. package/src/logger/messages/info.ts +1 -0
  55. package/src/sdkClient/client.ts +7 -9
  56. package/src/sdkClient/clientCS.ts +1 -1
  57. package/src/sdkClient/clientInputValidation.ts +8 -7
  58. package/src/sdkClient/sdkClient.ts +6 -9
  59. package/src/sdkClient/sdkClientMethod.ts +2 -2
  60. package/src/sdkClient/sdkClientMethodCS.ts +5 -11
  61. package/src/sdkClient/sdkClientMethodCSWithTT.ts +6 -16
  62. package/src/sdkFactory/index.ts +6 -5
  63. package/src/sdkFactory/types.ts +13 -4
  64. package/src/storages/inRedis/RedisAdapter.ts +8 -2
  65. package/src/sync/syncManagerOnline.ts +1 -1
  66. package/src/trackers/eventTracker.ts +11 -3
  67. package/src/trackers/impressionObserver/utils.ts +8 -1
  68. package/src/trackers/impressionsTracker.ts +7 -8
  69. package/src/types.ts +1 -1
  70. package/src/utils/inputValidation/attributes.ts +1 -2
  71. package/src/utils/lang/index.ts +7 -3
  72. package/src/utils/lang/maps.ts +15 -1
  73. package/src/utils/settingsValidation/index.ts +21 -3
  74. package/src/utils/settingsValidation/types.ts +5 -1
  75. package/types/consent/index.d.ts +2 -0
  76. package/types/consent/sdkUserConsent.d.ts +13 -0
  77. package/types/evaluator/types.d.ts +2 -2
  78. package/types/evaluator/value/index.d.ts +1 -1
  79. package/types/evaluator/value/sanitize.d.ts +1 -1
  80. package/types/logger/constants.d.ts +1 -0
  81. package/types/sdkClient/client.d.ts +2 -2
  82. package/types/sdkClient/clientInputValidation.d.ts +2 -3
  83. package/types/sdkClient/sdkClient.d.ts +2 -2
  84. package/types/sdkClient/sdkClientMethod.d.ts +2 -2
  85. package/types/sdkClient/sdkClientMethodCS.d.ts +2 -2
  86. package/types/sdkClient/sdkClientMethodCSWithTT.d.ts +2 -2
  87. package/types/sdkFactory/types.d.ts +12 -4
  88. package/types/storages/inRedis/RedisAdapter.d.ts +1 -1
  89. package/types/trackers/eventTracker.d.ts +2 -2
  90. package/types/trackers/impressionObserver/utils.d.ts +4 -0
  91. package/types/trackers/impressionsTracker.d.ts +2 -3
  92. package/types/types.d.ts +1 -1
  93. package/types/utils/lang/index.d.ts +2 -1
  94. package/types/utils/lang/maps.d.ts +7 -0
  95. package/types/utils/settingsValidation/types.d.ts +5 -1
  96. package/cjs/sdkClient/types.js +0 -2
  97. package/cjs/sdkFactory/userConsentProps.js +0 -37
  98. package/esm/sdkClient/types.js +0 -1
  99. package/esm/sdkFactory/userConsentProps.js +0 -33
  100. package/src/sdkClient/types.ts +0 -21
  101. package/src/sdkFactory/userConsentProps.ts +0 -40
@@ -0,0 +1,54 @@
1
+ import { ERROR_NOT_BOOLEAN, USER_CONSENT_UPDATED, USER_CONSENT_NOT_UPDATED, USER_CONSENT_INITIAL } from '../logger/constants';
2
+ import { isConsentGranted } from './index';
3
+ import { CONSENT_GRANTED, CONSENT_DECLINED, CONSENT_UNKNOWN } from '../utils/constants';
4
+ import { isBoolean } from '../utils/lang';
5
+ // User consent enum
6
+ var ConsentStatus = {
7
+ GRANTED: CONSENT_GRANTED,
8
+ DECLINED: CONSENT_DECLINED,
9
+ UNKNOWN: CONSENT_UNKNOWN,
10
+ };
11
+ /**
12
+ * The public user consent API exposed via SplitFactory, used to control if the SDK tracks and sends impressions and events or not.
13
+ */
14
+ export function createUserConsentAPI(params) {
15
+ var settings = params.settings, log = params.settings.log, syncManager = params.syncManager, _a = params.storage, events = _a.events, impressions = _a.impressions, impressionCounts = _a.impressionCounts;
16
+ if (!isConsentGranted(settings))
17
+ log.info(USER_CONSENT_INITIAL, [settings.userConsent]);
18
+ return {
19
+ setStatus: function (consent) {
20
+ var _a, _b;
21
+ // validate input param
22
+ if (!isBoolean(consent)) {
23
+ log.warn(ERROR_NOT_BOOLEAN, ['setUserConsent']);
24
+ return false;
25
+ }
26
+ var newConsentStatus = consent ? CONSENT_GRANTED : CONSENT_DECLINED;
27
+ if (settings.userConsent !== newConsentStatus) {
28
+ log.info(USER_CONSENT_UPDATED, [settings.userConsent, newConsentStatus]); // @ts-ignore, modify readonly prop
29
+ settings.userConsent = newConsentStatus;
30
+ if (consent) { // resumes submitters if transitioning to GRANTED
31
+ (_a = syncManager === null || syncManager === void 0 ? void 0 : syncManager.submitter) === null || _a === void 0 ? void 0 : _a.start();
32
+ }
33
+ else { // pauses submitters and drops tracked data if transitioning to DECLINED
34
+ (_b = syncManager === null || syncManager === void 0 ? void 0 : syncManager.submitter) === null || _b === void 0 ? void 0 : _b.stop();
35
+ // @ts-ignore, clear method is present in storage for standalone and partial consumer mode
36
+ if (events.clear)
37
+ events.clear(); // @ts-ignore
38
+ if (impressions.clear)
39
+ impressions.clear();
40
+ if (impressionCounts)
41
+ impressionCounts.clear();
42
+ }
43
+ }
44
+ else {
45
+ log.info(USER_CONSENT_NOT_UPDATED, [newConsentStatus]);
46
+ }
47
+ return true;
48
+ },
49
+ getStatus: function () {
50
+ return settings.userConsent;
51
+ },
52
+ Status: ConsentStatus
53
+ };
54
+ }
@@ -3,7 +3,7 @@ import { fromImpressionCountsCollector } from '../sync/submitters/impressionCoun
3
3
  import { OPTIMIZED, DEBUG } from '../utils/constants';
4
4
  import { objectAssign } from '../utils/lang/objectAssign';
5
5
  import { CLEANUP_REGISTERING, CLEANUP_DEREGISTERING } from '../logger/constants';
6
- import { isConsentGranted } from '../utils/consent';
6
+ import { isConsentGranted } from '../consent';
7
7
  // 'unload' event is used instead of 'beforeunload', since 'unload' is not a cancelable event, so no other listeners can stop the event from occurring.
8
8
  var UNLOAD_DOM_EVENT = 'unload';
9
9
  var EVENT_NAME = 'for unload page event.';
@@ -69,6 +69,7 @@ export var EVENTS_TRACKER_SUCCESS = 120;
69
69
  export var IMPRESSIONS_TRACKER_SUCCESS = 121;
70
70
  export var USER_CONSENT_UPDATED = 122;
71
71
  export var USER_CONSENT_NOT_UPDATED = 123;
72
+ export var USER_CONSENT_INITIAL = 124;
72
73
  export var ENGINE_VALUE_INVALID = 200;
73
74
  export var ENGINE_VALUE_NO_ATTRIBUTES = 201;
74
75
  export var CLIENT_NO_LISTENER = 202;
@@ -14,6 +14,7 @@ export var codesInfo = codesWarn.concat([
14
14
  [c.IMPRESSIONS_TRACKER_SUCCESS, c.LOG_PREFIX_IMPRESSIONS_TRACKER + 'Successfully stored %s impression(s).'],
15
15
  [c.USER_CONSENT_UPDATED, 'setUserConsent: consent status changed from %s to %s.'],
16
16
  [c.USER_CONSENT_NOT_UPDATED, 'setUserConsent: call had no effect because it was the current consent status (%s).'],
17
+ [c.USER_CONSENT_INITIAL, 'Starting the SDK with %s user consent. No data will be sent.'],
17
18
  // synchronizer
18
19
  [c.POLLING_SMART_PAUSING, c.LOG_PREFIX_SYNC_POLLING + 'Turning segments data polling %s.'],
19
20
  [c.POLLING_START, c.LOG_PREFIX_SYNC_POLLING + 'Starting polling'],
@@ -4,7 +4,7 @@ import { getMatching, getBucketing } from '../utils/key';
4
4
  import { validateSplitExistance } from '../utils/inputValidation/splitExistance';
5
5
  import { validateTrafficTypeExistance } from '../utils/inputValidation/trafficTypeExistance';
6
6
  import { SDK_NOT_READY } from '../utils/labels';
7
- import { CONSENT_DECLINED, CONTROL } from '../utils/constants';
7
+ import { CONTROL } from '../utils/constants';
8
8
  import { IMPRESSION, IMPRESSION_QUEUEING } from '../logger/constants';
9
9
  /**
10
10
  * Creator of base client with getTreatments and track methods.
@@ -18,8 +18,7 @@ export function clientFactory(params) {
18
18
  var wrapUp = function (evaluationResult) {
19
19
  var queue = [];
20
20
  var treatment = processEvaluation(evaluationResult, splitName, key, attributes, withConfig, "getTreatment" + (withConfig ? 'withConfig' : ''), queue);
21
- if (settings.userConsent !== CONSENT_DECLINED)
22
- impressionsTracker.track(queue, attributes);
21
+ impressionsTracker.track(queue, attributes);
23
22
  return treatment;
24
23
  };
25
24
  var evaluation = evaluateFeature(log, key, splitName, attributes, storage);
@@ -36,8 +35,7 @@ export function clientFactory(params) {
36
35
  Object.keys(evaluationResults).forEach(function (splitName) {
37
36
  treatments[splitName] = processEvaluation(evaluationResults[splitName], splitName, key, attributes, withConfig, "getTreatments" + (withConfig ? 'withConfig' : ''), queue);
38
37
  });
39
- if (settings.userConsent !== CONSENT_DECLINED)
40
- impressionsTracker.track(queue, attributes);
38
+ impressionsTracker.track(queue, attributes);
41
39
  return treatments;
42
40
  };
43
41
  var evaluations = evaluateFeatures(log, key, splitNames, attributes, storage);
@@ -91,10 +89,7 @@ export function clientFactory(params) {
91
89
  };
92
90
  // This may be async but we only warn, we don't actually care if it is valid or not in terms of queueing the event.
93
91
  validateTrafficTypeExistance(log, readinessManager, storage.splits, mode, trafficTypeName, 'track');
94
- if (settings.userConsent !== CONSENT_DECLINED)
95
- return eventTracker.track(eventData, size);
96
- else
97
- return false;
92
+ return eventTracker.track(eventData, size);
98
93
  }
99
94
  return {
100
95
  getTreatment: getTreatment,
@@ -102,6 +97,6 @@ export function clientFactory(params) {
102
97
  getTreatments: getTreatments,
103
98
  getTreatmentsWithConfig: getTreatmentsWithConfig,
104
99
  track: track,
105
- isBrowserClient: false
100
+ isClientSide: false
106
101
  };
107
102
  }
@@ -17,6 +17,6 @@ export function clientCSDecorator(log, client, key, trafficType) {
17
17
  getTreatmentsWithConfig: clientCS.getTreatmentsWithConfig.bind(clientCS, key),
18
18
  // Key is bound to the `track` method. Same thing happens with trafficType but only if provided
19
19
  track: trafficType ? clientCS.track.bind(clientCS, key, trafficType) : clientCS.track.bind(clientCS, key),
20
- isBrowserClient: true
20
+ isClientSide: true
21
21
  });
22
22
  }
@@ -2,12 +2,14 @@ import { objectAssign } from '../utils/lang/objectAssign';
2
2
  import { validateAttributes, validateEvent, validateEventValue, validateEventProperties, validateKey, validateSplit, validateSplits, validateTrafficType, validateIfNotDestroyed, validateIfOperational } from '../utils/inputValidation';
3
3
  import { startsWith } from '../utils/lang';
4
4
  import { CONTROL, CONTROL_WITH_CONFIG } from '../utils/constants';
5
+ import { isStorageSync } from '../trackers/impressionObserver/utils';
5
6
  /**
6
7
  * Decorator that validates the input before actually executing the client methods.
7
8
  * We should "guard" the client here, while not polluting the "real" implementation of those methods.
8
9
  */
9
- export function clientInputValidationDecorator(log, client, readinessManager, isStorageSync) {
10
- if (isStorageSync === void 0) { isStorageSync = false; }
10
+ export function clientInputValidationDecorator(settings, client, readinessManager) {
11
+ var log = settings.log;
12
+ var isSync = isStorageSync(settings);
11
13
  /**
12
14
  * Avoid repeating this validations code
13
15
  */
@@ -27,9 +29,7 @@ export function clientInputValidationDecorator(log, client, readinessManager, is
27
29
  };
28
30
  }
29
31
  function wrapResult(value) {
30
- if (isStorageSync)
31
- return value;
32
- return Promise.resolve(value);
32
+ return isSync ? value : Promise.resolve(value);
33
33
  }
34
34
  function getTreatment(maybeKey, maybeSplit, maybeAttributes) {
35
35
  var params = validateEvaluationParams(maybeKey, maybeSplit, maybeAttributes, 'getTreatment');
@@ -84,9 +84,7 @@ export function clientInputValidationDecorator(log, client, readinessManager, is
84
84
  return client.track(key, tt, event, eventValue, properties, size);
85
85
  }
86
86
  else {
87
- if (isStorageSync)
88
- return false;
89
- return Promise.resolve(false);
87
+ return isSync ? false : Promise.resolve(false);
90
88
  }
91
89
  }
92
90
  return {
@@ -1,20 +1,17 @@
1
1
  import { objectAssign } from '../utils/lang/objectAssign';
2
- import { CONSUMER_MODE, CONSUMER_PARTIAL_MODE } from '../utils/constants';
3
2
  import { releaseApiKey } from '../utils/inputValidation/apiKey';
4
3
  import { clientFactory } from './client';
5
4
  import { clientInputValidationDecorator } from './clientInputValidation';
6
5
  /**
7
6
  * Creates an Sdk client, i.e., a base client with status and destroy interface
8
7
  */
9
- export function sdkClientFactory(params) {
10
- var sdkReadinessManager = params.sdkReadinessManager, syncManager = params.syncManager, storage = params.storage, signalListener = params.signalListener, settings = params.settings, sharedClient = params.sharedClient;
8
+ export function sdkClientFactory(params, isSharedClient) {
9
+ var sdkReadinessManager = params.sdkReadinessManager, syncManager = params.syncManager, storage = params.storage, signalListener = params.signalListener, settings = params.settings;
11
10
  return objectAssign(
12
11
  // Proto-linkage of the readiness Event Emitter
13
12
  Object.create(sdkReadinessManager.sdkStatus),
14
13
  // Client API (getTreatment* & track methods)
15
- clientInputValidationDecorator(settings.log, clientFactory(params), sdkReadinessManager.readinessManager,
16
- // storage is async if and only if mode is consumer or partial consumer
17
- [CONSUMER_MODE, CONSUMER_PARTIAL_MODE].indexOf(settings.mode) === -1 ? true : false),
14
+ clientInputValidationDecorator(settings, clientFactory(params), sdkReadinessManager.readinessManager),
18
15
  // Sdk destroy
19
16
  {
20
17
  destroy: function () {
@@ -26,7 +23,7 @@ export function sdkClientFactory(params) {
26
23
  sdkReadinessManager.readinessManager.destroy();
27
24
  signalListener && signalListener.stop();
28
25
  // Release the API Key if it is the main client
29
- if (!sharedClient)
26
+ if (!isSharedClient)
30
27
  releaseApiKey(settings.core.authorizationKey);
31
28
  // Cleanup storage
32
29
  return storage.destroy();
@@ -16,12 +16,7 @@ var method = 'Client instantiation';
16
16
  */
17
17
  export function sdkClientMethodCSFactory(params) {
18
18
  var storage = params.storage, syncManager = params.syncManager, sdkReadinessManager = params.sdkReadinessManager, _a = params.settings, key = _a.core.key, readyTimeout = _a.startup.readyTimeout, log = _a.log;
19
- // Keeping similar behaviour as in the isomorphic JS SDK: if settings key is invalid,
20
- // `false` value is used as binded key of the default client, but trafficType is ignored
21
- // @TODO handle as a non-recoverable error
22
- var validKey = validateKey(log, key, method);
23
- var mainClientInstance = clientCSDecorator(log, sdkClientFactory(params), // @ts-ignore
24
- validKey);
19
+ var mainClientInstance = clientCSDecorator(log, sdkClientFactory(params), key);
25
20
  var parsedDefaultKey = keyParser(key);
26
21
  var defaultInstanceId = buildInstanceId(parsedDefaultKey);
27
22
  // Cache instances created per factory.
@@ -59,9 +54,8 @@ export function sdkClientMethodCSFactory(params) {
59
54
  sdkReadinessManager: sharedSdkReadiness_1,
60
55
  storage: sharedStorage || storage,
61
56
  syncManager: sharedSyncManager,
62
- signalListener: undefined,
63
- sharedClient: true
64
- })), validKey);
57
+ signalListener: undefined, // only the main client "destroy" method stops the signal listener
58
+ }), true), validKey);
65
59
  sharedSyncManager && sharedSyncManager.start();
66
60
  log.info(NEW_SHARED_CLIENT);
67
61
  }
@@ -18,16 +18,7 @@ var method = 'Client instantiation';
18
18
  */
19
19
  export function sdkClientMethodCSFactory(params) {
20
20
  var storage = params.storage, syncManager = params.syncManager, sdkReadinessManager = params.sdkReadinessManager, _a = params.settings, _b = _a.core, key = _b.key, trafficType = _b.trafficType, readyTimeout = _a.startup.readyTimeout, log = _a.log;
21
- // Keeping the behaviour as in the isomorphic JS SDK: if settings key or TT are invalid,
22
- // `false` value is used as binded key/TT of the default client, which leads to several issues.
23
- // @TODO update when supporting non-recoverable errors
24
- var validKey = validateKey(log, key, method);
25
- var validTrafficType;
26
- if (trafficType !== undefined) {
27
- validTrafficType = validateTrafficType(log, trafficType, method);
28
- }
29
- var mainClientInstance = clientCSDecorator(log, sdkClientFactory(params), // @ts-ignore
30
- validKey, validTrafficType);
21
+ var mainClientInstance = clientCSDecorator(log, sdkClientFactory(params), key, trafficType);
31
22
  var parsedDefaultKey = keyParser(key);
32
23
  var defaultInstanceId = buildInstanceId(parsedDefaultKey, trafficType);
33
24
  // Cache instances created per factory.
@@ -72,9 +63,8 @@ export function sdkClientMethodCSFactory(params) {
72
63
  sdkReadinessManager: sharedSdkReadiness_1,
73
64
  storage: sharedStorage || storage,
74
65
  syncManager: sharedSyncManager,
75
- signalListener: undefined,
76
- sharedClient: true
77
- })), validKey, validTrafficType);
66
+ signalListener: undefined, // only the main client "destroy" method stops the signal listener
67
+ }), true), validKey, validTrafficType);
78
68
  sharedSyncManager && sharedSyncManager.start();
79
69
  log.info(NEW_SHARED_CLIENT);
80
70
  }
@@ -13,7 +13,7 @@ import { objectAssign } from '../utils/lang/objectAssign';
13
13
  * Modular SDK factory
14
14
  */
15
15
  export function sdkFactory(params) {
16
- var settings = params.settings, platform = params.platform, storageFactory = params.storageFactory, splitApiFactory = params.splitApiFactory, extraProps = params.extraProps, syncManagerFactory = params.syncManagerFactory, SignalListener = params.SignalListener, impressionsObserverFactory = params.impressionsObserverFactory, impressionListener = params.impressionListener, integrationsManagerFactory = params.integrationsManagerFactory, sdkManagerFactory = params.sdkManagerFactory, sdkClientMethodFactory = params.sdkClientMethodFactory;
16
+ var settings = params.settings, platform = params.platform, storageFactory = params.storageFactory, splitApiFactory = params.splitApiFactory, extraProps = params.extraProps, syncManagerFactory = params.syncManagerFactory, SignalListener = params.SignalListener, impressionsObserverFactory = params.impressionsObserverFactory, integrationsManagerFactory = params.integrationsManagerFactory, sdkManagerFactory = params.sdkManagerFactory, sdkClientMethodFactory = params.sdkClientMethodFactory;
17
17
  var log = settings.log;
18
18
  // @TODO handle non-recoverable errors: not start sync, mark the SDK as destroyed, etc.
19
19
  // We will just log and allow for the SDK to end up throwing an SDK_TIMEOUT event for devs to handle.
@@ -56,12 +56,13 @@ export function sdkFactory(params) {
56
56
  var integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings: settings, storage: storage });
57
57
  // trackers
58
58
  var observer = impressionsObserverFactory && impressionsObserverFactory();
59
- var impressionsTracker = impressionsTrackerFactory(log, storage.impressions, settings, impressionListener, integrationsManager, observer, storage.impressionCounts);
60
- var eventTracker = eventTrackerFactory(log, storage.events, integrationsManager);
59
+ var impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, integrationsManager, observer, storage.impressionCounts);
60
+ var eventTracker = eventTrackerFactory(settings, storage.events, integrationsManager);
61
61
  // signal listener
62
62
  var signalListener = SignalListener && new SignalListener(syncManager, settings, storage, splitApi);
63
63
  // Sdk client and manager
64
- var clientMethod = sdkClientMethodFactory({ eventTracker: eventTracker, impressionsTracker: impressionsTracker, sdkReadinessManager: sdkReadinessManager, settings: settings, storage: storage, syncManager: syncManager, signalListener: signalListener });
64
+ var ctx = { eventTracker: eventTracker, impressionsTracker: impressionsTracker, sdkReadinessManager: sdkReadinessManager, settings: settings, storage: storage, syncManager: syncManager, signalListener: signalListener };
65
+ var clientMethod = sdkClientMethodFactory(ctx);
65
66
  var managerInstance = sdkManagerFactory(log, storage.splits, sdkReadinessManager);
66
67
  syncManager && syncManager.start();
67
68
  signalListener && signalListener.start();
@@ -78,5 +79,5 @@ export function sdkFactory(params) {
78
79
  // Logger wrapper API
79
80
  Logger: createLoggerAPI(settings.log),
80
81
  settings: settings,
81
- }, extraProps && extraProps(settings, syncManager));
82
+ }, extraProps && extraProps(ctx));
82
83
  }
@@ -143,13 +143,19 @@ var RedisAdapter = /** @class */ (function (_super) {
143
143
  else { // If it IS the string URL, that'll be the first param for ioredis.
144
144
  result.unshift(options.url);
145
145
  }
146
+ if (options.connectionTimeout) {
147
+ merge(opts, { connectTimeout: options.connectionTimeout });
148
+ }
149
+ if (options.tls) {
150
+ merge(opts, { tls: options.tls });
151
+ }
146
152
  return result;
147
153
  };
148
154
  /**
149
155
  * Parses the options into what we care about.
150
156
  */
151
157
  RedisAdapter._defineOptions = function (_a) {
152
- var connectionTimeout = _a.connectionTimeout, operationTimeout = _a.operationTimeout, url = _a.url, host = _a.host, port = _a.port, db = _a.db, pass = _a.pass;
158
+ var connectionTimeout = _a.connectionTimeout, operationTimeout = _a.operationTimeout, url = _a.url, host = _a.host, port = _a.port, db = _a.db, pass = _a.pass, tls = _a.tls;
153
159
  var parsedOptions = {
154
160
  connectionTimeout: connectionTimeout,
155
161
  operationTimeout: operationTimeout,
@@ -157,7 +163,8 @@ var RedisAdapter = /** @class */ (function (_super) {
157
163
  host: host,
158
164
  port: port,
159
165
  db: db,
160
- pass: pass
166
+ pass: pass,
167
+ tls: tls
161
168
  };
162
169
  return merge({}, DEFAULT_OPTIONS, parsedOptions);
163
170
  };
@@ -1,7 +1,7 @@
1
1
  import { submitterManagerFactory } from './submitters/submitterManager';
2
2
  import { PUSH_SUBSYSTEM_UP, PUSH_SUBSYSTEM_DOWN } from './streaming/constants';
3
3
  import { SYNC_START_POLLING, SYNC_CONTINUE_POLLING, SYNC_STOP_POLLING } from '../logger/constants';
4
- import { isConsentGranted } from '../utils/consent';
4
+ import { isConsentGranted } from '../consent';
5
5
  /**
6
6
  * Online SyncManager factory.
7
7
  * Can be used for server-side API, and client-side API with or without multiple clients.
@@ -1,13 +1,17 @@
1
1
  import { objectAssign } from '../utils/lang/objectAssign';
2
2
  import { thenable } from '../utils/promise/thenable';
3
3
  import { EVENTS_TRACKER_SUCCESS, ERROR_EVENTS_TRACKER } from '../logger/constants';
4
+ import { CONSENT_DECLINED } from '../utils/constants';
5
+ import { isStorageSync } from './impressionObserver/utils';
4
6
  /**
5
7
  * Event tracker stores events in cache and pass them to the integrations manager if provided.
6
8
  *
7
9
  * @param eventsCache cache to save events
8
10
  * @param integrationsManager optional event handler used for integrations
9
11
  */
10
- export function eventTrackerFactory(log, eventsCache, integrationsManager) {
12
+ export function eventTrackerFactory(settings, eventsCache, integrationsManager) {
13
+ var log = settings.log;
14
+ var isSync = isStorageSync(settings);
11
15
  function queueEventsCallback(eventData, tracked) {
12
16
  var eventTypeId = eventData.eventTypeId, trafficTypeName = eventData.trafficTypeName, key = eventData.key, value = eventData.value, timestamp = eventData.timestamp, properties = eventData.properties;
13
17
  // Logging every prop would be too much.
@@ -33,6 +37,9 @@ export function eventTrackerFactory(log, eventsCache, integrationsManager) {
33
37
  }
34
38
  return {
35
39
  track: function (eventData, size) {
40
+ if (settings.userConsent === CONSENT_DECLINED) {
41
+ return isSync ? false : Promise.resolve(false);
42
+ }
36
43
  var tracked = eventsCache.track(eventData, size);
37
44
  if (thenable(tracked)) {
38
45
  return tracked.then(queueEventsCallback.bind(null, eventData));
@@ -1,4 +1,4 @@
1
- import { CONSUMER_PARTIAL_MODE, OPTIMIZED, PRODUCER_MODE, STANDALONE_MODE } from '../../utils/constants';
1
+ import { CONSUMER_MODE, CONSUMER_PARTIAL_MODE, OPTIMIZED, PRODUCER_MODE, STANDALONE_MODE } from '../../utils/constants';
2
2
  /**
3
3
  * Checks if impressions previous time should be added or not.
4
4
  */
@@ -13,3 +13,9 @@ export function shouldBeOptimized(settings) {
13
13
  return false;
14
14
  return settings.sync.impressionsMode === OPTIMIZED ? true : false;
15
15
  }
16
+ /**
17
+ * Storage is async if mode is consumer or partial consumer
18
+ */
19
+ export function isStorageSync(settings) {
20
+ return [CONSUMER_MODE, CONSUMER_PARTIAL_MODE].indexOf(settings.mode) === -1 ? true : false;
21
+ }
@@ -2,6 +2,7 @@ import { objectAssign } from '../utils/lang/objectAssign';
2
2
  import { thenable } from '../utils/promise/thenable';
3
3
  import { truncateTimeFrame } from '../utils/time';
4
4
  import { IMPRESSIONS_TRACKER_SUCCESS, ERROR_IMPRESSIONS_TRACKER, ERROR_IMPRESSIONS_LISTENER } from '../logger/constants';
5
+ import { CONSENT_DECLINED } from '../utils/constants';
5
6
  /**
6
7
  * Impressions tracker stores impressions in cache and pass them to the listener and integrations manager if provided.
7
8
  *
@@ -12,16 +13,16 @@ import { IMPRESSIONS_TRACKER_SUCCESS, ERROR_IMPRESSIONS_TRACKER, ERROR_IMPRESSIO
12
13
  * @param observer optional impression observer. If provided, previous time (pt property) is included in impression instances
13
14
  * @param countsCache optional cache to save impressions count. If provided, impressions will be deduped (OPTIMIZED mode)
14
15
  */
15
- export function impressionsTrackerFactory(log, impressionsCache,
16
- // @TODO consider passing only an optional integrationsManager to handle impressions
17
- _a, impressionListener, integrationsManager,
16
+ export function impressionsTrackerFactory(settings, impressionsCache, integrationsManager,
18
17
  // if observer is provided, it implies `shouldAddPreviousTime` flag (i.e., if impressions previous time should be added or not)
19
18
  observer,
20
19
  // if countsCache is provided, it implies `isOptimized` flag (i.e., if impressions should be deduped or not)
21
20
  countsCache) {
22
- var _b = _a.runtime, ip = _b.ip, hostname = _b.hostname, version = _a.version;
21
+ var log = settings.log, impressionListener = settings.impressionListener, _a = settings.runtime, ip = _a.ip, hostname = _a.hostname, version = settings.version;
23
22
  return {
24
23
  track: function (impressions, attributes) {
24
+ if (settings.userConsent === CONSENT_DECLINED)
25
+ return;
25
26
  var impressionsCount = impressions.length;
26
27
  var impressionsToStore = []; // Track only the impressions that are going to be stored
27
28
  // Wraps impressions to store and adds previousTime if it corresponds
@@ -65,7 +66,7 @@ countsCache) {
65
66
  // integrationsManager.handleImpression does not throw errors
66
67
  if (integrationsManager)
67
68
  integrationsManager.handleImpression(impressionData);
68
- try { // An exception on the listeners should not break the SDK.
69
+ try { // @ts-ignore. An exception on the listeners should not break the SDK.
69
70
  if (impressionListener)
70
71
  impressionListener.logImpression(impressionData);
71
72
  }
@@ -3,7 +3,7 @@ import { validateAttribute } from './attribute';
3
3
  import { ERROR_NOT_PLAIN_OBJECT } from '../../logger/constants';
4
4
  export function validateAttributes(log, maybeAttrs, method) {
5
5
  // Attributes are optional
6
- if (isObject(maybeAttrs) || maybeAttrs == undefined) // eslint-disable-line eqeqeq
6
+ if (maybeAttrs == undefined || isObject(maybeAttrs)) // eslint-disable-line eqeqeq
7
7
  return maybeAttrs;
8
8
  log.error(ERROR_NOT_PLAIN_OBJECT, [method, 'attributes']);
9
9
  return false;
@@ -141,10 +141,12 @@ export function isNaNNumber(val) {
141
141
  return val !== val;
142
142
  }
143
143
  /**
144
- * Validates if a value is an object with the Object prototype (map object).
144
+ * Validates if a value is an object created by the Object constructor (plain object).
145
+ * It checks `constructor.name` to avoid false negatives when validating values on a separate VM context, which has its own global built-ins.
145
146
  */
146
147
  export function isObject(obj) {
147
- return obj !== null && typeof obj === 'object' && obj.constructor === Object;
148
+ return obj !== null && typeof obj === 'object' && (obj.constructor === Object ||
149
+ (obj.constructor != null && obj.constructor.name === 'Object'));
148
150
  }
149
151
  /**
150
152
  * Checks if a given value is a string.
@@ -68,4 +68,17 @@ var MapPoly = /** @class */ (function () {
68
68
  return MapPoly;
69
69
  }());
70
70
  export { MapPoly };
71
- export var _Map = typeof Map !== 'undefined' ? Map : MapPoly;
71
+ /**
72
+ * return the Map constructor to use. If native Map is not available or it doesn't support the required features (e.g., IE11),
73
+ * a ponyfill with minimal features is returned instead.
74
+ *
75
+ * Exported for testing purposes only.
76
+ */
77
+ export function __getMapConstructor() {
78
+ // eslint-disable-next-line compat/compat
79
+ if (typeof Array.from === 'function' && typeof Map === 'function' && Map.prototype && Map.prototype.values) {
80
+ return Map;
81
+ }
82
+ return MapPoly;
83
+ }
84
+ export var _Map = __getMapConstructor();
@@ -3,6 +3,8 @@ import { mode } from './mode';
3
3
  import { validateSplitFilters } from './splitFilters';
4
4
  import { STANDALONE_MODE, OPTIMIZED, LOCALHOST_MODE } from '../constants';
5
5
  import { validImpressionsMode } from './impressionsMode';
6
+ import { validateKey } from '../inputValidation/key';
7
+ import { validateTrafficType } from '../inputValidation/trafficType';
6
8
  var base = {
7
9
  // Define which kind of object you want to retrieve from SplitFactory
8
10
  mode: STANDALONE_MODE,
@@ -106,9 +108,25 @@ export function settingsValidation(config, validationParams) {
106
108
  // @ts-ignore, modify readonly prop
107
109
  if (storage)
108
110
  withDefaults.storage = storage(withDefaults);
109
- // Although `key` is mandatory according to TS declaration files, it can be omitted in LOCALHOST mode. In that case, the value `localhost_key` is used.
110
- if (withDefaults.mode === LOCALHOST_MODE && withDefaults.core.key === undefined) {
111
- withDefaults.core.key = 'localhost_key';
111
+ // Validate key and TT (for client-side)
112
+ if (validationParams.acceptKey) {
113
+ var maybeKey = withDefaults.core.key;
114
+ // Although `key` is required in client-side, it can be omitted in LOCALHOST mode. In that case, the value `localhost_key` is used.
115
+ if (withDefaults.mode === LOCALHOST_MODE && maybeKey === undefined) {
116
+ withDefaults.core.key = 'localhost_key';
117
+ }
118
+ else {
119
+ // Keeping same behaviour than JS SDK: if settings key or TT are invalid,
120
+ // `false` value is used as binded key/TT of the default client, which leads to some issues.
121
+ // @ts-ignore, @TODO handle invalid keys as a non-recoverable error?
122
+ withDefaults.core.key = validateKey(log, maybeKey, 'Client instantiation');
123
+ }
124
+ if (validationParams.acceptTT) {
125
+ var maybeTT = withDefaults.core.trafficType;
126
+ if (maybeTT !== undefined) { // @ts-ignore
127
+ withDefaults.core.trafficType = validateTrafficType(log, maybeTT, 'Client instantiation');
128
+ }
129
+ }
112
130
  }
113
131
  // Current ip/hostname information
114
132
  // @ts-ignore, modify readonly prop
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splitsoftware/splitio-commons",
3
- "version": "1.2.1-rc.7",
3
+ "version": "1.3.0",
4
4
  "description": "Split Javascript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
@@ -1,5 +1,5 @@
1
1
  import { ISettings } from '../types';
2
- import { CONSENT_GRANTED } from './constants';
2
+ import { CONSENT_GRANTED } from '../utils/constants';
3
3
 
4
4
  export function isConsentGranted(settings: ISettings) {
5
5
  const userConsent = settings.userConsent;
@@ -0,0 +1,58 @@
1
+ import { ERROR_NOT_BOOLEAN, USER_CONSENT_UPDATED, USER_CONSENT_NOT_UPDATED, USER_CONSENT_INITIAL } from '../logger/constants';
2
+ import { isConsentGranted } from './index';
3
+ import { CONSENT_GRANTED, CONSENT_DECLINED, CONSENT_UNKNOWN } from '../utils/constants';
4
+ import { isBoolean } from '../utils/lang';
5
+ import { ISdkFactoryContext } from '../sdkFactory/types';
6
+
7
+ // User consent enum
8
+ const ConsentStatus = {
9
+ GRANTED: CONSENT_GRANTED,
10
+ DECLINED: CONSENT_DECLINED,
11
+ UNKNOWN: CONSENT_UNKNOWN,
12
+ };
13
+
14
+ /**
15
+ * The public user consent API exposed via SplitFactory, used to control if the SDK tracks and sends impressions and events or not.
16
+ */
17
+ export function createUserConsentAPI(params: ISdkFactoryContext) {
18
+ const { settings, settings: { log }, syncManager, storage: { events, impressions, impressionCounts } } = params;
19
+
20
+ if (!isConsentGranted(settings)) log.info(USER_CONSENT_INITIAL, [settings.userConsent]);
21
+
22
+ return {
23
+ setStatus(consent: unknown) {
24
+ // validate input param
25
+ if (!isBoolean(consent)) {
26
+ log.warn(ERROR_NOT_BOOLEAN, ['setUserConsent']);
27
+ return false;
28
+ }
29
+
30
+ const newConsentStatus = consent ? CONSENT_GRANTED : CONSENT_DECLINED;
31
+
32
+ if (settings.userConsent !== newConsentStatus) {
33
+ log.info(USER_CONSENT_UPDATED, [settings.userConsent, newConsentStatus]); // @ts-ignore, modify readonly prop
34
+ settings.userConsent = newConsentStatus;
35
+
36
+ if (consent) { // resumes submitters if transitioning to GRANTED
37
+ syncManager?.submitter?.start();
38
+ } else { // pauses submitters and drops tracked data if transitioning to DECLINED
39
+ syncManager?.submitter?.stop();
40
+ // @ts-ignore, clear method is present in storage for standalone and partial consumer mode
41
+ if (events.clear) events.clear(); // @ts-ignore
42
+ if (impressions.clear) impressions.clear();
43
+ if (impressionCounts) impressionCounts.clear();
44
+ }
45
+ } else {
46
+ log.info(USER_CONSENT_NOT_UPDATED, [newConsentStatus]);
47
+ }
48
+
49
+ return true;
50
+ },
51
+
52
+ getStatus() {
53
+ return settings.userConsent;
54
+ },
55
+
56
+ Status: ConsentStatus
57
+ };
58
+ }
@@ -31,7 +31,7 @@ export function parser(log: ILogger, conditions: ISplitCondition[], storage: ISt
31
31
  const matcher = matcherFactory(log, matcherDto, storage);
32
32
 
33
33
  // Evaluator function.
34
- return (key: string, attributes: SplitIO.Attributes, splitEvaluator: ISplitEvaluator) => {
34
+ return (key: string, attributes: SplitIO.Attributes | undefined, splitEvaluator: ISplitEvaluator) => {
35
35
  const value = sanitizeValue(log, key, matcherDto, attributes);
36
36
  const result = value !== undefined && matcher ? matcher(value, splitEvaluator) : false;
37
37
 
@@ -6,7 +6,7 @@ import { ILogger } from '../logger/types';
6
6
 
7
7
  export interface IDependencyMatcherValue {
8
8
  key: SplitIO.SplitKey,
9
- attributes: SplitIO.Attributes
9
+ attributes?: SplitIO.Attributes
10
10
  }
11
11
 
12
12
  export interface IMatcherDto {
@@ -27,7 +27,7 @@ export interface IEvaluation {
27
27
 
28
28
  export type IEvaluationResult = IEvaluation & { treatment: string }
29
29
 
30
- export type ISplitEvaluator = (log: ILogger, key: SplitIO.SplitKey, splitName: string, attributes: SplitIO.Attributes, storage: IStorageSync | IStorageAsync) => MaybeThenable<IEvaluation>
30
+ export type ISplitEvaluator = (log: ILogger, key: SplitIO.SplitKey, splitName: string, attributes: SplitIO.Attributes | undefined, storage: IStorageSync | IStorageAsync) => MaybeThenable<IEvaluation>
31
31
 
32
32
  export type IEvaluator = (key: SplitIO.SplitKey, seed: number, trafficAllocation?: number, trafficAllocationSeed?: number, attributes?: SplitIO.Attributes, splitEvaluator?: ISplitEvaluator) => MaybeThenable<IEvaluation | undefined>
33
33