@splitsoftware/splitio-commons 1.2.1-rc.8 → 1.3.1-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 (98) hide show
  1. package/CHANGES.txt +17 -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/readiness/sdkReadinessManager.js +5 -3
  8. package/cjs/sdkClient/client.js +1 -1
  9. package/cjs/sdkClient/clientCS.js +1 -1
  10. package/cjs/sdkClient/sdkClient.js +3 -3
  11. package/cjs/sdkClient/sdkClientMethodCS.js +3 -9
  12. package/cjs/sdkClient/sdkClientMethodCSWithTT.js +3 -13
  13. package/cjs/sdkFactory/index.js +3 -2
  14. package/cjs/services/splitHttpClient.js +12 -11
  15. package/cjs/storages/inRedis/RedisAdapter.js +9 -2
  16. package/cjs/sync/syncManagerOnline.js +1 -1
  17. package/cjs/trackers/impressionsTracker.js +2 -2
  18. package/cjs/utils/inputValidation/attributes.js +1 -1
  19. package/cjs/utils/lang/index.js +4 -2
  20. package/cjs/utils/lang/maps.js +16 -2
  21. package/cjs/utils/settingsValidation/index.js +21 -3
  22. package/esm/{utils/consent.js → consent/index.js} +1 -1
  23. package/esm/consent/sdkUserConsent.js +54 -0
  24. package/esm/listeners/browser.js +1 -1
  25. package/esm/logger/constants.js +1 -0
  26. package/esm/logger/messages/info.js +1 -0
  27. package/esm/readiness/sdkReadinessManager.js +5 -3
  28. package/esm/sdkClient/client.js +1 -1
  29. package/esm/sdkClient/clientCS.js +1 -1
  30. package/esm/sdkClient/sdkClient.js +3 -3
  31. package/esm/sdkClient/sdkClientMethodCS.js +3 -9
  32. package/esm/sdkClient/sdkClientMethodCSWithTT.js +3 -13
  33. package/esm/sdkFactory/index.js +3 -2
  34. package/esm/services/splitHttpClient.js +12 -11
  35. package/esm/storages/inRedis/RedisAdapter.js +9 -2
  36. package/esm/sync/syncManagerOnline.js +1 -1
  37. package/esm/trackers/impressionsTracker.js +2 -2
  38. package/esm/utils/inputValidation/attributes.js +1 -1
  39. package/esm/utils/lang/index.js +4 -2
  40. package/esm/utils/lang/maps.js +14 -1
  41. package/esm/utils/settingsValidation/index.js +21 -3
  42. package/package.json +14 -2
  43. package/src/{utils/consent.ts → consent/index.ts} +1 -1
  44. package/src/consent/sdkUserConsent.ts +58 -0
  45. package/src/evaluator/parser/index.ts +1 -1
  46. package/src/evaluator/types.ts +2 -2
  47. package/src/evaluator/value/index.ts +2 -2
  48. package/src/evaluator/value/sanitize.ts +2 -2
  49. package/src/integrations/pluggable.ts +2 -2
  50. package/src/listeners/browser.ts +1 -1
  51. package/src/logger/constants.ts +1 -0
  52. package/src/logger/messages/info.ts +1 -0
  53. package/src/readiness/sdkReadinessManager.ts +7 -5
  54. package/src/sdkClient/client.ts +3 -4
  55. package/src/sdkClient/clientCS.ts +1 -1
  56. package/src/sdkClient/sdkClient.ts +4 -4
  57. package/src/sdkClient/sdkClientMethod.ts +2 -2
  58. package/src/sdkClient/sdkClientMethodCS.ts +5 -11
  59. package/src/sdkClient/sdkClientMethodCSWithTT.ts +6 -16
  60. package/src/sdkFactory/index.ts +3 -2
  61. package/src/sdkFactory/types.ts +13 -3
  62. package/src/services/splitHttpClient.ts +12 -10
  63. package/src/storages/inRedis/RedisAdapter.ts +8 -2
  64. package/src/sync/syncManagerOnline.ts +1 -1
  65. package/src/trackers/impressionsTracker.ts +4 -4
  66. package/src/types.ts +3 -3
  67. package/src/utils/inputValidation/attributes.ts +1 -2
  68. package/src/utils/lang/index.ts +7 -3
  69. package/src/utils/lang/maps.ts +15 -1
  70. package/src/utils/settingsValidation/index.ts +21 -3
  71. package/src/utils/settingsValidation/types.ts +5 -1
  72. package/types/consent/index.d.ts +2 -0
  73. package/types/consent/sdkUserConsent.d.ts +13 -0
  74. package/types/evaluator/types.d.ts +2 -2
  75. package/types/evaluator/value/index.d.ts +1 -1
  76. package/types/evaluator/value/sanitize.d.ts +1 -1
  77. package/types/logger/constants.d.ts +1 -0
  78. package/types/sdkClient/client.d.ts +2 -2
  79. package/types/sdkClient/sdkClient.d.ts +2 -2
  80. package/types/sdkClient/sdkClientMethod.d.ts +2 -2
  81. package/types/sdkClient/sdkClientMethodCS.d.ts +2 -2
  82. package/types/sdkClient/sdkClientMethodCSWithTT.d.ts +2 -2
  83. package/types/sdkFactory/types.d.ts +12 -3
  84. package/types/storages/inMemory/TelemetryCacheInMemory.d.ts +8 -10
  85. package/types/storages/inRedis/RedisAdapter.d.ts +1 -1
  86. package/types/storages/inRedis/TelemetryCacheInRedis.d.ts +21 -0
  87. package/types/storages/pluggable/TelemetryCachePluggable.d.ts +19 -1
  88. package/types/sync/submitters/telemetrySyncTask.d.ts +16 -6
  89. package/types/types.d.ts +3 -3
  90. package/types/utils/lang/index.d.ts +2 -1
  91. package/types/utils/lang/maps.d.ts +7 -0
  92. package/types/utils/settingsValidation/types.d.ts +5 -1
  93. package/cjs/sdkClient/types.js +0 -2
  94. package/cjs/sdkFactory/userConsentProps.js +0 -37
  95. package/esm/sdkClient/types.js +0 -1
  96. package/esm/sdkFactory/userConsentProps.js +0 -33
  97. package/src/sdkClient/types.ts +0 -21
  98. package/src/sdkFactory/userConsentProps.ts +0 -40
@@ -5,8 +5,8 @@ import { clientInputValidationDecorator } from './clientInputValidation';
5
5
  /**
6
6
  * Creates an Sdk client, i.e., a base client with status and destroy interface
7
7
  */
8
- export function sdkClientFactory(params) {
9
- 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;
10
10
  return objectAssign(
11
11
  // Proto-linkage of the readiness Event Emitter
12
12
  Object.create(sdkReadinessManager.sdkStatus),
@@ -23,7 +23,7 @@ export function sdkClientFactory(params) {
23
23
  sdkReadinessManager.readinessManager.destroy();
24
24
  signalListener && signalListener.stop();
25
25
  // Release the API Key if it is the main client
26
- if (!sharedClient)
26
+ if (!isSharedClient)
27
27
  releaseApiKey(settings.core.authorizationKey);
28
28
  // Cleanup storage
29
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
  }
@@ -61,7 +61,8 @@ export function sdkFactory(params) {
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
  }
@@ -9,25 +9,26 @@ var messageNoFetch = 'Global fetch API is not available.';
9
9
  * @param fetch optional http client to use instead of the global Fetch (for environments where Fetch API is not available such as Node)
10
10
  */
11
11
  export function splitHttpClientFactory(settings, getFetch, getOptions) {
12
- var log = settings.log, authorizationKey = settings.core.authorizationKey, version = settings.version, _a = settings.runtime, ip = _a.ip, hostname = _a.hostname;
12
+ var log = settings.log;
13
13
  var options = getOptions && getOptions();
14
14
  var fetch = getFetch && getFetch();
15
15
  // if fetch is not available, log Error
16
16
  if (!fetch)
17
17
  log.error(ERROR_CLIENT_CANNOT_GET_READY, [messageNoFetch]);
18
- var headers = {
19
- 'Accept': 'application/json',
20
- 'Content-Type': 'application/json',
21
- 'Authorization': "Bearer " + authorizationKey,
22
- 'SplitSDKVersion': version
23
- };
24
- if (ip)
25
- headers['SplitSDKMachineIP'] = ip;
26
- if (hostname)
27
- headers['SplitSDKMachineName'] = hostname;
28
18
  return function httpClient(url, reqOpts, logErrorsAsInfo) {
29
19
  if (reqOpts === void 0) { reqOpts = {}; }
30
20
  if (logErrorsAsInfo === void 0) { logErrorsAsInfo = false; }
21
+ var authorizationKey = settings.core.authorizationKey, version = settings.version, _a = settings.runtime, ip = _a.ip, hostname = _a.hostname;
22
+ var headers = {
23
+ 'Accept': 'application/json',
24
+ 'Content-Type': 'application/json',
25
+ 'Authorization': "Bearer " + authorizationKey,
26
+ 'SplitSDKVersion': version
27
+ };
28
+ if (ip)
29
+ headers['SplitSDKMachineIP'] = ip;
30
+ if (hostname)
31
+ headers['SplitSDKMachineName'] = hostname;
31
32
  var request = objectAssign({
32
33
  headers: reqOpts.headers ? objectAssign({}, headers, reqOpts.headers) : headers,
33
34
  method: reqOpts.method || 'GET',
@@ -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.
@@ -18,7 +18,7 @@ export function impressionsTrackerFactory(settings, impressionsCache, integratio
18
18
  observer,
19
19
  // if countsCache is provided, it implies `isOptimized` flag (i.e., if impressions should be deduped or not)
20
20
  countsCache) {
21
- var log = settings.log, impressionListener = settings.impressionListener, _a = settings.runtime, ip = _a.ip, hostname = _a.hostname, version = settings.version;
21
+ var log = settings.log, impressionListener = settings.impressionListener, _a = settings.runtime, ip = _a.ip, hostname = _a.hostname;
22
22
  return {
23
23
  track: function (impressions, attributes) {
24
24
  if (settings.userConsent === CONSENT_DECLINED)
@@ -59,7 +59,7 @@ countsCache) {
59
59
  attributes: attributes,
60
60
  ip: ip,
61
61
  hostname: hostname,
62
- sdkLanguageVersion: version
62
+ sdkLanguageVersion: settings.version
63
63
  };
64
64
  // Wrap in a timeout because we don't want it to be blocking.
65
65
  setTimeout(function () {
@@ -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.8",
3
+ "version": "1.3.1-rc.0",
4
4
  "description": "Split Javascript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
@@ -46,6 +46,18 @@
46
46
  "dependencies": {
47
47
  "tslib": "^2.3.1"
48
48
  },
49
+ "peerDependencies": {
50
+ "js-yaml": "^3.13.1",
51
+ "ioredis": "^4.28.0"
52
+ },
53
+ "peerDependenciesMeta": {
54
+ "js-yaml": {
55
+ "optional": true
56
+ },
57
+ "ioredis": {
58
+ "optional": true
59
+ }
60
+ },
49
61
  "devDependencies": {
50
62
  "@types/google.analytics": "0.0.40",
51
63
  "@types/ioredis": "^4.28.0",
@@ -62,7 +74,7 @@
62
74
  "ioredis": "^4.28.0",
63
75
  "jest": "^27.2.3",
64
76
  "jest-localstorage-mock": "^2.4.3",
65
- "js-yaml": "^3.14.0",
77
+ "js-yaml": "^3.13.1",
66
78
  "lodash": "^4.17.21",
67
79
  "node-fetch": "^2.6.7",
68
80
  "redis-server": "1.2.2",
@@ -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
 
@@ -4,7 +4,7 @@ import { ILogger } from '../../logger/types';
4
4
  import { sanitize } from './sanitize';
5
5
  import { ENGINE_VALUE, ENGINE_VALUE_NO_ATTRIBUTES, ENGINE_VALUE_INVALID } from '../../logger/constants';
6
6
 
7
- function parseValue(log: ILogger, key: string, attributeName: string | null, attributes: SplitIO.Attributes) {
7
+ function parseValue(log: ILogger, key: string, attributeName: string | null, attributes?: SplitIO.Attributes) {
8
8
  let value = undefined;
9
9
  if (attributeName) {
10
10
  if (attributes) {
@@ -23,7 +23,7 @@ function parseValue(log: ILogger, key: string, attributeName: string | null, att
23
23
  /**
24
24
  * Defines value to be matched (key / attribute).
25
25
  */
26
- export function sanitizeValue(log: ILogger, key: string, matcherDto: IMatcherDto, attributes: SplitIO.Attributes) {
26
+ export function sanitizeValue(log: ILogger, key: string, matcherDto: IMatcherDto, attributes?: SplitIO.Attributes) {
27
27
  const attributeName = matcherDto.attribute;
28
28
  const valueToMatch = parseValue(log, key, attributeName, attributes);
29
29
  const sanitizedValue = sanitize(log, matcherDto.type, valueToMatch, matcherDto.dataType, attributes);
@@ -41,7 +41,7 @@ function sanitizeBoolean(val: any): boolean | undefined {
41
41
  return undefined;
42
42
  }
43
43
 
44
- function dependencyProcessor(sanitizedValue: string, attributes: SplitIO.Attributes): IDependencyMatcherValue {
44
+ function dependencyProcessor(sanitizedValue: string, attributes?: SplitIO.Attributes): IDependencyMatcherValue {
45
45
  return {
46
46
  key: sanitizedValue,
47
47
  attributes
@@ -69,7 +69,7 @@ function getProcessingFunction(matcherTypeID: number, dataType: string) {
69
69
  /**
70
70
  * Sanitize matcher value
71
71
  */
72
- export function sanitize(log: ILogger, matcherTypeID: number, value: string | number | boolean | Array<string | number> | undefined, dataType: string, attributes: SplitIO.Attributes) {
72
+ export function sanitize(log: ILogger, matcherTypeID: number, value: string | number | boolean | Array<string | number> | undefined, dataType: string, attributes?: SplitIO.Attributes) {
73
73
  const processor = getProcessingFunction(matcherTypeID, dataType);
74
74
  let sanitizedValue: string | number | boolean | Array<string | number> | IDependencyMatcherValue | undefined;
75
75
 
@@ -29,10 +29,10 @@ export function pluggableIntegrationsManagerFactory(
29
29
 
30
30
  // Exception safe methods: each integration module is responsable for handling errors
31
31
  return {
32
- handleImpression: function (impressionData: SplitIO.ImpressionData) {
32
+ handleImpression(impressionData: SplitIO.ImpressionData) {
33
33
  listeners.forEach(listener => listener.queue({ type: SPLIT_IMPRESSION, payload: impressionData }));
34
34
  },
35
- handleEvent: function (eventData: SplitIO.EventData) {
35
+ handleEvent(eventData: SplitIO.EventData) {
36
36
  listeners.forEach(listener => listener.queue({ type: SPLIT_EVENT, payload: eventData }));
37
37
  }
38
38
  };
@@ -11,7 +11,7 @@ import { OPTIMIZED, DEBUG } from '../utils/constants';
11
11
  import { objectAssign } from '../utils/lang/objectAssign';
12
12
  import { CLEANUP_REGISTERING, CLEANUP_DEREGISTERING } from '../logger/constants';
13
13
  import { ISyncManager } from '../sync/types';
14
- import { isConsentGranted } from '../utils/consent';
14
+ import { isConsentGranted } from '../consent';
15
15
 
16
16
  // 'unload' event is used instead of 'beforeunload', since 'unload' is not a cancelable event, so no other listeners can stop the event from occurring.
17
17
  const UNLOAD_DOM_EVENT = 'unload';
@@ -70,6 +70,7 @@ export const EVENTS_TRACKER_SUCCESS = 120;
70
70
  export const IMPRESSIONS_TRACKER_SUCCESS = 121;
71
71
  export const USER_CONSENT_UPDATED = 122;
72
72
  export const USER_CONSENT_NOT_UPDATED = 123;
73
+ export const USER_CONSENT_INITIAL = 124;
73
74
 
74
75
  export const ENGINE_VALUE_INVALID = 200;
75
76
  export const ENGINE_VALUE_NO_ATTRIBUTES = 201;
@@ -16,6 +16,7 @@ export const codesInfo: [number, string][] = codesWarn.concat([
16
16
  [c.IMPRESSIONS_TRACKER_SUCCESS, c.LOG_PREFIX_IMPRESSIONS_TRACKER + 'Successfully stored %s impression(s).'],
17
17
  [c.USER_CONSENT_UPDATED, 'setUserConsent: consent status changed from %s to %s.'],
18
18
  [c.USER_CONSENT_NOT_UPDATED, 'setUserConsent: call had no effect because it was the current consent status (%s).'],
19
+ [c.USER_CONSENT_INITIAL, 'Starting the SDK with %s user consent. No data will be sent.'],
19
20
 
20
21
  // synchronizer
21
22
  [c.POLLING_SMART_PAUSING, c.LOG_PREFIX_SYNC_POLLING + 'Turning segments data polling %s.'],
@@ -51,7 +51,7 @@ export function sdkReadinessManagerFactory(
51
51
 
52
52
  // default onRejected handler, that just logs the error, if ready promise doesn't have one.
53
53
  function defaultOnRejected(err: any) {
54
- log.error(err);
54
+ log.error(err && err.message);
55
55
  }
56
56
 
57
57
  function generateReadyPromise() {
@@ -62,7 +62,9 @@ export function sdkReadinessManagerFactory(
62
62
  if (readyCbCount === internalReadyCbCount && !promise.hasOnFulfilled()) log.warn(CLIENT_NO_LISTENER);
63
63
  resolve();
64
64
  });
65
- readinessManager.gate.once(SDK_READY_TIMED_OUT, reject);
65
+ readinessManager.gate.once(SDK_READY_TIMED_OUT, (message: string) => {
66
+ reject(new Error(message));
67
+ });
66
68
  }), defaultOnRejected);
67
69
 
68
70
  return promise;
@@ -106,10 +108,10 @@ export function sdkReadinessManagerFactory(
106
108
  * @function ready
107
109
  * @returns {Promise<void>}
108
110
  */
109
- ready: () => {
111
+ ready() {
110
112
  if (readinessManager.hasTimedout()) {
111
113
  if (!readinessManager.isReady()) {
112
- return promiseWrapper(Promise.reject('Split SDK has emitted SDK_READY_TIMED_OUT event.'), defaultOnRejected);
114
+ return promiseWrapper(Promise.reject(new Error('Split SDK has emitted SDK_READY_TIMED_OUT event.')), defaultOnRejected);
113
115
  } else {
114
116
  return Promise.resolve();
115
117
  }
@@ -118,7 +120,7 @@ export function sdkReadinessManagerFactory(
118
120
  },
119
121
 
120
122
  // Expose status for internal purposes only. Not considered part of the public API, and might be updated eventually.
121
- __getStatus: () => {
123
+ __getStatus() {
122
124
  return {
123
125
  isReady: readinessManager.isReady(),
124
126
  isReadyFromCache: readinessManager.isReadyFromCache(),
@@ -5,17 +5,16 @@ import { validateSplitExistance } from '../utils/inputValidation/splitExistance'
5
5
  import { validateTrafficTypeExistance } from '../utils/inputValidation/trafficTypeExistance';
6
6
  import { SDK_NOT_READY } from '../utils/labels';
7
7
  import { CONTROL } from '../utils/constants';
8
- import { IClientFactoryParams } from './types';
9
8
  import { IEvaluationResult } from '../evaluator/types';
10
9
  import { SplitIO, ImpressionDTO } from '../types';
11
10
  import { IMPRESSION, IMPRESSION_QUEUEING } from '../logger/constants';
12
-
11
+ import { ISdkFactoryContext } from '../sdkFactory/types';
13
12
 
14
13
  /**
15
14
  * Creator of base client with getTreatments and track methods.
16
15
  */
17
16
  // @TODO missing time tracking to collect telemetry
18
- export function clientFactory(params: IClientFactoryParams): SplitIO.IClient | SplitIO.IAsyncClient {
17
+ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | SplitIO.IAsyncClient {
19
18
  const { sdkReadinessManager: { readinessManager }, storage, settings, impressionsTracker, eventTracker } = params;
20
19
  const { log, mode } = settings;
21
20
 
@@ -125,6 +124,6 @@ export function clientFactory(params: IClientFactoryParams): SplitIO.IClient | S
125
124
  getTreatments,
126
125
  getTreatmentsWithConfig,
127
126
  track,
128
- isBrowserClient: false
127
+ isClientSide: false
129
128
  } as SplitIO.IClient | SplitIO.IAsyncClient;
130
129
  }
@@ -25,6 +25,6 @@ export function clientCSDecorator(log: ILogger, client: SplitIO.IClient, key: Sp
25
25
  // Key is bound to the `track` method. Same thing happens with trafficType but only if provided
26
26
  track: trafficType ? clientCS.track.bind(clientCS, key, trafficType) : clientCS.track.bind(clientCS, key),
27
27
 
28
- isBrowserClient: true
28
+ isClientSide: true
29
29
  }) as SplitIO.ICsClient;
30
30
  }
@@ -3,13 +3,13 @@ import { IStatusInterface, SplitIO } from '../types';
3
3
  import { releaseApiKey } from '../utils/inputValidation/apiKey';
4
4
  import { clientFactory } from './client';
5
5
  import { clientInputValidationDecorator } from './clientInputValidation';
6
- import { ISdkClientFactoryParams } from './types';
6
+ import { ISdkFactoryContext } from '../sdkFactory/types';
7
7
 
8
8
  /**
9
9
  * Creates an Sdk client, i.e., a base client with status and destroy interface
10
10
  */
11
- export function sdkClientFactory(params: ISdkClientFactoryParams): SplitIO.IClient | SplitIO.IAsyncClient {
12
- const { sdkReadinessManager, syncManager, storage, signalListener, settings, sharedClient } = params;
11
+ export function sdkClientFactory(params: ISdkFactoryContext, isSharedClient?: boolean): SplitIO.IClient | SplitIO.IAsyncClient {
12
+ const { sdkReadinessManager, syncManager, storage, signalListener, settings } = params;
13
13
 
14
14
  return objectAssign(
15
15
  // Proto-linkage of the readiness Event Emitter
@@ -35,7 +35,7 @@ export function sdkClientFactory(params: ISdkClientFactoryParams): SplitIO.IClie
35
35
  signalListener && signalListener.stop();
36
36
 
37
37
  // Release the API Key if it is the main client
38
- if (!sharedClient) releaseApiKey(settings.core.authorizationKey);
38
+ if (!isSharedClient) releaseApiKey(settings.core.authorizationKey);
39
39
 
40
40
  // Cleanup storage
41
41
  return storage.destroy();
@@ -1,12 +1,12 @@
1
- import { ISdkClientFactoryParams } from './types';
2
1
  import { SplitIO } from '../types';
3
2
  import { sdkClientFactory } from './sdkClient';
4
3
  import { RETRIEVE_CLIENT_DEFAULT } from '../logger/constants';
4
+ import { ISdkFactoryContext } from '../sdkFactory/types';
5
5
 
6
6
  /**
7
7
  * Factory of client method for server-side SDKs (ISDK and IAsyncSDK)
8
8
  */
9
- export function sdkClientMethodFactory(params: ISdkClientFactoryParams): () => SplitIO.IClient | SplitIO.IAsyncClient {
9
+ export function sdkClientMethodFactory(params: ISdkFactoryContext): () => SplitIO.IClient | SplitIO.IAsyncClient {
10
10
  const log = params.settings.log;
11
11
  const clientInstance = sdkClientFactory(params);
12
12