@splitsoftware/splitio-commons 1.2.1-rc.5 → 1.2.1-rc.8
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/logger/constants.js +3 -2
- package/cjs/logger/messages/debug.js +3 -3
- package/cjs/logger/messages/error.js +2 -2
- package/cjs/logger/messages/info.js +4 -3
- package/cjs/sdkClient/client.js +3 -8
- package/cjs/sdkClient/clientInputValidation.js +6 -8
- package/cjs/sdkClient/sdkClient.js +1 -4
- package/cjs/sdkFactory/index.js +3 -3
- package/cjs/sdkFactory/userConsentProps.js +11 -8
- package/cjs/storages/KeyBuilder.js +1 -5
- package/cjs/sync/streaming/SSEClient/index.js +2 -1
- package/cjs/sync/submitters/eventsSyncTask.js +8 -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/index.js +8 -1
- package/cjs/utils/settingsValidation/consent.js +2 -2
- package/cjs/utils/settingsValidation/impressionsMode.js +2 -2
- package/esm/logger/constants.js +1 -0
- package/esm/logger/messages/debug.js +3 -3
- package/esm/logger/messages/error.js +2 -2
- package/esm/logger/messages/info.js +4 -3
- package/esm/sdkClient/client.js +4 -9
- package/esm/sdkClient/clientInputValidation.js +6 -8
- package/esm/sdkClient/sdkClient.js +1 -4
- package/esm/sdkFactory/index.js +3 -3
- package/esm/sdkFactory/userConsentProps.js +12 -9
- package/esm/storages/KeyBuilder.js +2 -6
- package/esm/sync/streaming/SSEClient/index.js +2 -1
- package/esm/sync/submitters/eventsSyncTask.js +8 -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/index.js +6 -0
- package/esm/utils/settingsValidation/consent.js +2 -2
- package/esm/utils/settingsValidation/impressionsMode.js +2 -2
- package/package.json +1 -1
- package/src/logger/constants.ts +1 -0
- package/src/logger/messages/debug.ts +3 -3
- package/src/logger/messages/error.ts +2 -2
- package/src/logger/messages/info.ts +4 -3
- package/src/sdkClient/client.ts +4 -5
- package/src/sdkClient/clientInputValidation.ts +8 -7
- package/src/sdkClient/sdkClient.ts +2 -5
- package/src/sdkFactory/index.ts +3 -3
- package/src/sdkFactory/types.ts +0 -1
- package/src/sdkFactory/userConsentProps.ts +12 -9
- package/src/storages/KeyBuilder.ts +2 -6
- package/src/sync/streaming/SSEClient/index.ts +2 -1
- package/src/sync/submitters/eventsSyncTask.ts +8 -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/utils/lang/index.ts +8 -1
- package/src/utils/settingsValidation/consent.ts +4 -2
- package/src/utils/settingsValidation/impressionsMode.ts +2 -1
- package/types/logger/constants.d.ts +1 -0
- package/types/sdkClient/clientInputValidation.d.ts +2 -3
- package/types/sdkFactory/types.d.ts +0 -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/utils/lang/index.d.ts +4 -0
- package/types/utils/settingsValidation/consent.d.ts +3 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { ERROR_NOT_BOOLEAN, USER_CONSENT_UPDATED } from '../logger/constants';
|
|
1
|
+
import { ERROR_NOT_BOOLEAN, USER_CONSENT_UPDATED, USER_CONSENT_NOT_UPDATED } from '../logger/constants';
|
|
2
2
|
import { CONSENT_GRANTED, CONSENT_DECLINED } from '../utils/constants';
|
|
3
|
+
import { isBoolean } from '../utils/lang';
|
|
3
4
|
// Extend client-side factory instances with user consent getter/setter
|
|
4
5
|
export function userConsentProps(settings, syncManager) {
|
|
5
6
|
var log = settings.log;
|
|
@@ -7,19 +8,21 @@ export function userConsentProps(settings, syncManager) {
|
|
|
7
8
|
setUserConsent: function (consent) {
|
|
8
9
|
var _a, _b;
|
|
9
10
|
// validate input param
|
|
10
|
-
if (
|
|
11
|
-
log.
|
|
11
|
+
if (!isBoolean(consent)) {
|
|
12
|
+
log.warn(ERROR_NOT_BOOLEAN, ['setUserConsent']);
|
|
12
13
|
return false;
|
|
13
14
|
}
|
|
14
15
|
var newConsentStatus = consent ? CONSENT_GRANTED : CONSENT_DECLINED;
|
|
15
|
-
if (settings.userConsent !== newConsentStatus) {
|
|
16
|
-
|
|
16
|
+
if (settings.userConsent !== newConsentStatus) { // @ts-ignore, modify readonly prop
|
|
17
|
+
settings.userConsent = newConsentStatus;
|
|
17
18
|
if (consent)
|
|
18
|
-
(_a = syncManager === null || syncManager === void 0 ? void 0 : syncManager.submitter) === null || _a === void 0 ? void 0 : _a.start();
|
|
19
|
+
(_a = syncManager === null || syncManager === void 0 ? void 0 : syncManager.submitter) === null || _a === void 0 ? void 0 : _a.start(); // resumes submitters if transitioning to GRANTED
|
|
19
20
|
else
|
|
20
|
-
(_b = syncManager === null || syncManager === void 0 ? void 0 : syncManager.submitter) === null || _b === void 0 ? void 0 : _b.stop();
|
|
21
|
-
log.info(USER_CONSENT_UPDATED, [settings.userConsent, newConsentStatus]);
|
|
22
|
-
|
|
21
|
+
(_b = syncManager === null || syncManager === void 0 ? void 0 : syncManager.submitter) === null || _b === void 0 ? void 0 : _b.stop(); // pauses submitters if transitioning to DECLINED
|
|
22
|
+
log.info(USER_CONSENT_UPDATED, [settings.userConsent, newConsentStatus]);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
log.info(USER_CONSENT_NOT_UPDATED, [newConsentStatus]);
|
|
23
26
|
}
|
|
24
27
|
return true;
|
|
25
28
|
},
|
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { startsWith } from '../utils/lang';
|
|
2
2
|
var everythingAtTheEnd = /[^.]+$/;
|
|
3
3
|
var DEFAULT_PREFIX = 'SPLITIO';
|
|
4
4
|
export function validatePrefix(prefix) {
|
|
5
|
-
return prefix
|
|
6
|
-
endsWith(prefix, '.' + DEFAULT_PREFIX) ?
|
|
7
|
-
prefix : // suffix already appended
|
|
8
|
-
prefix + '.' + DEFAULT_PREFIX : // append suffix
|
|
9
|
-
DEFAULT_PREFIX; // use default prefix if none is provided
|
|
5
|
+
return prefix ? prefix + '.SPLITIO' : 'SPLITIO';
|
|
10
6
|
}
|
|
11
7
|
var KeyBuilder = /** @class */ (function () {
|
|
12
8
|
function KeyBuilder(prefix) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isString } from '../../../utils/lang';
|
|
1
2
|
var VERSION = '1.1';
|
|
2
3
|
var CONTROL_CHANNEL_REGEX = /^control_/;
|
|
3
4
|
/**
|
|
@@ -8,7 +9,7 @@ var CONTROL_CHANNEL_REGEX = /^control_/;
|
|
|
8
9
|
*/
|
|
9
10
|
function buildSSEHeaders(settings) {
|
|
10
11
|
var headers = {
|
|
11
|
-
SplitSDKClientKey: settings.core.authorizationKey.slice(-4),
|
|
12
|
+
SplitSDKClientKey: isString(settings.core.authorizationKey) ? settings.core.authorizationKey.slice(-4) : '',
|
|
12
13
|
SplitSDKVersion: settings.version,
|
|
13
14
|
};
|
|
14
15
|
// ip and hostname are false if IPAddressesEnabled is false
|
|
@@ -7,18 +7,25 @@ var DATA_NAME = 'events';
|
|
|
7
7
|
export function eventsSyncTaskFactory(log, postEventsBulk, eventsCache, eventsPushRate, eventsFirstPushWindow, latencyTracker) {
|
|
8
8
|
// don't retry events.
|
|
9
9
|
var syncTask = submitterSyncTaskFactory(log, postEventsBulk, eventsCache, eventsPushRate, DATA_NAME, latencyTracker);
|
|
10
|
-
// Set a timer for the first push of events
|
|
10
|
+
// Set a timer for the first push window of events.
|
|
11
|
+
// Not implemented in the base submitter or sync task, since this feature is only used by the events submitter.
|
|
11
12
|
if (eventsFirstPushWindow > 0) {
|
|
13
|
+
var running_1 = false;
|
|
12
14
|
var stopEventPublisherTimeout_1;
|
|
13
15
|
var originalStart_1 = syncTask.start;
|
|
14
16
|
syncTask.start = function () {
|
|
17
|
+
running_1 = true;
|
|
15
18
|
stopEventPublisherTimeout_1 = setTimeout(originalStart_1, eventsFirstPushWindow);
|
|
16
19
|
};
|
|
17
20
|
var originalStop_1 = syncTask.stop;
|
|
18
21
|
syncTask.stop = function () {
|
|
22
|
+
running_1 = false;
|
|
19
23
|
clearTimeout(stopEventPublisherTimeout_1);
|
|
20
24
|
originalStop_1();
|
|
21
25
|
};
|
|
26
|
+
syncTask.isRunning = function () {
|
|
27
|
+
return running_1;
|
|
28
|
+
};
|
|
22
29
|
}
|
|
23
30
|
// register events submitter to be executed when events cache is full
|
|
24
31
|
eventsCache.setOnFullQueueCb(function () {
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
2
2
|
import { thenable } from '../utils/promise/thenable';
|
|
3
3
|
import { EVENTS_TRACKER_SUCCESS, ERROR_EVENTS_TRACKER } from '../logger/constants';
|
|
4
|
+
import { CONSENT_DECLINED } from '../utils/constants';
|
|
5
|
+
import { isStorageSync } from './impressionObserver/utils';
|
|
4
6
|
/**
|
|
5
7
|
* Event tracker stores events in cache and pass them to the integrations manager if provided.
|
|
6
8
|
*
|
|
7
9
|
* @param eventsCache cache to save events
|
|
8
10
|
* @param integrationsManager optional event handler used for integrations
|
|
9
11
|
*/
|
|
10
|
-
export function eventTrackerFactory(
|
|
12
|
+
export function eventTrackerFactory(settings, eventsCache, integrationsManager) {
|
|
13
|
+
var log = settings.log;
|
|
14
|
+
var isSync = isStorageSync(settings);
|
|
11
15
|
function queueEventsCallback(eventData, tracked) {
|
|
12
16
|
var eventTypeId = eventData.eventTypeId, trafficTypeName = eventData.trafficTypeName, key = eventData.key, value = eventData.value, timestamp = eventData.timestamp, properties = eventData.properties;
|
|
13
17
|
// Logging every prop would be too much.
|
|
@@ -33,6 +37,9 @@ export function eventTrackerFactory(log, eventsCache, integrationsManager) {
|
|
|
33
37
|
}
|
|
34
38
|
return {
|
|
35
39
|
track: function (eventData, size) {
|
|
40
|
+
if (settings.userConsent === CONSENT_DECLINED) {
|
|
41
|
+
return isSync ? false : Promise.resolve(false);
|
|
42
|
+
}
|
|
36
43
|
var tracked = eventsCache.track(eventData, size);
|
|
37
44
|
if (thenable(tracked)) {
|
|
38
45
|
return tracked.then(queueEventsCallback.bind(null, eventData));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CONSUMER_PARTIAL_MODE, OPTIMIZED, PRODUCER_MODE, STANDALONE_MODE } from '../../utils/constants';
|
|
1
|
+
import { CONSUMER_MODE, CONSUMER_PARTIAL_MODE, OPTIMIZED, PRODUCER_MODE, STANDALONE_MODE } from '../../utils/constants';
|
|
2
2
|
/**
|
|
3
3
|
* Checks if impressions previous time should be added or not.
|
|
4
4
|
*/
|
|
@@ -13,3 +13,9 @@ export function shouldBeOptimized(settings) {
|
|
|
13
13
|
return false;
|
|
14
14
|
return settings.sync.impressionsMode === OPTIMIZED ? true : false;
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Storage is async if mode is consumer or partial consumer
|
|
18
|
+
*/
|
|
19
|
+
export function isStorageSync(settings) {
|
|
20
|
+
return [CONSUMER_MODE, CONSUMER_PARTIAL_MODE].indexOf(settings.mode) === -1 ? true : false;
|
|
21
|
+
}
|
|
@@ -2,6 +2,7 @@ import { objectAssign } from '../utils/lang/objectAssign';
|
|
|
2
2
|
import { thenable } from '../utils/promise/thenable';
|
|
3
3
|
import { truncateTimeFrame } from '../utils/time';
|
|
4
4
|
import { IMPRESSIONS_TRACKER_SUCCESS, ERROR_IMPRESSIONS_TRACKER, ERROR_IMPRESSIONS_LISTENER } from '../logger/constants';
|
|
5
|
+
import { CONSENT_DECLINED } from '../utils/constants';
|
|
5
6
|
/**
|
|
6
7
|
* Impressions tracker stores impressions in cache and pass them to the listener and integrations manager if provided.
|
|
7
8
|
*
|
|
@@ -12,16 +13,16 @@ import { IMPRESSIONS_TRACKER_SUCCESS, ERROR_IMPRESSIONS_TRACKER, ERROR_IMPRESSIO
|
|
|
12
13
|
* @param observer optional impression observer. If provided, previous time (pt property) is included in impression instances
|
|
13
14
|
* @param countsCache optional cache to save impressions count. If provided, impressions will be deduped (OPTIMIZED mode)
|
|
14
15
|
*/
|
|
15
|
-
export function impressionsTrackerFactory(
|
|
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/index.js
CHANGED
|
@@ -152,6 +152,12 @@ export function isObject(obj) {
|
|
|
152
152
|
export function isString(val) {
|
|
153
153
|
return typeof val === 'string' || val instanceof String;
|
|
154
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* String sanitizer. Returns the provided value converted to uppercase if it is a string.
|
|
157
|
+
*/
|
|
158
|
+
export function stringToUpperCase(val) {
|
|
159
|
+
return isString(val) ? val.toUpperCase() : val;
|
|
160
|
+
}
|
|
155
161
|
/**
|
|
156
162
|
* Deep copy version of Object.assign using recursion.
|
|
157
163
|
* There are some assumptions here. It's for internal use and we don't need verbose errors
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { ERROR_INVALID_CONFIG_PARAM } from '../../logger/constants';
|
|
2
2
|
import { CONSENT_DECLINED, CONSENT_GRANTED, CONSENT_UNKNOWN } from '../constants';
|
|
3
|
+
import { stringToUpperCase } from '../lang';
|
|
3
4
|
var userConsentValues = [CONSENT_DECLINED, CONSENT_GRANTED, CONSENT_UNKNOWN];
|
|
4
5
|
export function validateConsent(_a) {
|
|
5
6
|
var userConsent = _a.userConsent, log = _a.log;
|
|
6
|
-
|
|
7
|
-
userConsent = userConsent.toUpperCase();
|
|
7
|
+
userConsent = stringToUpperCase(userConsent);
|
|
8
8
|
if (userConsentValues.indexOf(userConsent) > -1)
|
|
9
9
|
return userConsent;
|
|
10
10
|
log.error(ERROR_INVALID_CONFIG_PARAM, ['userConsent', userConsentValues, CONSENT_GRANTED]);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ERROR_INVALID_CONFIG_PARAM } from '../../logger/constants';
|
|
2
2
|
import { DEBUG, OPTIMIZED } from '../constants';
|
|
3
|
+
import { stringToUpperCase } from '../lang';
|
|
3
4
|
export function validImpressionsMode(log, impressionsMode) {
|
|
4
|
-
|
|
5
|
-
impressionsMode = impressionsMode.toUpperCase();
|
|
5
|
+
impressionsMode = stringToUpperCase(impressionsMode);
|
|
6
6
|
if ([DEBUG, OPTIMIZED].indexOf(impressionsMode) > -1)
|
|
7
7
|
return impressionsMode;
|
|
8
8
|
log.error(ERROR_INVALID_CONFIG_PARAM, ['impressionsMode', [DEBUG, OPTIMIZED], OPTIMIZED]);
|
package/package.json
CHANGED
package/src/logger/constants.ts
CHANGED
|
@@ -69,6 +69,7 @@ export const SYNC_STOP_POLLING = 119;
|
|
|
69
69
|
export const EVENTS_TRACKER_SUCCESS = 120;
|
|
70
70
|
export const IMPRESSIONS_TRACKER_SUCCESS = 121;
|
|
71
71
|
export const USER_CONSENT_UPDATED = 122;
|
|
72
|
+
export const USER_CONSENT_NOT_UPDATED = 123;
|
|
72
73
|
|
|
73
74
|
export const ENGINE_VALUE_INVALID = 200;
|
|
74
75
|
export const ENGINE_VALUE_NO_ATTRIBUTES = 201;
|
|
@@ -31,9 +31,9 @@ export const codesDebug: [number, string][] = codesInfo.concat([
|
|
|
31
31
|
// SDK
|
|
32
32
|
[c.CLEANUP_REGISTERING, c.LOG_PREFIX_CLEANUP + 'Registering cleanup handler %s'],
|
|
33
33
|
[c.CLEANUP_DEREGISTERING, c.LOG_PREFIX_CLEANUP + 'Deregistering cleanup handler %s'],
|
|
34
|
-
[c.RETRIEVE_CLIENT_DEFAULT, '
|
|
35
|
-
[c.RETRIEVE_CLIENT_EXISTING, '
|
|
36
|
-
[c.RETRIEVE_MANAGER, '
|
|
34
|
+
[c.RETRIEVE_CLIENT_DEFAULT, 'Retrieving default SDK client.'],
|
|
35
|
+
[c.RETRIEVE_CLIENT_EXISTING, 'Retrieving existing SDK client.'],
|
|
36
|
+
[c.RETRIEVE_MANAGER, 'Retrieving manager instance.'],
|
|
37
37
|
// synchronizer
|
|
38
38
|
[c.SYNC_OFFLINE_DATA, c.LOG_PREFIX_SYNC_OFFLINE + 'Splits data: \n%s'],
|
|
39
39
|
[c.SYNC_SPLITS_FETCH, c.LOG_PREFIX_SYNC_SPLITS + 'Spin up split update using since = %s'],
|
|
@@ -5,7 +5,7 @@ export const codesError: [number, string][] = [
|
|
|
5
5
|
[c.ERROR_ENGINE_COMBINER_IFELSEIF, c.LOG_PREFIX_ENGINE_COMBINER + 'Invalid Split, no valid rules found'],
|
|
6
6
|
// SDK
|
|
7
7
|
[c.ERROR_LOGLEVEL_INVALID, 'logger: Invalid Log Level - No changes to the logs will be applied.'],
|
|
8
|
-
[c.ERROR_CLIENT_CANNOT_GET_READY, '
|
|
8
|
+
[c.ERROR_CLIENT_CANNOT_GET_READY, 'The SDK will not get ready. Reason: %s'],
|
|
9
9
|
[c.ERROR_IMPRESSIONS_TRACKER, c.LOG_PREFIX_IMPRESSIONS_TRACKER + 'Could not store impressions bulk with %s impression(s). Error: %s'],
|
|
10
10
|
[c.ERROR_IMPRESSIONS_LISTENER, c.LOG_PREFIX_IMPRESSIONS_TRACKER + 'Impression listener logImpression method threw: %s.'],
|
|
11
11
|
[c.ERROR_EVENTS_TRACKER, c.LOG_PREFIX_EVENTS_TRACKER + 'Failed to queue %s'],
|
|
@@ -28,7 +28,7 @@ export const codesError: [number, string][] = [
|
|
|
28
28
|
[c.ERROR_INVALID, '%s: you passed an invalid %s. It must be a non-empty string.'],
|
|
29
29
|
[c.ERROR_EMPTY, '%s: you passed an empty %s. It must be a non-empty string.'],
|
|
30
30
|
[c.ERROR_EMPTY_ARRAY, '%s: %s must be a non-empty array.'],
|
|
31
|
-
[c.ERROR_NOT_BOOLEAN, '%s:
|
|
31
|
+
[c.ERROR_NOT_BOOLEAN, '%s: provided param must be a boolean value.'],
|
|
32
32
|
// initialization / settings validation
|
|
33
33
|
[c.ERROR_INVALID_CONFIG_PARAM, c.LOG_PREFIX_SETTINGS + ': you passed an invalid "%s" config param. It should be one of the following values: %s. Defaulting to "%s".'],
|
|
34
34
|
[c.ERROR_LOCALHOST_MODULE_REQUIRED, c.LOG_PREFIX_SETTINGS + ': an invalid value was received for "sync.localhostMode" config. A valid entity should be provided for localhost mode.'],
|
|
@@ -10,11 +10,12 @@ export const codesInfo: [number, string][] = codesWarn.concat([
|
|
|
10
10
|
// SDK
|
|
11
11
|
[c.IMPRESSION, c.LOG_PREFIX_IMPRESSIONS_TRACKER +'Split: %s. Key: %s. Evaluation: %s. Label: %s'],
|
|
12
12
|
[c.IMPRESSION_QUEUEING, c.LOG_PREFIX_IMPRESSIONS_TRACKER +'Queueing corresponding impression.'],
|
|
13
|
-
[c.NEW_SHARED_CLIENT, '
|
|
14
|
-
[c.NEW_FACTORY, '
|
|
13
|
+
[c.NEW_SHARED_CLIENT, 'New shared client instance created.'],
|
|
14
|
+
[c.NEW_FACTORY, 'New Split SDK instance created.'],
|
|
15
15
|
[c.EVENTS_TRACKER_SUCCESS, c.LOG_PREFIX_EVENTS_TRACKER + 'Successfully queued %s'],
|
|
16
16
|
[c.IMPRESSIONS_TRACKER_SUCCESS, c.LOG_PREFIX_IMPRESSIONS_TRACKER + 'Successfully stored %s impression(s).'],
|
|
17
|
-
[c.USER_CONSENT_UPDATED, '
|
|
17
|
+
[c.USER_CONSENT_UPDATED, 'setUserConsent: consent status changed from %s to %s.'],
|
|
18
|
+
[c.USER_CONSENT_NOT_UPDATED, 'setUserConsent: call had no effect because it was the current consent status (%s).'],
|
|
18
19
|
|
|
19
20
|
// synchronizer
|
|
20
21
|
[c.POLLING_SMART_PAUSING, c.LOG_PREFIX_SYNC_POLLING + 'Turning segments data polling %s.'],
|
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 {
|
|
@@ -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
|
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
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { ERROR_NOT_BOOLEAN, USER_CONSENT_UPDATED } from '../logger/constants';
|
|
1
|
+
import { ERROR_NOT_BOOLEAN, USER_CONSENT_UPDATED, USER_CONSENT_NOT_UPDATED } from '../logger/constants';
|
|
2
2
|
import { ISyncManager } from '../sync/types';
|
|
3
3
|
import { ISettings } from '../types';
|
|
4
4
|
import { CONSENT_GRANTED, CONSENT_DECLINED } from '../utils/constants';
|
|
5
|
+
import { isBoolean } from '../utils/lang';
|
|
5
6
|
|
|
6
7
|
// Extend client-side factory instances with user consent getter/setter
|
|
7
8
|
export function userConsentProps(settings: ISettings, syncManager?: ISyncManager) {
|
|
@@ -11,20 +12,22 @@ export function userConsentProps(settings: ISettings, syncManager?: ISyncManager
|
|
|
11
12
|
return {
|
|
12
13
|
setUserConsent(consent: unknown) {
|
|
13
14
|
// validate input param
|
|
14
|
-
if (
|
|
15
|
-
log.
|
|
15
|
+
if (!isBoolean(consent)) {
|
|
16
|
+
log.warn(ERROR_NOT_BOOLEAN, ['setUserConsent']);
|
|
16
17
|
return false;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
const newConsentStatus = consent ? CONSENT_GRANTED : CONSENT_DECLINED;
|
|
20
21
|
|
|
21
|
-
if (settings.userConsent !== newConsentStatus) {
|
|
22
|
-
// resume/pause submitters
|
|
23
|
-
if (consent) syncManager?.submitter?.start();
|
|
24
|
-
else syncManager?.submitter?.stop();
|
|
25
|
-
|
|
26
|
-
log.info(USER_CONSENT_UPDATED, [settings.userConsent, newConsentStatus]); // @ts-ignore, modify readonly prop
|
|
22
|
+
if (settings.userConsent !== newConsentStatus) { // @ts-ignore, modify readonly prop
|
|
27
23
|
settings.userConsent = newConsentStatus;
|
|
24
|
+
|
|
25
|
+
if (consent) syncManager?.submitter?.start(); // resumes submitters if transitioning to GRANTED
|
|
26
|
+
else syncManager?.submitter?.stop(); // pauses submitters if transitioning to DECLINED
|
|
27
|
+
|
|
28
|
+
log.info(USER_CONSENT_UPDATED, [settings.userConsent, newConsentStatus]);
|
|
29
|
+
} else {
|
|
30
|
+
log.info(USER_CONSENT_NOT_UPDATED, [newConsentStatus]);
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
return true;
|
|
@@ -1,15 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { startsWith } from '../utils/lang';
|
|
2
2
|
|
|
3
3
|
const everythingAtTheEnd = /[^.]+$/;
|
|
4
4
|
|
|
5
5
|
const DEFAULT_PREFIX = 'SPLITIO';
|
|
6
6
|
|
|
7
7
|
export function validatePrefix(prefix: unknown) {
|
|
8
|
-
return prefix
|
|
9
|
-
endsWith(prefix, '.' + DEFAULT_PREFIX) ?
|
|
10
|
-
prefix : // suffix already appended
|
|
11
|
-
prefix + '.' + DEFAULT_PREFIX : // append suffix
|
|
12
|
-
DEFAULT_PREFIX; // use default prefix if none is provided
|
|
8
|
+
return prefix ? prefix + '.SPLITIO' : 'SPLITIO';
|
|
13
9
|
}
|
|
14
10
|
|
|
15
11
|
export class KeyBuilder {
|
|
@@ -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
|
|
|
@@ -22,18 +22,25 @@ export function eventsSyncTaskFactory(
|
|
|
22
22
|
// don't retry events.
|
|
23
23
|
const syncTask = submitterSyncTaskFactory(log, postEventsBulk, eventsCache, eventsPushRate, DATA_NAME, latencyTracker);
|
|
24
24
|
|
|
25
|
-
// Set a timer for the first push of events
|
|
25
|
+
// Set a timer for the first push window of events.
|
|
26
|
+
// Not implemented in the base submitter or sync task, since this feature is only used by the events submitter.
|
|
26
27
|
if (eventsFirstPushWindow > 0) {
|
|
28
|
+
let running = false;
|
|
27
29
|
let stopEventPublisherTimeout: ReturnType<typeof setTimeout>;
|
|
28
30
|
const originalStart = syncTask.start;
|
|
29
31
|
syncTask.start = () => {
|
|
32
|
+
running = true;
|
|
30
33
|
stopEventPublisherTimeout = setTimeout(originalStart, eventsFirstPushWindow);
|
|
31
34
|
};
|
|
32
35
|
const originalStop = syncTask.stop;
|
|
33
36
|
syncTask.stop = () => {
|
|
37
|
+
running = false;
|
|
34
38
|
clearTimeout(stopEventPublisherTimeout);
|
|
35
39
|
originalStop();
|
|
36
40
|
};
|
|
41
|
+
syncTask.isRunning = () => {
|
|
42
|
+
return running;
|
|
43
|
+
};
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
// register events submitter to be executed when events cache is full
|
|
@@ -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]);
|