@splitsoftware/splitio-commons 1.2.1-rc.6 → 1.2.1-rc.9

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 (58) hide show
  1. package/cjs/sdkClient/client.js +4 -9
  2. package/cjs/sdkClient/clientCS.js +1 -1
  3. package/cjs/sdkClient/clientInputValidation.js +6 -8
  4. package/cjs/sdkClient/sdkClient.js +1 -4
  5. package/cjs/sdkClient/sdkClientMethodCS.js +1 -5
  6. package/cjs/sdkClient/sdkClientMethodCSWithTT.js +1 -9
  7. package/cjs/sdkFactory/index.js +3 -3
  8. package/cjs/storages/inRedis/RedisAdapter.js +9 -2
  9. package/cjs/sync/streaming/SSEClient/index.js +2 -1
  10. package/cjs/trackers/eventTracker.js +8 -1
  11. package/cjs/trackers/impressionObserver/utils.js +8 -1
  12. package/cjs/trackers/impressionsTracker.js +6 -5
  13. package/cjs/utils/lang/maps.js +16 -2
  14. package/cjs/utils/settingsValidation/index.js +20 -4
  15. package/esm/sdkClient/client.js +5 -10
  16. package/esm/sdkClient/clientCS.js +1 -1
  17. package/esm/sdkClient/clientInputValidation.js +6 -8
  18. package/esm/sdkClient/sdkClient.js +1 -4
  19. package/esm/sdkClient/sdkClientMethodCS.js +1 -5
  20. package/esm/sdkClient/sdkClientMethodCSWithTT.js +1 -9
  21. package/esm/sdkFactory/index.js +3 -3
  22. package/esm/storages/inRedis/RedisAdapter.js +9 -2
  23. package/esm/sync/streaming/SSEClient/index.js +2 -1
  24. package/esm/trackers/eventTracker.js +8 -1
  25. package/esm/trackers/impressionObserver/utils.js +7 -1
  26. package/esm/trackers/impressionsTracker.js +6 -5
  27. package/esm/utils/lang/maps.js +14 -1
  28. package/esm/utils/settingsValidation/index.js +20 -4
  29. package/package.json +1 -1
  30. package/src/integrations/pluggable.ts +2 -2
  31. package/src/sdkClient/client.ts +5 -6
  32. package/src/sdkClient/clientCS.ts +1 -1
  33. package/src/sdkClient/clientInputValidation.ts +8 -7
  34. package/src/sdkClient/sdkClient.ts +2 -5
  35. package/src/sdkClient/sdkClientMethodCS.ts +1 -6
  36. package/src/sdkClient/sdkClientMethodCSWithTT.ts +2 -11
  37. package/src/sdkFactory/index.ts +3 -3
  38. package/src/sdkFactory/types.ts +0 -1
  39. package/src/storages/inRedis/RedisAdapter.ts +8 -2
  40. package/src/sync/streaming/SSEClient/index.ts +2 -1
  41. package/src/trackers/eventTracker.ts +11 -3
  42. package/src/trackers/impressionObserver/utils.ts +8 -1
  43. package/src/trackers/impressionsTracker.ts +7 -8
  44. package/src/types.ts +1 -1
  45. package/src/utils/lang/maps.ts +15 -1
  46. package/src/utils/settingsValidation/consent.ts +2 -1
  47. package/src/utils/settingsValidation/index.ts +20 -4
  48. package/src/utils/settingsValidation/types.ts +3 -1
  49. package/types/sdkClient/clientInputValidation.d.ts +2 -3
  50. package/types/sdkFactory/types.d.ts +0 -1
  51. package/types/storages/inRedis/RedisAdapter.d.ts +1 -1
  52. package/types/trackers/eventTracker.d.ts +2 -2
  53. package/types/trackers/impressionObserver/utils.d.ts +4 -0
  54. package/types/trackers/impressionsTracker.d.ts +2 -3
  55. package/types/types.d.ts +1 -1
  56. package/types/utils/lang/maps.d.ts +7 -0
  57. package/types/utils/settingsValidation/consent.d.ts +3 -2
  58. package/types/utils/settingsValidation/types.d.ts +3 -1
@@ -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
  }
@@ -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,
@@ -80,7 +82,7 @@ function fromSecondsToMillis(n) {
80
82
  * @param validationParams defaults and fields validators used to validate and creates a settings object from a given config
81
83
  */
82
84
  export function settingsValidation(config, validationParams) {
83
- var defaults = validationParams.defaults, runtime = validationParams.runtime, storage = validationParams.storage, integrations = validationParams.integrations, logger = validationParams.logger, localhost = validationParams.localhost, consent = validationParams.consent;
85
+ var defaults = validationParams.defaults, isClientSide = validationParams.isClientSide, runtime = validationParams.runtime, storage = validationParams.storage, integrations = validationParams.integrations, logger = validationParams.logger, localhost = validationParams.localhost, consent = validationParams.consent;
84
86
  // creates a settings object merging base, defaults and config objects.
85
87
  var withDefaults = merge({}, base, defaults, config);
86
88
  // ensure a valid logger.
@@ -106,9 +108,23 @@ 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
+ // In client-side, validate key and TT
112
+ if (isClientSide) {
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
+ var maybeTT = withDefaults.core.trafficType;
125
+ if (maybeTT !== undefined) { // @ts-ignore, assigning false
126
+ withDefaults.core.trafficType = validateTrafficType(log, maybeTT, 'Client instantiation');
127
+ }
112
128
  }
113
129
  // Current ip/hostname information
114
130
  // @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.6",
3
+ "version": "1.2.1-rc.9",
4
4
  "description": "Split Javascript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
@@ -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
  };
@@ -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 { IClientFactoryParams } from './types';
9
9
  import { IEvaluationResult } from '../evaluator/types';
10
10
  import { SplitIO, ImpressionDTO } from '../types';
@@ -23,7 +23,7 @@ export function clientFactory(params: IClientFactoryParams): SplitIO.IClient | S
23
23
  const wrapUp = (evaluationResult: IEvaluationResult) => {
24
24
  const queue: ImpressionDTO[] = [];
25
25
  const treatment = processEvaluation(evaluationResult, splitName, key, attributes, withConfig, `getTreatment${withConfig ? 'withConfig' : ''}`, queue);
26
- if (settings.userConsent !== CONSENT_DECLINED) impressionsTracker.track(queue, attributes);
26
+ impressionsTracker.track(queue, attributes);
27
27
  return treatment;
28
28
  };
29
29
 
@@ -43,7 +43,7 @@ export function clientFactory(params: IClientFactoryParams): SplitIO.IClient | S
43
43
  Object.keys(evaluationResults).forEach(splitName => {
44
44
  treatments[splitName] = processEvaluation(evaluationResults[splitName], splitName, key, attributes, withConfig, `getTreatments${withConfig ? 'withConfig' : ''}`, queue);
45
45
  });
46
- if (settings.userConsent !== CONSENT_DECLINED) impressionsTracker.track(queue, attributes);
46
+ impressionsTracker.track(queue, attributes);
47
47
  return treatments;
48
48
  };
49
49
 
@@ -116,8 +116,7 @@ export function clientFactory(params: IClientFactoryParams): SplitIO.IClient | S
116
116
  // 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.
117
117
  validateTrafficTypeExistance(log, readinessManager, storage.splits, mode, trafficTypeName, 'track');
118
118
 
119
- if (settings.userConsent !== CONSENT_DECLINED) return eventTracker.track(eventData, size);
120
- else return false;
119
+ return eventTracker.track(eventData, size);
121
120
  }
122
121
 
123
122
  return {
@@ -126,6 +125,6 @@ export function clientFactory(params: IClientFactoryParams): SplitIO.IClient | S
126
125
  getTreatments,
127
126
  getTreatmentsWithConfig,
128
127
  track,
129
- isBrowserClient: false
128
+ isClientSide: false
130
129
  } as SplitIO.IClient | SplitIO.IAsyncClient;
131
130
  }
@@ -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
  }
@@ -15,14 +15,17 @@ import { startsWith } from '../utils/lang';
15
15
  import { CONTROL, CONTROL_WITH_CONFIG } from '../utils/constants';
16
16
  import { IReadinessManager } from '../readiness/types';
17
17
  import { MaybeThenable } from '../dtos/types';
18
- import { SplitIO } from '../types';
19
- import { ILogger } from '../logger/types';
18
+ import { ISettings, SplitIO } from '../types';
19
+ import { isStorageSync } from '../trackers/impressionObserver/utils';
20
20
 
21
21
  /**
22
22
  * Decorator that validates the input before actually executing the client methods.
23
23
  * We should "guard" the client here, while not polluting the "real" implementation of those methods.
24
24
  */
25
- export function clientInputValidationDecorator<TClient extends SplitIO.IClient | SplitIO.IAsyncClient>(log: ILogger, client: TClient, readinessManager: IReadinessManager, isStorageSync = false): TClient {
25
+ export function clientInputValidationDecorator<TClient extends SplitIO.IClient | SplitIO.IAsyncClient>(settings: ISettings, client: TClient, readinessManager: IReadinessManager): TClient {
26
+
27
+ const log = settings.log;
28
+ const isSync = isStorageSync(settings);
26
29
 
27
30
  /**
28
31
  * Avoid repeating this validations code
@@ -47,8 +50,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
47
50
  }
48
51
 
49
52
  function wrapResult<T>(value: T): MaybeThenable<T> {
50
- if (isStorageSync) return value;
51
- return Promise.resolve(value);
53
+ return isSync ? value : Promise.resolve(value);
52
54
  }
53
55
 
54
56
  function getTreatment(maybeKey: SplitIO.SplitKey, maybeSplit: string, maybeAttributes?: SplitIO.Attributes) {
@@ -108,8 +110,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
108
110
  if (isOperational && key && tt && event && eventValue !== false && properties !== false) { // @ts-expect-error
109
111
  return client.track(key, tt, event, eventValue, properties, size);
110
112
  } else {
111
- if (isStorageSync) return false;
112
- return Promise.resolve(false);
113
+ return isSync ? false : Promise.resolve(false);
113
114
  }
114
115
  }
115
116
 
@@ -1,6 +1,5 @@
1
1
  import { objectAssign } from '../utils/lang/objectAssign';
2
2
  import { IStatusInterface, SplitIO } from '../types';
3
- import { CONSUMER_MODE, CONSUMER_PARTIAL_MODE } from '../utils/constants';
4
3
  import { releaseApiKey } from '../utils/inputValidation/apiKey';
5
4
  import { clientFactory } from './client';
6
5
  import { clientInputValidationDecorator } from './clientInputValidation';
@@ -18,11 +17,9 @@ export function sdkClientFactory(params: ISdkClientFactoryParams): SplitIO.IClie
18
17
 
19
18
  // Client API (getTreatment* & track methods)
20
19
  clientInputValidationDecorator(
21
- settings.log,
20
+ settings,
22
21
  clientFactory(params),
23
- sdkReadinessManager.readinessManager,
24
- // storage is async if and only if mode is consumer or partial consumer
25
- [CONSUMER_MODE, CONSUMER_PARTIAL_MODE].indexOf(settings.mode) === -1 ? true : false,
22
+ sdkReadinessManager.readinessManager
26
23
  ),
27
24
 
28
25
  // Sdk destroy
@@ -23,15 +23,10 @@ const method = 'Client instantiation';
23
23
  export function sdkClientMethodCSFactory(params: ISdkClientFactoryParams): (key?: SplitIO.SplitKey) => SplitIO.ICsClient {
24
24
  const { storage, syncManager, sdkReadinessManager, settings: { core: { key }, startup: { readyTimeout }, log } } = params;
25
25
 
26
- // Keeping similar behaviour as in the isomorphic JS SDK: if settings key is invalid,
27
- // `false` value is used as binded key of the default client, but trafficType is ignored
28
- // @TODO handle as a non-recoverable error
29
- const validKey = validateKey(log, key, method);
30
-
31
26
  const mainClientInstance = clientCSDecorator(
32
27
  log,
33
28
  sdkClientFactory(params) as SplitIO.IClient, // @ts-ignore
34
- validKey
29
+ key
35
30
  );
36
31
 
37
32
  const parsedDefaultKey = keyParser(key);
@@ -25,20 +25,11 @@ const method = 'Client instantiation';
25
25
  export function sdkClientMethodCSFactory(params: ISdkClientFactoryParams): (key?: SplitIO.SplitKey, trafficType?: string) => SplitIO.ICsClient {
26
26
  const { storage, syncManager, sdkReadinessManager, settings: { core: { key, trafficType }, startup: { readyTimeout }, log } } = params;
27
27
 
28
- // Keeping the behaviour as in the isomorphic JS SDK: if settings key or TT are invalid,
29
- // `false` value is used as binded key/TT of the default client, which leads to several issues.
30
- // @TODO update when supporting non-recoverable errors
31
- const validKey = validateKey(log, key, method);
32
- let validTrafficType;
33
- if (trafficType !== undefined) {
34
- validTrafficType = validateTrafficType(log, trafficType, method);
35
- }
36
-
37
28
  const mainClientInstance = clientCSDecorator(
38
29
  log,
39
30
  sdkClientFactory(params) as SplitIO.IClient, // @ts-ignore
40
- validKey,
41
- validTrafficType
31
+ key,
32
+ trafficType
42
33
  );
43
34
 
44
35
  const parsedDefaultKey = keyParser(key);
@@ -20,7 +20,7 @@ import { objectAssign } from '../utils/lang/objectAssign';
20
20
  export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.ISDK | SplitIO.IAsyncSDK {
21
21
 
22
22
  const { settings, platform, storageFactory, splitApiFactory, extraProps,
23
- syncManagerFactory, SignalListener, impressionsObserverFactory, impressionListener,
23
+ syncManagerFactory, SignalListener, impressionsObserverFactory,
24
24
  integrationsManagerFactory, sdkManagerFactory, sdkClientMethodFactory } = params;
25
25
  const log = settings.log;
26
26
 
@@ -74,8 +74,8 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
74
74
 
75
75
  // trackers
76
76
  const observer = impressionsObserverFactory && impressionsObserverFactory();
77
- const impressionsTracker = impressionsTrackerFactory(log, storage.impressions, settings, impressionListener, integrationsManager, observer, storage.impressionCounts);
78
- const eventTracker = eventTrackerFactory(log, storage.events, integrationsManager);
77
+ const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, integrationsManager, observer, storage.impressionCounts);
78
+ const eventTracker = eventTrackerFactory(settings, storage.events, integrationsManager);
79
79
 
80
80
  // signal listener
81
81
  const signalListener = SignalListener && new SignalListener(syncManager, settings, storage, splitApi);
@@ -64,7 +64,6 @@ export interface ISdkFactoryParams {
64
64
  serviceApi: ISplitApi | undefined) => ISignalListener, // Used by BrowserSignalListener
65
65
 
66
66
  // @TODO review impressionListener and integrations interfaces. What about handling impressionListener as an integration ?
67
- impressionListener?: SplitIO.IImpressionListener,
68
67
  integrationsManagerFactory?: (params: IIntegrationFactoryParams) => IIntegrationManager | undefined,
69
68
 
70
69
  // Impression observer factory. If provided, will be used for impressions dedupe
@@ -164,6 +164,12 @@ export class RedisAdapter extends ioredis {
164
164
  } else { // If it IS the string URL, that'll be the first param for ioredis.
165
165
  result.unshift(options.url);
166
166
  }
167
+ if (options.connectionTimeout) {
168
+ merge(opts, { connectTimeout: options.connectionTimeout });
169
+ }
170
+ if (options.tls) {
171
+ merge(opts, { tls: options.tls });
172
+ }
167
173
 
168
174
  return result;
169
175
  }
@@ -171,9 +177,9 @@ export class RedisAdapter extends ioredis {
171
177
  /**
172
178
  * Parses the options into what we care about.
173
179
  */
174
- static _defineOptions({ connectionTimeout, operationTimeout, url, host, port, db, pass }: Record<string, any>) {
180
+ static _defineOptions({ connectionTimeout, operationTimeout, url, host, port, db, pass, tls }: Record<string, any>) {
175
181
  const parsedOptions = {
176
- connectionTimeout, operationTimeout, url, host, port, db, pass
182
+ connectionTimeout, operationTimeout, url, host, port, db, pass, tls
177
183
  };
178
184
 
179
185
  return merge({}, DEFAULT_OPTIONS, parsedOptions);
@@ -1,5 +1,6 @@
1
1
  import { IEventSourceConstructor } from '../../../services/types';
2
2
  import { ISettings } from '../../../types';
3
+ import { isString } from '../../../utils/lang';
3
4
  import { IAuthTokenPushEnabled } from '../AuthClient/types';
4
5
  import { ISSEClient, ISseEventHandler } from './types';
5
6
 
@@ -15,7 +16,7 @@ const CONTROL_CHANNEL_REGEX = /^control_/;
15
16
  */
16
17
  function buildSSEHeaders(settings: ISettings) {
17
18
  const headers: Record<string, string> = {
18
- SplitSDKClientKey: settings.core.authorizationKey.slice(-4),
19
+ SplitSDKClientKey: isString(settings.core.authorizationKey) ? settings.core.authorizationKey.slice(-4) : '',
19
20
  SplitSDKVersion: settings.version,
20
21
  };
21
22
 
@@ -2,9 +2,10 @@ import { objectAssign } from '../utils/lang/objectAssign';
2
2
  import { thenable } from '../utils/promise/thenable';
3
3
  import { IEventsCacheBase } from '../storages/types';
4
4
  import { IEventsHandler, IEventTracker } from './types';
5
- import { SplitIO } from '../types';
6
- import { ILogger } from '../logger/types';
5
+ import { ISettings, SplitIO } from '../types';
7
6
  import { EVENTS_TRACKER_SUCCESS, ERROR_EVENTS_TRACKER } from '../logger/constants';
7
+ import { CONSENT_DECLINED } from '../utils/constants';
8
+ import { isStorageSync } from './impressionObserver/utils';
8
9
 
9
10
  /**
10
11
  * Event tracker stores events in cache and pass them to the integrations manager if provided.
@@ -13,11 +14,14 @@ import { EVENTS_TRACKER_SUCCESS, ERROR_EVENTS_TRACKER } from '../logger/constant
13
14
  * @param integrationsManager optional event handler used for integrations
14
15
  */
15
16
  export function eventTrackerFactory(
16
- log: ILogger,
17
+ settings: ISettings,
17
18
  eventsCache: IEventsCacheBase,
18
19
  integrationsManager?: IEventsHandler
19
20
  ): IEventTracker {
20
21
 
22
+ const log = settings.log;
23
+ const isSync = isStorageSync(settings);
24
+
21
25
  function queueEventsCallback(eventData: SplitIO.EventData, tracked: boolean) {
22
26
  const { eventTypeId, trafficTypeName, key, value, timestamp, properties } = eventData;
23
27
  // Logging every prop would be too much.
@@ -44,6 +48,10 @@ export function eventTrackerFactory(
44
48
 
45
49
  return {
46
50
  track(eventData: SplitIO.EventData, size?: number) {
51
+ if (settings.userConsent === CONSENT_DECLINED) {
52
+ return isSync ? false : Promise.resolve(false);
53
+ }
54
+
47
55
  const tracked = eventsCache.track(eventData, size);
48
56
 
49
57
  if (thenable(tracked)) {
@@ -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
  import { ISettings } from '../../types';
3
3
 
4
4
  /**
@@ -15,3 +15,10 @@ export function shouldBeOptimized(settings: ISettings) {
15
15
  if (!shouldAddPt(settings)) return false;
16
16
  return settings.sync.impressionsMode === OPTIMIZED ? true : false;
17
17
  }
18
+
19
+ /**
20
+ * Storage is async if mode is consumer or partial consumer
21
+ */
22
+ export function isStorageSync(settings: ISettings) {
23
+ return [CONSUMER_MODE, CONSUMER_PARTIAL_MODE].indexOf(settings.mode) === -1 ? true : false;
24
+ }
@@ -5,8 +5,8 @@ import { IImpressionCountsCacheSync, IImpressionsCacheBase } from '../storages/t
5
5
  import { IImpressionsHandler, IImpressionsTracker } from './types';
6
6
  import { SplitIO, ImpressionDTO, ISettings } from '../types';
7
7
  import { IImpressionObserver } from './impressionObserver/types';
8
- import { ILogger } from '../logger/types';
9
8
  import { IMPRESSIONS_TRACKER_SUCCESS, ERROR_IMPRESSIONS_TRACKER, ERROR_IMPRESSIONS_LISTENER } from '../logger/constants';
9
+ import { CONSENT_DECLINED } from '../utils/constants';
10
10
 
11
11
  /**
12
12
  * Impressions tracker stores impressions in cache and pass them to the listener and integrations manager if provided.
@@ -19,22 +19,21 @@ import { IMPRESSIONS_TRACKER_SUCCESS, ERROR_IMPRESSIONS_TRACKER, ERROR_IMPRESSIO
19
19
  * @param countsCache optional cache to save impressions count. If provided, impressions will be deduped (OPTIMIZED mode)
20
20
  */
21
21
  export function impressionsTrackerFactory(
22
- log: ILogger,
22
+ settings: ISettings,
23
23
  impressionsCache: IImpressionsCacheBase,
24
-
25
- // @TODO consider passing only an optional integrationsManager to handle impressions
26
- { runtime: { ip, hostname }, version }: Pick<ISettings, 'version' | 'runtime'>,
27
- impressionListener?: SplitIO.IImpressionListener,
28
24
  integrationsManager?: IImpressionsHandler,
29
-
30
25
  // if observer is provided, it implies `shouldAddPreviousTime` flag (i.e., if impressions previous time should be added or not)
31
26
  observer?: IImpressionObserver,
32
27
  // if countsCache is provided, it implies `isOptimized` flag (i.e., if impressions should be deduped or not)
33
28
  countsCache?: IImpressionCountsCacheSync
34
29
  ): IImpressionsTracker {
35
30
 
31
+ const { log, impressionListener, runtime: { ip, hostname }, version } = settings;
32
+
36
33
  return {
37
34
  track(impressions: ImpressionDTO[], attributes?: SplitIO.Attributes) {
35
+ if (settings.userConsent === CONSENT_DECLINED) return;
36
+
38
37
  const impressionsCount = impressions.length;
39
38
 
40
39
  const impressionsToStore: ImpressionDTO[] = []; // Track only the impressions that are going to be stored
@@ -85,7 +84,7 @@ export function impressionsTrackerFactory(
85
84
  // integrationsManager.handleImpression does not throw errors
86
85
  if (integrationsManager) integrationsManager.handleImpression(impressionData);
87
86
 
88
- try { // An exception on the listeners should not break the SDK.
87
+ try { // @ts-ignore. An exception on the listeners should not break the SDK.
89
88
  if (impressionListener) impressionListener.logImpression(impressionData);
90
89
  } catch (err) {
91
90
  log.error(ERROR_IMPRESSIONS_LISTENER, [err]);
package/src/types.ts CHANGED
@@ -398,7 +398,7 @@ interface IBasicClient extends IStatusInterface {
398
398
 
399
399
  // Whether the client implements the client-side API, i.e, with bound key, (true), or the server-side API (false).
400
400
  // Exposed for internal purposes only. Not considered part of the public API, and might be renamed eventually.
401
- isBrowserClient: boolean
401
+ isClientSide: boolean
402
402
  }
403
403
  /**
404
404
  * Common definitions between SDK instances for different environments interface.
@@ -79,4 +79,18 @@ interface IMapConstructor {
79
79
  readonly prototype: IMap<any, any>;
80
80
  }
81
81
 
82
- export const _Map: IMapConstructor = typeof Map !== 'undefined' ? Map : MapPoly;
82
+ /**
83
+ * return the Map constructor to use. If native Map is not available or it doesn't support the required features (e.g., IE11),
84
+ * a ponyfill with minimal features is returned instead.
85
+ *
86
+ * Exported for testing purposes only.
87
+ */
88
+ export function __getMapConstructor(): IMapConstructor {
89
+ // eslint-disable-next-line compat/compat
90
+ if (typeof Array.from === 'function' && typeof Map === 'function' && Map.prototype && Map.prototype.values) {
91
+ return Map;
92
+ }
93
+ return MapPoly;
94
+ }
95
+
96
+ export const _Map = __getMapConstructor();
@@ -1,11 +1,12 @@
1
1
  import { ERROR_INVALID_CONFIG_PARAM } from '../../logger/constants';
2
2
  import { ILogger } from '../../logger/types';
3
+ import { ConsentStatus } from '../../types';
3
4
  import { CONSENT_DECLINED, CONSENT_GRANTED, CONSENT_UNKNOWN } from '../constants';
4
5
  import { stringToUpperCase } from '../lang';
5
6
 
6
7
  const userConsentValues = [CONSENT_DECLINED, CONSENT_GRANTED, CONSENT_UNKNOWN];
7
8
 
8
- export function validateConsent({ userConsent, log }: { userConsent: any, log: ILogger }) {
9
+ export function validateConsent({ userConsent, log }: { userConsent?: any, log: ILogger }): ConsentStatus {
9
10
  userConsent = stringToUpperCase(userConsent);
10
11
 
11
12
  if (userConsentValues.indexOf(userConsent) > -1) return userConsent;
@@ -5,6 +5,8 @@ import { STANDALONE_MODE, OPTIMIZED, LOCALHOST_MODE } from '../constants';
5
5
  import { validImpressionsMode } from './impressionsMode';
6
6
  import { ISettingsValidationParams } from './types';
7
7
  import { ISettings } from '../../types';
8
+ import { validateKey } from '../inputValidation/key';
9
+ import { validateTrafficType } from '../inputValidation/trafficType';
8
10
 
9
11
  const base = {
10
12
  // Define which kind of object you want to retrieve from SplitFactory
@@ -97,7 +99,7 @@ function fromSecondsToMillis(n: number) {
97
99
  */
98
100
  export function settingsValidation(config: unknown, validationParams: ISettingsValidationParams) {
99
101
 
100
- const { defaults, runtime, storage, integrations, logger, localhost, consent } = validationParams;
102
+ const { defaults, isClientSide, runtime, storage, integrations, logger, localhost, consent } = validationParams;
101
103
 
102
104
  // creates a settings object merging base, defaults and config objects.
103
105
  const withDefaults = merge({}, base, defaults, config) as ISettings;
@@ -129,9 +131,23 @@ export function settingsValidation(config: unknown, validationParams: ISettingsV
129
131
  // @ts-ignore, modify readonly prop
130
132
  if (storage) withDefaults.storage = storage(withDefaults);
131
133
 
132
- // 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.
133
- if (withDefaults.mode === LOCALHOST_MODE && withDefaults.core.key === undefined) {
134
- withDefaults.core.key = 'localhost_key';
134
+ // In client-side, validate key and TT
135
+ if (isClientSide) {
136
+ const maybeKey = withDefaults.core.key;
137
+ // Although `key` is required in client-side, it can be omitted in LOCALHOST mode. In that case, the value `localhost_key` is used.
138
+ if (withDefaults.mode === LOCALHOST_MODE && maybeKey === undefined) {
139
+ withDefaults.core.key = 'localhost_key';
140
+ } else {
141
+ // Keeping same behaviour than JS SDK: if settings key or TT are invalid,
142
+ // `false` value is used as binded key/TT of the default client, which leads to some issues.
143
+ // @ts-ignore, @TODO handle invalid keys as a non-recoverable error?
144
+ withDefaults.core.key = validateKey(log, maybeKey, 'Client instantiation');
145
+ }
146
+
147
+ const maybeTT = withDefaults.core.trafficType;
148
+ if (maybeTT !== undefined) { // @ts-ignore, assigning false
149
+ withDefaults.core.trafficType = validateTrafficType(log, maybeTT, 'Client instantiation');
150
+ }
135
151
  }
136
152
 
137
153
  // Current ip/hostname information
@@ -10,7 +10,9 @@ export interface ISettingsValidationParams {
10
10
  * Version and startup properties are required, because they are not defined in the base settings.
11
11
  */
12
12
  defaults: Partial<ISettings> & { version: string } & { startup: ISettings['startup'] },
13
- /** Function to define runtime values (`settings.runtime`) */
13
+ /** If true, validates core.key and core.trafficType */
14
+ isClientSide?: boolean,
15
+ /** Define runtime values (`settings.runtime`) */
14
16
  runtime: (settings: ISettings) => ISettings['runtime'],
15
17
  /** Storage validator (`settings.storage`) */
16
18
  storage?: (settings: ISettings) => ISettings['storage'],
@@ -1,8 +1,7 @@
1
1
  import { IReadinessManager } from '../readiness/types';
2
- import { SplitIO } from '../types';
3
- import { ILogger } from '../logger/types';
2
+ import { ISettings, SplitIO } from '../types';
4
3
  /**
5
4
  * Decorator that validates the input before actually executing the client methods.
6
5
  * We should "guard" the client here, while not polluting the "real" implementation of those methods.
7
6
  */
8
- export declare function clientInputValidationDecorator<TClient extends SplitIO.IClient | SplitIO.IAsyncClient>(log: ILogger, client: TClient, readinessManager: IReadinessManager, isStorageSync?: boolean): TClient;
7
+ export declare function clientInputValidationDecorator<TClient extends SplitIO.IClient | SplitIO.IAsyncClient>(settings: ISettings, client: TClient, readinessManager: IReadinessManager): TClient;
@@ -36,7 +36,6 @@ export interface ISdkFactoryParams {
36
36
  settings: ISettings, // Used by BrowserSignalListener
37
37
  storage: IStorageSync | IStorageAsync, // Used by BrowserSignalListener
38
38
  serviceApi: ISplitApi | undefined) => ISignalListener;
39
- impressionListener?: SplitIO.IImpressionListener;
40
39
  integrationsManagerFactory?: (params: IIntegrationFactoryParams) => IIntegrationManager | undefined;
41
40
  impressionsObserverFactory?: () => IImpressionObserver;
42
41
  extraProps?: (settings: ISettings, syncManager?: ISyncManager) => object;
@@ -20,5 +20,5 @@ export declare class RedisAdapter extends ioredis {
20
20
  /**
21
21
  * Parses the options into what we care about.
22
22
  */
23
- static _defineOptions({ connectionTimeout, operationTimeout, url, host, port, db, pass }: Record<string, any>): object;
23
+ static _defineOptions({ connectionTimeout, operationTimeout, url, host, port, db, pass, tls }: Record<string, any>): object;
24
24
  }
@@ -1,10 +1,10 @@
1
1
  import { IEventsCacheBase } from '../storages/types';
2
2
  import { IEventsHandler, IEventTracker } from './types';
3
- import { ILogger } from '../logger/types';
3
+ import { ISettings } from '../types';
4
4
  /**
5
5
  * Event tracker stores events in cache and pass them to the integrations manager if provided.
6
6
  *
7
7
  * @param eventsCache cache to save events
8
8
  * @param integrationsManager optional event handler used for integrations
9
9
  */
10
- export declare function eventTrackerFactory(log: ILogger, eventsCache: IEventsCacheBase, integrationsManager?: IEventsHandler): IEventTracker;
10
+ export declare function eventTrackerFactory(settings: ISettings, eventsCache: IEventsCacheBase, integrationsManager?: IEventsHandler): IEventTracker;
@@ -7,3 +7,7 @@ export declare function shouldAddPt(settings: ISettings): boolean;
7
7
  * Checks if it should dedupe impressions or not.
8
8
  */
9
9
  export declare function shouldBeOptimized(settings: ISettings): boolean;
10
+ /**
11
+ * Storage is async if mode is consumer or partial consumer
12
+ */
13
+ export declare function isStorageSync(settings: ISettings): boolean;