@splitsoftware/splitio-commons 2.7.1 → 2.7.9-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES.txt +3 -3
- package/cjs/evaluator/fallbackTreatmentsCalculator/constants.js +8 -0
- package/cjs/evaluator/fallbackTreatmentsCalculator/fallbackSanitizer/index.js +47 -0
- package/cjs/evaluator/fallbackTreatmentsCalculator/index.js +48 -0
- package/cjs/logger/index.js +3 -0
- package/cjs/sdkClient/client.js +9 -2
- package/cjs/sdkClient/clientInputValidation.js +17 -6
- package/cjs/sdkClient/sdkClient.js +1 -1
- package/cjs/sdkFactory/index.js +3 -1
- package/cjs/utils/inputValidation/splitExistence.js +1 -1
- package/cjs/utils/labels/index.js +3 -1
- package/esm/evaluator/fallbackTreatmentsCalculator/constants.js +5 -0
- package/esm/evaluator/fallbackTreatmentsCalculator/fallbackSanitizer/index.js +44 -0
- package/esm/evaluator/fallbackTreatmentsCalculator/index.js +45 -0
- package/esm/logger/index.js +3 -0
- package/esm/sdkClient/client.js +9 -2
- package/esm/sdkClient/clientInputValidation.js +18 -7
- package/esm/sdkClient/sdkClient.js +1 -1
- package/esm/sdkFactory/index.js +3 -1
- package/esm/utils/inputValidation/splitExistence.js +2 -2
- package/esm/utils/labels/index.js +2 -0
- package/package.json +1 -1
- package/src/evaluator/fallbackTreatmentsCalculator/constants.ts +4 -0
- package/src/evaluator/fallbackTreatmentsCalculator/fallbackSanitizer/index.ts +62 -0
- package/src/evaluator/fallbackTreatmentsCalculator/index.ts +57 -0
- package/src/logger/index.ts +2 -0
- package/src/sdkClient/client.ts +11 -2
- package/src/sdkClient/clientInputValidation.ts +22 -7
- package/src/sdkClient/sdkClient.ts +2 -1
- package/src/sdkFactory/index.ts +4 -1
- package/src/sdkFactory/types.ts +2 -0
- package/src/utils/inputValidation/splitExistence.ts +2 -2
- package/src/utils/labels/index.ts +3 -0
- package/types/splitio.d.ts +17 -1
package/CHANGES.txt
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
2.
|
|
2
|
-
|
|
1
|
+
2.8.0 (October XX, 2025)
|
|
2
|
+
- Added new configuration for Fallback Treatments, which allows setting a treatment value and optional config to be returned in place of "control", either globally or by flag. Read more in our docs.
|
|
3
3
|
|
|
4
4
|
2.7.0 (October 7, 2025)
|
|
5
|
-
- Added support for custom loggers: added `logger` configuration option and `
|
|
5
|
+
- Added support for custom loggers: added `logger` configuration option and `LoggerAPI.setLogger` method to allow the SDK to use a custom logger.
|
|
6
6
|
|
|
7
7
|
2.6.0 (September 18, 2025)
|
|
8
8
|
- Added `storage.wrapper` configuration option to allow the SDK to use a custom storage wrapper for the storage type `LOCALSTORAGE`. Default value is `window.localStorage`.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FallbackDiscardReason = void 0;
|
|
4
|
+
var FallbackDiscardReason;
|
|
5
|
+
(function (FallbackDiscardReason) {
|
|
6
|
+
FallbackDiscardReason["FlagName"] = "Invalid flag name (max 100 chars, no spaces)";
|
|
7
|
+
FallbackDiscardReason["Treatment"] = "Invalid treatment (max 100 chars and must match pattern)";
|
|
8
|
+
})(FallbackDiscardReason = exports.FallbackDiscardReason || (exports.FallbackDiscardReason = {}));
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FallbacksSanitizer = void 0;
|
|
4
|
+
var lang_1 = require("../../../utils/lang");
|
|
5
|
+
var constants_1 = require("../constants");
|
|
6
|
+
var FallbacksSanitizer = /** @class */ (function () {
|
|
7
|
+
function FallbacksSanitizer() {
|
|
8
|
+
}
|
|
9
|
+
FallbacksSanitizer.isValidFlagName = function (name) {
|
|
10
|
+
return name.length <= 100 && !name.includes(' ');
|
|
11
|
+
};
|
|
12
|
+
FallbacksSanitizer.isValidTreatment = function (t) {
|
|
13
|
+
var treatment = (0, lang_1.isObject)(t) ? t.treatment : t;
|
|
14
|
+
if (!(0, lang_1.isString)(treatment) || treatment.length > 100) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
return FallbacksSanitizer.pattern.test(treatment);
|
|
18
|
+
};
|
|
19
|
+
FallbacksSanitizer.sanitizeGlobal = function (logger, treatment) {
|
|
20
|
+
if (!this.isValidTreatment(treatment)) {
|
|
21
|
+
logger.error("Fallback treatments - Discarded fallback: " + constants_1.FallbackDiscardReason.Treatment);
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
return treatment;
|
|
25
|
+
};
|
|
26
|
+
FallbacksSanitizer.sanitizeByFlag = function (logger, byFlagFallbacks) {
|
|
27
|
+
var _this = this;
|
|
28
|
+
var sanitizedByFlag = {};
|
|
29
|
+
var entries = Object.keys(byFlagFallbacks);
|
|
30
|
+
entries.forEach(function (flag) {
|
|
31
|
+
var t = byFlagFallbacks[flag];
|
|
32
|
+
if (!_this.isValidFlagName(flag)) {
|
|
33
|
+
logger.error("Fallback treatments - Discarded flag '" + flag + "': " + constants_1.FallbackDiscardReason.FlagName);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (!_this.isValidTreatment(t)) {
|
|
37
|
+
logger.error("Fallback treatments - Discarded treatment for flag '" + flag + "': " + constants_1.FallbackDiscardReason.Treatment);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
sanitizedByFlag[flag] = t;
|
|
41
|
+
});
|
|
42
|
+
return sanitizedByFlag;
|
|
43
|
+
};
|
|
44
|
+
FallbacksSanitizer.pattern = /^[0-9]+[.a-zA-Z0-9_-]*$|^[a-zA-Z]+[a-zA-Z0-9_-]*$/;
|
|
45
|
+
return FallbacksSanitizer;
|
|
46
|
+
}());
|
|
47
|
+
exports.FallbacksSanitizer = FallbacksSanitizer;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FallbackTreatmentsCalculator = exports.FALLBACK_PREFIX = void 0;
|
|
4
|
+
var fallbackSanitizer_1 = require("./fallbackSanitizer");
|
|
5
|
+
var constants_1 = require("../../utils/constants");
|
|
6
|
+
var lang_1 = require("../../utils/lang");
|
|
7
|
+
exports.FALLBACK_PREFIX = 'fallback - ';
|
|
8
|
+
var FallbackTreatmentsCalculator = /** @class */ (function () {
|
|
9
|
+
function FallbackTreatmentsCalculator(logger, fallbacks) {
|
|
10
|
+
var sanitizedGlobal = (fallbacks === null || fallbacks === void 0 ? void 0 : fallbacks.global) ? fallbackSanitizer_1.FallbacksSanitizer.sanitizeGlobal(logger, fallbacks.global) : undefined;
|
|
11
|
+
var sanitizedByFlag = (fallbacks === null || fallbacks === void 0 ? void 0 : fallbacks.byFlag) ? fallbackSanitizer_1.FallbacksSanitizer.sanitizeByFlag(logger, fallbacks.byFlag) : {};
|
|
12
|
+
this.fallbacks = {
|
|
13
|
+
global: sanitizedGlobal,
|
|
14
|
+
byFlag: sanitizedByFlag
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
FallbackTreatmentsCalculator.prototype.resolve = function (flagName, label) {
|
|
18
|
+
var _a;
|
|
19
|
+
var treatment = (_a = this.fallbacks.byFlag) === null || _a === void 0 ? void 0 : _a[flagName];
|
|
20
|
+
if (treatment) {
|
|
21
|
+
return this.copyWithLabel(treatment, label);
|
|
22
|
+
}
|
|
23
|
+
if (this.fallbacks.global) {
|
|
24
|
+
return this.copyWithLabel(this.fallbacks.global, label);
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
treatment: constants_1.CONTROL,
|
|
28
|
+
config: null,
|
|
29
|
+
label: label,
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
FallbackTreatmentsCalculator.prototype.copyWithLabel = function (fallback, label) {
|
|
33
|
+
if ((0, lang_1.isString)(fallback)) {
|
|
34
|
+
return {
|
|
35
|
+
treatment: fallback,
|
|
36
|
+
config: null,
|
|
37
|
+
label: "" + exports.FALLBACK_PREFIX + label,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
treatment: fallback.treatment,
|
|
42
|
+
config: fallback.config,
|
|
43
|
+
label: "" + exports.FALLBACK_PREFIX + label,
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
return FallbackTreatmentsCalculator;
|
|
47
|
+
}());
|
|
48
|
+
exports.FallbackTreatmentsCalculator = FallbackTreatmentsCalculator;
|
package/cjs/logger/index.js
CHANGED
|
@@ -63,6 +63,9 @@ var Logger = /** @class */ (function () {
|
|
|
63
63
|
if (logger) {
|
|
64
64
|
if ((0, commons_1.isLogger)(logger)) {
|
|
65
65
|
this.logger = logger;
|
|
66
|
+
// If custom logger is set, all logs are either enabled or disabled
|
|
67
|
+
if (this.logLevel !== LogLevelIndexes.NONE)
|
|
68
|
+
this.setLogLevel(exports.LogLevels.DEBUG);
|
|
66
69
|
return;
|
|
67
70
|
}
|
|
68
71
|
else {
|
package/cjs/sdkClient/client.js
CHANGED
|
@@ -30,7 +30,7 @@ function stringify(options) {
|
|
|
30
30
|
* Creator of base client with getTreatments and track methods.
|
|
31
31
|
*/
|
|
32
32
|
function clientFactory(params) {
|
|
33
|
-
var readinessManager = params.sdkReadinessManager.readinessManager, storage = params.storage, settings = params.settings, impressionsTracker = params.impressionsTracker, eventTracker = params.eventTracker, telemetryTracker = params.telemetryTracker;
|
|
33
|
+
var readinessManager = params.sdkReadinessManager.readinessManager, storage = params.storage, settings = params.settings, impressionsTracker = params.impressionsTracker, eventTracker = params.eventTracker, telemetryTracker = params.telemetryTracker, fallbackTreatmentsCalculator = params.fallbackTreatmentsCalculator;
|
|
34
34
|
var log = settings.log, mode = settings.mode;
|
|
35
35
|
var isAsync = (0, mode_1.isConsumerMode)(mode);
|
|
36
36
|
function getTreatment(key, featureFlagName, attributes, options, withConfig, methodName) {
|
|
@@ -115,7 +115,14 @@ function clientFactory(params) {
|
|
|
115
115
|
function processEvaluation(evaluation, featureFlagName, key, properties, withConfig, invokingMethodName, queue) {
|
|
116
116
|
var matchingKey = (0, key_1.getMatching)(key);
|
|
117
117
|
var bucketingKey = (0, key_1.getBucketing)(key);
|
|
118
|
-
var
|
|
118
|
+
var changeNumber = evaluation.changeNumber, impressionsDisabled = evaluation.impressionsDisabled;
|
|
119
|
+
var treatment = evaluation.treatment, label = evaluation.label, _a = evaluation.config, config = _a === void 0 ? null : _a;
|
|
120
|
+
if (treatment === constants_1.CONTROL) {
|
|
121
|
+
var fallbackTreatment = fallbackTreatmentsCalculator.resolve(featureFlagName, label);
|
|
122
|
+
treatment = fallbackTreatment.treatment;
|
|
123
|
+
label = fallbackTreatment.label ? fallbackTreatment.label : label;
|
|
124
|
+
config = fallbackTreatment.config;
|
|
125
|
+
}
|
|
119
126
|
log.info(constants_2.IMPRESSION, [featureFlagName, matchingKey, treatment, label]);
|
|
120
127
|
if ((0, splitExistence_1.validateSplitExistence)(log, readinessManager, featureFlagName, label, invokingMethodName)) {
|
|
121
128
|
log.info(constants_2.IMPRESSION_QUEUEING);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.clientInputValidationDecorator = void 0;
|
|
4
|
-
var objectAssign_1 = require("../utils/lang/objectAssign");
|
|
5
4
|
var inputValidation_1 = require("../utils/inputValidation");
|
|
6
5
|
var lang_1 = require("../utils/lang");
|
|
7
6
|
var constants_1 = require("../utils/constants");
|
|
@@ -11,7 +10,7 @@ var splitFilters_1 = require("../utils/settingsValidation/splitFilters");
|
|
|
11
10
|
* Decorator that validates the input before actually executing the client methods.
|
|
12
11
|
* We should "guard" the client here, while not polluting the "real" implementation of those methods.
|
|
13
12
|
*/
|
|
14
|
-
function clientInputValidationDecorator(settings, client, readinessManager) {
|
|
13
|
+
function clientInputValidationDecorator(settings, client, readinessManager, fallbackTreatmentsCalculator) {
|
|
15
14
|
var log = settings.log, mode = settings.mode;
|
|
16
15
|
var isAsync = (0, mode_1.isConsumerMode)(mode);
|
|
17
16
|
/**
|
|
@@ -37,6 +36,16 @@ function clientInputValidationDecorator(settings, client, readinessManager) {
|
|
|
37
36
|
options: options
|
|
38
37
|
};
|
|
39
38
|
}
|
|
39
|
+
function evaluateFallBackTreatment(featureFlagName, withConfig) {
|
|
40
|
+
var _a = fallbackTreatmentsCalculator.resolve(featureFlagName, ''), treatment = _a.treatment, config = _a.config;
|
|
41
|
+
if (withConfig) {
|
|
42
|
+
return {
|
|
43
|
+
treatment: treatment,
|
|
44
|
+
config: config
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return treatment;
|
|
48
|
+
}
|
|
40
49
|
function wrapResult(value) {
|
|
41
50
|
return isAsync ? Promise.resolve(value) : value;
|
|
42
51
|
}
|
|
@@ -46,7 +55,8 @@ function clientInputValidationDecorator(settings, client, readinessManager) {
|
|
|
46
55
|
return client.getTreatment(params.key, params.nameOrNames, params.attributes, params.options);
|
|
47
56
|
}
|
|
48
57
|
else {
|
|
49
|
-
|
|
58
|
+
var result = evaluateFallBackTreatment(params.nameOrNames, false);
|
|
59
|
+
return wrapResult(result);
|
|
50
60
|
}
|
|
51
61
|
}
|
|
52
62
|
function getTreatmentWithConfig(maybeKey, maybeFeatureFlagName, maybeAttributes, maybeOptions) {
|
|
@@ -55,7 +65,8 @@ function clientInputValidationDecorator(settings, client, readinessManager) {
|
|
|
55
65
|
return client.getTreatmentWithConfig(params.key, params.nameOrNames, params.attributes, params.options);
|
|
56
66
|
}
|
|
57
67
|
else {
|
|
58
|
-
|
|
68
|
+
var result = evaluateFallBackTreatment(params.nameOrNames, true);
|
|
69
|
+
return wrapResult(result);
|
|
59
70
|
}
|
|
60
71
|
}
|
|
61
72
|
function getTreatments(maybeKey, maybeFeatureFlagNames, maybeAttributes, maybeOptions) {
|
|
@@ -66,7 +77,7 @@ function clientInputValidationDecorator(settings, client, readinessManager) {
|
|
|
66
77
|
else {
|
|
67
78
|
var res_1 = {};
|
|
68
79
|
if (params.nameOrNames)
|
|
69
|
-
params.nameOrNames.forEach(function (split) { return res_1[split] =
|
|
80
|
+
params.nameOrNames.forEach(function (split) { return res_1[split] = evaluateFallBackTreatment(split, false); });
|
|
70
81
|
return wrapResult(res_1);
|
|
71
82
|
}
|
|
72
83
|
}
|
|
@@ -78,7 +89,7 @@ function clientInputValidationDecorator(settings, client, readinessManager) {
|
|
|
78
89
|
else {
|
|
79
90
|
var res_2 = {};
|
|
80
91
|
if (params.nameOrNames)
|
|
81
|
-
params.nameOrNames.forEach(function (split) { return res_2[split] = (
|
|
92
|
+
params.nameOrNames.forEach(function (split) { return res_2[split] = evaluateFallBackTreatment(split, true); });
|
|
82
93
|
return wrapResult(res_2);
|
|
83
94
|
}
|
|
84
95
|
}
|
|
@@ -35,7 +35,7 @@ function sdkClientFactory(params, isSharedClient) {
|
|
|
35
35
|
// Proto-linkage of the readiness Event Emitter
|
|
36
36
|
Object.create(sdkReadinessManager.sdkStatus),
|
|
37
37
|
// Client API (getTreatment* & track methods)
|
|
38
|
-
(0, clientInputValidation_1.clientInputValidationDecorator)(settings, (0, client_1.clientFactory)(params), sdkReadinessManager.readinessManager),
|
|
38
|
+
(0, clientInputValidation_1.clientInputValidationDecorator)(settings, (0, client_1.clientFactory)(params), sdkReadinessManager.readinessManager, params.fallbackTreatmentsCalculator),
|
|
39
39
|
// Sdk destroy
|
|
40
40
|
{
|
|
41
41
|
flush: function () {
|
package/cjs/sdkFactory/index.js
CHANGED
|
@@ -17,6 +17,7 @@ var uniqueKeysTracker_1 = require("../trackers/uniqueKeysTracker");
|
|
|
17
17
|
var constants_3 = require("../utils/constants");
|
|
18
18
|
var setRolloutPlan_1 = require("../storages/setRolloutPlan");
|
|
19
19
|
var key_1 = require("../utils/key");
|
|
20
|
+
var fallbackTreatmentsCalculator_1 = require("../evaluator/fallbackTreatmentsCalculator");
|
|
20
21
|
/**
|
|
21
22
|
* Modular SDK factory
|
|
22
23
|
*/
|
|
@@ -51,6 +52,7 @@ function sdkFactory(params) {
|
|
|
51
52
|
readiness.splits.emit(constants_2.SDK_SPLITS_CACHE_LOADED);
|
|
52
53
|
}
|
|
53
54
|
});
|
|
55
|
+
var fallbackTreatmentsCalculator = new fallbackTreatmentsCalculator_1.FallbackTreatmentsCalculator(settings.log, settings.fallbackTreatments);
|
|
54
56
|
if (initialRolloutPlan) {
|
|
55
57
|
(0, setRolloutPlan_1.setRolloutPlan)(log, initialRolloutPlan, storage, key && (0, key_1.getMatching)(key));
|
|
56
58
|
if (storage.splits.getChangeNumber() > -1)
|
|
@@ -71,7 +73,7 @@ function sdkFactory(params) {
|
|
|
71
73
|
var eventTracker = (0, eventTracker_1.eventTrackerFactory)(settings, storage.events, whenInit, integrationsManager, storage.telemetry);
|
|
72
74
|
// splitApi is used by SyncManager and Browser signal listener
|
|
73
75
|
var splitApi = splitApiFactory && splitApiFactory(settings, platform, telemetryTracker);
|
|
74
|
-
var ctx = { clients: clients, splitApi: splitApi, eventTracker: eventTracker, impressionsTracker: impressionsTracker, telemetryTracker: telemetryTracker, uniqueKeysTracker: uniqueKeysTracker, sdkReadinessManager: sdkReadinessManager, readiness: readiness, settings: settings, storage: storage, platform: platform };
|
|
76
|
+
var ctx = { clients: clients, splitApi: splitApi, eventTracker: eventTracker, impressionsTracker: impressionsTracker, telemetryTracker: telemetryTracker, uniqueKeysTracker: uniqueKeysTracker, sdkReadinessManager: sdkReadinessManager, readiness: readiness, settings: settings, storage: storage, platform: platform, fallbackTreatmentsCalculator: fallbackTreatmentsCalculator };
|
|
75
77
|
var syncManager = syncManagerFactory && syncManagerFactory(ctx);
|
|
76
78
|
ctx.syncManager = syncManager;
|
|
77
79
|
var signalListener = SignalListener && new SignalListener(syncManager, settings, storage, splitApi);
|
|
@@ -9,7 +9,7 @@ var constants_1 = require("../../logger/constants");
|
|
|
9
9
|
*/
|
|
10
10
|
function validateSplitExistence(log, readinessManager, splitName, labelOrSplitObj, method) {
|
|
11
11
|
if (readinessManager.isReady()) { // Only if it's ready we validate this, otherwise it may just be that the SDK is not ready yet.
|
|
12
|
-
if (labelOrSplitObj === labels_1.SPLIT_NOT_FOUND || labelOrSplitObj == null) {
|
|
12
|
+
if (labelOrSplitObj === labels_1.SPLIT_NOT_FOUND || labelOrSplitObj == null || labelOrSplitObj === labels_1.FALLBACK_SPLIT_NOT_FOUND) {
|
|
13
13
|
log.warn(constants_1.WARN_NOT_EXISTENT_SPLIT, [method, splitName]);
|
|
14
14
|
return false;
|
|
15
15
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PREREQUISITES_NOT_MET = exports.UNSUPPORTED_MATCHER_TYPE = exports.NOT_IN_SPLIT = exports.SPLIT_ARCHIVED = exports.EXCEPTION = exports.SDK_NOT_READY = exports.SPLIT_NOT_FOUND = exports.NO_CONDITION_MATCH = exports.SPLIT_KILLED = void 0;
|
|
3
|
+
exports.FALLBACK_SPLIT_NOT_FOUND = exports.PREREQUISITES_NOT_MET = exports.UNSUPPORTED_MATCHER_TYPE = exports.NOT_IN_SPLIT = exports.SPLIT_ARCHIVED = exports.EXCEPTION = exports.SDK_NOT_READY = exports.SPLIT_NOT_FOUND = exports.NO_CONDITION_MATCH = exports.SPLIT_KILLED = void 0;
|
|
4
|
+
var fallbackTreatmentsCalculator_1 = require("../../evaluator/fallbackTreatmentsCalculator");
|
|
4
5
|
exports.SPLIT_KILLED = 'killed';
|
|
5
6
|
exports.NO_CONDITION_MATCH = 'default rule';
|
|
6
7
|
exports.SPLIT_NOT_FOUND = 'definition not found';
|
|
@@ -10,3 +11,4 @@ exports.SPLIT_ARCHIVED = 'archived';
|
|
|
10
11
|
exports.NOT_IN_SPLIT = 'not in split';
|
|
11
12
|
exports.UNSUPPORTED_MATCHER_TYPE = 'targeting rule type unsupported by sdk';
|
|
12
13
|
exports.PREREQUISITES_NOT_MET = 'prerequisites not met';
|
|
14
|
+
exports.FALLBACK_SPLIT_NOT_FOUND = fallbackTreatmentsCalculator_1.FALLBACK_PREFIX + exports.SPLIT_NOT_FOUND;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export var FallbackDiscardReason;
|
|
2
|
+
(function (FallbackDiscardReason) {
|
|
3
|
+
FallbackDiscardReason["FlagName"] = "Invalid flag name (max 100 chars, no spaces)";
|
|
4
|
+
FallbackDiscardReason["Treatment"] = "Invalid treatment (max 100 chars and must match pattern)";
|
|
5
|
+
})(FallbackDiscardReason || (FallbackDiscardReason = {}));
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { isObject, isString } from '../../../utils/lang';
|
|
2
|
+
import { FallbackDiscardReason } from '../constants';
|
|
3
|
+
var FallbacksSanitizer = /** @class */ (function () {
|
|
4
|
+
function FallbacksSanitizer() {
|
|
5
|
+
}
|
|
6
|
+
FallbacksSanitizer.isValidFlagName = function (name) {
|
|
7
|
+
return name.length <= 100 && !name.includes(' ');
|
|
8
|
+
};
|
|
9
|
+
FallbacksSanitizer.isValidTreatment = function (t) {
|
|
10
|
+
var treatment = isObject(t) ? t.treatment : t;
|
|
11
|
+
if (!isString(treatment) || treatment.length > 100) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
return FallbacksSanitizer.pattern.test(treatment);
|
|
15
|
+
};
|
|
16
|
+
FallbacksSanitizer.sanitizeGlobal = function (logger, treatment) {
|
|
17
|
+
if (!this.isValidTreatment(treatment)) {
|
|
18
|
+
logger.error("Fallback treatments - Discarded fallback: " + FallbackDiscardReason.Treatment);
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
return treatment;
|
|
22
|
+
};
|
|
23
|
+
FallbacksSanitizer.sanitizeByFlag = function (logger, byFlagFallbacks) {
|
|
24
|
+
var _this = this;
|
|
25
|
+
var sanitizedByFlag = {};
|
|
26
|
+
var entries = Object.keys(byFlagFallbacks);
|
|
27
|
+
entries.forEach(function (flag) {
|
|
28
|
+
var t = byFlagFallbacks[flag];
|
|
29
|
+
if (!_this.isValidFlagName(flag)) {
|
|
30
|
+
logger.error("Fallback treatments - Discarded flag '" + flag + "': " + FallbackDiscardReason.FlagName);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (!_this.isValidTreatment(t)) {
|
|
34
|
+
logger.error("Fallback treatments - Discarded treatment for flag '" + flag + "': " + FallbackDiscardReason.Treatment);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
sanitizedByFlag[flag] = t;
|
|
38
|
+
});
|
|
39
|
+
return sanitizedByFlag;
|
|
40
|
+
};
|
|
41
|
+
FallbacksSanitizer.pattern = /^[0-9]+[.a-zA-Z0-9_-]*$|^[a-zA-Z]+[a-zA-Z0-9_-]*$/;
|
|
42
|
+
return FallbacksSanitizer;
|
|
43
|
+
}());
|
|
44
|
+
export { FallbacksSanitizer };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { FallbacksSanitizer } from './fallbackSanitizer';
|
|
2
|
+
import { CONTROL } from '../../utils/constants';
|
|
3
|
+
import { isString } from '../../utils/lang';
|
|
4
|
+
export var FALLBACK_PREFIX = 'fallback - ';
|
|
5
|
+
var FallbackTreatmentsCalculator = /** @class */ (function () {
|
|
6
|
+
function FallbackTreatmentsCalculator(logger, fallbacks) {
|
|
7
|
+
var sanitizedGlobal = (fallbacks === null || fallbacks === void 0 ? void 0 : fallbacks.global) ? FallbacksSanitizer.sanitizeGlobal(logger, fallbacks.global) : undefined;
|
|
8
|
+
var sanitizedByFlag = (fallbacks === null || fallbacks === void 0 ? void 0 : fallbacks.byFlag) ? FallbacksSanitizer.sanitizeByFlag(logger, fallbacks.byFlag) : {};
|
|
9
|
+
this.fallbacks = {
|
|
10
|
+
global: sanitizedGlobal,
|
|
11
|
+
byFlag: sanitizedByFlag
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
FallbackTreatmentsCalculator.prototype.resolve = function (flagName, label) {
|
|
15
|
+
var _a;
|
|
16
|
+
var treatment = (_a = this.fallbacks.byFlag) === null || _a === void 0 ? void 0 : _a[flagName];
|
|
17
|
+
if (treatment) {
|
|
18
|
+
return this.copyWithLabel(treatment, label);
|
|
19
|
+
}
|
|
20
|
+
if (this.fallbacks.global) {
|
|
21
|
+
return this.copyWithLabel(this.fallbacks.global, label);
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
treatment: CONTROL,
|
|
25
|
+
config: null,
|
|
26
|
+
label: label,
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
FallbackTreatmentsCalculator.prototype.copyWithLabel = function (fallback, label) {
|
|
30
|
+
if (isString(fallback)) {
|
|
31
|
+
return {
|
|
32
|
+
treatment: fallback,
|
|
33
|
+
config: null,
|
|
34
|
+
label: "" + FALLBACK_PREFIX + label,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
treatment: fallback.treatment,
|
|
39
|
+
config: fallback.config,
|
|
40
|
+
label: "" + FALLBACK_PREFIX + label,
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
return FallbackTreatmentsCalculator;
|
|
44
|
+
}());
|
|
45
|
+
export { FallbackTreatmentsCalculator };
|
package/esm/logger/index.js
CHANGED
|
@@ -58,6 +58,9 @@ var Logger = /** @class */ (function () {
|
|
|
58
58
|
if (logger) {
|
|
59
59
|
if (isLogger(logger)) {
|
|
60
60
|
this.logger = logger;
|
|
61
|
+
// If custom logger is set, all logs are either enabled or disabled
|
|
62
|
+
if (this.logLevel !== LogLevelIndexes.NONE)
|
|
63
|
+
this.setLogLevel(LogLevels.DEBUG);
|
|
61
64
|
return;
|
|
62
65
|
}
|
|
63
66
|
else {
|
package/esm/sdkClient/client.js
CHANGED
|
@@ -27,7 +27,7 @@ function stringify(options) {
|
|
|
27
27
|
* Creator of base client with getTreatments and track methods.
|
|
28
28
|
*/
|
|
29
29
|
export function clientFactory(params) {
|
|
30
|
-
var readinessManager = params.sdkReadinessManager.readinessManager, storage = params.storage, settings = params.settings, impressionsTracker = params.impressionsTracker, eventTracker = params.eventTracker, telemetryTracker = params.telemetryTracker;
|
|
30
|
+
var readinessManager = params.sdkReadinessManager.readinessManager, storage = params.storage, settings = params.settings, impressionsTracker = params.impressionsTracker, eventTracker = params.eventTracker, telemetryTracker = params.telemetryTracker, fallbackTreatmentsCalculator = params.fallbackTreatmentsCalculator;
|
|
31
31
|
var log = settings.log, mode = settings.mode;
|
|
32
32
|
var isAsync = isConsumerMode(mode);
|
|
33
33
|
function getTreatment(key, featureFlagName, attributes, options, withConfig, methodName) {
|
|
@@ -112,7 +112,14 @@ export function clientFactory(params) {
|
|
|
112
112
|
function processEvaluation(evaluation, featureFlagName, key, properties, withConfig, invokingMethodName, queue) {
|
|
113
113
|
var matchingKey = getMatching(key);
|
|
114
114
|
var bucketingKey = getBucketing(key);
|
|
115
|
-
var
|
|
115
|
+
var changeNumber = evaluation.changeNumber, impressionsDisabled = evaluation.impressionsDisabled;
|
|
116
|
+
var treatment = evaluation.treatment, label = evaluation.label, _a = evaluation.config, config = _a === void 0 ? null : _a;
|
|
117
|
+
if (treatment === CONTROL) {
|
|
118
|
+
var fallbackTreatment = fallbackTreatmentsCalculator.resolve(featureFlagName, label);
|
|
119
|
+
treatment = fallbackTreatment.treatment;
|
|
120
|
+
label = fallbackTreatment.label ? fallbackTreatment.label : label;
|
|
121
|
+
config = fallbackTreatment.config;
|
|
122
|
+
}
|
|
116
123
|
log.info(IMPRESSION, [featureFlagName, matchingKey, treatment, label]);
|
|
117
124
|
if (validateSplitExistence(log, readinessManager, featureFlagName, label, invokingMethodName)) {
|
|
118
125
|
log.info(IMPRESSION_QUEUEING);
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { objectAssign } from '../utils/lang/objectAssign';
|
|
2
1
|
import { validateAttributes, validateEvent, validateEventValue, validateEventProperties, validateKey, validateSplit, validateSplits, validateTrafficType, validateIfNotDestroyed, validateIfOperational, validateEvaluationOptions } from '../utils/inputValidation';
|
|
3
2
|
import { startsWith } from '../utils/lang';
|
|
4
|
-
import {
|
|
3
|
+
import { 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
4
|
import { isConsumerMode } from '../utils/settingsValidation/mode';
|
|
6
5
|
import { validateFlagSets } from '../utils/settingsValidation/splitFilters';
|
|
7
6
|
/**
|
|
8
7
|
* Decorator that validates the input before actually executing the client methods.
|
|
9
8
|
* We should "guard" the client here, while not polluting the "real" implementation of those methods.
|
|
10
9
|
*/
|
|
11
|
-
export function clientInputValidationDecorator(settings, client, readinessManager) {
|
|
10
|
+
export function clientInputValidationDecorator(settings, client, readinessManager, fallbackTreatmentsCalculator) {
|
|
12
11
|
var log = settings.log, mode = settings.mode;
|
|
13
12
|
var isAsync = isConsumerMode(mode);
|
|
14
13
|
/**
|
|
@@ -34,6 +33,16 @@ export function clientInputValidationDecorator(settings, client, readinessManage
|
|
|
34
33
|
options: options
|
|
35
34
|
};
|
|
36
35
|
}
|
|
36
|
+
function evaluateFallBackTreatment(featureFlagName, withConfig) {
|
|
37
|
+
var _a = fallbackTreatmentsCalculator.resolve(featureFlagName, ''), treatment = _a.treatment, config = _a.config;
|
|
38
|
+
if (withConfig) {
|
|
39
|
+
return {
|
|
40
|
+
treatment: treatment,
|
|
41
|
+
config: config
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return treatment;
|
|
45
|
+
}
|
|
37
46
|
function wrapResult(value) {
|
|
38
47
|
return isAsync ? Promise.resolve(value) : value;
|
|
39
48
|
}
|
|
@@ -43,7 +52,8 @@ export function clientInputValidationDecorator(settings, client, readinessManage
|
|
|
43
52
|
return client.getTreatment(params.key, params.nameOrNames, params.attributes, params.options);
|
|
44
53
|
}
|
|
45
54
|
else {
|
|
46
|
-
|
|
55
|
+
var result = evaluateFallBackTreatment(params.nameOrNames, false);
|
|
56
|
+
return wrapResult(result);
|
|
47
57
|
}
|
|
48
58
|
}
|
|
49
59
|
function getTreatmentWithConfig(maybeKey, maybeFeatureFlagName, maybeAttributes, maybeOptions) {
|
|
@@ -52,7 +62,8 @@ export function clientInputValidationDecorator(settings, client, readinessManage
|
|
|
52
62
|
return client.getTreatmentWithConfig(params.key, params.nameOrNames, params.attributes, params.options);
|
|
53
63
|
}
|
|
54
64
|
else {
|
|
55
|
-
|
|
65
|
+
var result = evaluateFallBackTreatment(params.nameOrNames, true);
|
|
66
|
+
return wrapResult(result);
|
|
56
67
|
}
|
|
57
68
|
}
|
|
58
69
|
function getTreatments(maybeKey, maybeFeatureFlagNames, maybeAttributes, maybeOptions) {
|
|
@@ -63,7 +74,7 @@ export function clientInputValidationDecorator(settings, client, readinessManage
|
|
|
63
74
|
else {
|
|
64
75
|
var res_1 = {};
|
|
65
76
|
if (params.nameOrNames)
|
|
66
|
-
params.nameOrNames.forEach(function (split) { return res_1[split] =
|
|
77
|
+
params.nameOrNames.forEach(function (split) { return res_1[split] = evaluateFallBackTreatment(split, false); });
|
|
67
78
|
return wrapResult(res_1);
|
|
68
79
|
}
|
|
69
80
|
}
|
|
@@ -75,7 +86,7 @@ export function clientInputValidationDecorator(settings, client, readinessManage
|
|
|
75
86
|
else {
|
|
76
87
|
var res_2 = {};
|
|
77
88
|
if (params.nameOrNames)
|
|
78
|
-
params.nameOrNames.forEach(function (split) { return res_2[split] =
|
|
89
|
+
params.nameOrNames.forEach(function (split) { return res_2[split] = evaluateFallBackTreatment(split, true); });
|
|
79
90
|
return wrapResult(res_2);
|
|
80
91
|
}
|
|
81
92
|
}
|
|
@@ -32,7 +32,7 @@ export function sdkClientFactory(params, isSharedClient) {
|
|
|
32
32
|
// Proto-linkage of the readiness Event Emitter
|
|
33
33
|
Object.create(sdkReadinessManager.sdkStatus),
|
|
34
34
|
// Client API (getTreatment* & track methods)
|
|
35
|
-
clientInputValidationDecorator(settings, clientFactory(params), sdkReadinessManager.readinessManager),
|
|
35
|
+
clientInputValidationDecorator(settings, clientFactory(params), sdkReadinessManager.readinessManager, params.fallbackTreatmentsCalculator),
|
|
36
36
|
// Sdk destroy
|
|
37
37
|
{
|
|
38
38
|
flush: function () {
|
package/esm/sdkFactory/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import { uniqueKeysTrackerFactory } from '../trackers/uniqueKeysTracker';
|
|
|
14
14
|
import { DEBUG, OPTIMIZED } from '../utils/constants';
|
|
15
15
|
import { setRolloutPlan } from '../storages/setRolloutPlan';
|
|
16
16
|
import { getMatching } from '../utils/key';
|
|
17
|
+
import { FallbackTreatmentsCalculator } from '../evaluator/fallbackTreatmentsCalculator';
|
|
17
18
|
/**
|
|
18
19
|
* Modular SDK factory
|
|
19
20
|
*/
|
|
@@ -48,6 +49,7 @@ export function sdkFactory(params) {
|
|
|
48
49
|
readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
|
|
49
50
|
}
|
|
50
51
|
});
|
|
52
|
+
var fallbackTreatmentsCalculator = new FallbackTreatmentsCalculator(settings.log, settings.fallbackTreatments);
|
|
51
53
|
if (initialRolloutPlan) {
|
|
52
54
|
setRolloutPlan(log, initialRolloutPlan, storage, key && getMatching(key));
|
|
53
55
|
if (storage.splits.getChangeNumber() > -1)
|
|
@@ -68,7 +70,7 @@ export function sdkFactory(params) {
|
|
|
68
70
|
var eventTracker = eventTrackerFactory(settings, storage.events, whenInit, integrationsManager, storage.telemetry);
|
|
69
71
|
// splitApi is used by SyncManager and Browser signal listener
|
|
70
72
|
var splitApi = splitApiFactory && splitApiFactory(settings, platform, telemetryTracker);
|
|
71
|
-
var ctx = { clients: clients, splitApi: splitApi, eventTracker: eventTracker, impressionsTracker: impressionsTracker, telemetryTracker: telemetryTracker, uniqueKeysTracker: uniqueKeysTracker, sdkReadinessManager: sdkReadinessManager, readiness: readiness, settings: settings, storage: storage, platform: platform };
|
|
73
|
+
var ctx = { clients: clients, splitApi: splitApi, eventTracker: eventTracker, impressionsTracker: impressionsTracker, telemetryTracker: telemetryTracker, uniqueKeysTracker: uniqueKeysTracker, sdkReadinessManager: sdkReadinessManager, readiness: readiness, settings: settings, storage: storage, platform: platform, fallbackTreatmentsCalculator: fallbackTreatmentsCalculator };
|
|
72
74
|
var syncManager = syncManagerFactory && syncManagerFactory(ctx);
|
|
73
75
|
ctx.syncManager = syncManager;
|
|
74
76
|
var signalListener = SignalListener && new SignalListener(syncManager, settings, storage, splitApi);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SPLIT_NOT_FOUND } from '../labels';
|
|
1
|
+
import { FALLBACK_SPLIT_NOT_FOUND, SPLIT_NOT_FOUND } from '../labels';
|
|
2
2
|
import { WARN_NOT_EXISTENT_SPLIT } from '../../logger/constants';
|
|
3
3
|
/**
|
|
4
4
|
* This is defined here and in this format mostly because of the logger and the fact that it's considered a validation at product level.
|
|
@@ -6,7 +6,7 @@ import { WARN_NOT_EXISTENT_SPLIT } from '../../logger/constants';
|
|
|
6
6
|
*/
|
|
7
7
|
export function validateSplitExistence(log, readinessManager, splitName, labelOrSplitObj, method) {
|
|
8
8
|
if (readinessManager.isReady()) { // Only if it's ready we validate this, otherwise it may just be that the SDK is not ready yet.
|
|
9
|
-
if (labelOrSplitObj === SPLIT_NOT_FOUND || labelOrSplitObj == null) {
|
|
9
|
+
if (labelOrSplitObj === SPLIT_NOT_FOUND || labelOrSplitObj == null || labelOrSplitObj === FALLBACK_SPLIT_NOT_FOUND) {
|
|
10
10
|
log.warn(WARN_NOT_EXISTENT_SPLIT, [method, splitName]);
|
|
11
11
|
return false;
|
|
12
12
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FALLBACK_PREFIX } from '../../evaluator/fallbackTreatmentsCalculator';
|
|
1
2
|
export var SPLIT_KILLED = 'killed';
|
|
2
3
|
export var NO_CONDITION_MATCH = 'default rule';
|
|
3
4
|
export var SPLIT_NOT_FOUND = 'definition not found';
|
|
@@ -7,3 +8,4 @@ export var SPLIT_ARCHIVED = 'archived';
|
|
|
7
8
|
export var NOT_IN_SPLIT = 'not in split';
|
|
8
9
|
export var UNSUPPORTED_MATCHER_TYPE = 'targeting rule type unsupported by sdk';
|
|
9
10
|
export var PREREQUISITES_NOT_MET = 'prerequisites not met';
|
|
11
|
+
export var FALLBACK_SPLIT_NOT_FOUND = FALLBACK_PREFIX + SPLIT_NOT_FOUND;
|
package/package.json
CHANGED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Treatment, TreatmentWithConfig } from '../../../../types/splitio';
|
|
2
|
+
import { ILogger } from '../../../logger/types';
|
|
3
|
+
import { isObject, isString } from '../../../utils/lang';
|
|
4
|
+
import { FallbackDiscardReason } from '../constants';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export class FallbacksSanitizer {
|
|
8
|
+
|
|
9
|
+
private static readonly pattern = /^[0-9]+[.a-zA-Z0-9_-]*$|^[a-zA-Z]+[a-zA-Z0-9_-]*$/;
|
|
10
|
+
|
|
11
|
+
private static isValidFlagName(name: string): boolean {
|
|
12
|
+
return name.length <= 100 && !name.includes(' ');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
private static isValidTreatment(t?: Treatment | TreatmentWithConfig): boolean {
|
|
16
|
+
const treatment = isObject(t) ? (t as TreatmentWithConfig).treatment : t;
|
|
17
|
+
|
|
18
|
+
if (!isString(treatment) || treatment.length > 100) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
return FallbacksSanitizer.pattern.test(treatment);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static sanitizeGlobal(logger: ILogger, treatment?: Treatment | TreatmentWithConfig): Treatment | TreatmentWithConfig | undefined {
|
|
25
|
+
if (!this.isValidTreatment(treatment)) {
|
|
26
|
+
logger.error(
|
|
27
|
+
`Fallback treatments - Discarded fallback: ${FallbackDiscardReason.Treatment}`
|
|
28
|
+
);
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
return treatment;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static sanitizeByFlag(
|
|
35
|
+
logger: ILogger,
|
|
36
|
+
byFlagFallbacks: Record<string, Treatment | TreatmentWithConfig>
|
|
37
|
+
): Record<string, Treatment | TreatmentWithConfig> {
|
|
38
|
+
const sanitizedByFlag: Record<string, Treatment | TreatmentWithConfig> = {};
|
|
39
|
+
|
|
40
|
+
const entries = Object.keys(byFlagFallbacks);
|
|
41
|
+
entries.forEach((flag) => {
|
|
42
|
+
const t = byFlagFallbacks[flag];
|
|
43
|
+
if (!this.isValidFlagName(flag)) {
|
|
44
|
+
logger.error(
|
|
45
|
+
`Fallback treatments - Discarded flag '${flag}': ${FallbackDiscardReason.FlagName}`
|
|
46
|
+
);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!this.isValidTreatment(t)) {
|
|
51
|
+
logger.error(
|
|
52
|
+
`Fallback treatments - Discarded treatment for flag '${flag}': ${FallbackDiscardReason.Treatment}`
|
|
53
|
+
);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
sanitizedByFlag[flag] = t;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return sanitizedByFlag;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { FallbackTreatmentConfiguration, Treatment, TreatmentWithConfig } from '../../../types/splitio';
|
|
2
|
+
import { FallbacksSanitizer } from './fallbackSanitizer';
|
|
3
|
+
import { CONTROL } from '../../utils/constants';
|
|
4
|
+
import { isString } from '../../utils/lang';
|
|
5
|
+
import { ILogger } from '../../logger/types';
|
|
6
|
+
|
|
7
|
+
export type IFallbackTreatmentsCalculator = {
|
|
8
|
+
resolve(flagName: string, label: string): TreatmentWithConfig & { label: string };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const FALLBACK_PREFIX = 'fallback - ';
|
|
12
|
+
|
|
13
|
+
export class FallbackTreatmentsCalculator implements IFallbackTreatmentsCalculator {
|
|
14
|
+
private readonly fallbacks: FallbackTreatmentConfiguration;
|
|
15
|
+
|
|
16
|
+
constructor(logger: ILogger, fallbacks?: FallbackTreatmentConfiguration) {
|
|
17
|
+
const sanitizedGlobal = fallbacks?.global ? FallbacksSanitizer.sanitizeGlobal(logger, fallbacks.global) : undefined;
|
|
18
|
+
const sanitizedByFlag = fallbacks?.byFlag ? FallbacksSanitizer.sanitizeByFlag(logger, fallbacks.byFlag) : {};
|
|
19
|
+
this.fallbacks = {
|
|
20
|
+
global: sanitizedGlobal,
|
|
21
|
+
byFlag: sanitizedByFlag
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
resolve(flagName: string, label: string): TreatmentWithConfig & { label: string } {
|
|
26
|
+
const treatment = this.fallbacks.byFlag?.[flagName];
|
|
27
|
+
if (treatment) {
|
|
28
|
+
return this.copyWithLabel(treatment, label);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (this.fallbacks.global) {
|
|
32
|
+
return this.copyWithLabel(this.fallbacks.global, label);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
treatment: CONTROL,
|
|
37
|
+
config: null,
|
|
38
|
+
label,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private copyWithLabel(fallback: Treatment | TreatmentWithConfig, label: string): TreatmentWithConfig & { label: string } {
|
|
43
|
+
if (isString(fallback)) {
|
|
44
|
+
return {
|
|
45
|
+
treatment: fallback,
|
|
46
|
+
config: null,
|
|
47
|
+
label: `${FALLBACK_PREFIX}${label}`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
treatment: fallback.treatment,
|
|
53
|
+
config: fallback.config,
|
|
54
|
+
label: `${FALLBACK_PREFIX}${label}`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
package/src/logger/index.ts
CHANGED
|
@@ -72,6 +72,8 @@ export class Logger implements ILogger {
|
|
|
72
72
|
if (logger) {
|
|
73
73
|
if (isLogger(logger)) {
|
|
74
74
|
this.logger = logger;
|
|
75
|
+
// If custom logger is set, all logs are either enabled or disabled
|
|
76
|
+
if (this.logLevel !== LogLevelIndexes.NONE) this.setLogLevel(LogLevels.DEBUG);
|
|
75
77
|
return;
|
|
76
78
|
} else {
|
|
77
79
|
this.error('Invalid `logger` instance. It must be an object with `debug`, `info`, `warn` and `error` methods. Defaulting to `console.log`');
|
package/src/sdkClient/client.ts
CHANGED
|
@@ -35,7 +35,7 @@ function stringify(options?: SplitIO.EvaluationOptions) {
|
|
|
35
35
|
* Creator of base client with getTreatments and track methods.
|
|
36
36
|
*/
|
|
37
37
|
export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | SplitIO.IAsyncClient {
|
|
38
|
-
const { sdkReadinessManager: { readinessManager }, storage, settings, impressionsTracker, eventTracker, telemetryTracker } = params;
|
|
38
|
+
const { sdkReadinessManager: { readinessManager }, storage, settings, impressionsTracker, eventTracker, telemetryTracker, fallbackTreatmentsCalculator } = params;
|
|
39
39
|
const { log, mode } = settings;
|
|
40
40
|
const isAsync = isConsumerMode(mode);
|
|
41
41
|
|
|
@@ -143,7 +143,16 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
143
143
|
const matchingKey = getMatching(key);
|
|
144
144
|
const bucketingKey = getBucketing(key);
|
|
145
145
|
|
|
146
|
-
const {
|
|
146
|
+
const { changeNumber, impressionsDisabled } = evaluation;
|
|
147
|
+
let { treatment, label, config = null } = evaluation;
|
|
148
|
+
|
|
149
|
+
if (treatment === CONTROL) {
|
|
150
|
+
const fallbackTreatment = fallbackTreatmentsCalculator.resolve(featureFlagName, label);
|
|
151
|
+
treatment = fallbackTreatment.treatment;
|
|
152
|
+
label = fallbackTreatment.label ? fallbackTreatment.label : label;
|
|
153
|
+
config = fallbackTreatment.config;
|
|
154
|
+
}
|
|
155
|
+
|
|
147
156
|
log.info(IMPRESSION, [featureFlagName, matchingKey, treatment, label]);
|
|
148
157
|
|
|
149
158
|
if (validateSplitExistence(log, readinessManager, featureFlagName, label, invokingMethodName)) {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { objectAssign } from '../utils/lang/objectAssign';
|
|
2
1
|
import {
|
|
3
2
|
validateAttributes,
|
|
4
3
|
validateEvent,
|
|
@@ -13,19 +12,20 @@ import {
|
|
|
13
12
|
validateEvaluationOptions
|
|
14
13
|
} from '../utils/inputValidation';
|
|
15
14
|
import { startsWith } from '../utils/lang';
|
|
16
|
-
import {
|
|
15
|
+
import { 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';
|
|
17
16
|
import { IReadinessManager } from '../readiness/types';
|
|
18
17
|
import { MaybeThenable } from '../dtos/types';
|
|
19
18
|
import { ISettings } from '../types';
|
|
20
19
|
import SplitIO from '../../types/splitio';
|
|
21
20
|
import { isConsumerMode } from '../utils/settingsValidation/mode';
|
|
22
21
|
import { validateFlagSets } from '../utils/settingsValidation/splitFilters';
|
|
22
|
+
import { IFallbackTreatmentsCalculator } from '../evaluator/fallbackTreatmentsCalculator';
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Decorator that validates the input before actually executing the client methods.
|
|
26
26
|
* We should "guard" the client here, while not polluting the "real" implementation of those methods.
|
|
27
27
|
*/
|
|
28
|
-
export function clientInputValidationDecorator<TClient extends SplitIO.IClient | SplitIO.IAsyncClient>(settings: ISettings, client: TClient, readinessManager: IReadinessManager): TClient {
|
|
28
|
+
export function clientInputValidationDecorator<TClient extends SplitIO.IClient | SplitIO.IAsyncClient>(settings: ISettings, client: TClient, readinessManager: IReadinessManager, fallbackTreatmentsCalculator: IFallbackTreatmentsCalculator): TClient {
|
|
29
29
|
|
|
30
30
|
const { log, mode } = settings;
|
|
31
31
|
const isAsync = isConsumerMode(mode);
|
|
@@ -59,6 +59,19 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
|
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
function evaluateFallBackTreatment(featureFlagName: string, withConfig: boolean): SplitIO.Treatment | SplitIO.TreatmentWithConfig {
|
|
63
|
+
const {treatment, config} = fallbackTreatmentsCalculator.resolve(featureFlagName, '');
|
|
64
|
+
|
|
65
|
+
if (withConfig) {
|
|
66
|
+
return {
|
|
67
|
+
treatment,
|
|
68
|
+
config
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return treatment;
|
|
73
|
+
}
|
|
74
|
+
|
|
62
75
|
function wrapResult<T>(value: T): MaybeThenable<T> {
|
|
63
76
|
return isAsync ? Promise.resolve(value) : value;
|
|
64
77
|
}
|
|
@@ -69,7 +82,8 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
|
|
|
69
82
|
if (params.valid) {
|
|
70
83
|
return client.getTreatment(params.key as SplitIO.SplitKey, params.nameOrNames as string, params.attributes as SplitIO.Attributes | undefined, params.options);
|
|
71
84
|
} else {
|
|
72
|
-
|
|
85
|
+
const result = evaluateFallBackTreatment(params.nameOrNames as string, false);
|
|
86
|
+
return wrapResult(result);
|
|
73
87
|
}
|
|
74
88
|
}
|
|
75
89
|
|
|
@@ -79,7 +93,8 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
|
|
|
79
93
|
if (params.valid) {
|
|
80
94
|
return client.getTreatmentWithConfig(params.key as SplitIO.SplitKey, params.nameOrNames as string, params.attributes as SplitIO.Attributes | undefined, params.options);
|
|
81
95
|
} else {
|
|
82
|
-
|
|
96
|
+
const result = evaluateFallBackTreatment(params.nameOrNames as string, true);
|
|
97
|
+
return wrapResult(result);
|
|
83
98
|
}
|
|
84
99
|
}
|
|
85
100
|
|
|
@@ -90,7 +105,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
|
|
|
90
105
|
return client.getTreatments(params.key as SplitIO.SplitKey, params.nameOrNames as string[], params.attributes as SplitIO.Attributes | undefined, params.options);
|
|
91
106
|
} else {
|
|
92
107
|
const res: SplitIO.Treatments = {};
|
|
93
|
-
if (params.nameOrNames) (params.nameOrNames as string[]).forEach((split: string) => res[split] =
|
|
108
|
+
if (params.nameOrNames) (params.nameOrNames as string[]).forEach((split: string) => res[split] = evaluateFallBackTreatment(split, false) as SplitIO.Treatment);
|
|
94
109
|
|
|
95
110
|
return wrapResult(res);
|
|
96
111
|
}
|
|
@@ -103,7 +118,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
|
|
|
103
118
|
return client.getTreatmentsWithConfig(params.key as SplitIO.SplitKey, params.nameOrNames as string[], params.attributes as SplitIO.Attributes | undefined, params.options);
|
|
104
119
|
} else {
|
|
105
120
|
const res: SplitIO.TreatmentsWithConfig = {};
|
|
106
|
-
if (params.nameOrNames) (params.nameOrNames as string[]).forEach(split => res[split] =
|
|
121
|
+
if (params.nameOrNames) (params.nameOrNames as string[]).forEach(split => res[split] = evaluateFallBackTreatment(split, true) as SplitIO.TreatmentWithConfig);
|
|
107
122
|
|
|
108
123
|
return wrapResult(res);
|
|
109
124
|
}
|
|
@@ -43,7 +43,8 @@ export function sdkClientFactory(params: ISdkFactoryContext, isSharedClient?: bo
|
|
|
43
43
|
clientInputValidationDecorator(
|
|
44
44
|
settings,
|
|
45
45
|
clientFactory(params),
|
|
46
|
-
sdkReadinessManager.readinessManager
|
|
46
|
+
sdkReadinessManager.readinessManager,
|
|
47
|
+
params.fallbackTreatmentsCalculator
|
|
47
48
|
),
|
|
48
49
|
|
|
49
50
|
// Sdk destroy
|
package/src/sdkFactory/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { DEBUG, OPTIMIZED } from '../utils/constants';
|
|
|
17
17
|
import { setRolloutPlan } from '../storages/setRolloutPlan';
|
|
18
18
|
import { IStorageSync } from '../storages/types';
|
|
19
19
|
import { getMatching } from '../utils/key';
|
|
20
|
+
import { FallbackTreatmentsCalculator } from '../evaluator/fallbackTreatmentsCalculator';
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Modular SDK factory
|
|
@@ -60,6 +61,8 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
|
|
|
60
61
|
}
|
|
61
62
|
});
|
|
62
63
|
|
|
64
|
+
const fallbackTreatmentsCalculator = new FallbackTreatmentsCalculator(settings.log, settings.fallbackTreatments);
|
|
65
|
+
|
|
63
66
|
if (initialRolloutPlan) {
|
|
64
67
|
setRolloutPlan(log, initialRolloutPlan, storage as IStorageSync, key && getMatching(key));
|
|
65
68
|
if ((storage as IStorageSync).splits.getChangeNumber() > -1) readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
|
|
@@ -85,7 +88,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
|
|
|
85
88
|
// splitApi is used by SyncManager and Browser signal listener
|
|
86
89
|
const splitApi = splitApiFactory && splitApiFactory(settings, platform, telemetryTracker);
|
|
87
90
|
|
|
88
|
-
const ctx: ISdkFactoryContext = { clients, splitApi, eventTracker, impressionsTracker, telemetryTracker, uniqueKeysTracker, sdkReadinessManager, readiness, settings, storage, platform };
|
|
91
|
+
const ctx: ISdkFactoryContext = { clients, splitApi, eventTracker, impressionsTracker, telemetryTracker, uniqueKeysTracker, sdkReadinessManager, readiness, settings, storage, platform, fallbackTreatmentsCalculator };
|
|
89
92
|
|
|
90
93
|
const syncManager = syncManagerFactory && syncManagerFactory(ctx as ISdkFactoryContextSync);
|
|
91
94
|
ctx.syncManager = syncManager;
|
package/src/sdkFactory/types.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { ISignalListener } from '../listeners/types';
|
|
|
3
3
|
import { IReadinessManager, ISdkReadinessManager } from '../readiness/types';
|
|
4
4
|
import type { sdkManagerFactory } from '../sdkManager';
|
|
5
5
|
import type { splitApiFactory } from '../services/splitApi';
|
|
6
|
+
import type { IFallbackTreatmentsCalculator } from '../evaluator/fallbackTreatmentsCalculator';
|
|
6
7
|
import { IFetch, ISplitApi, IEventSourceConstructor } from '../services/types';
|
|
7
8
|
import { IStorageAsync, IStorageSync, IStorageFactoryParams } from '../storages/types';
|
|
8
9
|
import { ISyncManager } from '../sync/types';
|
|
@@ -51,6 +52,7 @@ export interface ISdkFactoryContext {
|
|
|
51
52
|
splitApi?: ISplitApi
|
|
52
53
|
syncManager?: ISyncManager,
|
|
53
54
|
clients: Record<string, SplitIO.IBasicClient>,
|
|
55
|
+
fallbackTreatmentsCalculator: IFallbackTreatmentsCalculator
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
export interface ISdkFactoryContextSync extends ISdkFactoryContext {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SPLIT_NOT_FOUND } from '../labels';
|
|
1
|
+
import { FALLBACK_SPLIT_NOT_FOUND, SPLIT_NOT_FOUND } from '../labels';
|
|
2
2
|
import { IReadinessManager } from '../../readiness/types';
|
|
3
3
|
import { ILogger } from '../../logger/types';
|
|
4
4
|
import { WARN_NOT_EXISTENT_SPLIT } from '../../logger/constants';
|
|
@@ -9,7 +9,7 @@ import { WARN_NOT_EXISTENT_SPLIT } from '../../logger/constants';
|
|
|
9
9
|
*/
|
|
10
10
|
export function validateSplitExistence(log: ILogger, readinessManager: IReadinessManager, splitName: string, labelOrSplitObj: any, method: string): boolean {
|
|
11
11
|
if (readinessManager.isReady()) { // Only if it's ready we validate this, otherwise it may just be that the SDK is not ready yet.
|
|
12
|
-
if (labelOrSplitObj === SPLIT_NOT_FOUND || labelOrSplitObj == null) {
|
|
12
|
+
if (labelOrSplitObj === SPLIT_NOT_FOUND || labelOrSplitObj == null || labelOrSplitObj === FALLBACK_SPLIT_NOT_FOUND) {
|
|
13
13
|
log.warn(WARN_NOT_EXISTENT_SPLIT, [method, splitName]);
|
|
14
14
|
return false;
|
|
15
15
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { FALLBACK_PREFIX } from '../../evaluator/fallbackTreatmentsCalculator';
|
|
2
|
+
|
|
1
3
|
export const SPLIT_KILLED = 'killed';
|
|
2
4
|
export const NO_CONDITION_MATCH = 'default rule';
|
|
3
5
|
export const SPLIT_NOT_FOUND = 'definition not found';
|
|
@@ -7,3 +9,4 @@ export const SPLIT_ARCHIVED = 'archived';
|
|
|
7
9
|
export const NOT_IN_SPLIT = 'not in split';
|
|
8
10
|
export const UNSUPPORTED_MATCHER_TYPE = 'targeting rule type unsupported by sdk';
|
|
9
11
|
export const PREREQUISITES_NOT_MET = 'prerequisites not met';
|
|
12
|
+
export const FALLBACK_SPLIT_NOT_FOUND = FALLBACK_PREFIX + SPLIT_NOT_FOUND;
|
package/types/splitio.d.ts
CHANGED
|
@@ -93,7 +93,6 @@ interface ISharedSettings {
|
|
|
93
93
|
urls?: SplitIO.UrlSettings;
|
|
94
94
|
/**
|
|
95
95
|
* Custom logger object. If not provided, the SDK will use the default `console.log` method for all log levels.
|
|
96
|
-
* Set together with `debug` option to `true` or a log level string to enable logging.
|
|
97
96
|
*/
|
|
98
97
|
logger?: SplitIO.Logger;
|
|
99
98
|
}
|
|
@@ -146,6 +145,8 @@ interface IPluggableSharedSettings {
|
|
|
146
145
|
* config.debug = ErrorLogger()
|
|
147
146
|
* ```
|
|
148
147
|
*
|
|
148
|
+
* When combined with the `logger` option, any log level other than `NONE` (false) will be set to `DEBUG` (true), delegating log level control to the custom logger.
|
|
149
|
+
*
|
|
149
150
|
* @defaultValue `false`
|
|
150
151
|
*/
|
|
151
152
|
debug?: boolean | SplitIO.LogLevel | SplitIO.ILogger;
|
|
@@ -169,6 +170,8 @@ interface INonPluggableSharedSettings {
|
|
|
169
170
|
* config.debug = 'WARN'
|
|
170
171
|
* ```
|
|
171
172
|
*
|
|
173
|
+
* When combined with the `logger` option, any log level other than `NONE` (false) will be set to `DEBUG` (true), delegating log level control to the custom logger.
|
|
174
|
+
*
|
|
172
175
|
* @defaultValue `false`
|
|
173
176
|
*/
|
|
174
177
|
debug?: boolean | SplitIO.LogLevel;
|
|
@@ -618,6 +621,10 @@ declare namespace SplitIO {
|
|
|
618
621
|
* User consent status if using in client-side. Undefined if using in server-side (Node.js).
|
|
619
622
|
*/
|
|
620
623
|
readonly userConsent?: ConsentStatus;
|
|
624
|
+
/**
|
|
625
|
+
* Fallback treatments to be used when the SDK is not ready or the flag is not found.
|
|
626
|
+
*/
|
|
627
|
+
readonly fallbackTreatments?: FallbackTreatmentConfiguration;
|
|
621
628
|
}
|
|
622
629
|
/**
|
|
623
630
|
* Log levels.
|
|
@@ -1225,6 +1232,15 @@ declare namespace SplitIO {
|
|
|
1225
1232
|
* User consent status.
|
|
1226
1233
|
*/
|
|
1227
1234
|
type ConsentStatus = 'GRANTED' | 'DECLINED' | 'UNKNOWN';
|
|
1235
|
+
/**
|
|
1236
|
+
* Fallback treatments to be used when the SDK is not ready or the flag is not found.
|
|
1237
|
+
*/
|
|
1238
|
+
type FallbackTreatmentConfiguration = {
|
|
1239
|
+
global?: Treatment | TreatmentWithConfig,
|
|
1240
|
+
byFlag?: {
|
|
1241
|
+
[featureFlagName: string]: Treatment | TreatmentWithConfig
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1228
1244
|
/**
|
|
1229
1245
|
* Logger. Its interface details are not part of the public API. It shouldn't be used directly.
|
|
1230
1246
|
*/
|