@splitsoftware/splitio-commons 2.7.9-rc.0 → 2.7.9-rc.1
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 +8 -3
- package/cjs/logger/constants.js +2 -2
- package/cjs/logger/index.js +0 -3
- package/cjs/logger/messages/warn.js +2 -2
- package/cjs/readiness/readinessManager.js +3 -5
- package/cjs/readiness/sdkReadinessManager.js +33 -0
- package/cjs/sdkClient/client.js +4 -4
- package/cjs/sdkClient/clientInputValidation.js +1 -1
- package/cjs/sdkManager/index.js +3 -3
- package/cjs/sync/streaming/SSEHandler/index.js +1 -1
- package/cjs/sync/submitters/telemetrySubmitter.js +3 -3
- package/cjs/trackers/telemetryTracker.js +6 -6
- package/cjs/utils/inputValidation/index.js +2 -1
- package/cjs/utils/inputValidation/isOperational.js +9 -4
- package/cjs/utils/inputValidation/splitExistence.js +3 -3
- package/esm/logger/constants.js +1 -1
- package/esm/logger/index.js +0 -3
- package/esm/logger/messages/warn.js +2 -2
- package/esm/readiness/readinessManager.js +3 -5
- package/esm/readiness/sdkReadinessManager.js +33 -0
- package/esm/sdkClient/client.js +4 -4
- package/esm/sdkClient/clientInputValidation.js +2 -2
- package/esm/sdkManager/index.js +4 -4
- package/esm/sync/streaming/SSEHandler/index.js +1 -1
- package/esm/sync/submitters/telemetrySubmitter.js +3 -3
- package/esm/trackers/telemetryTracker.js +6 -6
- package/esm/utils/inputValidation/index.js +1 -1
- package/esm/utils/inputValidation/isOperational.js +8 -4
- package/esm/utils/inputValidation/splitExistence.js +3 -3
- package/package.json +1 -1
- package/src/logger/constants.ts +1 -1
- package/src/logger/index.ts +0 -2
- package/src/logger/messages/warn.ts +2 -2
- package/src/readiness/readinessManager.ts +3 -4
- package/src/readiness/sdkReadinessManager.ts +30 -0
- package/src/sdkClient/client.ts +4 -4
- package/src/sdkClient/clientInputValidation.ts +3 -3
- package/src/sdkManager/index.ts +4 -4
- package/src/sync/streaming/SSEHandler/index.ts +1 -1
- package/src/sync/submitters/telemetrySubmitter.ts +3 -3
- package/src/trackers/telemetryTracker.ts +6 -6
- package/src/utils/inputValidation/index.ts +1 -1
- package/src/utils/inputValidation/isOperational.ts +9 -4
- package/src/utils/inputValidation/splitExistence.ts +3 -3
- package/types/splitio.d.ts +25 -10
package/CHANGES.txt
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
2.8.0 (October
|
|
2
|
-
|
|
1
|
+
2.8.0 (October 28, 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
|
+
- Added `client.whenReady()` and `client.whenReadyFromCache()` methods to replace the deprecated `client.ready()` method, which has an issue causing the returned promise to hang when using async/await syntax if it was rejected.
|
|
4
|
+
- Updated the SDK_READY_FROM_CACHE event to be emitted alongside the SDK_READY event if it hasn’t already been emitted.
|
|
5
|
+
|
|
6
|
+
2.7.1 (October 8, 2025)
|
|
7
|
+
- Bugfix - Update `debug` option to support log levels when `logger` option is used.
|
|
3
8
|
|
|
4
9
|
2.7.0 (October 7, 2025)
|
|
5
|
-
- Added support for custom loggers: added `logger` configuration option and `
|
|
10
|
+
- Added support for custom loggers: added `logger` configuration option and `factory.Logger.setLogger` method to allow the SDK to use a custom logger.
|
|
6
11
|
|
|
7
12
|
2.6.0 (September 18, 2025)
|
|
8
13
|
- 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`.
|
package/cjs/logger/constants.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SUBMITTERS_PUSH_PAGE_HIDDEN = exports.USER_CONSENT_INITIAL = exports.USER_CONSENT_NOT_UPDATED = exports.USER_CONSENT_UPDATED = exports.IMPRESSIONS_TRACKER_SUCCESS = exports.EVENTS_TRACKER_SUCCESS = exports.SYNC_STOP_POLLING = exports.SYNC_CONTINUE_POLLING = exports.SYNC_START_POLLING = exports.SUBMITTERS_PUSH = exports.SUBMITTERS_PUSH_FULL_QUEUE = exports.STREAMING_DISCONNECTING = exports.STREAMING_DISABLED = exports.STREAMING_CONNECTING = exports.STREAMING_RECONNECT = exports.STREAMING_REFRESH_TOKEN = exports.SYNC_SPLITS_FETCH_RETRY = exports.POLLING_STOP = exports.POLLING_START = exports.POLLING_SMART_PAUSING = exports.NEW_FACTORY = exports.NEW_SHARED_CLIENT = exports.IMPRESSION_QUEUEING = exports.IMPRESSION = exports.CLIENT_READY = exports.CLIENT_READY_FROM_CACHE = exports.ENGINE_DEFAULT = exports.ENGINE_MATCHER_RESULT = exports.SETTINGS_SPLITS_FILTER = exports.SYNC_TASK_STOP = exports.SYNC_TASK_EXECUTE = exports.SYNC_TASK_START = exports.STREAMING_NEW_MESSAGE = exports.SYNC_RBS_UPDATE = exports.SYNC_SPLITS_UPDATE = exports.SYNC_SPLITS_FETCH = exports.SYNC_OFFLINE_DATA = exports.RETRIEVE_MANAGER = exports.RETRIEVE_CLIENT_EXISTING = exports.RETRIEVE_CLIENT_DEFAULT = exports.CLEANUP_DEREGISTERING = exports.CLEANUP_REGISTERING = exports.ENGINE_SANITIZE = exports.ENGINE_VALUE = exports.ENGINE_MATCHER_DEPENDENCY_PRE = exports.ENGINE_MATCHER_DEPENDENCY = exports.ENGINE_BUCKET = exports.ENGINE_COMBINER_IFELSEIF_NO_TREATMENT = exports.ENGINE_COMBINER_IFELSEIF = exports.ENGINE_COMBINER_AND = void 0;
|
|
4
|
-
exports.ERROR_EMPTY_ARRAY = exports.ERROR_EMPTY = exports.ERROR_INVALID = exports.ERROR_INVALID_KEY_OBJECT = exports.ERROR_TOO_LONG = exports.ERROR_NULL = exports.ERROR_CLIENT_DESTROYED = exports.ERROR_NOT_FINITE = exports.ERROR_SIZE_EXCEEDED = exports.ERROR_NOT_PLAIN_OBJECT = exports.ERROR_EVENT_TYPE_FORMAT = exports.ERROR_EVENTS_TRACKER = exports.ERROR_IMPRESSIONS_LISTENER = exports.ERROR_IMPRESSIONS_TRACKER = exports.ERROR_STREAMING_AUTH = exports.ERROR_STREAMING_SSE = exports.ERROR_SYNC_OFFLINE_LOADING = exports.ERROR_CLIENT_CANNOT_GET_READY = exports.ERROR_CLIENT_LISTENER = exports.ERROR_LOGLEVEL_INVALID = exports.ERROR_ENGINE_COMBINER_IFELSEIF = exports.WARN_FLAGSET_WITHOUT_FLAGS = exports.WARN_FLAGSET_NOT_CONFIGURED = exports.WARN_LOWERCASE_FLAGSET = exports.WARN_INVALID_FLAGSET = exports.STREAMING_PARSING_SPLIT_UPDATE = exports.STREAMING_PARSING_MEMBERSHIPS_UPDATE = exports.WARN_SDK_KEY = exports.WARN_SPLITS_FILTER_EMPTY = exports.WARN_SPLITS_FILTER_INVALID = exports.WARN_SPLITS_FILTER_IGNORED = exports.WARN_INTEGRATION_INVALID = exports.WARN_NOT_EXISTENT_TT = exports.WARN_LOWERCASE_TRAFFIC_TYPE = exports.WARN_NOT_EXISTENT_SPLIT = exports.WARN_TRIMMING = exports.WARN_CONVERTING = exports.WARN_TRIMMING_PROPERTIES = exports.WARN_SETTING_NULL = exports.SUBMITTERS_PUSH_RETRY = exports.SUBMITTERS_PUSH_FAILS = exports.STREAMING_FALLBACK = exports.STREAMING_PARSING_MESSAGE_FAILS = exports.STREAMING_PARSING_ERROR_FAILS = exports.SYNC_SPLITS_FETCH_FAILS = exports.SYNC_MYSEGMENTS_FETCH_RETRY = exports.
|
|
4
|
+
exports.ERROR_EMPTY_ARRAY = exports.ERROR_EMPTY = exports.ERROR_INVALID = exports.ERROR_INVALID_KEY_OBJECT = exports.ERROR_TOO_LONG = exports.ERROR_NULL = exports.ERROR_CLIENT_DESTROYED = exports.ERROR_NOT_FINITE = exports.ERROR_SIZE_EXCEEDED = exports.ERROR_NOT_PLAIN_OBJECT = exports.ERROR_EVENT_TYPE_FORMAT = exports.ERROR_EVENTS_TRACKER = exports.ERROR_IMPRESSIONS_LISTENER = exports.ERROR_IMPRESSIONS_TRACKER = exports.ERROR_STREAMING_AUTH = exports.ERROR_STREAMING_SSE = exports.ERROR_SYNC_OFFLINE_LOADING = exports.ERROR_CLIENT_CANNOT_GET_READY = exports.ERROR_CLIENT_LISTENER = exports.ERROR_LOGLEVEL_INVALID = exports.ERROR_ENGINE_COMBINER_IFELSEIF = exports.WARN_FLAGSET_WITHOUT_FLAGS = exports.WARN_FLAGSET_NOT_CONFIGURED = exports.WARN_LOWERCASE_FLAGSET = exports.WARN_INVALID_FLAGSET = exports.STREAMING_PARSING_SPLIT_UPDATE = exports.STREAMING_PARSING_MEMBERSHIPS_UPDATE = exports.WARN_SDK_KEY = exports.WARN_SPLITS_FILTER_EMPTY = exports.WARN_SPLITS_FILTER_INVALID = exports.WARN_SPLITS_FILTER_IGNORED = exports.WARN_INTEGRATION_INVALID = exports.WARN_NOT_EXISTENT_TT = exports.WARN_LOWERCASE_TRAFFIC_TYPE = exports.WARN_NOT_EXISTENT_SPLIT = exports.WARN_TRIMMING = exports.WARN_CONVERTING = exports.WARN_TRIMMING_PROPERTIES = exports.WARN_SETTING_NULL = exports.SUBMITTERS_PUSH_RETRY = exports.SUBMITTERS_PUSH_FAILS = exports.STREAMING_FALLBACK = exports.STREAMING_PARSING_MESSAGE_FAILS = exports.STREAMING_PARSING_ERROR_FAILS = exports.SYNC_SPLITS_FETCH_FAILS = exports.SYNC_MYSEGMENTS_FETCH_RETRY = exports.CLIENT_NOT_READY_FROM_CACHE = exports.CLIENT_NO_LISTENER = exports.ENGINE_VALUE_NO_ATTRIBUTES = exports.ENGINE_VALUE_INVALID = void 0;
|
|
5
5
|
exports.LOG_PREFIX_CLEANUP = exports.LOG_PREFIX_UNIQUE_KEYS_TRACKER = exports.LOG_PREFIX_EVENTS_TRACKER = exports.LOG_PREFIX_IMPRESSIONS_TRACKER = exports.LOG_PREFIX_SYNC_SUBMITTERS = exports.LOG_PREFIX_SYNC_POLLING = exports.LOG_PREFIX_SYNC_MYSEGMENTS = exports.LOG_PREFIX_SYNC_SEGMENTS = exports.LOG_PREFIX_SYNC_SPLITS = exports.LOG_PREFIX_SYNC_STREAMING = exports.LOG_PREFIX_SYNC_OFFLINE = exports.LOG_PREFIX_SYNC_MANAGER = exports.LOG_PREFIX_SYNC = exports.LOG_PREFIX_ENGINE_VALUE = exports.LOG_PREFIX_ENGINE_MATCHER = exports.LOG_PREFIX_ENGINE_COMBINER = exports.LOG_PREFIX_ENGINE = exports.LOG_PREFIX_CLIENT_INSTANTIATION = exports.LOG_PREFIX_INSTANTIATION = exports.LOG_PREFIX_SETTINGS = exports.ENGINE_MATCHER_ERROR = exports.ERROR_SETS_FILTER_EXCLUSIVE = exports.ERROR_TOO_MANY_SETS = exports.ERROR_MIN_CONFIG_PARAM = exports.ERROR_NOT_BOOLEAN = exports.ERROR_STORAGE_INVALID = exports.ERROR_HTTP = exports.ERROR_INVALID_CONFIG_PARAM = void 0;
|
|
6
6
|
/**
|
|
7
7
|
* Message codes used to trim string log messages from commons and client-side API modules,
|
|
@@ -63,7 +63,7 @@ exports.SUBMITTERS_PUSH_PAGE_HIDDEN = 125;
|
|
|
63
63
|
exports.ENGINE_VALUE_INVALID = 200;
|
|
64
64
|
exports.ENGINE_VALUE_NO_ATTRIBUTES = 201;
|
|
65
65
|
exports.CLIENT_NO_LISTENER = 202;
|
|
66
|
-
exports.
|
|
66
|
+
exports.CLIENT_NOT_READY_FROM_CACHE = 203;
|
|
67
67
|
exports.SYNC_MYSEGMENTS_FETCH_RETRY = 204;
|
|
68
68
|
exports.SYNC_SPLITS_FETCH_FAILS = 205;
|
|
69
69
|
exports.STREAMING_PARSING_ERROR_FAILS = 206;
|
package/cjs/logger/index.js
CHANGED
|
@@ -63,9 +63,6 @@ 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);
|
|
69
66
|
return;
|
|
70
67
|
}
|
|
71
68
|
else {
|
|
@@ -17,8 +17,8 @@ exports.codesWarn = error_1.codesError.concat([
|
|
|
17
17
|
[c.SUBMITTERS_PUSH_FAILS, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Dropping %s after retry. Reason: %s.'],
|
|
18
18
|
[c.SUBMITTERS_PUSH_RETRY, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Failed to push %s, keeping data to retry on next iteration. Reason: %s.'],
|
|
19
19
|
// client status
|
|
20
|
-
[c.
|
|
21
|
-
[c.CLIENT_NO_LISTENER, 'No listeners for
|
|
20
|
+
[c.CLIENT_NOT_READY_FROM_CACHE, '%s: the SDK is not ready to evaluate. Results may be incorrect%s. Make sure to wait for SDK readiness before using this method.'],
|
|
21
|
+
[c.CLIENT_NO_LISTENER, 'No listeners for SDK_READY event detected. Incorrect control treatments could have been logged if you called getTreatment/s while the SDK was not yet synchronized with the backend.'],
|
|
22
22
|
// input validation
|
|
23
23
|
[c.WARN_SETTING_NULL, '%s: Property "%s" is of invalid type. Setting value to null.'],
|
|
24
24
|
[c.WARN_TRIMMING_PROPERTIES, '%s: more than 300 properties were provided. Some of them will be trimmed when processed.'],
|
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.readinessManagerFactory = void 0;
|
|
4
4
|
var objectAssign_1 = require("../utils/lang/objectAssign");
|
|
5
5
|
var constants_1 = require("./constants");
|
|
6
|
-
var constants_2 = require("../utils/constants");
|
|
7
6
|
function splitsEventEmitterFactory(EventEmitter) {
|
|
8
7
|
var splitsEventEmitter = (0, objectAssign_1.objectAssign)(new EventEmitter(), {
|
|
9
8
|
splitsArrived: false,
|
|
@@ -75,7 +74,7 @@ function readinessManagerFactory(EventEmitter, settings, splits, isShared) {
|
|
|
75
74
|
if (!isReady && !isDestroyed) {
|
|
76
75
|
try {
|
|
77
76
|
syncLastUpdate();
|
|
78
|
-
gate.emit(constants_1.SDK_READY_FROM_CACHE);
|
|
77
|
+
gate.emit(constants_1.SDK_READY_FROM_CACHE, isReady);
|
|
79
78
|
}
|
|
80
79
|
catch (e) {
|
|
81
80
|
// throws user callback exceptions in next tick
|
|
@@ -84,7 +83,6 @@ function readinessManagerFactory(EventEmitter, settings, splits, isShared) {
|
|
|
84
83
|
}
|
|
85
84
|
}
|
|
86
85
|
function checkIsReadyOrUpdate(diff) {
|
|
87
|
-
var _a;
|
|
88
86
|
if (isDestroyed)
|
|
89
87
|
return;
|
|
90
88
|
if (isReady) {
|
|
@@ -103,9 +101,9 @@ function readinessManagerFactory(EventEmitter, settings, splits, isShared) {
|
|
|
103
101
|
isReady = true;
|
|
104
102
|
try {
|
|
105
103
|
syncLastUpdate();
|
|
106
|
-
if (!isReadyFromCache
|
|
104
|
+
if (!isReadyFromCache) {
|
|
107
105
|
isReadyFromCache = true;
|
|
108
|
-
gate.emit(constants_1.SDK_READY_FROM_CACHE);
|
|
106
|
+
gate.emit(constants_1.SDK_READY_FROM_CACHE, isReady);
|
|
109
107
|
}
|
|
110
108
|
gate.emit(constants_1.SDK_READY);
|
|
111
109
|
}
|
|
@@ -8,6 +8,7 @@ var constants_1 = require("./constants");
|
|
|
8
8
|
var constants_2 = require("../logger/constants");
|
|
9
9
|
var NEW_LISTENER_EVENT = 'newListener';
|
|
10
10
|
var REMOVE_LISTENER_EVENT = 'removeListener';
|
|
11
|
+
var TIMEOUT_ERROR = new Error(constants_1.SDK_READY_TIMED_OUT);
|
|
11
12
|
/**
|
|
12
13
|
* SdkReadinessManager factory, which provides the public status API of SDK clients and manager: ready promise, readiness event emitter and constants (SDK_READY, etc).
|
|
13
14
|
* It also updates logs related warnings and errors.
|
|
@@ -34,6 +35,9 @@ function sdkReadinessManagerFactory(EventEmitter, settings, readinessManager) {
|
|
|
34
35
|
readyCbCount++;
|
|
35
36
|
}
|
|
36
37
|
}
|
|
38
|
+
else if (event === constants_1.SDK_READY_FROM_CACHE && readinessManager.isReadyFromCache()) {
|
|
39
|
+
log.error(constants_2.ERROR_CLIENT_LISTENER, ['SDK_READY_FROM_CACHE']);
|
|
40
|
+
}
|
|
37
41
|
});
|
|
38
42
|
/** Ready promise */
|
|
39
43
|
var readyPromise = generateReadyPromise();
|
|
@@ -76,6 +80,7 @@ function sdkReadinessManagerFactory(EventEmitter, settings, readinessManager) {
|
|
|
76
80
|
SDK_UPDATE: constants_1.SDK_UPDATE,
|
|
77
81
|
SDK_READY_TIMED_OUT: constants_1.SDK_READY_TIMED_OUT,
|
|
78
82
|
},
|
|
83
|
+
// @TODO: remove in next major
|
|
79
84
|
ready: function () {
|
|
80
85
|
if (readinessManager.hasTimedout()) {
|
|
81
86
|
if (!readinessManager.isReady()) {
|
|
@@ -87,6 +92,34 @@ function sdkReadinessManagerFactory(EventEmitter, settings, readinessManager) {
|
|
|
87
92
|
}
|
|
88
93
|
return readyPromise;
|
|
89
94
|
},
|
|
95
|
+
whenReady: function () {
|
|
96
|
+
return new Promise(function (resolve, reject) {
|
|
97
|
+
if (readinessManager.isReady()) {
|
|
98
|
+
resolve();
|
|
99
|
+
}
|
|
100
|
+
else if (readinessManager.hasTimedout()) {
|
|
101
|
+
reject(TIMEOUT_ERROR);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
readinessManager.gate.once(constants_1.SDK_READY, resolve);
|
|
105
|
+
readinessManager.gate.once(constants_1.SDK_READY_TIMED_OUT, function () { return reject(TIMEOUT_ERROR); });
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
whenReadyFromCache: function () {
|
|
110
|
+
return new Promise(function (resolve, reject) {
|
|
111
|
+
if (readinessManager.isReadyFromCache()) {
|
|
112
|
+
resolve(readinessManager.isReady());
|
|
113
|
+
}
|
|
114
|
+
else if (readinessManager.hasTimedout()) {
|
|
115
|
+
reject(TIMEOUT_ERROR);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
readinessManager.gate.once(constants_1.SDK_READY_FROM_CACHE, function () { return resolve(readinessManager.isReady()); });
|
|
119
|
+
readinessManager.gate.once(constants_1.SDK_READY_TIMED_OUT, function () { return reject(TIMEOUT_ERROR); });
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
},
|
|
90
123
|
__getStatus: function () {
|
|
91
124
|
return {
|
|
92
125
|
isReady: readinessManager.isReady(),
|
package/cjs/sdkClient/client.js
CHANGED
|
@@ -44,7 +44,7 @@ function clientFactory(params) {
|
|
|
44
44
|
stopTelemetryTracker(queue[0] && queue[0].imp.label);
|
|
45
45
|
return treatment;
|
|
46
46
|
};
|
|
47
|
-
var evaluation = readinessManager.
|
|
47
|
+
var evaluation = readinessManager.isReadyFromCache() ?
|
|
48
48
|
(0, evaluator_1.evaluateFeature)(log, key, featureFlagName, attributes, storage) :
|
|
49
49
|
isAsync ? // If the SDK is not ready, treatment may be incorrect due to having splits but not segments data, or storage is not connected
|
|
50
50
|
Promise.resolve(treatmentNotReady) :
|
|
@@ -69,7 +69,7 @@ function clientFactory(params) {
|
|
|
69
69
|
stopTelemetryTracker(queue[0] && queue[0].imp.label);
|
|
70
70
|
return treatments;
|
|
71
71
|
};
|
|
72
|
-
var evaluations = readinessManager.
|
|
72
|
+
var evaluations = readinessManager.isReadyFromCache() ?
|
|
73
73
|
(0, evaluator_1.evaluateFeatures)(log, key, featureFlagNames, attributes, storage) :
|
|
74
74
|
isAsync ? // If the SDK is not ready, treatment may be incorrect due to having splits but not segments data, or storage is not connected
|
|
75
75
|
Promise.resolve(treatmentsNotReady(featureFlagNames)) :
|
|
@@ -95,7 +95,7 @@ function clientFactory(params) {
|
|
|
95
95
|
stopTelemetryTracker(queue[0] && queue[0].imp.label);
|
|
96
96
|
return treatments;
|
|
97
97
|
};
|
|
98
|
-
var evaluations = readinessManager.
|
|
98
|
+
var evaluations = readinessManager.isReadyFromCache() ?
|
|
99
99
|
(0, evaluator_1.evaluateFeaturesByFlagSets)(log, key, flagSetNames, attributes, storage, methodName) :
|
|
100
100
|
isAsync ?
|
|
101
101
|
Promise.resolve({}) :
|
|
@@ -120,7 +120,7 @@ function clientFactory(params) {
|
|
|
120
120
|
if (treatment === constants_1.CONTROL) {
|
|
121
121
|
var fallbackTreatment = fallbackTreatmentsCalculator.resolve(featureFlagName, label);
|
|
122
122
|
treatment = fallbackTreatment.treatment;
|
|
123
|
-
label = fallbackTreatment.label
|
|
123
|
+
label = fallbackTreatment.label;
|
|
124
124
|
config = fallbackTreatment.config;
|
|
125
125
|
}
|
|
126
126
|
log.info(constants_2.IMPRESSION, [featureFlagName, matchingKey, treatment, label]);
|
|
@@ -26,7 +26,7 @@ function clientInputValidationDecorator(settings, client, readinessManager, fall
|
|
|
26
26
|
var attributes = (0, inputValidation_1.validateAttributes)(log, maybeAttributes, methodName);
|
|
27
27
|
var isNotDestroyed = (0, inputValidation_1.validateIfNotDestroyed)(log, readinessManager, methodName);
|
|
28
28
|
var options = (0, inputValidation_1.validateEvaluationOptions)(log, maybeOptions, methodName);
|
|
29
|
-
(0, inputValidation_1.
|
|
29
|
+
(0, inputValidation_1.validateIfReadyFromCache)(log, readinessManager, methodName, nameOrNames);
|
|
30
30
|
var valid = isNotDestroyed && key && nameOrNames && attributes !== false;
|
|
31
31
|
return {
|
|
32
32
|
valid: valid,
|
package/cjs/sdkManager/index.js
CHANGED
|
@@ -54,7 +54,7 @@ function sdkManagerFactory(settings, splits, _a) {
|
|
|
54
54
|
*/
|
|
55
55
|
split: function (featureFlagName) {
|
|
56
56
|
var splitName = (0, inputValidation_1.validateSplit)(log, featureFlagName, constants_1.SPLIT_FN_LABEL);
|
|
57
|
-
if (!(0, inputValidation_1.
|
|
57
|
+
if (!(0, inputValidation_1.validateIfOperational)(log, readinessManager, constants_1.SPLIT_FN_LABEL) || !splitName) {
|
|
58
58
|
return isAsync ? Promise.resolve(null) : null;
|
|
59
59
|
}
|
|
60
60
|
var split = splits.getSplit(splitName);
|
|
@@ -71,7 +71,7 @@ function sdkManagerFactory(settings, splits, _a) {
|
|
|
71
71
|
* Get the feature flag objects present on the factory storage
|
|
72
72
|
*/
|
|
73
73
|
splits: function () {
|
|
74
|
-
if (!(0, inputValidation_1.
|
|
74
|
+
if (!(0, inputValidation_1.validateIfOperational)(log, readinessManager, constants_1.SPLITS_FN_LABEL)) {
|
|
75
75
|
return isAsync ? Promise.resolve([]) : [];
|
|
76
76
|
}
|
|
77
77
|
var currentSplits = splits.getAll();
|
|
@@ -83,7 +83,7 @@ function sdkManagerFactory(settings, splits, _a) {
|
|
|
83
83
|
* Get the feature flag names present on the factory storage
|
|
84
84
|
*/
|
|
85
85
|
names: function () {
|
|
86
|
-
if (!(0, inputValidation_1.
|
|
86
|
+
if (!(0, inputValidation_1.validateIfOperational)(log, readinessManager, constants_1.NAMES_FN_LABEL)) {
|
|
87
87
|
return isAsync ? Promise.resolve([]) : [];
|
|
88
88
|
}
|
|
89
89
|
var splitNames = splits.getSplitNames();
|
|
@@ -19,7 +19,7 @@ function SSEHandlerFactory(log, pushEmitter, telemetryTracker) {
|
|
|
19
19
|
// Ably error
|
|
20
20
|
var code = error.parsedData.code;
|
|
21
21
|
telemetryTracker.streamingEvent(constants_3.ABLY_ERROR, code);
|
|
22
|
-
// 401 errors due to invalid or expired token (e.g., if refresh token
|
|
22
|
+
// 401 errors due to invalid or expired token (e.g., if refresh token couldn't be executed)
|
|
23
23
|
if (40140 <= code && code <= 40149)
|
|
24
24
|
return true;
|
|
25
25
|
// Others 4XX errors (e.g., bad request from the SDK)
|
|
@@ -107,14 +107,14 @@ function telemetrySubmitterFactory(params) {
|
|
|
107
107
|
if (!telemetry || !now)
|
|
108
108
|
return; // No submitter created if telemetry cache is not defined
|
|
109
109
|
var settings = params.settings, _a = params.settings, log = _a.log, telemetryRefreshRate = _a.scheduler.telemetryRefreshRate, splitApi = params.splitApi, readiness = params.readiness, sdkReadinessManager = params.sdkReadinessManager;
|
|
110
|
-
var
|
|
110
|
+
var stopTimer = (0, timer_1.timer)(now);
|
|
111
111
|
var submitter = (0, submitter_1.firstPushWindowDecorator)((0, submitter_1.submitterFactory)(log, splitApi.postMetricsUsage, telemetry, telemetryRefreshRate, undefined, 0, true), telemetryRefreshRate);
|
|
112
112
|
readiness.gate.once(constants_2.SDK_READY_FROM_CACHE, function () {
|
|
113
|
-
telemetry.recordTimeUntilReadyFromCache(
|
|
113
|
+
telemetry.recordTimeUntilReadyFromCache(stopTimer());
|
|
114
114
|
});
|
|
115
115
|
sdkReadinessManager.incInternalReadyCbCount();
|
|
116
116
|
readiness.gate.once(constants_2.SDK_READY, function () {
|
|
117
|
-
telemetry.recordTimeUntilReady(
|
|
117
|
+
telemetry.recordTimeUntilReady(stopTimer());
|
|
118
118
|
// Post config data when the SDK is ready and if the telemetry submitter was started
|
|
119
119
|
if (submitter.isRunning()) {
|
|
120
120
|
var postMetricsConfigTask = (0, submitter_1.submitterFactory)(log, splitApi.postMetricsConfig, telemetryCacheConfigAdapter(telemetry, settings), 0, undefined, 0, true);
|
|
@@ -6,10 +6,10 @@ var timer_1 = require("../utils/timeTracker/timer");
|
|
|
6
6
|
var constants_1 = require("../utils/constants");
|
|
7
7
|
function telemetryTrackerFactory(telemetryCache, now) {
|
|
8
8
|
if (telemetryCache && now) {
|
|
9
|
-
var
|
|
9
|
+
var sessionTimer_1 = (0, timer_1.timer)(now);
|
|
10
10
|
return {
|
|
11
11
|
trackEval: function (method) {
|
|
12
|
-
var
|
|
12
|
+
var evalTimer = (0, timer_1.timer)(now);
|
|
13
13
|
return function (label) {
|
|
14
14
|
switch (label) {
|
|
15
15
|
case labels_1.EXCEPTION:
|
|
@@ -19,13 +19,13 @@ function telemetryTrackerFactory(telemetryCache, now) {
|
|
|
19
19
|
if (telemetryCache.recordNonReadyUsage)
|
|
20
20
|
telemetryCache.recordNonReadyUsage();
|
|
21
21
|
}
|
|
22
|
-
telemetryCache.recordLatency(method,
|
|
22
|
+
telemetryCache.recordLatency(method, evalTimer());
|
|
23
23
|
};
|
|
24
24
|
},
|
|
25
25
|
trackHttp: function (operation) {
|
|
26
|
-
var
|
|
26
|
+
var httpTimer = (0, timer_1.timer)(now);
|
|
27
27
|
return function (error) {
|
|
28
|
-
telemetryCache.recordHttpLatency(operation,
|
|
28
|
+
telemetryCache.recordHttpLatency(operation, httpTimer());
|
|
29
29
|
if (error && error.statusCode)
|
|
30
30
|
telemetryCache.recordHttpError(operation, error.statusCode);
|
|
31
31
|
else
|
|
@@ -34,7 +34,7 @@ function telemetryTrackerFactory(telemetryCache, now) {
|
|
|
34
34
|
},
|
|
35
35
|
sessionLength: function () {
|
|
36
36
|
if (telemetryCache.recordSessionLength)
|
|
37
|
-
telemetryCache.recordSessionLength(
|
|
37
|
+
telemetryCache.recordSessionLength(sessionTimer_1());
|
|
38
38
|
},
|
|
39
39
|
streamingEvent: function (e, d) {
|
|
40
40
|
if (e === constants_1.AUTH_REJECTION) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.validateEvaluationOptions = exports.validateTrafficTypeExistence = exports.validateSplitExistence = exports.validateIfOperational = exports.validateIfNotDestroyed = exports.validateTrafficType = exports.validateSplits = exports.validateSplit = exports.validateKey = exports.validateEventProperties = exports.validateEventValue = exports.validateEvent = exports.validateAttributes = exports.releaseApiKey = exports.validateAndTrackApiKey = exports.validateApiKey = void 0;
|
|
3
|
+
exports.validateEvaluationOptions = exports.validateTrafficTypeExistence = exports.validateSplitExistence = exports.validateIfOperational = exports.validateIfReadyFromCache = exports.validateIfNotDestroyed = exports.validateTrafficType = exports.validateSplits = exports.validateSplit = exports.validateKey = exports.validateEventProperties = exports.validateEventValue = exports.validateEvent = exports.validateAttributes = exports.releaseApiKey = exports.validateAndTrackApiKey = exports.validateApiKey = void 0;
|
|
4
4
|
var apiKey_1 = require("./apiKey");
|
|
5
5
|
Object.defineProperty(exports, "validateApiKey", { enumerable: true, get: function () { return apiKey_1.validateApiKey; } });
|
|
6
6
|
Object.defineProperty(exports, "validateAndTrackApiKey", { enumerable: true, get: function () { return apiKey_1.validateAndTrackApiKey; } });
|
|
@@ -23,6 +23,7 @@ var trafficType_1 = require("./trafficType");
|
|
|
23
23
|
Object.defineProperty(exports, "validateTrafficType", { enumerable: true, get: function () { return trafficType_1.validateTrafficType; } });
|
|
24
24
|
var isOperational_1 = require("./isOperational");
|
|
25
25
|
Object.defineProperty(exports, "validateIfNotDestroyed", { enumerable: true, get: function () { return isOperational_1.validateIfNotDestroyed; } });
|
|
26
|
+
Object.defineProperty(exports, "validateIfReadyFromCache", { enumerable: true, get: function () { return isOperational_1.validateIfReadyFromCache; } });
|
|
26
27
|
Object.defineProperty(exports, "validateIfOperational", { enumerable: true, get: function () { return isOperational_1.validateIfOperational; } });
|
|
27
28
|
var splitExistence_1 = require("./splitExistence");
|
|
28
29
|
Object.defineProperty(exports, "validateSplitExistence", { enumerable: true, get: function () { return splitExistence_1.validateSplitExistence; } });
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.validateIfOperational = exports.validateIfNotDestroyed = void 0;
|
|
3
|
+
exports.validateIfOperational = exports.validateIfReadyFromCache = exports.validateIfNotDestroyed = void 0;
|
|
4
4
|
var constants_1 = require("../../logger/constants");
|
|
5
5
|
function validateIfNotDestroyed(log, readinessManager, method) {
|
|
6
6
|
if (!readinessManager.isDestroyed())
|
|
@@ -9,10 +9,15 @@ function validateIfNotDestroyed(log, readinessManager, method) {
|
|
|
9
9
|
return false;
|
|
10
10
|
}
|
|
11
11
|
exports.validateIfNotDestroyed = validateIfNotDestroyed;
|
|
12
|
-
function
|
|
13
|
-
if (readinessManager.
|
|
12
|
+
function validateIfReadyFromCache(log, readinessManager, method, featureFlagNameOrNames) {
|
|
13
|
+
if (readinessManager.isReadyFromCache())
|
|
14
14
|
return true;
|
|
15
|
-
log.warn(constants_1.
|
|
15
|
+
log.warn(constants_1.CLIENT_NOT_READY_FROM_CACHE, [method, featureFlagNameOrNames ? " for feature flag " + featureFlagNameOrNames.toString() : '']);
|
|
16
16
|
return false;
|
|
17
17
|
}
|
|
18
|
+
exports.validateIfReadyFromCache = validateIfReadyFromCache;
|
|
19
|
+
// Operational means that the SDK is ready to evaluate (not destroyed and ready from cache)
|
|
20
|
+
function validateIfOperational(log, readinessManager, method, featureFlagNameOrNames) {
|
|
21
|
+
return validateIfNotDestroyed(log, readinessManager, method) && validateIfReadyFromCache(log, readinessManager, method, featureFlagNameOrNames);
|
|
22
|
+
}
|
|
18
23
|
exports.validateIfOperational = validateIfOperational;
|
|
@@ -5,11 +5,11 @@ var labels_1 = require("../labels");
|
|
|
5
5
|
var constants_1 = require("../../logger/constants");
|
|
6
6
|
/**
|
|
7
7
|
* 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.
|
|
8
|
-
* But it's not going to run on the input validation layer. In any case, the most
|
|
8
|
+
* But it's not going to run on the input validation layer. In any case, the most compelling reason to use it as we do is to avoid going to Redis and get a split twice.
|
|
9
9
|
*/
|
|
10
10
|
function validateSplitExistence(log, readinessManager, splitName, labelOrSplitObj, method) {
|
|
11
|
-
if (readinessManager.isReady()) { // Only if it's ready we validate this, otherwise it may just be that the SDK is
|
|
12
|
-
if (labelOrSplitObj === labels_1.SPLIT_NOT_FOUND || labelOrSplitObj
|
|
11
|
+
if (readinessManager.isReady()) { // Only if it's ready (synced with BE) we validate this, otherwise it may just be that the SDK is still syncing
|
|
12
|
+
if (labelOrSplitObj === labels_1.SPLIT_NOT_FOUND || labelOrSplitObj === labels_1.FALLBACK_SPLIT_NOT_FOUND || labelOrSplitObj == null) {
|
|
13
13
|
log.warn(constants_1.WARN_NOT_EXISTENT_SPLIT, [method, splitName]);
|
|
14
14
|
return false;
|
|
15
15
|
}
|
package/esm/logger/constants.js
CHANGED
|
@@ -58,7 +58,7 @@ export var SUBMITTERS_PUSH_PAGE_HIDDEN = 125;
|
|
|
58
58
|
export var ENGINE_VALUE_INVALID = 200;
|
|
59
59
|
export var ENGINE_VALUE_NO_ATTRIBUTES = 201;
|
|
60
60
|
export var CLIENT_NO_LISTENER = 202;
|
|
61
|
-
export var
|
|
61
|
+
export var CLIENT_NOT_READY_FROM_CACHE = 203;
|
|
62
62
|
export var SYNC_MYSEGMENTS_FETCH_RETRY = 204;
|
|
63
63
|
export var SYNC_SPLITS_FETCH_FAILS = 205;
|
|
64
64
|
export var STREAMING_PARSING_ERROR_FAILS = 206;
|
package/esm/logger/index.js
CHANGED
|
@@ -58,9 +58,6 @@ 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);
|
|
64
61
|
return;
|
|
65
62
|
}
|
|
66
63
|
else {
|
|
@@ -13,8 +13,8 @@ export var codesWarn = codesError.concat([
|
|
|
13
13
|
[c.SUBMITTERS_PUSH_FAILS, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Dropping %s after retry. Reason: %s.'],
|
|
14
14
|
[c.SUBMITTERS_PUSH_RETRY, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Failed to push %s, keeping data to retry on next iteration. Reason: %s.'],
|
|
15
15
|
// client status
|
|
16
|
-
[c.
|
|
17
|
-
[c.CLIENT_NO_LISTENER, 'No listeners for
|
|
16
|
+
[c.CLIENT_NOT_READY_FROM_CACHE, '%s: the SDK is not ready to evaluate. Results may be incorrect%s. Make sure to wait for SDK readiness before using this method.'],
|
|
17
|
+
[c.CLIENT_NO_LISTENER, 'No listeners for SDK_READY event detected. Incorrect control treatments could have been logged if you called getTreatment/s while the SDK was not yet synchronized with the backend.'],
|
|
18
18
|
// input validation
|
|
19
19
|
[c.WARN_SETTING_NULL, '%s: Property "%s" is of invalid type. Setting value to null.'],
|
|
20
20
|
[c.WARN_TRIMMING_PROPERTIES, '%s: more than 300 properties were provided. Some of them will be trimmed when processed.'],
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
2
2
|
import { SDK_SPLITS_ARRIVED, SDK_SPLITS_CACHE_LOADED, SDK_SEGMENTS_ARRIVED, SDK_READY_TIMED_OUT, SDK_READY_FROM_CACHE, SDK_UPDATE, SDK_READY } from './constants';
|
|
3
|
-
import { STORAGE_LOCALSTORAGE } from '../utils/constants';
|
|
4
3
|
function splitsEventEmitterFactory(EventEmitter) {
|
|
5
4
|
var splitsEventEmitter = objectAssign(new EventEmitter(), {
|
|
6
5
|
splitsArrived: false,
|
|
@@ -72,7 +71,7 @@ export function readinessManagerFactory(EventEmitter, settings, splits, isShared
|
|
|
72
71
|
if (!isReady && !isDestroyed) {
|
|
73
72
|
try {
|
|
74
73
|
syncLastUpdate();
|
|
75
|
-
gate.emit(SDK_READY_FROM_CACHE);
|
|
74
|
+
gate.emit(SDK_READY_FROM_CACHE, isReady);
|
|
76
75
|
}
|
|
77
76
|
catch (e) {
|
|
78
77
|
// throws user callback exceptions in next tick
|
|
@@ -81,7 +80,6 @@ export function readinessManagerFactory(EventEmitter, settings, splits, isShared
|
|
|
81
80
|
}
|
|
82
81
|
}
|
|
83
82
|
function checkIsReadyOrUpdate(diff) {
|
|
84
|
-
var _a;
|
|
85
83
|
if (isDestroyed)
|
|
86
84
|
return;
|
|
87
85
|
if (isReady) {
|
|
@@ -100,9 +98,9 @@ export function readinessManagerFactory(EventEmitter, settings, splits, isShared
|
|
|
100
98
|
isReady = true;
|
|
101
99
|
try {
|
|
102
100
|
syncLastUpdate();
|
|
103
|
-
if (!isReadyFromCache
|
|
101
|
+
if (!isReadyFromCache) {
|
|
104
102
|
isReadyFromCache = true;
|
|
105
|
-
gate.emit(SDK_READY_FROM_CACHE);
|
|
103
|
+
gate.emit(SDK_READY_FROM_CACHE, isReady);
|
|
106
104
|
}
|
|
107
105
|
gate.emit(SDK_READY);
|
|
108
106
|
}
|
|
@@ -5,6 +5,7 @@ import { SDK_READY, SDK_READY_TIMED_OUT, SDK_READY_FROM_CACHE, SDK_UPDATE } from
|
|
|
5
5
|
import { ERROR_CLIENT_LISTENER, CLIENT_READY_FROM_CACHE, CLIENT_READY, CLIENT_NO_LISTENER } from '../logger/constants';
|
|
6
6
|
var NEW_LISTENER_EVENT = 'newListener';
|
|
7
7
|
var REMOVE_LISTENER_EVENT = 'removeListener';
|
|
8
|
+
var TIMEOUT_ERROR = new Error(SDK_READY_TIMED_OUT);
|
|
8
9
|
/**
|
|
9
10
|
* SdkReadinessManager factory, which provides the public status API of SDK clients and manager: ready promise, readiness event emitter and constants (SDK_READY, etc).
|
|
10
11
|
* It also updates logs related warnings and errors.
|
|
@@ -31,6 +32,9 @@ export function sdkReadinessManagerFactory(EventEmitter, settings, readinessMana
|
|
|
31
32
|
readyCbCount++;
|
|
32
33
|
}
|
|
33
34
|
}
|
|
35
|
+
else if (event === SDK_READY_FROM_CACHE && readinessManager.isReadyFromCache()) {
|
|
36
|
+
log.error(ERROR_CLIENT_LISTENER, ['SDK_READY_FROM_CACHE']);
|
|
37
|
+
}
|
|
34
38
|
});
|
|
35
39
|
/** Ready promise */
|
|
36
40
|
var readyPromise = generateReadyPromise();
|
|
@@ -73,6 +77,7 @@ export function sdkReadinessManagerFactory(EventEmitter, settings, readinessMana
|
|
|
73
77
|
SDK_UPDATE: SDK_UPDATE,
|
|
74
78
|
SDK_READY_TIMED_OUT: SDK_READY_TIMED_OUT,
|
|
75
79
|
},
|
|
80
|
+
// @TODO: remove in next major
|
|
76
81
|
ready: function () {
|
|
77
82
|
if (readinessManager.hasTimedout()) {
|
|
78
83
|
if (!readinessManager.isReady()) {
|
|
@@ -84,6 +89,34 @@ export function sdkReadinessManagerFactory(EventEmitter, settings, readinessMana
|
|
|
84
89
|
}
|
|
85
90
|
return readyPromise;
|
|
86
91
|
},
|
|
92
|
+
whenReady: function () {
|
|
93
|
+
return new Promise(function (resolve, reject) {
|
|
94
|
+
if (readinessManager.isReady()) {
|
|
95
|
+
resolve();
|
|
96
|
+
}
|
|
97
|
+
else if (readinessManager.hasTimedout()) {
|
|
98
|
+
reject(TIMEOUT_ERROR);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
readinessManager.gate.once(SDK_READY, resolve);
|
|
102
|
+
readinessManager.gate.once(SDK_READY_TIMED_OUT, function () { return reject(TIMEOUT_ERROR); });
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
},
|
|
106
|
+
whenReadyFromCache: function () {
|
|
107
|
+
return new Promise(function (resolve, reject) {
|
|
108
|
+
if (readinessManager.isReadyFromCache()) {
|
|
109
|
+
resolve(readinessManager.isReady());
|
|
110
|
+
}
|
|
111
|
+
else if (readinessManager.hasTimedout()) {
|
|
112
|
+
reject(TIMEOUT_ERROR);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
readinessManager.gate.once(SDK_READY_FROM_CACHE, function () { return resolve(readinessManager.isReady()); });
|
|
116
|
+
readinessManager.gate.once(SDK_READY_TIMED_OUT, function () { return reject(TIMEOUT_ERROR); });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
},
|
|
87
120
|
__getStatus: function () {
|
|
88
121
|
return {
|
|
89
122
|
isReady: readinessManager.isReady(),
|
package/esm/sdkClient/client.js
CHANGED
|
@@ -41,7 +41,7 @@ export function clientFactory(params) {
|
|
|
41
41
|
stopTelemetryTracker(queue[0] && queue[0].imp.label);
|
|
42
42
|
return treatment;
|
|
43
43
|
};
|
|
44
|
-
var evaluation = readinessManager.
|
|
44
|
+
var evaluation = readinessManager.isReadyFromCache() ?
|
|
45
45
|
evaluateFeature(log, key, featureFlagName, attributes, storage) :
|
|
46
46
|
isAsync ? // If the SDK is not ready, treatment may be incorrect due to having splits but not segments data, or storage is not connected
|
|
47
47
|
Promise.resolve(treatmentNotReady) :
|
|
@@ -66,7 +66,7 @@ export function clientFactory(params) {
|
|
|
66
66
|
stopTelemetryTracker(queue[0] && queue[0].imp.label);
|
|
67
67
|
return treatments;
|
|
68
68
|
};
|
|
69
|
-
var evaluations = readinessManager.
|
|
69
|
+
var evaluations = readinessManager.isReadyFromCache() ?
|
|
70
70
|
evaluateFeatures(log, key, featureFlagNames, attributes, storage) :
|
|
71
71
|
isAsync ? // If the SDK is not ready, treatment may be incorrect due to having splits but not segments data, or storage is not connected
|
|
72
72
|
Promise.resolve(treatmentsNotReady(featureFlagNames)) :
|
|
@@ -92,7 +92,7 @@ export function clientFactory(params) {
|
|
|
92
92
|
stopTelemetryTracker(queue[0] && queue[0].imp.label);
|
|
93
93
|
return treatments;
|
|
94
94
|
};
|
|
95
|
-
var evaluations = readinessManager.
|
|
95
|
+
var evaluations = readinessManager.isReadyFromCache() ?
|
|
96
96
|
evaluateFeaturesByFlagSets(log, key, flagSetNames, attributes, storage, methodName) :
|
|
97
97
|
isAsync ?
|
|
98
98
|
Promise.resolve({}) :
|
|
@@ -117,7 +117,7 @@ export function clientFactory(params) {
|
|
|
117
117
|
if (treatment === CONTROL) {
|
|
118
118
|
var fallbackTreatment = fallbackTreatmentsCalculator.resolve(featureFlagName, label);
|
|
119
119
|
treatment = fallbackTreatment.treatment;
|
|
120
|
-
label = fallbackTreatment.label
|
|
120
|
+
label = fallbackTreatment.label;
|
|
121
121
|
config = fallbackTreatment.config;
|
|
122
122
|
}
|
|
123
123
|
log.info(IMPRESSION, [featureFlagName, matchingKey, treatment, label]);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { validateAttributes, validateEvent, validateEventValue, validateEventProperties, validateKey, validateSplit, validateSplits, validateTrafficType, validateIfNotDestroyed,
|
|
1
|
+
import { validateAttributes, validateEvent, validateEventValue, validateEventProperties, validateKey, validateSplit, validateSplits, validateTrafficType, validateIfNotDestroyed, validateIfReadyFromCache, validateEvaluationOptions } from '../utils/inputValidation';
|
|
2
2
|
import { startsWith } from '../utils/lang';
|
|
3
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';
|
|
4
4
|
import { isConsumerMode } from '../utils/settingsValidation/mode';
|
|
@@ -23,7 +23,7 @@ export function clientInputValidationDecorator(settings, client, readinessManage
|
|
|
23
23
|
var attributes = validateAttributes(log, maybeAttributes, methodName);
|
|
24
24
|
var isNotDestroyed = validateIfNotDestroyed(log, readinessManager, methodName);
|
|
25
25
|
var options = validateEvaluationOptions(log, maybeOptions, methodName);
|
|
26
|
-
|
|
26
|
+
validateIfReadyFromCache(log, readinessManager, methodName, nameOrNames);
|
|
27
27
|
var valid = isNotDestroyed && key && nameOrNames && attributes !== false;
|
|
28
28
|
return {
|
|
29
29
|
valid: valid,
|
package/esm/sdkManager/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
2
2
|
import { thenable } from '../utils/promise/thenable';
|
|
3
3
|
import { find } from '../utils/lang';
|
|
4
|
-
import { validateSplit, validateSplitExistence,
|
|
4
|
+
import { validateSplit, validateSplitExistence, validateIfOperational } from '../utils/inputValidation';
|
|
5
5
|
import { isConsumerMode } from '../utils/settingsValidation/mode';
|
|
6
6
|
import { SPLIT_FN_LABEL, SPLITS_FN_LABEL, NAMES_FN_LABEL } from '../utils/constants';
|
|
7
7
|
function collectTreatments(splitObject) {
|
|
@@ -51,7 +51,7 @@ export function sdkManagerFactory(settings, splits, _a) {
|
|
|
51
51
|
*/
|
|
52
52
|
split: function (featureFlagName) {
|
|
53
53
|
var splitName = validateSplit(log, featureFlagName, SPLIT_FN_LABEL);
|
|
54
|
-
if (!
|
|
54
|
+
if (!validateIfOperational(log, readinessManager, SPLIT_FN_LABEL) || !splitName) {
|
|
55
55
|
return isAsync ? Promise.resolve(null) : null;
|
|
56
56
|
}
|
|
57
57
|
var split = splits.getSplit(splitName);
|
|
@@ -68,7 +68,7 @@ export function sdkManagerFactory(settings, splits, _a) {
|
|
|
68
68
|
* Get the feature flag objects present on the factory storage
|
|
69
69
|
*/
|
|
70
70
|
splits: function () {
|
|
71
|
-
if (!
|
|
71
|
+
if (!validateIfOperational(log, readinessManager, SPLITS_FN_LABEL)) {
|
|
72
72
|
return isAsync ? Promise.resolve([]) : [];
|
|
73
73
|
}
|
|
74
74
|
var currentSplits = splits.getAll();
|
|
@@ -80,7 +80,7 @@ export function sdkManagerFactory(settings, splits, _a) {
|
|
|
80
80
|
* Get the feature flag names present on the factory storage
|
|
81
81
|
*/
|
|
82
82
|
names: function () {
|
|
83
|
-
if (!
|
|
83
|
+
if (!validateIfOperational(log, readinessManager, NAMES_FN_LABEL)) {
|
|
84
84
|
return isAsync ? Promise.resolve([]) : [];
|
|
85
85
|
}
|
|
86
86
|
var splitNames = splits.getSplitNames();
|
|
@@ -16,7 +16,7 @@ export function SSEHandlerFactory(log, pushEmitter, telemetryTracker) {
|
|
|
16
16
|
// Ably error
|
|
17
17
|
var code = error.parsedData.code;
|
|
18
18
|
telemetryTracker.streamingEvent(ABLY_ERROR, code);
|
|
19
|
-
// 401 errors due to invalid or expired token (e.g., if refresh token
|
|
19
|
+
// 401 errors due to invalid or expired token (e.g., if refresh token couldn't be executed)
|
|
20
20
|
if (40140 <= code && code <= 40149)
|
|
21
21
|
return true;
|
|
22
22
|
// Others 4XX errors (e.g., bad request from the SDK)
|
|
@@ -102,14 +102,14 @@ export function telemetrySubmitterFactory(params) {
|
|
|
102
102
|
if (!telemetry || !now)
|
|
103
103
|
return; // No submitter created if telemetry cache is not defined
|
|
104
104
|
var settings = params.settings, _a = params.settings, log = _a.log, telemetryRefreshRate = _a.scheduler.telemetryRefreshRate, splitApi = params.splitApi, readiness = params.readiness, sdkReadinessManager = params.sdkReadinessManager;
|
|
105
|
-
var
|
|
105
|
+
var stopTimer = timer(now);
|
|
106
106
|
var submitter = firstPushWindowDecorator(submitterFactory(log, splitApi.postMetricsUsage, telemetry, telemetryRefreshRate, undefined, 0, true), telemetryRefreshRate);
|
|
107
107
|
readiness.gate.once(SDK_READY_FROM_CACHE, function () {
|
|
108
|
-
telemetry.recordTimeUntilReadyFromCache(
|
|
108
|
+
telemetry.recordTimeUntilReadyFromCache(stopTimer());
|
|
109
109
|
});
|
|
110
110
|
sdkReadinessManager.incInternalReadyCbCount();
|
|
111
111
|
readiness.gate.once(SDK_READY, function () {
|
|
112
|
-
telemetry.recordTimeUntilReady(
|
|
112
|
+
telemetry.recordTimeUntilReady(stopTimer());
|
|
113
113
|
// Post config data when the SDK is ready and if the telemetry submitter was started
|
|
114
114
|
if (submitter.isRunning()) {
|
|
115
115
|
var postMetricsConfigTask = submitterFactory(log, splitApi.postMetricsConfig, telemetryCacheConfigAdapter(telemetry, settings), 0, undefined, 0, true);
|
|
@@ -3,10 +3,10 @@ import { timer } from '../utils/timeTracker/timer';
|
|
|
3
3
|
import { TOKEN_REFRESH, AUTH_REJECTION } from '../utils/constants';
|
|
4
4
|
export function telemetryTrackerFactory(telemetryCache, now) {
|
|
5
5
|
if (telemetryCache && now) {
|
|
6
|
-
var
|
|
6
|
+
var sessionTimer_1 = timer(now);
|
|
7
7
|
return {
|
|
8
8
|
trackEval: function (method) {
|
|
9
|
-
var
|
|
9
|
+
var evalTimer = timer(now);
|
|
10
10
|
return function (label) {
|
|
11
11
|
switch (label) {
|
|
12
12
|
case EXCEPTION:
|
|
@@ -16,13 +16,13 @@ export function telemetryTrackerFactory(telemetryCache, now) {
|
|
|
16
16
|
if (telemetryCache.recordNonReadyUsage)
|
|
17
17
|
telemetryCache.recordNonReadyUsage();
|
|
18
18
|
}
|
|
19
|
-
telemetryCache.recordLatency(method,
|
|
19
|
+
telemetryCache.recordLatency(method, evalTimer());
|
|
20
20
|
};
|
|
21
21
|
},
|
|
22
22
|
trackHttp: function (operation) {
|
|
23
|
-
var
|
|
23
|
+
var httpTimer = timer(now);
|
|
24
24
|
return function (error) {
|
|
25
|
-
telemetryCache.recordHttpLatency(operation,
|
|
25
|
+
telemetryCache.recordHttpLatency(operation, httpTimer());
|
|
26
26
|
if (error && error.statusCode)
|
|
27
27
|
telemetryCache.recordHttpError(operation, error.statusCode);
|
|
28
28
|
else
|
|
@@ -31,7 +31,7 @@ export function telemetryTrackerFactory(telemetryCache, now) {
|
|
|
31
31
|
},
|
|
32
32
|
sessionLength: function () {
|
|
33
33
|
if (telemetryCache.recordSessionLength)
|
|
34
|
-
telemetryCache.recordSessionLength(
|
|
34
|
+
telemetryCache.recordSessionLength(sessionTimer_1());
|
|
35
35
|
},
|
|
36
36
|
streamingEvent: function (e, d) {
|
|
37
37
|
if (e === AUTH_REJECTION) {
|
|
@@ -7,7 +7,7 @@ export { validateKey } from './key';
|
|
|
7
7
|
export { validateSplit } from './split';
|
|
8
8
|
export { validateSplits } from './splits';
|
|
9
9
|
export { validateTrafficType } from './trafficType';
|
|
10
|
-
export { validateIfNotDestroyed, validateIfOperational } from './isOperational';
|
|
10
|
+
export { validateIfNotDestroyed, validateIfReadyFromCache, validateIfOperational } from './isOperational';
|
|
11
11
|
export { validateSplitExistence } from './splitExistence';
|
|
12
12
|
export { validateTrafficTypeExistence } from './trafficTypeExistence';
|
|
13
13
|
export { validateEvaluationOptions } from './eventProperties';
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import { ERROR_CLIENT_DESTROYED,
|
|
1
|
+
import { ERROR_CLIENT_DESTROYED, CLIENT_NOT_READY_FROM_CACHE } from '../../logger/constants';
|
|
2
2
|
export function validateIfNotDestroyed(log, readinessManager, method) {
|
|
3
3
|
if (!readinessManager.isDestroyed())
|
|
4
4
|
return true;
|
|
5
5
|
log.error(ERROR_CLIENT_DESTROYED, [method]);
|
|
6
6
|
return false;
|
|
7
7
|
}
|
|
8
|
-
export function
|
|
9
|
-
if (readinessManager.
|
|
8
|
+
export function validateIfReadyFromCache(log, readinessManager, method, featureFlagNameOrNames) {
|
|
9
|
+
if (readinessManager.isReadyFromCache())
|
|
10
10
|
return true;
|
|
11
|
-
log.warn(
|
|
11
|
+
log.warn(CLIENT_NOT_READY_FROM_CACHE, [method, featureFlagNameOrNames ? " for feature flag " + featureFlagNameOrNames.toString() : '']);
|
|
12
12
|
return false;
|
|
13
13
|
}
|
|
14
|
+
// Operational means that the SDK is ready to evaluate (not destroyed and ready from cache)
|
|
15
|
+
export function validateIfOperational(log, readinessManager, method, featureFlagNameOrNames) {
|
|
16
|
+
return validateIfNotDestroyed(log, readinessManager, method) && validateIfReadyFromCache(log, readinessManager, method, featureFlagNameOrNames);
|
|
17
|
+
}
|
|
@@ -2,11 +2,11 @@ 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.
|
|
5
|
-
* But it's not going to run on the input validation layer. In any case, the most
|
|
5
|
+
* But it's not going to run on the input validation layer. In any case, the most compelling reason to use it as we do is to avoid going to Redis and get a split twice.
|
|
6
6
|
*/
|
|
7
7
|
export function validateSplitExistence(log, readinessManager, splitName, labelOrSplitObj, method) {
|
|
8
|
-
if (readinessManager.isReady()) { // Only if it's ready we validate this, otherwise it may just be that the SDK is
|
|
9
|
-
if (labelOrSplitObj === SPLIT_NOT_FOUND || labelOrSplitObj
|
|
8
|
+
if (readinessManager.isReady()) { // Only if it's ready (synced with BE) we validate this, otherwise it may just be that the SDK is still syncing
|
|
9
|
+
if (labelOrSplitObj === SPLIT_NOT_FOUND || labelOrSplitObj === FALLBACK_SPLIT_NOT_FOUND || labelOrSplitObj == null) {
|
|
10
10
|
log.warn(WARN_NOT_EXISTENT_SPLIT, [method, splitName]);
|
|
11
11
|
return false;
|
|
12
12
|
}
|
package/package.json
CHANGED
package/src/logger/constants.ts
CHANGED
|
@@ -60,7 +60,7 @@ export const SUBMITTERS_PUSH_PAGE_HIDDEN = 125;
|
|
|
60
60
|
export const ENGINE_VALUE_INVALID = 200;
|
|
61
61
|
export const ENGINE_VALUE_NO_ATTRIBUTES = 201;
|
|
62
62
|
export const CLIENT_NO_LISTENER = 202;
|
|
63
|
-
export const
|
|
63
|
+
export const CLIENT_NOT_READY_FROM_CACHE = 203;
|
|
64
64
|
export const SYNC_MYSEGMENTS_FETCH_RETRY = 204;
|
|
65
65
|
export const SYNC_SPLITS_FETCH_FAILS = 205;
|
|
66
66
|
export const STREAMING_PARSING_ERROR_FAILS = 206;
|
package/src/logger/index.ts
CHANGED
|
@@ -72,8 +72,6 @@ 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);
|
|
77
75
|
return;
|
|
78
76
|
} else {
|
|
79
77
|
this.error('Invalid `logger` instance. It must be an object with `debug`, `info`, `warn` and `error` methods. Defaulting to `console.log`');
|
|
@@ -14,8 +14,8 @@ export const codesWarn: [number, string][] = codesError.concat([
|
|
|
14
14
|
[c.SUBMITTERS_PUSH_FAILS, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Dropping %s after retry. Reason: %s.'],
|
|
15
15
|
[c.SUBMITTERS_PUSH_RETRY, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Failed to push %s, keeping data to retry on next iteration. Reason: %s.'],
|
|
16
16
|
// client status
|
|
17
|
-
[c.
|
|
18
|
-
[c.CLIENT_NO_LISTENER, 'No listeners for
|
|
17
|
+
[c.CLIENT_NOT_READY_FROM_CACHE, '%s: the SDK is not ready to evaluate. Results may be incorrect%s. Make sure to wait for SDK readiness before using this method.'],
|
|
18
|
+
[c.CLIENT_NO_LISTENER, 'No listeners for SDK_READY event detected. Incorrect control treatments could have been logged if you called getTreatment/s while the SDK was not yet synchronized with the backend.'],
|
|
19
19
|
// input validation
|
|
20
20
|
[c.WARN_SETTING_NULL, '%s: Property "%s" is of invalid type. Setting value to null.'],
|
|
21
21
|
[c.WARN_TRIMMING_PROPERTIES, '%s: more than 300 properties were provided. Some of them will be trimmed when processed.'],
|
|
@@ -3,7 +3,6 @@ import { ISettings } from '../types';
|
|
|
3
3
|
import SplitIO from '../../types/splitio';
|
|
4
4
|
import { SDK_SPLITS_ARRIVED, SDK_SPLITS_CACHE_LOADED, SDK_SEGMENTS_ARRIVED, SDK_READY_TIMED_OUT, SDK_READY_FROM_CACHE, SDK_UPDATE, SDK_READY } from './constants';
|
|
5
5
|
import { IReadinessEventEmitter, IReadinessManager, ISegmentsEventEmitter, ISplitsEventEmitter } from './types';
|
|
6
|
-
import { STORAGE_LOCALSTORAGE } from '../utils/constants';
|
|
7
6
|
|
|
8
7
|
function splitsEventEmitterFactory(EventEmitter: new () => SplitIO.IEventEmitter): ISplitsEventEmitter {
|
|
9
8
|
const splitsEventEmitter = objectAssign(new EventEmitter(), {
|
|
@@ -91,7 +90,7 @@ export function readinessManagerFactory(
|
|
|
91
90
|
if (!isReady && !isDestroyed) {
|
|
92
91
|
try {
|
|
93
92
|
syncLastUpdate();
|
|
94
|
-
gate.emit(SDK_READY_FROM_CACHE);
|
|
93
|
+
gate.emit(SDK_READY_FROM_CACHE, isReady);
|
|
95
94
|
} catch (e) {
|
|
96
95
|
// throws user callback exceptions in next tick
|
|
97
96
|
setTimeout(() => { throw e; }, 0);
|
|
@@ -115,9 +114,9 @@ export function readinessManagerFactory(
|
|
|
115
114
|
isReady = true;
|
|
116
115
|
try {
|
|
117
116
|
syncLastUpdate();
|
|
118
|
-
if (!isReadyFromCache
|
|
117
|
+
if (!isReadyFromCache) {
|
|
119
118
|
isReadyFromCache = true;
|
|
120
|
-
gate.emit(SDK_READY_FROM_CACHE);
|
|
119
|
+
gate.emit(SDK_READY_FROM_CACHE, isReady);
|
|
121
120
|
}
|
|
122
121
|
gate.emit(SDK_READY);
|
|
123
122
|
} catch (e) {
|
|
@@ -9,6 +9,7 @@ import { ERROR_CLIENT_LISTENER, CLIENT_READY_FROM_CACHE, CLIENT_READY, CLIENT_NO
|
|
|
9
9
|
|
|
10
10
|
const NEW_LISTENER_EVENT = 'newListener';
|
|
11
11
|
const REMOVE_LISTENER_EVENT = 'removeListener';
|
|
12
|
+
const TIMEOUT_ERROR = new Error(SDK_READY_TIMED_OUT);
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* SdkReadinessManager factory, which provides the public status API of SDK clients and manager: ready promise, readiness event emitter and constants (SDK_READY, etc).
|
|
@@ -38,6 +39,8 @@ export function sdkReadinessManagerFactory(
|
|
|
38
39
|
} else if (event === SDK_READY) {
|
|
39
40
|
readyCbCount++;
|
|
40
41
|
}
|
|
42
|
+
} else if (event === SDK_READY_FROM_CACHE && readinessManager.isReadyFromCache()) {
|
|
43
|
+
log.error(ERROR_CLIENT_LISTENER, ['SDK_READY_FROM_CACHE']);
|
|
41
44
|
}
|
|
42
45
|
});
|
|
43
46
|
|
|
@@ -93,6 +96,7 @@ export function sdkReadinessManagerFactory(
|
|
|
93
96
|
SDK_READY_TIMED_OUT,
|
|
94
97
|
},
|
|
95
98
|
|
|
99
|
+
// @TODO: remove in next major
|
|
96
100
|
ready() {
|
|
97
101
|
if (readinessManager.hasTimedout()) {
|
|
98
102
|
if (!readinessManager.isReady()) {
|
|
@@ -104,6 +108,32 @@ export function sdkReadinessManagerFactory(
|
|
|
104
108
|
return readyPromise;
|
|
105
109
|
},
|
|
106
110
|
|
|
111
|
+
whenReady() {
|
|
112
|
+
return new Promise<void>((resolve, reject) => {
|
|
113
|
+
if (readinessManager.isReady()) {
|
|
114
|
+
resolve();
|
|
115
|
+
} else if (readinessManager.hasTimedout()) {
|
|
116
|
+
reject(TIMEOUT_ERROR);
|
|
117
|
+
} else {
|
|
118
|
+
readinessManager.gate.once(SDK_READY, resolve);
|
|
119
|
+
readinessManager.gate.once(SDK_READY_TIMED_OUT, () => reject(TIMEOUT_ERROR));
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
whenReadyFromCache() {
|
|
125
|
+
return new Promise<boolean>((resolve, reject) => {
|
|
126
|
+
if (readinessManager.isReadyFromCache()) {
|
|
127
|
+
resolve(readinessManager.isReady());
|
|
128
|
+
} else if (readinessManager.hasTimedout()) {
|
|
129
|
+
reject(TIMEOUT_ERROR);
|
|
130
|
+
} else {
|
|
131
|
+
readinessManager.gate.once(SDK_READY_FROM_CACHE, () => resolve(readinessManager.isReady()));
|
|
132
|
+
readinessManager.gate.once(SDK_READY_TIMED_OUT, () => reject(TIMEOUT_ERROR));
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
},
|
|
136
|
+
|
|
107
137
|
__getStatus() {
|
|
108
138
|
return {
|
|
109
139
|
isReady: readinessManager.isReady(),
|
package/src/sdkClient/client.ts
CHANGED
|
@@ -51,7 +51,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
51
51
|
return treatment;
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
-
const evaluation = readinessManager.
|
|
54
|
+
const evaluation = readinessManager.isReadyFromCache() ?
|
|
55
55
|
evaluateFeature(log, key, featureFlagName, attributes, storage) :
|
|
56
56
|
isAsync ? // If the SDK is not ready, treatment may be incorrect due to having splits but not segments data, or storage is not connected
|
|
57
57
|
Promise.resolve(treatmentNotReady) :
|
|
@@ -80,7 +80,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
80
80
|
return treatments;
|
|
81
81
|
};
|
|
82
82
|
|
|
83
|
-
const evaluations = readinessManager.
|
|
83
|
+
const evaluations = readinessManager.isReadyFromCache() ?
|
|
84
84
|
evaluateFeatures(log, key, featureFlagNames, attributes, storage) :
|
|
85
85
|
isAsync ? // If the SDK is not ready, treatment may be incorrect due to having splits but not segments data, or storage is not connected
|
|
86
86
|
Promise.resolve(treatmentsNotReady(featureFlagNames)) :
|
|
@@ -109,7 +109,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
109
109
|
return treatments;
|
|
110
110
|
};
|
|
111
111
|
|
|
112
|
-
const evaluations = readinessManager.
|
|
112
|
+
const evaluations = readinessManager.isReadyFromCache() ?
|
|
113
113
|
evaluateFeaturesByFlagSets(log, key, flagSetNames, attributes, storage, methodName) :
|
|
114
114
|
isAsync ?
|
|
115
115
|
Promise.resolve({}) :
|
|
@@ -149,7 +149,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
149
149
|
if (treatment === CONTROL) {
|
|
150
150
|
const fallbackTreatment = fallbackTreatmentsCalculator.resolve(featureFlagName, label);
|
|
151
151
|
treatment = fallbackTreatment.treatment;
|
|
152
|
-
label = fallbackTreatment.label
|
|
152
|
+
label = fallbackTreatment.label;
|
|
153
153
|
config = fallbackTreatment.config;
|
|
154
154
|
}
|
|
155
155
|
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
validateSplits,
|
|
9
9
|
validateTrafficType,
|
|
10
10
|
validateIfNotDestroyed,
|
|
11
|
-
|
|
11
|
+
validateIfReadyFromCache,
|
|
12
12
|
validateEvaluationOptions
|
|
13
13
|
} from '../utils/inputValidation';
|
|
14
14
|
import { startsWith } from '../utils/lang';
|
|
@@ -46,7 +46,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
|
|
|
46
46
|
const isNotDestroyed = validateIfNotDestroyed(log, readinessManager, methodName);
|
|
47
47
|
const options = validateEvaluationOptions(log, maybeOptions, methodName);
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
validateIfReadyFromCache(log, readinessManager, methodName, nameOrNames);
|
|
50
50
|
|
|
51
51
|
const valid = isNotDestroyed && key && nameOrNames && attributes !== false;
|
|
52
52
|
|
|
@@ -60,7 +60,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
function evaluateFallBackTreatment(featureFlagName: string, withConfig: boolean): SplitIO.Treatment | SplitIO.TreatmentWithConfig {
|
|
63
|
-
const {treatment, config} = fallbackTreatmentsCalculator.resolve(featureFlagName, '');
|
|
63
|
+
const { treatment, config } = fallbackTreatmentsCalculator.resolve(featureFlagName, '');
|
|
64
64
|
|
|
65
65
|
if (withConfig) {
|
|
66
66
|
return {
|
package/src/sdkManager/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
2
2
|
import { thenable } from '../utils/promise/thenable';
|
|
3
3
|
import { find } from '../utils/lang';
|
|
4
|
-
import { validateSplit, validateSplitExistence,
|
|
4
|
+
import { validateSplit, validateSplitExistence, validateIfOperational } from '../utils/inputValidation';
|
|
5
5
|
import { ISplitsCacheAsync, ISplitsCacheSync } from '../storages/types';
|
|
6
6
|
import { ISdkReadinessManager } from '../readiness/types';
|
|
7
7
|
import { ISplit } from '../dtos/types';
|
|
@@ -66,7 +66,7 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits
|
|
|
66
66
|
*/
|
|
67
67
|
split(featureFlagName: string) {
|
|
68
68
|
const splitName = validateSplit(log, featureFlagName, SPLIT_FN_LABEL);
|
|
69
|
-
if (!
|
|
69
|
+
if (!validateIfOperational(log, readinessManager, SPLIT_FN_LABEL) || !splitName) {
|
|
70
70
|
return isAsync ? Promise.resolve(null) : null;
|
|
71
71
|
}
|
|
72
72
|
|
|
@@ -87,7 +87,7 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits
|
|
|
87
87
|
* Get the feature flag objects present on the factory storage
|
|
88
88
|
*/
|
|
89
89
|
splits() {
|
|
90
|
-
if (!
|
|
90
|
+
if (!validateIfOperational(log, readinessManager, SPLITS_FN_LABEL)) {
|
|
91
91
|
return isAsync ? Promise.resolve([]) : [];
|
|
92
92
|
}
|
|
93
93
|
const currentSplits = splits.getAll();
|
|
@@ -100,7 +100,7 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits
|
|
|
100
100
|
* Get the feature flag names present on the factory storage
|
|
101
101
|
*/
|
|
102
102
|
names() {
|
|
103
|
-
if (!
|
|
103
|
+
if (!validateIfOperational(log, readinessManager, NAMES_FN_LABEL)) {
|
|
104
104
|
return isAsync ? Promise.resolve([]) : [];
|
|
105
105
|
}
|
|
106
106
|
const splitNames = splits.getSplitNames();
|
|
@@ -25,7 +25,7 @@ export function SSEHandlerFactory(log: ILogger, pushEmitter: IPushEventEmitter,
|
|
|
25
25
|
const code = error.parsedData.code;
|
|
26
26
|
telemetryTracker.streamingEvent(ABLY_ERROR, code);
|
|
27
27
|
|
|
28
|
-
// 401 errors due to invalid or expired token (e.g., if refresh token
|
|
28
|
+
// 401 errors due to invalid or expired token (e.g., if refresh token couldn't be executed)
|
|
29
29
|
if (40140 <= code && code <= 40149) return true;
|
|
30
30
|
// Others 4XX errors (e.g., bad request from the SDK)
|
|
31
31
|
if (40000 <= code && code <= 49999) return false;
|
|
@@ -119,7 +119,7 @@ export function telemetrySubmitterFactory(params: ISdkFactoryContextSync) {
|
|
|
119
119
|
if (!telemetry || !now) return; // No submitter created if telemetry cache is not defined
|
|
120
120
|
|
|
121
121
|
const { settings, settings: { log, scheduler: { telemetryRefreshRate } }, splitApi, readiness, sdkReadinessManager } = params;
|
|
122
|
-
const
|
|
122
|
+
const stopTimer = timer(now);
|
|
123
123
|
|
|
124
124
|
const submitter = firstPushWindowDecorator(
|
|
125
125
|
submitterFactory(
|
|
@@ -131,12 +131,12 @@ export function telemetrySubmitterFactory(params: ISdkFactoryContextSync) {
|
|
|
131
131
|
);
|
|
132
132
|
|
|
133
133
|
readiness.gate.once(SDK_READY_FROM_CACHE, () => {
|
|
134
|
-
telemetry.recordTimeUntilReadyFromCache(
|
|
134
|
+
telemetry.recordTimeUntilReadyFromCache(stopTimer());
|
|
135
135
|
});
|
|
136
136
|
|
|
137
137
|
sdkReadinessManager.incInternalReadyCbCount();
|
|
138
138
|
readiness.gate.once(SDK_READY, () => {
|
|
139
|
-
telemetry.recordTimeUntilReady(
|
|
139
|
+
telemetry.recordTimeUntilReady(stopTimer());
|
|
140
140
|
|
|
141
141
|
// Post config data when the SDK is ready and if the telemetry submitter was started
|
|
142
142
|
if (submitter.isRunning()) {
|
|
@@ -11,11 +11,11 @@ export function telemetryTrackerFactory(
|
|
|
11
11
|
): ITelemetryTracker {
|
|
12
12
|
|
|
13
13
|
if (telemetryCache && now) {
|
|
14
|
-
const
|
|
14
|
+
const sessionTimer = timer(now);
|
|
15
15
|
|
|
16
16
|
return {
|
|
17
17
|
trackEval(method) {
|
|
18
|
-
const
|
|
18
|
+
const evalTimer = timer(now);
|
|
19
19
|
|
|
20
20
|
return (label) => {
|
|
21
21
|
switch (label) {
|
|
@@ -25,20 +25,20 @@ export function telemetryTrackerFactory(
|
|
|
25
25
|
case SDK_NOT_READY: // @ts-ignore ITelemetryCacheAsync doesn't implement the method
|
|
26
26
|
if (telemetryCache.recordNonReadyUsage) telemetryCache.recordNonReadyUsage();
|
|
27
27
|
}
|
|
28
|
-
telemetryCache.recordLatency(method,
|
|
28
|
+
telemetryCache.recordLatency(method, evalTimer());
|
|
29
29
|
};
|
|
30
30
|
},
|
|
31
31
|
trackHttp(operation) {
|
|
32
|
-
const
|
|
32
|
+
const httpTimer = timer(now);
|
|
33
33
|
|
|
34
34
|
return (error) => {
|
|
35
|
-
(telemetryCache as ITelemetryCacheSync).recordHttpLatency(operation,
|
|
35
|
+
(telemetryCache as ITelemetryCacheSync).recordHttpLatency(operation, httpTimer());
|
|
36
36
|
if (error && error.statusCode) (telemetryCache as ITelemetryCacheSync).recordHttpError(operation, error.statusCode);
|
|
37
37
|
else (telemetryCache as ITelemetryCacheSync).recordSuccessfulSync(operation, Date.now());
|
|
38
38
|
};
|
|
39
39
|
},
|
|
40
40
|
sessionLength() { // @ts-ignore ITelemetryCacheAsync doesn't implement the method
|
|
41
|
-
if (telemetryCache.recordSessionLength) telemetryCache.recordSessionLength(
|
|
41
|
+
if (telemetryCache.recordSessionLength) telemetryCache.recordSessionLength(sessionTimer());
|
|
42
42
|
},
|
|
43
43
|
streamingEvent(e, d) {
|
|
44
44
|
if (e === AUTH_REJECTION) {
|
|
@@ -7,7 +7,7 @@ export { validateKey } from './key';
|
|
|
7
7
|
export { validateSplit } from './split';
|
|
8
8
|
export { validateSplits } from './splits';
|
|
9
9
|
export { validateTrafficType } from './trafficType';
|
|
10
|
-
export { validateIfNotDestroyed, validateIfOperational } from './isOperational';
|
|
10
|
+
export { validateIfNotDestroyed, validateIfReadyFromCache, validateIfOperational } from './isOperational';
|
|
11
11
|
export { validateSplitExistence } from './splitExistence';
|
|
12
12
|
export { validateTrafficTypeExistence } from './trafficTypeExistence';
|
|
13
13
|
export { validateEvaluationOptions } from './eventProperties';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ERROR_CLIENT_DESTROYED,
|
|
1
|
+
import { ERROR_CLIENT_DESTROYED, CLIENT_NOT_READY_FROM_CACHE } from '../../logger/constants';
|
|
2
2
|
import { ILogger } from '../../logger/types';
|
|
3
3
|
import { IReadinessManager } from '../../readiness/types';
|
|
4
4
|
|
|
@@ -9,9 +9,14 @@ export function validateIfNotDestroyed(log: ILogger, readinessManager: IReadines
|
|
|
9
9
|
return false;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export function
|
|
13
|
-
if (readinessManager.
|
|
12
|
+
export function validateIfReadyFromCache(log: ILogger, readinessManager: IReadinessManager, method: string, featureFlagNameOrNames?: string | string[] | false) {
|
|
13
|
+
if (readinessManager.isReadyFromCache()) return true;
|
|
14
14
|
|
|
15
|
-
log.warn(
|
|
15
|
+
log.warn(CLIENT_NOT_READY_FROM_CACHE, [method, featureFlagNameOrNames ? ` for feature flag ${featureFlagNameOrNames.toString()}` : '']);
|
|
16
16
|
return false;
|
|
17
17
|
}
|
|
18
|
+
|
|
19
|
+
// Operational means that the SDK is ready to evaluate (not destroyed and ready from cache)
|
|
20
|
+
export function validateIfOperational(log: ILogger, readinessManager: IReadinessManager, method: string, featureFlagNameOrNames?: string | string[] | false) {
|
|
21
|
+
return validateIfNotDestroyed(log, readinessManager, method) && validateIfReadyFromCache(log, readinessManager, method, featureFlagNameOrNames);
|
|
22
|
+
}
|
|
@@ -5,11 +5,11 @@ import { WARN_NOT_EXISTENT_SPLIT } from '../../logger/constants';
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* 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.
|
|
8
|
-
* But it's not going to run on the input validation layer. In any case, the most
|
|
8
|
+
* But it's not going to run on the input validation layer. In any case, the most compelling reason to use it as we do is to avoid going to Redis and get a split twice.
|
|
9
9
|
*/
|
|
10
10
|
export function validateSplitExistence(log: ILogger, readinessManager: IReadinessManager, splitName: string, labelOrSplitObj: any, method: string): boolean {
|
|
11
|
-
if (readinessManager.isReady()) { // Only if it's ready we validate this, otherwise it may just be that the SDK is
|
|
12
|
-
if (labelOrSplitObj === SPLIT_NOT_FOUND || labelOrSplitObj
|
|
11
|
+
if (readinessManager.isReady()) { // Only if it's ready (synced with BE) we validate this, otherwise it may just be that the SDK is still syncing
|
|
12
|
+
if (labelOrSplitObj === SPLIT_NOT_FOUND || labelOrSplitObj === FALLBACK_SPLIT_NOT_FOUND || labelOrSplitObj == null) {
|
|
13
13
|
log.warn(WARN_NOT_EXISTENT_SPLIT, [method, splitName]);
|
|
14
14
|
return false;
|
|
15
15
|
}
|
package/types/splitio.d.ts
CHANGED
|
@@ -93,6 +93,7 @@ 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.
|
|
96
97
|
*/
|
|
97
98
|
logger?: SplitIO.Logger;
|
|
98
99
|
}
|
|
@@ -145,8 +146,6 @@ interface IPluggableSharedSettings {
|
|
|
145
146
|
* config.debug = ErrorLogger()
|
|
146
147
|
* ```
|
|
147
148
|
*
|
|
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
|
-
*
|
|
150
149
|
* @defaultValue `false`
|
|
151
150
|
*/
|
|
152
151
|
debug?: boolean | SplitIO.LogLevel | SplitIO.ILogger;
|
|
@@ -170,8 +169,6 @@ interface INonPluggableSharedSettings {
|
|
|
170
169
|
* config.debug = 'WARN'
|
|
171
170
|
* ```
|
|
172
171
|
*
|
|
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
|
-
*
|
|
175
172
|
* @defaultValue `false`
|
|
176
173
|
*/
|
|
177
174
|
debug?: boolean | SplitIO.LogLevel;
|
|
@@ -528,19 +525,19 @@ declare namespace SplitIO {
|
|
|
528
525
|
*/
|
|
529
526
|
type EventConsts = {
|
|
530
527
|
/**
|
|
531
|
-
* The ready event.
|
|
528
|
+
* The ready event emitted once the SDK is ready to evaluate feature flags with cache synchronized with the backend.
|
|
532
529
|
*/
|
|
533
530
|
SDK_READY: 'init::ready';
|
|
534
531
|
/**
|
|
535
|
-
* The ready event
|
|
532
|
+
* The ready event emitted once the SDK is ready to evaluate feature flags with cache that could be stale. Use SDK_READY if you want to be sure the cache is in sync with the backend.
|
|
536
533
|
*/
|
|
537
534
|
SDK_READY_FROM_CACHE: 'init::cache-ready';
|
|
538
535
|
/**
|
|
539
|
-
* The timeout event.
|
|
536
|
+
* The timeout event emitted after `startup.readyTimeout` seconds if the SDK_READY event was not emitted.
|
|
540
537
|
*/
|
|
541
538
|
SDK_READY_TIMED_OUT: 'init::timeout';
|
|
542
539
|
/**
|
|
543
|
-
* The update event.
|
|
540
|
+
* The update event emitted when the SDK cache is updated with new data from the backend.
|
|
544
541
|
*/
|
|
545
542
|
SDK_UPDATE: 'state::update';
|
|
546
543
|
};
|
|
@@ -707,7 +704,7 @@ declare namespace SplitIO {
|
|
|
707
704
|
*/
|
|
708
705
|
Event: EventConsts;
|
|
709
706
|
/**
|
|
710
|
-
* Returns a promise that resolves
|
|
707
|
+
* Returns a promise that resolves when the SDK has finished initial synchronization with the backend (`SDK_READY` event emitted), or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted).
|
|
711
708
|
* As it's meant to provide similar flexibility to the event approach, given that the SDK might be eventually ready after a timeout event, the `ready` method will return a resolved promise once the SDK is ready.
|
|
712
709
|
*
|
|
713
710
|
* Caveats: the method was designed to avoid an unhandled Promise rejection if the rejection case is not handled, so that `onRejected` handler is optional when using promises.
|
|
@@ -722,8 +719,26 @@ declare namespace SplitIO {
|
|
|
722
719
|
* ```
|
|
723
720
|
*
|
|
724
721
|
* @returns A promise that resolves once the SDK is ready or rejects if the SDK has timedout.
|
|
722
|
+
* @deprecated Use `whenReady` instead.
|
|
725
723
|
*/
|
|
726
724
|
ready(): Promise<void>;
|
|
725
|
+
/**
|
|
726
|
+
* Returns a promise that resolves when the SDK has finished initial synchronization with the backend (`SDK_READY` event emitted), or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted).
|
|
727
|
+
* As it's meant to provide similar flexibility than event listeners, given that the SDK might be ready after a timeout event, the `whenReady` method will return a resolved promise once the SDK is ready.
|
|
728
|
+
* You must handle the promise rejection to avoid an unhandled promise rejection error, or set the `startup.readyTimeout` configuration option to 0 to avoid the timeout and thus the rejection.
|
|
729
|
+
*
|
|
730
|
+
* @returns A promise that resolves once the SDK_READY event is emitted or rejects if the SDK has timedout.
|
|
731
|
+
*/
|
|
732
|
+
whenReady(): Promise<void>;
|
|
733
|
+
/**
|
|
734
|
+
* Returns a promise that resolves when the SDK is ready for evaluations using cached data, which might not yet be synchronized with the backend (`SDK_READY_FROM_CACHE` event emitted), or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted).
|
|
735
|
+
* As it's meant to provide similar flexibility than event listeners, given that the SDK might be ready from cache after a timeout event, the `whenReadyFromCache` method will return a resolved promise once the SDK is ready from cache.
|
|
736
|
+
* You must handle the promise rejection to avoid an unhandled promise rejection error, or set the `startup.readyTimeout` configuration option to 0 to avoid the timeout and thus the rejection.
|
|
737
|
+
*
|
|
738
|
+
* @returns A promise that resolves once the SDK_READY_FROM_CACHE event is emitted or rejects if the SDK has timedout. The promise resolves with a boolean value that
|
|
739
|
+
* indicates whether the SDK_READY_FROM_CACHE event was emitted together with the SDK_READY event (i.e., the SDK is ready and synchronized with the backend) or not.
|
|
740
|
+
*/
|
|
741
|
+
whenReadyFromCache(): Promise<boolean>;
|
|
727
742
|
}
|
|
728
743
|
/**
|
|
729
744
|
* Common definitions between clients for different environments interface.
|
|
@@ -1666,7 +1681,7 @@ declare namespace SplitIO {
|
|
|
1666
1681
|
* Wait for the SDK client to be ready before calling this method.
|
|
1667
1682
|
*
|
|
1668
1683
|
* ```js
|
|
1669
|
-
* await factory.client().
|
|
1684
|
+
* await factory.client().whenReady();
|
|
1670
1685
|
* const rolloutPlan = factory.getRolloutPlan();
|
|
1671
1686
|
* ```
|
|
1672
1687
|
*
|