@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.
- package/cjs/sdkClient/client.js +4 -9
- package/cjs/sdkClient/clientCS.js +1 -1
- package/cjs/sdkClient/clientInputValidation.js +6 -8
- package/cjs/sdkClient/sdkClient.js +1 -4
- package/cjs/sdkClient/sdkClientMethodCS.js +1 -5
- package/cjs/sdkClient/sdkClientMethodCSWithTT.js +1 -9
- package/cjs/sdkFactory/index.js +3 -3
- package/cjs/storages/inRedis/RedisAdapter.js +9 -2
- package/cjs/sync/streaming/SSEClient/index.js +2 -1
- package/cjs/trackers/eventTracker.js +8 -1
- package/cjs/trackers/impressionObserver/utils.js +8 -1
- package/cjs/trackers/impressionsTracker.js +6 -5
- package/cjs/utils/lang/maps.js +16 -2
- package/cjs/utils/settingsValidation/index.js +20 -4
- package/esm/sdkClient/client.js +5 -10
- package/esm/sdkClient/clientCS.js +1 -1
- package/esm/sdkClient/clientInputValidation.js +6 -8
- package/esm/sdkClient/sdkClient.js +1 -4
- package/esm/sdkClient/sdkClientMethodCS.js +1 -5
- package/esm/sdkClient/sdkClientMethodCSWithTT.js +1 -9
- package/esm/sdkFactory/index.js +3 -3
- package/esm/storages/inRedis/RedisAdapter.js +9 -2
- package/esm/sync/streaming/SSEClient/index.js +2 -1
- package/esm/trackers/eventTracker.js +8 -1
- package/esm/trackers/impressionObserver/utils.js +7 -1
- package/esm/trackers/impressionsTracker.js +6 -5
- package/esm/utils/lang/maps.js +14 -1
- package/esm/utils/settingsValidation/index.js +20 -4
- package/package.json +1 -1
- package/src/integrations/pluggable.ts +2 -2
- package/src/sdkClient/client.ts +5 -6
- package/src/sdkClient/clientCS.ts +1 -1
- package/src/sdkClient/clientInputValidation.ts +8 -7
- package/src/sdkClient/sdkClient.ts +2 -5
- package/src/sdkClient/sdkClientMethodCS.ts +1 -6
- package/src/sdkClient/sdkClientMethodCSWithTT.ts +2 -11
- package/src/sdkFactory/index.ts +3 -3
- package/src/sdkFactory/types.ts +0 -1
- package/src/storages/inRedis/RedisAdapter.ts +8 -2
- package/src/sync/streaming/SSEClient/index.ts +2 -1
- package/src/trackers/eventTracker.ts +11 -3
- package/src/trackers/impressionObserver/utils.ts +8 -1
- package/src/trackers/impressionsTracker.ts +7 -8
- package/src/types.ts +1 -1
- package/src/utils/lang/maps.ts +15 -1
- package/src/utils/settingsValidation/consent.ts +2 -1
- package/src/utils/settingsValidation/index.ts +20 -4
- package/src/utils/settingsValidation/types.ts +3 -1
- package/types/sdkClient/clientInputValidation.d.ts +2 -3
- package/types/sdkFactory/types.d.ts +0 -1
- package/types/storages/inRedis/RedisAdapter.d.ts +1 -1
- package/types/trackers/eventTracker.d.ts +2 -2
- package/types/trackers/impressionObserver/utils.d.ts +4 -0
- package/types/trackers/impressionsTracker.d.ts +2 -3
- package/types/types.d.ts +1 -1
- package/types/utils/lang/maps.d.ts +7 -0
- package/types/utils/settingsValidation/consent.d.ts +3 -2
- 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(
|
|
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
|
|
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
|
}
|
package/esm/utils/lang/maps.js
CHANGED
|
@@ -68,4 +68,17 @@ var MapPoly = /** @class */ (function () {
|
|
|
68
68
|
return MapPoly;
|
|
69
69
|
}());
|
|
70
70
|
export { MapPoly };
|
|
71
|
-
|
|
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
|
-
//
|
|
110
|
-
if (
|
|
111
|
-
withDefaults.core.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
|
@@ -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
|
|
32
|
+
handleImpression(impressionData: SplitIO.ImpressionData) {
|
|
33
33
|
listeners.forEach(listener => listener.queue({ type: SPLIT_IMPRESSION, payload: impressionData }));
|
|
34
34
|
},
|
|
35
|
-
handleEvent
|
|
35
|
+
handleEvent(eventData: SplitIO.EventData) {
|
|
36
36
|
listeners.forEach(listener => listener.queue({ type: SPLIT_EVENT, payload: eventData }));
|
|
37
37
|
}
|
|
38
38
|
};
|
package/src/sdkClient/client.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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>(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
41
|
-
|
|
31
|
+
key,
|
|
32
|
+
trafficType
|
|
42
33
|
);
|
|
43
34
|
|
|
44
35
|
const parsedDefaultKey = keyParser(key);
|
package/src/sdkFactory/index.ts
CHANGED
|
@@ -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,
|
|
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(
|
|
78
|
-
const eventTracker = eventTrackerFactory(
|
|
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);
|
package/src/sdkFactory/types.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
401
|
+
isClientSide: boolean
|
|
402
402
|
}
|
|
403
403
|
/**
|
|
404
404
|
* Common definitions between SDK instances for different environments interface.
|
package/src/utils/lang/maps.ts
CHANGED
|
@@ -79,4 +79,18 @@ interface IMapConstructor {
|
|
|
79
79
|
readonly prototype: IMap<any, any>;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
|
|
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
|
|
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
|
-
//
|
|
133
|
-
if (
|
|
134
|
-
withDefaults.core.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
|
-
/**
|
|
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>(
|
|
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 {
|
|
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(
|
|
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;
|