@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
@@ -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.'],
@@ -4,18 +4,17 @@ 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';
8
- import { IClientFactoryParams } from './types';
7
+ import { CONTROL } from '../utils/constants';
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
 
@@ -23,7 +22,7 @@ export function clientFactory(params: IClientFactoryParams): SplitIO.IClient | S
23
22
  const wrapUp = (evaluationResult: IEvaluationResult) => {
24
23
  const queue: ImpressionDTO[] = [];
25
24
  const treatment = processEvaluation(evaluationResult, splitName, key, attributes, withConfig, `getTreatment${withConfig ? 'withConfig' : ''}`, queue);
26
- if (settings.userConsent !== CONSENT_DECLINED) impressionsTracker.track(queue, attributes);
25
+ impressionsTracker.track(queue, attributes);
27
26
  return treatment;
28
27
  };
29
28
 
@@ -43,7 +42,7 @@ export function clientFactory(params: IClientFactoryParams): SplitIO.IClient | S
43
42
  Object.keys(evaluationResults).forEach(splitName => {
44
43
  treatments[splitName] = processEvaluation(evaluationResults[splitName], splitName, key, attributes, withConfig, `getTreatments${withConfig ? 'withConfig' : ''}`, queue);
45
44
  });
46
- if (settings.userConsent !== CONSENT_DECLINED) impressionsTracker.track(queue, attributes);
45
+ impressionsTracker.track(queue, attributes);
47
46
  return treatments;
48
47
  };
49
48
 
@@ -116,8 +115,7 @@ export function clientFactory(params: IClientFactoryParams): SplitIO.IClient | S
116
115
  // 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
116
  validateTrafficTypeExistance(log, readinessManager, storage.splits, mode, trafficTypeName, 'track');
118
117
 
119
- if (settings.userConsent !== CONSENT_DECLINED) return eventTracker.track(eventData, size);
120
- else return false;
118
+ return eventTracker.track(eventData, size);
121
119
  }
122
120
 
123
121
  return {
@@ -126,6 +124,6 @@ export function clientFactory(params: IClientFactoryParams): SplitIO.IClient | S
126
124
  getTreatments,
127
125
  getTreatmentsWithConfig,
128
126
  track,
129
- isBrowserClient: false
127
+ isClientSide: false
130
128
  } as SplitIO.IClient | SplitIO.IAsyncClient;
131
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
  }
@@ -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,16 +1,15 @@
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';
7
- import { ISdkClientFactoryParams } from './types';
6
+ import { ISdkFactoryContext } from '../sdkFactory/types';
8
7
 
9
8
  /**
10
9
  * Creates an Sdk client, i.e., a base client with status and destroy interface
11
10
  */
12
- export function sdkClientFactory(params: ISdkClientFactoryParams): SplitIO.IClient | SplitIO.IAsyncClient {
13
- 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;
14
13
 
15
14
  return objectAssign(
16
15
  // Proto-linkage of the readiness Event Emitter
@@ -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
@@ -38,7 +35,7 @@ export function sdkClientFactory(params: ISdkClientFactoryParams): SplitIO.IClie
38
35
  signalListener && signalListener.stop();
39
36
 
40
37
  // Release the API Key if it is the main client
41
- if (!sharedClient) releaseApiKey(settings.core.authorizationKey);
38
+ if (!isSharedClient) releaseApiKey(settings.core.authorizationKey);
42
39
 
43
40
  // Cleanup storage
44
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
 
@@ -1,5 +1,4 @@
1
1
  import { clientCSDecorator } from './clientCS';
2
- import { ISdkClientFactoryParams } from './types';
3
2
  import { SplitIO } from '../types';
4
3
  import { validateKey } from '../utils/inputValidation/key';
5
4
  import { getMatching, keyParser } from '../utils/key';
@@ -8,6 +7,7 @@ import { ISyncManagerCS } from '../sync/types';
8
7
  import { objectAssign } from '../utils/lang/objectAssign';
9
8
  import { RETRIEVE_CLIENT_DEFAULT, NEW_SHARED_CLIENT, RETRIEVE_CLIENT_EXISTING } from '../logger/constants';
10
9
  import { SDK_SEGMENTS_ARRIVED } from '../readiness/constants';
10
+ import { ISdkFactoryContext } from '../sdkFactory/types';
11
11
 
12
12
  function buildInstanceId(key: SplitIO.SplitKey) {
13
13
  // @ts-ignore
@@ -20,18 +20,13 @@ const method = 'Client instantiation';
20
20
  * Factory of client method for the client-side API variant where TT is ignored and thus
21
21
  * clients don't have a binded TT for the track method.
22
22
  */
23
- export function sdkClientMethodCSFactory(params: ISdkClientFactoryParams): (key?: SplitIO.SplitKey) => SplitIO.ICsClient {
23
+ export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (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
- sdkClientFactory(params) as SplitIO.IClient, // @ts-ignore
34
- validKey
28
+ sdkClientFactory(params) as SplitIO.IClient,
29
+ key
35
30
  );
36
31
 
37
32
  const parsedDefaultKey = keyParser(key);
@@ -81,8 +76,7 @@ export function sdkClientMethodCSFactory(params: ISdkClientFactoryParams): (key?
81
76
  storage: sharedStorage || storage,
82
77
  syncManager: sharedSyncManager,
83
78
  signalListener: undefined, // only the main client "destroy" method stops the signal listener
84
- sharedClient: true
85
- })) as SplitIO.IClient,
79
+ }), true) as SplitIO.IClient,
86
80
  validKey
87
81
  );
88
82
 
@@ -1,5 +1,4 @@
1
1
  import { clientCSDecorator } from './clientCS';
2
- import { ISdkClientFactoryParams } from './types';
3
2
  import { SplitIO } from '../types';
4
3
  import { validateKey } from '../utils/inputValidation/key';
5
4
  import { validateTrafficType } from '../utils/inputValidation/trafficType';
@@ -9,6 +8,7 @@ import { ISyncManagerCS } from '../sync/types';
9
8
  import { objectAssign } from '../utils/lang/objectAssign';
10
9
  import { RETRIEVE_CLIENT_DEFAULT, NEW_SHARED_CLIENT, RETRIEVE_CLIENT_EXISTING } from '../logger/constants';
11
10
  import { SDK_SEGMENTS_ARRIVED } from '../readiness/constants';
11
+ import { ISdkFactoryContext } from '../sdkFactory/types';
12
12
 
13
13
  function buildInstanceId(key: SplitIO.SplitKey, trafficType?: string) {
14
14
  // @ts-ignore
@@ -22,23 +22,14 @@ const method = 'Client instantiation';
22
22
  * where clients can have a binded TT for the track method, which is provided via the settings
23
23
  * (default client) or the client method (shared clients).
24
24
  */
25
- export function sdkClientMethodCSFactory(params: ISdkClientFactoryParams): (key?: SplitIO.SplitKey, trafficType?: string) => SplitIO.ICsClient {
25
+ export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (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
- sdkClientFactory(params) as SplitIO.IClient, // @ts-ignore
40
- validKey,
41
- validTrafficType
30
+ sdkClientFactory(params) as SplitIO.IClient,
31
+ key,
32
+ trafficType
42
33
  );
43
34
 
44
35
  const parsedDefaultKey = keyParser(key);
@@ -95,8 +86,7 @@ export function sdkClientMethodCSFactory(params: ISdkClientFactoryParams): (key?
95
86
  storage: sharedStorage || storage,
96
87
  syncManager: sharedSyncManager,
97
88
  signalListener: undefined, // only the main client "destroy" method stops the signal listener
98
- sharedClient: true
99
- })) as SplitIO.IClient,
89
+ }), true) as SplitIO.IClient,
100
90
  validKey,
101
91
  validTrafficType
102
92
  );
@@ -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,14 +74,15 @@ 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);
82
82
 
83
83
  // Sdk client and manager
84
- const clientMethod = sdkClientMethodFactory({ eventTracker, impressionsTracker, sdkReadinessManager, settings, storage, syncManager, signalListener });
84
+ const ctx = { eventTracker, impressionsTracker, sdkReadinessManager, settings, storage, syncManager, signalListener };
85
+ const clientMethod = sdkClientMethodFactory(ctx);
85
86
  const managerInstance = sdkManagerFactory(log, storage.splits, sdkReadinessManager);
86
87
 
87
88
  syncManager && syncManager.start();
@@ -104,5 +105,5 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
104
105
  Logger: createLoggerAPI(settings.log),
105
106
 
106
107
  settings,
107
- }, extraProps && extraProps(settings, syncManager));
108
+ }, extraProps && extraProps(ctx));
108
109
  }
@@ -2,13 +2,23 @@ import { IIntegrationManager, IIntegrationFactoryParams } from '../integrations/
2
2
  import { ISignalListener } from '../listeners/types';
3
3
  import { ILogger } from '../logger/types';
4
4
  import { ISdkReadinessManager } from '../readiness/types';
5
- import { ISdkClientFactoryParams } from '../sdkClient/types';
6
5
  import { IFetch, ISplitApi, IEventSourceConstructor } from '../services/types';
7
6
  import { IStorageAsync, IStorageSync, ISplitsCacheSync, ISplitsCacheAsync, IStorageFactoryParams } from '../storages/types';
8
7
  import { ISyncManager, ISyncManagerFactoryParams } from '../sync/types';
9
8
  import { IImpressionObserver } from '../trackers/impressionObserver/types';
9
+ import { IImpressionsTracker, IEventTracker } from '../trackers/types';
10
10
  import { SplitIO, ISettings, IEventEmitter } from '../types';
11
11
 
12
+ export interface ISdkFactoryContext {
13
+ storage: IStorageSync | IStorageAsync,
14
+ sdkReadinessManager: ISdkReadinessManager,
15
+ settings: ISettings
16
+ impressionsTracker: IImpressionsTracker,
17
+ eventTracker: IEventTracker,
18
+ signalListener?: ISignalListener
19
+ syncManager?: ISyncManager,
20
+ }
21
+
12
22
  /**
13
23
  * Environment related dependencies.
14
24
  * These getters are called a fixed number of times per factory instantiation.
@@ -53,7 +63,7 @@ export interface ISdkFactoryParams {
53
63
 
54
64
  // Sdk client method factory (ISDK::client method).
55
65
  // It Allows to distinguish SDK clients with the client-side API (`ICsSDK`) or server-side API (`ISDK` or `IAsyncSDK`).
56
- sdkClientMethodFactory: (params: ISdkClientFactoryParams) => ({ (): SplitIO.ICsClient; (key: SplitIO.SplitKey, trafficType?: string | undefined): SplitIO.ICsClient; } | (() => SplitIO.IClient) | (() => SplitIO.IAsyncClient))
66
+ sdkClientMethodFactory: (params: ISdkFactoryContext) => ({ (): SplitIO.ICsClient; (key: SplitIO.SplitKey, trafficType?: string | undefined): SplitIO.ICsClient; } | (() => SplitIO.IClient) | (() => SplitIO.IAsyncClient))
57
67
 
58
68
  // Optional signal listener constructor. Used to handle special app states, like shutdown, app paused or resumed.
59
69
  // Pass only if `syncManager` (used by Node listener) and `splitApi` (used by Browser listener) are passed.
@@ -64,12 +74,11 @@ export interface ISdkFactoryParams {
64
74
  serviceApi: ISplitApi | undefined) => ISignalListener, // Used by BrowserSignalListener
65
75
 
66
76
  // @TODO review impressionListener and integrations interfaces. What about handling impressionListener as an integration ?
67
- impressionListener?: SplitIO.IImpressionListener,
68
77
  integrationsManagerFactory?: (params: IIntegrationFactoryParams) => IIntegrationManager | undefined,
69
78
 
70
79
  // Impression observer factory. If provided, will be used for impressions dedupe
71
80
  impressionsObserverFactory?: () => IImpressionObserver
72
81
 
73
82
  // Optional function to assign additional properties to the factory instance
74
- extraProps?: (settings: ISettings, syncManager?: ISyncManager) => object
83
+ extraProps?: (params: ISdkFactoryContext) => object
75
84
  }
@@ -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);
@@ -6,7 +6,7 @@ import { IPushManager } from './streaming/types';
6
6
  import { IPollingManager, IPollingManagerCS } from './polling/types';
7
7
  import { PUSH_SUBSYSTEM_UP, PUSH_SUBSYSTEM_DOWN } from './streaming/constants';
8
8
  import { SYNC_START_POLLING, SYNC_CONTINUE_POLLING, SYNC_STOP_POLLING } from '../logger/constants';
9
- import { isConsentGranted } from '../utils/consent';
9
+ import { isConsentGranted } from '../consent';
10
10
 
11
11
  /**
12
12
  * Online SyncManager factory.
@@ -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.
@@ -6,7 +6,7 @@ import { ERROR_NOT_PLAIN_OBJECT } from '../../logger/constants';
6
6
 
7
7
  export function validateAttributes(log: ILogger, maybeAttrs: any, method: string): SplitIO.Attributes | undefined | false {
8
8
  // Attributes are optional
9
- if (isObject(maybeAttrs) || maybeAttrs == undefined) // eslint-disable-line eqeqeq
9
+ if (maybeAttrs == undefined || isObject(maybeAttrs)) // eslint-disable-line eqeqeq
10
10
  return maybeAttrs;
11
11
 
12
12
  log.error(ERROR_NOT_PLAIN_OBJECT, [method, 'attributes']);
@@ -23,5 +23,4 @@ export function validateAttributesDeep(log: ILogger, maybeAttributes: Record<str
23
23
  });
24
24
 
25
25
  return result;
26
-
27
26
  }
@@ -151,10 +151,14 @@ export function isNaNNumber(val: any): boolean {
151
151
  }
152
152
 
153
153
  /**
154
- * Validates if a value is an object with the Object prototype (map object).
154
+ * Validates if a value is an object created by the Object constructor (plain object).
155
+ * It checks `constructor.name` to avoid false negatives when validating values on a separate VM context, which has its own global built-ins.
155
156
  */
156
- export function isObject(obj: any): boolean {
157
- return obj !== null && typeof obj === 'object' && obj.constructor === Object;
157
+ export function isObject(obj: any) {
158
+ return obj !== null && typeof obj === 'object' && (
159
+ obj.constructor === Object ||
160
+ (obj.constructor != null && obj.constructor.name === 'Object')
161
+ );
158
162
  }
159
163
 
160
164
  /**
@@ -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();