@splitsoftware/splitio-commons 2.7.2-rc.0 → 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.
Files changed (49) hide show
  1. package/CHANGES.txt +2 -5
  2. package/cjs/evaluator/fallbackTreatmentsCalculator/constants.js +8 -0
  3. package/cjs/evaluator/fallbackTreatmentsCalculator/fallbackSanitizer/index.js +47 -0
  4. package/cjs/evaluator/fallbackTreatmentsCalculator/index.js +48 -0
  5. package/cjs/logger/index.js +3 -0
  6. package/cjs/logger/messages/info.js +1 -1
  7. package/cjs/logger/messages/warn.js +1 -1
  8. package/cjs/sdkClient/client.js +9 -2
  9. package/cjs/sdkClient/clientInputValidation.js +17 -6
  10. package/cjs/sdkClient/sdkClient.js +1 -1
  11. package/cjs/sdkFactory/index.js +3 -1
  12. package/cjs/sync/polling/syncTasks/segmentsSyncTask.js +1 -1
  13. package/cjs/sync/polling/updaters/segmentChangesUpdater.js +5 -16
  14. package/cjs/sync/polling/updaters/splitChangesUpdater.js +2 -2
  15. package/cjs/utils/inputValidation/splitExistence.js +1 -1
  16. package/cjs/utils/labels/index.js +3 -1
  17. package/esm/evaluator/fallbackTreatmentsCalculator/constants.js +5 -0
  18. package/esm/evaluator/fallbackTreatmentsCalculator/fallbackSanitizer/index.js +44 -0
  19. package/esm/evaluator/fallbackTreatmentsCalculator/index.js +45 -0
  20. package/esm/logger/index.js +3 -0
  21. package/esm/logger/messages/info.js +1 -1
  22. package/esm/logger/messages/warn.js +1 -1
  23. package/esm/sdkClient/client.js +9 -2
  24. package/esm/sdkClient/clientInputValidation.js +18 -7
  25. package/esm/sdkClient/sdkClient.js +1 -1
  26. package/esm/sdkFactory/index.js +3 -1
  27. package/esm/sync/polling/syncTasks/segmentsSyncTask.js +1 -1
  28. package/esm/sync/polling/updaters/segmentChangesUpdater.js +5 -16
  29. package/esm/sync/polling/updaters/splitChangesUpdater.js +2 -2
  30. package/esm/utils/inputValidation/splitExistence.js +2 -2
  31. package/esm/utils/labels/index.js +2 -0
  32. package/package.json +1 -1
  33. package/src/evaluator/fallbackTreatmentsCalculator/constants.ts +4 -0
  34. package/src/evaluator/fallbackTreatmentsCalculator/fallbackSanitizer/index.ts +62 -0
  35. package/src/evaluator/fallbackTreatmentsCalculator/index.ts +57 -0
  36. package/src/logger/index.ts +2 -0
  37. package/src/logger/messages/info.ts +1 -1
  38. package/src/logger/messages/warn.ts +1 -1
  39. package/src/sdkClient/client.ts +11 -2
  40. package/src/sdkClient/clientInputValidation.ts +22 -7
  41. package/src/sdkClient/sdkClient.ts +2 -1
  42. package/src/sdkFactory/index.ts +4 -1
  43. package/src/sdkFactory/types.ts +2 -0
  44. package/src/sync/polling/syncTasks/segmentsSyncTask.ts +0 -2
  45. package/src/sync/polling/updaters/segmentChangesUpdater.ts +4 -17
  46. package/src/sync/polling/updaters/splitChangesUpdater.ts +5 -4
  47. package/src/utils/inputValidation/splitExistence.ts +2 -2
  48. package/src/utils/labels/index.ts +3 -0
  49. package/types/splitio.d.ts +17 -1
package/CHANGES.txt CHANGED
@@ -1,11 +1,8 @@
1
1
  2.8.0 (October XX, 2025)
2
- - Updated the SDK’s initial synchronization in Node.js (server-side) to use `startup.requestTimeoutBeforeReady` and `startup.retriesOnFailureBeforeReady` to control the timeout and retry behavior of segment requests.
3
-
4
- 2.7.1 (October 8, 2025)
5
- - Bugfix - Update `debug` option to support log levels when `logger` option is used.
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.
6
3
 
7
4
  2.7.0 (October 7, 2025)
8
- - Added support for custom loggers: added `logger` configuration option and `factory.Logger.setLogger` method to allow the SDK to use a custom logger.
5
+ - Added support for custom loggers: added `logger` configuration option and `LoggerAPI.setLogger` method to allow the SDK to use a custom logger.
9
6
 
10
7
  2.6.0 (September 18, 2025)
11
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;
@@ -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 {
@@ -23,7 +23,7 @@ exports.codesInfo = warn_1.codesWarn.concat([
23
23
  [c.POLLING_SMART_PAUSING, c.LOG_PREFIX_SYNC_POLLING + 'Turning segments data polling %s.'],
24
24
  [c.POLLING_START, c.LOG_PREFIX_SYNC_POLLING + 'Starting polling'],
25
25
  [c.POLLING_STOP, c.LOG_PREFIX_SYNC_POLLING + 'Stopping polling'],
26
- [c.SYNC_SPLITS_FETCH_RETRY, c.LOG_PREFIX_SYNC_SPLITS + 'Retrying fetch of feature flags (attempt #%s). Reason: %s'],
26
+ [c.SYNC_SPLITS_FETCH_RETRY, c.LOG_PREFIX_SYNC_SPLITS + 'Retrying download of feature flags #%s. Reason: %s'],
27
27
  [c.SUBMITTERS_PUSH_FULL_QUEUE, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Flushing full %s queue and resetting timer.'],
28
28
  [c.SUBMITTERS_PUSH, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Pushing %s.'],
29
29
  [c.SUBMITTERS_PUSH_PAGE_HIDDEN, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Flushing %s because page became hidden.'],
@@ -9,7 +9,7 @@ exports.codesWarn = error_1.codesError.concat([
9
9
  [c.ENGINE_VALUE_INVALID, c.LOG_PREFIX_ENGINE_VALUE + 'Value %s doesn\'t match with expected type.'],
10
10
  [c.ENGINE_VALUE_NO_ATTRIBUTES, c.LOG_PREFIX_ENGINE_VALUE + 'Defined attribute `%s`. No attributes received.'],
11
11
  // synchronizer
12
- [c.SYNC_MYSEGMENTS_FETCH_RETRY, c.LOG_PREFIX_SYNC_MYSEGMENTS + 'Retrying fetch of memberships (attempt #%s). Reason: %s'],
12
+ [c.SYNC_MYSEGMENTS_FETCH_RETRY, c.LOG_PREFIX_SYNC_MYSEGMENTS + 'Retrying download of segments #%s. Reason: %s'],
13
13
  [c.SYNC_SPLITS_FETCH_FAILS, c.LOG_PREFIX_SYNC_SPLITS + 'Error while doing fetch of feature flags. %s'],
14
14
  [c.STREAMING_PARSING_ERROR_FAILS, c.LOG_PREFIX_SYNC_STREAMING + 'Error parsing SSE error notification: %s'],
15
15
  [c.STREAMING_PARSING_MESSAGE_FAILS, c.LOG_PREFIX_SYNC_STREAMING + 'Error parsing SSE message notification: %s'],
@@ -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 treatment = evaluation.treatment, label = evaluation.label, changeNumber = evaluation.changeNumber, _a = evaluation.config, config = _a === void 0 ? null : _a, impressionsDisabled = evaluation.impressionsDisabled;
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
- return wrapResult(constants_1.CONTROL);
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
- return wrapResult((0, objectAssign_1.objectAssign)({}, constants_1.CONTROL_WITH_CONFIG));
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] = constants_1.CONTROL; });
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] = (0, objectAssign_1.objectAssign)({}, constants_1.CONTROL_WITH_CONFIG); });
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 () {
@@ -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);
@@ -8,6 +8,6 @@ var segmentChangesUpdater_1 = require("../updaters/segmentChangesUpdater");
8
8
  * Creates a sync task that periodically executes a `segmentChangesUpdater` task
9
9
  */
10
10
  function segmentsSyncTaskFactory(fetchSegmentChanges, storage, readiness, settings) {
11
- return (0, syncTask_1.syncTaskFactory)(settings.log, (0, segmentChangesUpdater_1.segmentChangesUpdaterFactory)(settings.log, (0, segmentChangesFetcher_1.segmentChangesFetcherFactory)(fetchSegmentChanges), storage.segments, readiness, settings.startup.requestTimeoutBeforeReady, settings.startup.retriesOnFailureBeforeReady), settings.scheduler.segmentsRefreshRate, 'segmentChangesUpdater');
11
+ return (0, syncTask_1.syncTaskFactory)(settings.log, (0, segmentChangesUpdater_1.segmentChangesUpdaterFactory)(settings.log, (0, segmentChangesFetcher_1.segmentChangesFetcherFactory)(fetchSegmentChanges), storage.segments, readiness), settings.scheduler.segmentsRefreshRate, 'segmentChangesUpdater');
12
12
  }
13
13
  exports.segmentsSyncTaskFactory = segmentsSyncTaskFactory;
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.segmentChangesUpdaterFactory = void 0;
4
4
  var constants_1 = require("../../../readiness/constants");
5
5
  var constants_2 = require("../../../logger/constants");
6
- var timeout_1 = require("../../../utils/promise/timeout");
7
6
  /**
8
7
  * Factory of SegmentChanges updater, a task that:
9
8
  * - fetches segment changes using `segmentChangesFetcher`
@@ -15,33 +14,22 @@ var timeout_1 = require("../../../utils/promise/timeout");
15
14
  * @param segments - segments storage, with sync or async methods
16
15
  * @param readiness - optional readiness manager. Not required for synchronizer or producer mode.
17
16
  */
18
- function segmentChangesUpdaterFactory(log, segmentChangesFetcher, segments, readiness, requestTimeoutBeforeReady, retriesOnFailureBeforeReady) {
17
+ function segmentChangesUpdaterFactory(log, segmentChangesFetcher, segments, readiness) {
19
18
  var readyOnAlreadyExistentState = true;
20
- function _promiseDecorator(promise) {
21
- if (readyOnAlreadyExistentState && requestTimeoutBeforeReady)
22
- promise = (0, timeout_1.timeout)(requestTimeoutBeforeReady, promise);
23
- return promise;
24
- }
25
- function updateSegment(segmentName, noCache, till, fetchOnlyNew, retries) {
19
+ function updateSegment(segmentName, noCache, till, fetchOnlyNew) {
26
20
  log.debug(constants_2.LOG_PREFIX_SYNC_SEGMENTS + "Processing segment " + segmentName);
27
21
  var sincePromise = Promise.resolve(segments.getChangeNumber(segmentName));
28
22
  return sincePromise.then(function (since) {
29
23
  // if fetchOnlyNew flag, avoid processing already fetched segments
30
24
  return fetchOnlyNew && since !== undefined ?
31
25
  false :
32
- segmentChangesFetcher(since || -1, segmentName, noCache, till, _promiseDecorator).then(function (changes) {
26
+ segmentChangesFetcher(since || -1, segmentName, noCache, till).then(function (changes) {
33
27
  return Promise.all(changes.map(function (x) {
34
28
  log.debug(constants_2.LOG_PREFIX_SYNC_SEGMENTS + "Processing " + segmentName + " with till = " + x.till + ". Added: " + x.added.length + ". Removed: " + x.removed.length);
35
29
  return segments.update(segmentName, x.added, x.removed, x.till);
36
30
  })).then(function (updates) {
37
31
  return updates.some(function (update) { return update; });
38
32
  });
39
- }).catch(function (error) {
40
- if (retries) {
41
- log.warn(constants_2.LOG_PREFIX_SYNC_SEGMENTS + "Retrying fetch of segment " + segmentName + " (attempt #" + retries + "). Reason: " + error);
42
- return updateSegment(segmentName, noCache, till, fetchOnlyNew, retries - 1);
43
- }
44
- throw error;
45
33
  });
46
34
  });
47
35
  }
@@ -61,7 +49,8 @@ function segmentChangesUpdaterFactory(log, segmentChangesFetcher, segments, read
61
49
  // If not a segment name provided, read list of available segments names to be updated.
62
50
  var segmentsPromise = Promise.resolve(segmentName ? [segmentName] : segments.getRegisteredSegments());
63
51
  return segmentsPromise.then(function (segmentNames) {
64
- var updaters = segmentNames.map(function (segmentName) { return updateSegment(segmentName, noCache, till, fetchOnlyNew, readyOnAlreadyExistentState ? retriesOnFailureBeforeReady : 0); });
52
+ // Async fetchers
53
+ var updaters = segmentNames.map(function (segmentName) { return updateSegment(segmentName, noCache, till, fetchOnlyNew); });
65
54
  return Promise.all(updaters).then(function (shouldUpdateFlags) {
66
55
  // if at least one segment fetch succeeded, mark segments ready
67
56
  if (shouldUpdateFlags.some(function (update) { return update; }) || readyOnAlreadyExistentState) {
@@ -171,14 +171,14 @@ function splitChangesUpdaterFactory(log, splitChangesFetcher, storage, splitFilt
171
171
  });
172
172
  })
173
173
  .catch(function (error) {
174
+ log.warn(constants_2.SYNC_SPLITS_FETCH_FAILS, [error]);
174
175
  if (startingUp && retriesOnFailureBeforeReady > retry) {
175
176
  retry += 1;
176
- log.warn(constants_2.SYNC_SPLITS_FETCH_RETRY, [retry, error]);
177
+ log.info(constants_2.SYNC_SPLITS_FETCH_RETRY, [retry, error]);
177
178
  return _splitChangesUpdater(sinces, retry);
178
179
  }
179
180
  else {
180
181
  startingUp = false;
181
- log.warn(constants_2.SYNC_SPLITS_FETCH_FAILS, [error]);
182
182
  }
183
183
  return false;
184
184
  });
@@ -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 };
@@ -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 {
@@ -19,7 +19,7 @@ export var codesInfo = codesWarn.concat([
19
19
  [c.POLLING_SMART_PAUSING, c.LOG_PREFIX_SYNC_POLLING + 'Turning segments data polling %s.'],
20
20
  [c.POLLING_START, c.LOG_PREFIX_SYNC_POLLING + 'Starting polling'],
21
21
  [c.POLLING_STOP, c.LOG_PREFIX_SYNC_POLLING + 'Stopping polling'],
22
- [c.SYNC_SPLITS_FETCH_RETRY, c.LOG_PREFIX_SYNC_SPLITS + 'Retrying fetch of feature flags (attempt #%s). Reason: %s'],
22
+ [c.SYNC_SPLITS_FETCH_RETRY, c.LOG_PREFIX_SYNC_SPLITS + 'Retrying download of feature flags #%s. Reason: %s'],
23
23
  [c.SUBMITTERS_PUSH_FULL_QUEUE, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Flushing full %s queue and resetting timer.'],
24
24
  [c.SUBMITTERS_PUSH, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Pushing %s.'],
25
25
  [c.SUBMITTERS_PUSH_PAGE_HIDDEN, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Flushing %s because page became hidden.'],
@@ -5,7 +5,7 @@ export var codesWarn = codesError.concat([
5
5
  [c.ENGINE_VALUE_INVALID, c.LOG_PREFIX_ENGINE_VALUE + 'Value %s doesn\'t match with expected type.'],
6
6
  [c.ENGINE_VALUE_NO_ATTRIBUTES, c.LOG_PREFIX_ENGINE_VALUE + 'Defined attribute `%s`. No attributes received.'],
7
7
  // synchronizer
8
- [c.SYNC_MYSEGMENTS_FETCH_RETRY, c.LOG_PREFIX_SYNC_MYSEGMENTS + 'Retrying fetch of memberships (attempt #%s). Reason: %s'],
8
+ [c.SYNC_MYSEGMENTS_FETCH_RETRY, c.LOG_PREFIX_SYNC_MYSEGMENTS + 'Retrying download of segments #%s. Reason: %s'],
9
9
  [c.SYNC_SPLITS_FETCH_FAILS, c.LOG_PREFIX_SYNC_SPLITS + 'Error while doing fetch of feature flags. %s'],
10
10
  [c.STREAMING_PARSING_ERROR_FAILS, c.LOG_PREFIX_SYNC_STREAMING + 'Error parsing SSE error notification: %s'],
11
11
  [c.STREAMING_PARSING_MESSAGE_FAILS, c.LOG_PREFIX_SYNC_STREAMING + 'Error parsing SSE message notification: %s'],
@@ -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 treatment = evaluation.treatment, label = evaluation.label, changeNumber = evaluation.changeNumber, _a = evaluation.config, config = _a === void 0 ? null : _a, impressionsDisabled = evaluation.impressionsDisabled;
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 { 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';
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
- return wrapResult(CONTROL);
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
- return wrapResult(objectAssign({}, CONTROL_WITH_CONFIG));
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] = CONTROL; });
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] = objectAssign({}, CONTROL_WITH_CONFIG); });
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 () {