@splitsoftware/splitio-commons 1.12.1-rc.4 → 1.12.1-rc.6
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/CHANGES.txt +1 -1
- package/cjs/logger/constants.js +3 -3
- package/cjs/logger/messages/warn.js +2 -2
- package/cjs/sdkClient/client.js +11 -8
- package/cjs/sdkClient/clientInputValidation.js +6 -6
- package/cjs/sdkManager/index.js +6 -6
- package/cjs/storages/KeyBuilder.js +13 -1
- package/cjs/storages/KeyBuilderCS.js +1 -4
- package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +12 -16
- package/cjs/storages/inLocalStorage/index.js +1 -1
- package/cjs/storages/inRedis/SplitsCacheInRedis.js +2 -2
- package/cjs/storages/inRedis/index.js +1 -1
- package/cjs/storages/pluggable/SplitsCachePluggable.js +2 -2
- package/cjs/storages/pluggable/index.js +16 -3
- package/cjs/trackers/eventTracker.js +4 -4
- package/cjs/utils/lang/sets.js +3 -3
- package/cjs/utils/settingsValidation/index.js +1 -1
- package/cjs/utils/settingsValidation/mode.js +10 -3
- package/cjs/utils/settingsValidation/splitFilters.js +20 -19
- package/esm/logger/constants.js +2 -2
- package/esm/logger/messages/warn.js +2 -2
- package/esm/sdkClient/client.js +11 -8
- package/esm/sdkClient/clientInputValidation.js +7 -7
- package/esm/sdkManager/index.js +6 -6
- package/esm/storages/KeyBuilder.js +11 -0
- package/esm/storages/KeyBuilderCS.js +1 -4
- package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +12 -16
- package/esm/storages/inLocalStorage/index.js +1 -1
- package/esm/storages/inRedis/SplitsCacheInRedis.js +3 -3
- package/esm/storages/inRedis/index.js +1 -1
- package/esm/storages/pluggable/SplitsCachePluggable.js +3 -3
- package/esm/storages/pluggable/index.js +17 -4
- package/esm/trackers/eventTracker.js +4 -4
- package/esm/utils/lang/sets.js +1 -1
- package/esm/utils/settingsValidation/index.js +2 -2
- package/esm/utils/settingsValidation/mode.js +7 -1
- package/esm/utils/settingsValidation/splitFilters.js +11 -10
- package/package.json +1 -1
- package/src/logger/constants.ts +2 -2
- package/src/logger/messages/warn.ts +2 -2
- package/src/sdkClient/client.ts +11 -8
- package/src/sdkClient/clientInputValidation.ts +7 -7
- package/src/sdkManager/index.ts +6 -6
- package/src/storages/KeyBuilder.ts +14 -1
- package/src/storages/KeyBuilderCS.ts +1 -5
- package/src/storages/KeyBuilderSS.ts +4 -4
- package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +16 -14
- package/src/storages/inLocalStorage/index.ts +1 -1
- package/src/storages/inRedis/SplitsCacheInRedis.ts +3 -3
- package/src/storages/inRedis/index.ts +1 -1
- package/src/storages/pluggable/SplitsCachePluggable.ts +3 -3
- package/src/storages/pluggable/index.ts +17 -5
- package/src/storages/types.ts +3 -3
- package/src/trackers/eventTracker.ts +4 -4
- package/src/utils/lang/sets.ts +1 -1
- package/src/utils/murmur3/murmur3.ts +0 -1
- package/src/utils/settingsValidation/index.ts +2 -2
- package/src/utils/settingsValidation/mode.ts +8 -1
- package/src/utils/settingsValidation/splitFilters.ts +11 -10
- package/types/logger/constants.d.ts +2 -2
- package/types/storages/AbstractSplitsCache.d.ts +46 -0
- package/types/storages/KeyBuilder.d.ts +8 -1
- package/types/storages/KeyBuilderCS.d.ts +0 -1
- package/types/storages/KeyBuilderSS.d.ts +4 -4
- package/types/storages/inLocalStorage/SplitsCacheInLocal.d.ts +5 -5
- package/types/utils/lang/sets.d.ts +1 -1
- package/types/utils/settingsValidation/mode.d.ts +5 -1
- package/types/utils/settingsValidation/splitFilters.d.ts +1 -1
- package/cjs/trackers/impressionObserver/utils.js +0 -11
- package/cjs/utils/redis/RedisMock.js +0 -31
- package/esm/trackers/impressionObserver/utils.js +0 -7
- package/esm/utils/redis/RedisMock.js +0 -28
- package/src/trackers/impressionObserver/utils.ts +0 -9
- package/src/utils/redis/RedisMock.ts +0 -31
package/esm/sdkClient/client.js
CHANGED
|
@@ -6,7 +6,7 @@ import { validateTrafficTypeExistence } from '../utils/inputValidation/trafficTy
|
|
|
6
6
|
import { SDK_NOT_READY } from '../utils/labels';
|
|
7
7
|
import { CONTROL, TREATMENT, TREATMENTS, TREATMENT_WITH_CONFIG, TREATMENTS_WITH_CONFIG, TRACK, TREATMENTS_WITH_CONFIG_BY_FLAGSETS, TREATMENTS_BY_FLAGSETS, TREATMENTS_BY_FLAGSET, TREATMENTS_WITH_CONFIG_BY_FLAGSET, GET_TREATMENTS_WITH_CONFIG, GET_TREATMENTS_BY_FLAG_SETS, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, GET_TREATMENTS_BY_FLAG_SET, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, GET_TREATMENT_WITH_CONFIG, GET_TREATMENT, GET_TREATMENTS, TRACK_FN_LABEL } from '../utils/constants';
|
|
8
8
|
import { IMPRESSION, IMPRESSION_QUEUEING } from '../logger/constants';
|
|
9
|
-
import {
|
|
9
|
+
import { isConsumerMode } from '../utils/settingsValidation/mode';
|
|
10
10
|
var treatmentNotReady = { treatment: CONTROL, label: SDK_NOT_READY };
|
|
11
11
|
function treatmentsNotReady(featureFlagNames) {
|
|
12
12
|
var evaluations = {};
|
|
@@ -21,6 +21,7 @@ function treatmentsNotReady(featureFlagNames) {
|
|
|
21
21
|
export function clientFactory(params) {
|
|
22
22
|
var readinessManager = params.sdkReadinessManager.readinessManager, storage = params.storage, settings = params.settings, impressionsTracker = params.impressionsTracker, eventTracker = params.eventTracker, telemetryTracker = params.telemetryTracker;
|
|
23
23
|
var log = settings.log, mode = settings.mode;
|
|
24
|
+
var isAsync = isConsumerMode(mode);
|
|
24
25
|
function getTreatment(key, featureFlagName, attributes, withConfig, methodName) {
|
|
25
26
|
if (withConfig === void 0) { withConfig = false; }
|
|
26
27
|
if (methodName === void 0) { methodName = GET_TREATMENT; }
|
|
@@ -34,9 +35,9 @@ export function clientFactory(params) {
|
|
|
34
35
|
};
|
|
35
36
|
var evaluation = readinessManager.isReady() || readinessManager.isReadyFromCache() ?
|
|
36
37
|
evaluateFeature(log, key, featureFlagName, attributes, storage) :
|
|
37
|
-
|
|
38
|
-
treatmentNotReady :
|
|
39
|
-
|
|
38
|
+
isAsync ? // If the SDK is not ready, treatment may be incorrect due to having splits but not segments data, or storage is not connected
|
|
39
|
+
Promise.resolve(treatmentNotReady) :
|
|
40
|
+
treatmentNotReady;
|
|
40
41
|
return thenable(evaluation) ? evaluation.then(function (res) { return wrapUp(res); }) : wrapUp(evaluation);
|
|
41
42
|
}
|
|
42
43
|
function getTreatmentWithConfig(key, featureFlagName, attributes) {
|
|
@@ -58,9 +59,9 @@ export function clientFactory(params) {
|
|
|
58
59
|
};
|
|
59
60
|
var evaluations = readinessManager.isReady() || readinessManager.isReadyFromCache() ?
|
|
60
61
|
evaluateFeatures(log, key, featureFlagNames, attributes, storage) :
|
|
61
|
-
|
|
62
|
-
treatmentsNotReady(featureFlagNames) :
|
|
63
|
-
|
|
62
|
+
isAsync ? // If the SDK is not ready, treatment may be incorrect due to having splits but not segments data, or storage is not connected
|
|
63
|
+
Promise.resolve(treatmentsNotReady(featureFlagNames)) :
|
|
64
|
+
treatmentsNotReady(featureFlagNames);
|
|
64
65
|
return thenable(evaluations) ? evaluations.then(function (res) { return wrapUp(res); }) : wrapUp(evaluations);
|
|
65
66
|
}
|
|
66
67
|
function getTreatmentsWithConfig(key, featureFlagNames, attributes) {
|
|
@@ -84,7 +85,9 @@ export function clientFactory(params) {
|
|
|
84
85
|
};
|
|
85
86
|
var evaluations = readinessManager.isReady() || readinessManager.isReadyFromCache() ?
|
|
86
87
|
evaluateFeaturesByFlagSets(log, key, flagSetNames, attributes, storage, methodName) :
|
|
87
|
-
|
|
88
|
+
isAsync ?
|
|
89
|
+
Promise.resolve({}) :
|
|
90
|
+
{};
|
|
88
91
|
return thenable(evaluations) ? evaluations.then(function (res) { return wrapUp(res); }) : wrapUp(evaluations);
|
|
89
92
|
}
|
|
90
93
|
function getTreatmentsWithConfigByFlagSets(key, flagSetNames, attributes) {
|
|
@@ -2,15 +2,15 @@ import { objectAssign } from '../utils/lang/objectAssign';
|
|
|
2
2
|
import { validateAttributes, validateEvent, validateEventValue, validateEventProperties, validateKey, validateSplit, validateSplits, validateTrafficType, validateIfNotDestroyed, validateIfOperational } from '../utils/inputValidation';
|
|
3
3
|
import { startsWith } from '../utils/lang';
|
|
4
4
|
import { CONTROL, CONTROL_WITH_CONFIG, GET_TREATMENT, GET_TREATMENTS, GET_TREATMENTS_BY_FLAG_SET, GET_TREATMENTS_BY_FLAG_SETS, GET_TREATMENTS_WITH_CONFIG, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, GET_TREATMENT_WITH_CONFIG, TRACK_FN_LABEL } from '../utils/constants';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { isConsumerMode } from '../utils/settingsValidation/mode';
|
|
6
|
+
import { validateFlagSets } from '../utils/settingsValidation/splitFilters';
|
|
7
7
|
/**
|
|
8
8
|
* Decorator that validates the input before actually executing the client methods.
|
|
9
9
|
* We should "guard" the client here, while not polluting the "real" implementation of those methods.
|
|
10
10
|
*/
|
|
11
11
|
export function clientInputValidationDecorator(settings, client, readinessManager) {
|
|
12
|
-
var log = settings.log;
|
|
13
|
-
var
|
|
12
|
+
var log = settings.log, mode = settings.mode;
|
|
13
|
+
var isAsync = isConsumerMode(mode);
|
|
14
14
|
/**
|
|
15
15
|
* Avoid repeating this validations code
|
|
16
16
|
*/
|
|
@@ -25,7 +25,7 @@ export function clientInputValidationDecorator(settings, client, readinessManage
|
|
|
25
25
|
var attributes = validateAttributes(log, maybeAttributes, methodName);
|
|
26
26
|
var isNotDestroyed = validateIfNotDestroyed(log, readinessManager, methodName);
|
|
27
27
|
if (maybeFlagSetNameOrNames) {
|
|
28
|
-
flagSetOrFlagSets =
|
|
28
|
+
flagSetOrFlagSets = validateFlagSets(log, methodName, maybeFlagSetNameOrNames, settings.sync.__splitFiltersValidation.groupedFilters.bySet);
|
|
29
29
|
}
|
|
30
30
|
validateIfOperational(log, readinessManager, methodName, splitOrSplits);
|
|
31
31
|
var valid = isNotDestroyed && key && (splitOrSplits || flagSetOrFlagSets.length > 0) && attributes !== false;
|
|
@@ -38,7 +38,7 @@ export function clientInputValidationDecorator(settings, client, readinessManage
|
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
40
|
function wrapResult(value) {
|
|
41
|
-
return
|
|
41
|
+
return isAsync ? Promise.resolve(value) : value;
|
|
42
42
|
}
|
|
43
43
|
function getTreatment(maybeKey, maybeFeatureFlagName, maybeAttributes) {
|
|
44
44
|
var params = validateEvaluationParams(maybeKey, maybeFeatureFlagName, maybeAttributes, GET_TREATMENT);
|
|
@@ -129,7 +129,7 @@ export function clientInputValidationDecorator(settings, client, readinessManage
|
|
|
129
129
|
return client.track(key, tt, event, eventValue, properties, size);
|
|
130
130
|
}
|
|
131
131
|
else {
|
|
132
|
-
return
|
|
132
|
+
return isAsync ? Promise.resolve(false) : false;
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
return {
|
package/esm/sdkManager/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { objectAssign } from '../utils/lang/objectAssign';
|
|
|
2
2
|
import { thenable } from '../utils/promise/thenable';
|
|
3
3
|
import { find } from '../utils/lang';
|
|
4
4
|
import { validateSplit, validateSplitExistence, validateIfNotDestroyed, validateIfOperational } from '../utils/inputValidation';
|
|
5
|
-
import {
|
|
5
|
+
import { isConsumerMode } from '../utils/settingsValidation/mode';
|
|
6
6
|
import { SPLIT_FN_LABEL, SPLITS_FN_LABEL, NAMES_FN_LABEL } from '../utils/constants';
|
|
7
7
|
function collectTreatments(splitObject) {
|
|
8
8
|
var conditions = splitObject.conditions;
|
|
@@ -39,8 +39,8 @@ function objectsToViews(splitObjects) {
|
|
|
39
39
|
}
|
|
40
40
|
export function sdkManagerFactory(settings, splits, _a) {
|
|
41
41
|
var readinessManager = _a.readinessManager, sdkStatus = _a.sdkStatus;
|
|
42
|
-
var log = settings.log;
|
|
43
|
-
var
|
|
42
|
+
var log = settings.log, mode = settings.mode;
|
|
43
|
+
var isAsync = isConsumerMode(mode);
|
|
44
44
|
return objectAssign(
|
|
45
45
|
// Proto-linkage of the readiness Event Emitter
|
|
46
46
|
Object.create(sdkStatus), {
|
|
@@ -50,7 +50,7 @@ export function sdkManagerFactory(settings, splits, _a) {
|
|
|
50
50
|
split: function (featureFlagName) {
|
|
51
51
|
var splitName = validateSplit(log, featureFlagName, SPLIT_FN_LABEL);
|
|
52
52
|
if (!validateIfNotDestroyed(log, readinessManager, SPLIT_FN_LABEL) || !validateIfOperational(log, readinessManager, SPLIT_FN_LABEL) || !splitName) {
|
|
53
|
-
return
|
|
53
|
+
return isAsync ? Promise.resolve(null) : null;
|
|
54
54
|
}
|
|
55
55
|
var split = splits.getSplit(splitName);
|
|
56
56
|
if (thenable(split)) {
|
|
@@ -67,7 +67,7 @@ export function sdkManagerFactory(settings, splits, _a) {
|
|
|
67
67
|
*/
|
|
68
68
|
splits: function () {
|
|
69
69
|
if (!validateIfNotDestroyed(log, readinessManager, SPLITS_FN_LABEL) || !validateIfOperational(log, readinessManager, SPLITS_FN_LABEL)) {
|
|
70
|
-
return
|
|
70
|
+
return isAsync ? Promise.resolve([]) : [];
|
|
71
71
|
}
|
|
72
72
|
var currentSplits = splits.getAll();
|
|
73
73
|
return thenable(currentSplits) ?
|
|
@@ -79,7 +79,7 @@ export function sdkManagerFactory(settings, splits, _a) {
|
|
|
79
79
|
*/
|
|
80
80
|
names: function () {
|
|
81
81
|
if (!validateIfNotDestroyed(log, readinessManager, NAMES_FN_LABEL) || !validateIfOperational(log, readinessManager, NAMES_FN_LABEL)) {
|
|
82
|
-
return
|
|
82
|
+
return isAsync ? Promise.resolve([]) : [];
|
|
83
83
|
}
|
|
84
84
|
var splitNames = splits.getSplitNames();
|
|
85
85
|
return thenable(splitNames) ?
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { startsWith } from '../utils/lang';
|
|
2
|
+
import { hash } from '../utils/murmur3/murmur3';
|
|
2
3
|
var everythingAtTheEnd = /[^.]+$/;
|
|
3
4
|
var DEFAULT_PREFIX = 'SPLITIO';
|
|
4
5
|
export function validatePrefix(prefix) {
|
|
@@ -54,6 +55,16 @@ var KeyBuilder = /** @class */ (function () {
|
|
|
54
55
|
throw new Error('Invalid latency key provided');
|
|
55
56
|
}
|
|
56
57
|
};
|
|
58
|
+
KeyBuilder.prototype.buildHashKey = function () {
|
|
59
|
+
return this.prefix + ".hash";
|
|
60
|
+
};
|
|
57
61
|
return KeyBuilder;
|
|
58
62
|
}());
|
|
59
63
|
export { KeyBuilder };
|
|
64
|
+
/**
|
|
65
|
+
* Generates a murmur32 hash based on the authorization key and the feature flags filter query.
|
|
66
|
+
* The hash is in hexadecimal format (8 characters max, 32 bits).
|
|
67
|
+
*/
|
|
68
|
+
export function getStorageHash(settings) {
|
|
69
|
+
return hash(settings.core.authorizationKey + "::" + settings.sync.__splitFiltersValidation.queryString).toString(16);
|
|
70
|
+
}
|
|
@@ -6,7 +6,7 @@ var KeyBuilderCS = /** @class */ (function (_super) {
|
|
|
6
6
|
function KeyBuilderCS(prefix, matchingKey) {
|
|
7
7
|
var _this = _super.call(this, prefix) || this;
|
|
8
8
|
_this.matchingKey = matchingKey;
|
|
9
|
-
_this.regexSplitsCacheKey = new RegExp("^" + prefix + "\\.
|
|
9
|
+
_this.regexSplitsCacheKey = new RegExp("^" + prefix + "\\.");
|
|
10
10
|
return _this;
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
@@ -36,9 +36,6 @@ var KeyBuilderCS = /** @class */ (function (_super) {
|
|
|
36
36
|
KeyBuilderCS.prototype.isSplitsCacheKey = function (key) {
|
|
37
37
|
return this.regexSplitsCacheKey.test(key);
|
|
38
38
|
};
|
|
39
|
-
KeyBuilderCS.prototype.buildSplitsFilterQueryKey = function () {
|
|
40
|
-
return this.prefix + ".splits.filterQuery";
|
|
41
|
-
};
|
|
42
39
|
return KeyBuilderCS;
|
|
43
40
|
}(KeyBuilder));
|
|
44
41
|
export { KeyBuilderCS };
|
|
@@ -3,6 +3,7 @@ import { AbstractSplitsCacheSync, usesSegments } from '../AbstractSplitsCacheSyn
|
|
|
3
3
|
import { isFiniteNumber, toNumber, isNaNNumber } from '../../utils/lang';
|
|
4
4
|
import { LOG_PREFIX } from './constants';
|
|
5
5
|
import { _Set, setToArray } from '../../utils/lang/sets';
|
|
6
|
+
import { getStorageHash } from '../KeyBuilder';
|
|
6
7
|
/**
|
|
7
8
|
* ISplitsCacheSync implementation that stores split definitions in browser LocalStorage.
|
|
8
9
|
*/
|
|
@@ -13,13 +14,12 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
13
14
|
* @param {number | undefined} expirationTimestamp
|
|
14
15
|
* @param {ISplitFiltersValidation} splitFiltersValidation
|
|
15
16
|
*/
|
|
16
|
-
function SplitsCacheInLocal(
|
|
17
|
-
if (splitFiltersValidation === void 0) { splitFiltersValidation = { queryString: null, groupedFilters: { bySet: [], byName: [], byPrefix: [] }, validFilters: [] }; }
|
|
17
|
+
function SplitsCacheInLocal(settings, keys, expirationTimestamp) {
|
|
18
18
|
var _this = _super.call(this) || this;
|
|
19
|
-
_this.log = log;
|
|
20
19
|
_this.keys = keys;
|
|
21
|
-
_this.
|
|
22
|
-
_this.
|
|
20
|
+
_this.log = settings.log;
|
|
21
|
+
_this.storageHash = getStorageHash(settings);
|
|
22
|
+
_this.flagSetsFilter = settings.sync.__splitFiltersValidation.groupedFilters.bySet;
|
|
23
23
|
_this._checkExpiration(expirationTimestamp);
|
|
24
24
|
_this._checkFilterQuery();
|
|
25
25
|
return _this;
|
|
@@ -123,14 +123,10 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
123
123
|
SplitsCacheInLocal.prototype.setChangeNumber = function (changeNumber) {
|
|
124
124
|
// when using a new split query, we must update it at the store
|
|
125
125
|
if (this.updateNewFilter) {
|
|
126
|
-
this.log.info(LOG_PREFIX + '
|
|
127
|
-
var
|
|
128
|
-
var queryString = this.splitFiltersValidation.queryString;
|
|
126
|
+
this.log.info(LOG_PREFIX + 'SDK key or feature flag filter criteria was modified. Updating cache.');
|
|
127
|
+
var storageHashKey = this.keys.buildHashKey();
|
|
129
128
|
try {
|
|
130
|
-
|
|
131
|
-
localStorage.setItem(queryKey, queryString);
|
|
132
|
-
else
|
|
133
|
-
localStorage.removeItem(queryKey);
|
|
129
|
+
localStorage.setItem(storageHashKey, this.storageHash);
|
|
134
130
|
}
|
|
135
131
|
catch (e) {
|
|
136
132
|
this.log.error(LOG_PREFIX + e);
|
|
@@ -208,11 +204,11 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
208
204
|
this.clear();
|
|
209
205
|
}
|
|
210
206
|
};
|
|
207
|
+
// @TODO eventually remove `_checkFilterQuery`. Cache should be cleared at the storage level, reusing same logic than PluggableStorage
|
|
211
208
|
SplitsCacheInLocal.prototype._checkFilterQuery = function () {
|
|
212
|
-
var
|
|
213
|
-
var
|
|
214
|
-
|
|
215
|
-
if (currentQueryString !== queryString) {
|
|
209
|
+
var storageHashKey = this.keys.buildHashKey();
|
|
210
|
+
var storageHash = localStorage.getItem(storageHashKey);
|
|
211
|
+
if (storageHash !== this.storageHash) {
|
|
216
212
|
try {
|
|
217
213
|
// mark cache to update the new query filter on first successful splits fetch
|
|
218
214
|
this.updateNewFilter = true;
|
|
@@ -31,7 +31,7 @@ export function InLocalStorage(options) {
|
|
|
31
31
|
var matchingKey = getMatching(settings.core.key);
|
|
32
32
|
var keys = new KeyBuilderCS(prefix, matchingKey);
|
|
33
33
|
var expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
|
|
34
|
-
var splits = new SplitsCacheInLocal(
|
|
34
|
+
var splits = new SplitsCacheInLocal(settings, keys, expirationTimestamp);
|
|
35
35
|
var segments = new MySegmentsCacheInLocal(log, keys);
|
|
36
36
|
return {
|
|
37
37
|
splits: splits,
|
|
@@ -2,7 +2,7 @@ import { __extends, __spreadArray } from "tslib";
|
|
|
2
2
|
import { isFiniteNumber, isNaNNumber } from '../../utils/lang';
|
|
3
3
|
import { LOG_PREFIX } from './constants';
|
|
4
4
|
import { AbstractSplitsCacheAsync } from '../AbstractSplitsCacheAsync';
|
|
5
|
-
import { _Set,
|
|
5
|
+
import { _Set, returnDifference } from '../../utils/lang/sets';
|
|
6
6
|
/**
|
|
7
7
|
* Discard errors for an answer of multiple operations.
|
|
8
8
|
*/
|
|
@@ -49,8 +49,8 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
|
|
|
49
49
|
};
|
|
50
50
|
SplitsCacheInRedis.prototype._updateFlagSets = function (featureFlagName, flagSetsOfRemovedFlag, flagSetsOfAddedFlag) {
|
|
51
51
|
var _this = this;
|
|
52
|
-
var removeFromFlagSets =
|
|
53
|
-
var addToFlagSets =
|
|
52
|
+
var removeFromFlagSets = returnDifference(flagSetsOfRemovedFlag, flagSetsOfAddedFlag);
|
|
53
|
+
var addToFlagSets = returnDifference(flagSetsOfAddedFlag, flagSetsOfRemovedFlag);
|
|
54
54
|
if (this.flagSetsFilter.length > 0) {
|
|
55
55
|
addToFlagSets = addToFlagSets.filter(function (flagSet) {
|
|
56
56
|
return _this.flagSetsFilter.some(function (filterFlagSet) { return filterFlagSet === flagSet; });
|
|
@@ -36,7 +36,7 @@ export function InRedisStorage(options) {
|
|
|
36
36
|
telemetry.recordConfig();
|
|
37
37
|
});
|
|
38
38
|
return {
|
|
39
|
-
splits: new SplitsCacheInRedis(log, keys, redisClient),
|
|
39
|
+
splits: new SplitsCacheInRedis(log, keys, redisClient, settings.sync.__splitFiltersValidation),
|
|
40
40
|
segments: new SegmentsCacheInRedis(log, keys, redisClient),
|
|
41
41
|
impressions: new ImpressionsCacheInRedis(log, keys.buildImpressionsKey(), redisClient, metadata),
|
|
42
42
|
impressionCounts: impressionCountsCache,
|
|
@@ -2,7 +2,7 @@ import { __extends, __spreadArray } from "tslib";
|
|
|
2
2
|
import { isFiniteNumber, isNaNNumber } from '../../utils/lang';
|
|
3
3
|
import { LOG_PREFIX } from './constants';
|
|
4
4
|
import { AbstractSplitsCacheAsync } from '../AbstractSplitsCacheAsync';
|
|
5
|
-
import { _Set,
|
|
5
|
+
import { _Set, returnDifference } from '../../utils/lang/sets';
|
|
6
6
|
/**
|
|
7
7
|
* ISplitsCacheAsync implementation for pluggable storages.
|
|
8
8
|
*/
|
|
@@ -36,8 +36,8 @@ var SplitsCachePluggable = /** @class */ (function (_super) {
|
|
|
36
36
|
};
|
|
37
37
|
SplitsCachePluggable.prototype._updateFlagSets = function (featureFlagName, flagSetsOfRemovedFlag, flagSetsOfAddedFlag) {
|
|
38
38
|
var _this = this;
|
|
39
|
-
var removeFromFlagSets =
|
|
40
|
-
var addToFlagSets =
|
|
39
|
+
var removeFromFlagSets = returnDifference(flagSetsOfRemovedFlag, flagSetsOfAddedFlag);
|
|
40
|
+
var addToFlagSets = returnDifference(flagSetsOfAddedFlag, flagSetsOfRemovedFlag);
|
|
41
41
|
if (this.flagSetsFilter.length > 0) {
|
|
42
42
|
addToFlagSets = addToFlagSets.filter(function (flagSet) {
|
|
43
43
|
return _this.flagSetsFilter.some(function (filterFlagSet) { return filterFlagSet === flagSet; });
|
|
@@ -6,7 +6,7 @@ import { ImpressionsCachePluggable } from './ImpressionsCachePluggable';
|
|
|
6
6
|
import { EventsCachePluggable } from './EventsCachePluggable';
|
|
7
7
|
import { wrapperAdapter, METHODS_TO_PROMISE_WRAP } from './wrapperAdapter';
|
|
8
8
|
import { isObject } from '../../utils/lang';
|
|
9
|
-
import { validatePrefix } from '../KeyBuilder';
|
|
9
|
+
import { getStorageHash, validatePrefix } from '../KeyBuilder';
|
|
10
10
|
import { CONSUMER_PARTIAL_MODE, DEBUG, NONE, STORAGE_PLUGGABLE } from '../../utils/constants';
|
|
11
11
|
import { ImpressionsCacheInMemory } from '../inMemory/ImpressionsCacheInMemory';
|
|
12
12
|
import { EventsCacheInMemory } from '../inMemory/EventsCacheInMemory';
|
|
@@ -73,15 +73,28 @@ export function PluggableStorage(options) {
|
|
|
73
73
|
undefined;
|
|
74
74
|
// Connects to wrapper and emits SDK_READY event on main client
|
|
75
75
|
var connectPromise = wrapper.connect().then(function () {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
if (isSyncronizer) {
|
|
77
|
+
// In standalone or producer mode, clear storage if SDK key or feature flag filter has changed
|
|
78
|
+
return wrapper.get(keys.buildHashKey()).then(function (hash) {
|
|
79
|
+
var currentHash = getStorageHash(settings);
|
|
80
|
+
if (hash !== currentHash) {
|
|
81
|
+
return wrapper.getKeysByPrefix(keys.prefix + ".").then(function (storageKeys) {
|
|
82
|
+
return Promise.all(storageKeys.map(function (storageKey) { return wrapper.del(storageKey); }));
|
|
83
|
+
}).then(function () { return wrapper.set(keys.buildHashKey(), currentHash); });
|
|
84
|
+
}
|
|
85
|
+
}).then(function () {
|
|
86
|
+
onReadyCb();
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
// Start periodic flush of async storages if not running synchronizer (producer mode)
|
|
79
91
|
if (impressionCountsCache && impressionCountsCache.start)
|
|
80
92
|
impressionCountsCache.start();
|
|
81
93
|
if (uniqueKeysCache && uniqueKeysCache.start)
|
|
82
94
|
uniqueKeysCache.start();
|
|
83
95
|
if (telemetry && telemetry.recordConfig)
|
|
84
96
|
telemetry.recordConfig();
|
|
97
|
+
onReadyCb();
|
|
85
98
|
}
|
|
86
99
|
}).catch(function (e) {
|
|
87
100
|
e = e || new Error('Error connecting wrapper');
|
|
@@ -2,7 +2,7 @@ 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
4
|
import { CONSENT_DECLINED, DROPPED, QUEUED } from '../utils/constants';
|
|
5
|
-
import {
|
|
5
|
+
import { isConsumerMode } from '../utils/settingsValidation/mode';
|
|
6
6
|
/**
|
|
7
7
|
* Event tracker stores events in cache and pass them to the integrations manager if provided.
|
|
8
8
|
*
|
|
@@ -10,8 +10,8 @@ import { isStorageSync } from './impressionObserver/utils';
|
|
|
10
10
|
* @param integrationsManager optional event handler used for integrations
|
|
11
11
|
*/
|
|
12
12
|
export function eventTrackerFactory(settings, eventsCache, integrationsManager, telemetryCache) {
|
|
13
|
-
var log = settings.log;
|
|
14
|
-
var
|
|
13
|
+
var log = settings.log, mode = settings.mode;
|
|
14
|
+
var isAsync = isConsumerMode(mode);
|
|
15
15
|
function queueEventsCallback(eventData, tracked) {
|
|
16
16
|
var eventTypeId = eventData.eventTypeId, trafficTypeName = eventData.trafficTypeName, key = eventData.key, value = eventData.value, timestamp = eventData.timestamp, properties = eventData.properties;
|
|
17
17
|
// Logging every prop would be too much.
|
|
@@ -38,7 +38,7 @@ export function eventTrackerFactory(settings, eventsCache, integrationsManager,
|
|
|
38
38
|
return {
|
|
39
39
|
track: function (eventData, size) {
|
|
40
40
|
if (settings.userConsent === CONSENT_DECLINED) {
|
|
41
|
-
return
|
|
41
|
+
return isAsync ? Promise.resolve(false) : false;
|
|
42
42
|
}
|
|
43
43
|
var tracked = eventsCache.track(eventData, size);
|
|
44
44
|
if (thenable(tracked)) {
|
package/esm/utils/lang/sets.js
CHANGED
|
@@ -102,7 +102,7 @@ export function returnSetsUnion(set, set2) {
|
|
|
102
102
|
});
|
|
103
103
|
return result;
|
|
104
104
|
}
|
|
105
|
-
export function
|
|
105
|
+
export function returnDifference(list, list2) {
|
|
106
106
|
if (list === void 0) { list = []; }
|
|
107
107
|
if (list2 === void 0) { list2 = []; }
|
|
108
108
|
var result = new _Set(list);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { merge, get } from '../lang';
|
|
2
|
-
import {
|
|
2
|
+
import { validateMode } from './mode';
|
|
3
3
|
import { validateSplitFilters } from './splitFilters';
|
|
4
4
|
import { STANDALONE_MODE, OPTIMIZED, LOCALHOST_MODE, DEBUG } from '../constants';
|
|
5
5
|
import { validImpressionsMode } from './impressionsMode';
|
|
@@ -123,7 +123,7 @@ export function settingsValidation(config, validationParams) {
|
|
|
123
123
|
startup.eventsFirstPushWindow = fromSecondsToMillis(startup.eventsFirstPushWindow);
|
|
124
124
|
// ensure a valid SDK mode
|
|
125
125
|
// @ts-ignore, modify readonly prop
|
|
126
|
-
withDefaults.mode =
|
|
126
|
+
withDefaults.mode = validateMode(withDefaults.core.authorizationKey, withDefaults.mode);
|
|
127
127
|
// ensure a valid Storage based on mode defined.
|
|
128
128
|
// @ts-ignore, modify readonly prop
|
|
129
129
|
if (storage)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LOCALHOST_MODE, STANDALONE_MODE, PRODUCER_MODE, CONSUMER_MODE, CONSUMER_PARTIAL_MODE } from '../constants';
|
|
2
|
-
export function
|
|
2
|
+
export function validateMode(key, mode) {
|
|
3
3
|
// Leaving the comparison as is, in case we change the mode name but not the setting.
|
|
4
4
|
if (key === 'localhost')
|
|
5
5
|
return LOCALHOST_MODE;
|
|
@@ -7,3 +7,9 @@ export function mode(key, mode) {
|
|
|
7
7
|
throw Error('Invalid mode provided');
|
|
8
8
|
return mode;
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Storage is async if mode is consumer or partial consumer
|
|
12
|
+
*/
|
|
13
|
+
export function isConsumerMode(mode) {
|
|
14
|
+
return CONSUMER_MODE === mode || CONSUMER_PARTIAL_MODE === mode;
|
|
15
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { CONSUMER_MODE, CONSUMER_PARTIAL_MODE } from '../constants';
|
|
2
1
|
import { validateSplits } from '../inputValidation/splits';
|
|
3
|
-
import { WARN_SPLITS_FILTER_IGNORED, WARN_SPLITS_FILTER_EMPTY, WARN_SPLITS_FILTER_INVALID, SETTINGS_SPLITS_FILTER, LOG_PREFIX_SETTINGS, ERROR_SETS_FILTER_EXCLUSIVE,
|
|
2
|
+
import { WARN_SPLITS_FILTER_IGNORED, WARN_SPLITS_FILTER_EMPTY, WARN_SPLITS_FILTER_INVALID, SETTINGS_SPLITS_FILTER, LOG_PREFIX_SETTINGS, ERROR_SETS_FILTER_EXCLUSIVE, WARN_LOWERCASE_FLAGSET, WARN_INVALID_FLAGSET, WARN_FLAGSET_NOT_CONFIGURED } from '../../logger/constants';
|
|
4
3
|
import { objectAssign } from '../lang/objectAssign';
|
|
5
4
|
import { find, uniq } from '../lang';
|
|
5
|
+
import { isConsumerMode } from './mode';
|
|
6
6
|
// Split filters metadata.
|
|
7
7
|
// Ordered according to their precedency when forming the filter query string: `&names=<values>&prefixes=<values>`
|
|
8
8
|
var FILTERS_METADATA = [
|
|
@@ -45,7 +45,7 @@ function validateSplitFilter(log, type, values, maxLength) {
|
|
|
45
45
|
var result = validateSplits(log, values, LOG_PREFIX_SETTINGS, type + " filter", type + " filter value");
|
|
46
46
|
if (result) {
|
|
47
47
|
if (type === 'bySet') {
|
|
48
|
-
result = sanitizeFlagSets(log, result);
|
|
48
|
+
result = sanitizeFlagSets(log, result, LOG_PREFIX_SETTINGS);
|
|
49
49
|
}
|
|
50
50
|
// check max length
|
|
51
51
|
if (result.length > maxLength)
|
|
@@ -78,7 +78,7 @@ function queryStringBuilder(groupedFilters) {
|
|
|
78
78
|
return queryParams.length > 0 ? '&' + queryParams.join('&') : null;
|
|
79
79
|
}
|
|
80
80
|
/**
|
|
81
|
-
* Sanitizes set names list taking
|
|
81
|
+
* Sanitizes set names list taking into account:
|
|
82
82
|
* - It should be lowercase
|
|
83
83
|
* - Must adhere the following regular expression /^[a-z0-9][_a-z0-9]{0,49}$/ that means
|
|
84
84
|
* - must start with a letter or number
|
|
@@ -88,20 +88,21 @@ function queryStringBuilder(groupedFilters) {
|
|
|
88
88
|
*
|
|
89
89
|
* @param {ILogger} log
|
|
90
90
|
* @param {string[]} flagSets
|
|
91
|
+
* @param {string} method
|
|
91
92
|
* @returns sanitized list of set names
|
|
92
93
|
*/
|
|
93
|
-
function sanitizeFlagSets(log, flagSets) {
|
|
94
|
+
function sanitizeFlagSets(log, flagSets, method) {
|
|
94
95
|
var sanitizedSets = flagSets
|
|
95
96
|
.map(function (flagSet) {
|
|
96
97
|
if (CAPITAL_LETTERS_REGEX.test(flagSet)) {
|
|
97
|
-
log.warn(
|
|
98
|
+
log.warn(WARN_LOWERCASE_FLAGSET, [method, flagSet]);
|
|
98
99
|
flagSet = flagSet.toLowerCase();
|
|
99
100
|
}
|
|
100
101
|
return flagSet;
|
|
101
102
|
})
|
|
102
103
|
.filter(function (flagSet) {
|
|
103
104
|
if (!VALID_FLAGSET_REGEX.test(flagSet)) {
|
|
104
|
-
log.warn(
|
|
105
|
+
log.warn(WARN_INVALID_FLAGSET, [method, flagSet, VALID_FLAGSET_REGEX, flagSet]);
|
|
105
106
|
return false;
|
|
106
107
|
}
|
|
107
108
|
if (typeof flagSet !== 'string')
|
|
@@ -137,7 +138,7 @@ export function validateSplitFilters(log, maybeSplitFilters, mode) {
|
|
|
137
138
|
if (!maybeSplitFilters)
|
|
138
139
|
return res;
|
|
139
140
|
// Warn depending on the mode
|
|
140
|
-
if (mode
|
|
141
|
+
if (isConsumerMode(mode)) {
|
|
141
142
|
log.warn(WARN_SPLITS_FILTER_IGNORED);
|
|
142
143
|
return res;
|
|
143
144
|
}
|
|
@@ -175,9 +176,9 @@ export function validateSplitFilters(log, maybeSplitFilters, mode) {
|
|
|
175
176
|
log.debug(SETTINGS_SPLITS_FILTER, [res.queryString]);
|
|
176
177
|
return res;
|
|
177
178
|
}
|
|
178
|
-
export function
|
|
179
|
+
export function validateFlagSets(log, method, flagSets, flagSetsInConfig) {
|
|
179
180
|
var sets = validateSplits(log, flagSets, method, 'flag sets', 'flag set');
|
|
180
|
-
var toReturn = sets ? sanitizeFlagSets(log, sets) : [];
|
|
181
|
+
var toReturn = sets ? sanitizeFlagSets(log, sets, method) : [];
|
|
181
182
|
if (flagSetsInConfig.length > 0) {
|
|
182
183
|
toReturn = toReturn.filter(function (flagSet) {
|
|
183
184
|
if (flagSetsInConfig.indexOf(flagSet) > -1) {
|
package/package.json
CHANGED
package/src/logger/constants.ts
CHANGED
|
@@ -97,8 +97,8 @@ export const WARN_SPLITS_FILTER_EMPTY = 221;
|
|
|
97
97
|
export const WARN_SDK_KEY = 222;
|
|
98
98
|
export const STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2 = 223;
|
|
99
99
|
export const STREAMING_PARSING_SPLIT_UPDATE = 224;
|
|
100
|
-
export const
|
|
101
|
-
export const
|
|
100
|
+
export const WARN_INVALID_FLAGSET = 225;
|
|
101
|
+
export const WARN_LOWERCASE_FLAGSET = 226;
|
|
102
102
|
export const WARN_FLAGSET_NOT_CONFIGURED = 227;
|
|
103
103
|
export const WARN_FLAGSET_WITHOUT_FLAGS = 228;
|
|
104
104
|
|
|
@@ -34,7 +34,7 @@ export const codesWarn: [number, string][] = codesError.concat([
|
|
|
34
34
|
|
|
35
35
|
[c.STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2, c.LOG_PREFIX_SYNC_STREAMING + 'Fetching MySegments due to an error processing %s notification: %s'],
|
|
36
36
|
[c.STREAMING_PARSING_SPLIT_UPDATE, c.LOG_PREFIX_SYNC_STREAMING + 'Fetching SplitChanges due to an error processing SPLIT_UPDATE notification: %s'],
|
|
37
|
-
[c.
|
|
38
|
-
[c.
|
|
37
|
+
[c.WARN_INVALID_FLAGSET, '%s: you passed %s, flag set must adhere to the regular expressions %s. This means a flag set must start with a letter or number, be in lowercase, alphanumeric and have a max length of 50 characters. %s was discarded.'],
|
|
38
|
+
[c.WARN_LOWERCASE_FLAGSET, '%s: flag set %s should be all lowercase - converting string to lowercase.'],
|
|
39
39
|
[c.WARN_FLAGSET_WITHOUT_FLAGS, '%s: you passed %s flag set that does not contain cached feature flag names. Please double check what flag sets are in use in the Split user interface.'],
|
|
40
40
|
]);
|
package/src/sdkClient/client.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { IEvaluationResult } from '../evaluator/types';
|
|
|
9
9
|
import { SplitIO, ImpressionDTO } from '../types';
|
|
10
10
|
import { IMPRESSION, IMPRESSION_QUEUEING } from '../logger/constants';
|
|
11
11
|
import { ISdkFactoryContext } from '../sdkFactory/types';
|
|
12
|
-
import {
|
|
12
|
+
import { isConsumerMode } from '../utils/settingsValidation/mode';
|
|
13
13
|
import { Method } from '../sync/submitters/types';
|
|
14
14
|
|
|
15
15
|
const treatmentNotReady = { treatment: CONTROL, label: SDK_NOT_READY };
|
|
@@ -28,6 +28,7 @@ function treatmentsNotReady(featureFlagNames: string[]) {
|
|
|
28
28
|
export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | SplitIO.IAsyncClient {
|
|
29
29
|
const { sdkReadinessManager: { readinessManager }, storage, settings, impressionsTracker, eventTracker, telemetryTracker } = params;
|
|
30
30
|
const { log, mode } = settings;
|
|
31
|
+
const isAsync = isConsumerMode(mode);
|
|
31
32
|
|
|
32
33
|
function getTreatment(key: SplitIO.SplitKey, featureFlagName: string, attributes: SplitIO.Attributes | undefined, withConfig = false, methodName = GET_TREATMENT) {
|
|
33
34
|
const stopTelemetryTracker = telemetryTracker.trackEval(withConfig ? TREATMENT_WITH_CONFIG : TREATMENT);
|
|
@@ -43,9 +44,9 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
43
44
|
|
|
44
45
|
const evaluation = readinessManager.isReady() || readinessManager.isReadyFromCache() ?
|
|
45
46
|
evaluateFeature(log, key, featureFlagName, attributes, storage) :
|
|
46
|
-
|
|
47
|
-
treatmentNotReady :
|
|
48
|
-
|
|
47
|
+
isAsync ? // If the SDK is not ready, treatment may be incorrect due to having splits but not segments data, or storage is not connected
|
|
48
|
+
Promise.resolve(treatmentNotReady) :
|
|
49
|
+
treatmentNotReady;
|
|
49
50
|
|
|
50
51
|
return thenable(evaluation) ? evaluation.then((res) => wrapUp(res)) : wrapUp(evaluation);
|
|
51
52
|
}
|
|
@@ -71,9 +72,9 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
71
72
|
|
|
72
73
|
const evaluations = readinessManager.isReady() || readinessManager.isReadyFromCache() ?
|
|
73
74
|
evaluateFeatures(log, key, featureFlagNames, attributes, storage) :
|
|
74
|
-
|
|
75
|
-
treatmentsNotReady(featureFlagNames) :
|
|
76
|
-
|
|
75
|
+
isAsync ? // If the SDK is not ready, treatment may be incorrect due to having splits but not segments data, or storage is not connected
|
|
76
|
+
Promise.resolve(treatmentsNotReady(featureFlagNames)) :
|
|
77
|
+
treatmentsNotReady(featureFlagNames);
|
|
77
78
|
|
|
78
79
|
return thenable(evaluations) ? evaluations.then((res) => wrapUp(res)) : wrapUp(evaluations);
|
|
79
80
|
}
|
|
@@ -100,7 +101,9 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
100
101
|
|
|
101
102
|
const evaluations = readinessManager.isReady() || readinessManager.isReadyFromCache() ?
|
|
102
103
|
evaluateFeaturesByFlagSets(log, key, flagSetNames, attributes, storage, methodName) :
|
|
103
|
-
|
|
104
|
+
isAsync ?
|
|
105
|
+
Promise.resolve({}) :
|
|
106
|
+
{};
|
|
104
107
|
|
|
105
108
|
return thenable(evaluations) ? evaluations.then((res) => wrapUp(res)) : wrapUp(evaluations);
|
|
106
109
|
}
|