@splitsoftware/splitio-commons 3.1.1-rc.1 → 3.1.1-rc.11
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 +12 -1
- package/cjs/evaluator/index.js +11 -6
- package/cjs/presets/serverSide.js +3 -3
- package/cjs/sdkClient/client.js +3 -5
- package/cjs/sdkClient/sdkLifecycle.js +2 -0
- package/cjs/services/authProvider.js +23 -10
- package/cjs/services/secureSplitHttpClient.js +10 -13
- package/cjs/services/serviceApi.js +23 -21
- package/cjs/services/splitApi.js +10 -9
- package/cjs/storages/inRedis/RedisAdapter.js +8 -5
- package/cjs/sync/polling/pollingManagerCS.js +84 -79
- package/cjs/sync/polling/pollingManagerSS.js +43 -35
- package/cjs/sync/polling/updaters/definitionChangesUpdater.js +2 -2
- package/cjs/sync/polling/updaters/segmentChangesUpdater.js +1 -1
- package/cjs/sync/streaming/AuthClient/index.js +13 -14
- package/cjs/sync/streaming/SSEHandler/index.js +1 -0
- package/cjs/sync/streaming/constants.js +2 -1
- package/cjs/sync/streaming/pushManager.js +5 -10
- package/cjs/sync/submitters/impressionsSubmitter.js +1 -1
- package/cjs/sync/syncManagerOnline.js +2 -7
- package/cjs/utils/Backoff.js +16 -4
- package/esm/evaluator/index.js +11 -6
- package/esm/presets/serverSide.js +3 -3
- package/esm/sdkClient/client.js +3 -5
- package/esm/sdkClient/sdkLifecycle.js +2 -0
- package/esm/services/authProvider.js +24 -11
- package/esm/services/secureSplitHttpClient.js +10 -13
- package/esm/services/serviceApi.js +24 -22
- package/esm/services/splitApi.js +10 -9
- package/esm/storages/inRedis/RedisAdapter.js +8 -5
- package/esm/sync/polling/pollingManagerCS.js +84 -79
- package/esm/sync/polling/pollingManagerSS.js +43 -35
- package/esm/sync/polling/updaters/definitionChangesUpdater.js +3 -3
- package/esm/sync/polling/updaters/segmentChangesUpdater.js +1 -1
- package/esm/sync/streaming/AuthClient/index.js +11 -11
- package/esm/sync/streaming/SSEHandler/index.js +2 -1
- package/esm/sync/streaming/constants.js +1 -0
- package/esm/sync/streaming/pushManager.js +6 -11
- package/esm/sync/submitters/impressionsSubmitter.js +1 -1
- package/esm/sync/syncManagerOnline.js +2 -7
- package/esm/utils/Backoff.js +16 -4
- package/package.json +6 -7
- package/src/dtos/types.ts +1 -1
- package/src/evaluator/index.ts +11 -5
- package/src/evaluator/types.ts +1 -0
- package/src/presets/serverSide.ts +3 -3
- package/src/sdkClient/client.ts +3 -4
- package/src/sdkClient/sdkLifecycle.ts +3 -0
- package/src/services/authProvider.ts +41 -19
- package/src/services/secureSplitHttpClient.ts +10 -18
- package/src/services/serviceApi.ts +27 -24
- package/src/services/splitApi.ts +10 -9
- package/src/services/types.ts +5 -4
- package/src/storages/inRedis/RedisAdapter.ts +8 -5
- package/src/sync/polling/pollingManagerCS.ts +80 -78
- package/src/sync/polling/pollingManagerSS.ts +49 -45
- package/src/sync/polling/updaters/definitionChangesUpdater.ts +3 -3
- package/src/sync/polling/updaters/segmentChangesUpdater.ts +1 -1
- package/src/sync/streaming/AuthClient/index.ts +14 -15
- package/src/sync/streaming/AuthClient/types.ts +9 -13
- package/src/sync/streaming/SSEClient/index.ts +2 -2
- package/src/sync/streaming/SSEClient/types.ts +2 -2
- package/src/sync/streaming/SSEHandler/index.ts +2 -1
- package/src/sync/streaming/SSEHandler/types.ts +2 -2
- package/src/sync/streaming/constants.ts +1 -0
- package/src/sync/streaming/pushManager.ts +32 -42
- package/src/sync/streaming/types.ts +3 -2
- package/src/sync/submitters/impressionsSubmitter.ts +3 -3
- package/src/sync/submitters/types.ts +2 -2
- package/src/sync/syncManagerOnline.ts +2 -8
- package/src/utils/Backoff.ts +16 -4
- package/types/splitio.d.ts +5 -4
package/CHANGES.txt
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
3.2.0 (
|
|
1
|
+
3.2.0 (XXX XX, 2026)
|
|
2
2
|
- Added support for AI configs.
|
|
3
|
+
- Added support for ioredis v6 (Related to issue https://github.com/splitio/javascript-client/issues/951). Note that ioredis v6 requires Node.js 20 or above; stay on ioredis v4 or v5 for older Node.js versions.
|
|
4
|
+
- Removed the `@types/ioredis` dependency, which is no longer required since the public type definitions don't depend on ioredis types. The `@types/node` dependency, previously installed transitively by `@types/ioredis`, is now declared explicitly, since the public type definitions use Node.js types for the `agent` and `tls` options.
|
|
3
5
|
- Removed Configs SDK-related types and modules and moved them to the Configs SDK repository.
|
|
6
|
+
- Bugfix - Fixed a memory leak in the `Backoff` utility, where promises returned by pending scheduled calls were never settled when the backoff was reset.
|
|
7
|
+
- Bugfix - Fixed the authentication service to not send unauthenticated requests when the SDK is destroyed while an authentication retry is pending.
|
|
4
8
|
|
|
5
9
|
3.1.0 (July 23, 2026)
|
|
6
10
|
- Added support for rule-based segments in /api/v1/configs endpoint.
|
|
@@ -20,6 +24,13 @@
|
|
|
20
24
|
- Removed the misleading warning log 'No listeners for SDK_READY event detected', since users can use the `whenReady` method to wait for the SDK to be ready.
|
|
21
25
|
- BREAKING CHANGES: Renamed some types and modules, and updated the signatures of others for clarity and reusability.
|
|
22
26
|
|
|
27
|
+
2.13.0 (September 24, 2026)
|
|
28
|
+
- Added support for ioredis v6 (Related to issue https://github.com/splitio/javascript-client/issues/951). Note that ioredis v6 requires Node.js 20 or above; stay on ioredis v4 or v5 for older Node.js versions.
|
|
29
|
+
- Removed the `@types/ioredis` dependency, which is no longer required since the public type definitions don't depend on ioredis types. The `@types/node` dependency, previously installed transitively by `@types/ioredis`, is now declared explicitly, since the public type definitions use Node.js types for the `agent` and `tls` options.
|
|
30
|
+
|
|
31
|
+
2.12.2 (September 23, 2026)
|
|
32
|
+
- Updated some transitive dependencies for vulnerability fixes.
|
|
33
|
+
|
|
23
34
|
2.12.1 (June 25, 2026)
|
|
24
35
|
- Updated internal modules to handle `null` values from `/splitChanges` response.
|
|
25
36
|
- Updated some dependencies for vulnerability fixes.
|
package/cjs/evaluator/index.js
CHANGED
|
@@ -25,6 +25,12 @@ function treatmentsException(definitionNames) {
|
|
|
25
25
|
});
|
|
26
26
|
return evaluations;
|
|
27
27
|
}
|
|
28
|
+
function setEvaluationDataFromDefinition(evaluation, definition) {
|
|
29
|
+
return (0, objectAssign_1.objectAssign)(evaluation, {
|
|
30
|
+
definition: definition,
|
|
31
|
+
config: definition.configurations && definition.configurations[evaluation.treatment] || null
|
|
32
|
+
});
|
|
33
|
+
}
|
|
28
34
|
function evaluateFeature(log, key, definitionName, attributes, storage) {
|
|
29
35
|
var definition;
|
|
30
36
|
try {
|
|
@@ -98,8 +104,8 @@ function getEvaluation(log, key, definition, attributes, storage) {
|
|
|
98
104
|
var split = (0, Engine_1.engineParser)(log, definition, storage);
|
|
99
105
|
var evaluation = split.getTreatment(key, attributes, evaluateFeature);
|
|
100
106
|
return (0, thenable_1.thenable)(evaluation) ?
|
|
101
|
-
evaluation.then(function (result) { return (
|
|
102
|
-
(
|
|
107
|
+
evaluation.then(function (result) { return setEvaluationDataFromDefinition(result, definition); }) :
|
|
108
|
+
setEvaluationDataFromDefinition(evaluation, definition);
|
|
103
109
|
}
|
|
104
110
|
return EVALUATION_DEFINITION_NOT_FOUND;
|
|
105
111
|
}
|
|
@@ -131,11 +137,10 @@ function evaluateDefaultTreatment(definitionName, storage) {
|
|
|
131
137
|
exports.evaluateDefaultTreatment = evaluateDefaultTreatment;
|
|
132
138
|
function getDefaultTreatment(definition) {
|
|
133
139
|
if (definition) {
|
|
134
|
-
return {
|
|
140
|
+
return setEvaluationDataFromDefinition({
|
|
135
141
|
treatment: definition.defaultTreatment,
|
|
136
|
-
label: labels_1.NO_CONDITION_MATCH,
|
|
137
|
-
|
|
138
|
-
};
|
|
142
|
+
label: labels_1.NO_CONDITION_MATCH, // "default rule"
|
|
143
|
+
}, definition);
|
|
139
144
|
}
|
|
140
145
|
return EVALUATION_DEFINITION_NOT_FOUND;
|
|
141
146
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.serverSideModules = void 0;
|
|
4
|
-
var
|
|
4
|
+
var splitApi_1 = require("../services/splitApi");
|
|
5
5
|
var syncManagerOnline_1 = require("../sync/syncManagerOnline");
|
|
6
6
|
var pushManager_1 = require("../sync/streaming/pushManager");
|
|
7
7
|
var pollingManagerSS_1 = require("../sync/polling/pollingManagerSS");
|
|
@@ -9,10 +9,10 @@ var InMemoryStorage_1 = require("../storages/inMemory/InMemoryStorage");
|
|
|
9
9
|
var sdkManager_1 = require("../sdkManager");
|
|
10
10
|
var sdkClientMethod_1 = require("../sdkClient/sdkClientMethod");
|
|
11
11
|
var impressionObserverSS_1 = require("../trackers/impressionObserver/impressionObserverSS");
|
|
12
|
-
var syncManagerOnlineSSFactory = (0, syncManagerOnline_1.syncManagerOnlineFactory)(pollingManagerSS_1.pollingManagerSSFactory, pushManager_1.pushManagerFactory);
|
|
12
|
+
var syncManagerOnlineSSFactory = (0, syncManagerOnline_1.syncManagerOnlineFactory)((0, pollingManagerSS_1.pollingManagerSSFactory)(), pushManager_1.pushManagerFactory);
|
|
13
13
|
exports.serverSideModules = {
|
|
14
14
|
storageFactory: InMemoryStorage_1.InMemoryStorageFactory,
|
|
15
|
-
serviceApiFactory:
|
|
15
|
+
serviceApiFactory: splitApi_1.splitApiFactory,
|
|
16
16
|
syncManagerFactory: syncManagerOnlineSSFactory,
|
|
17
17
|
sdkManagerFactory: sdkManager_1.sdkManagerFactory,
|
|
18
18
|
sdkClientMethodFactory: sdkClientMethod_1.sdkClientMethodFactory,
|
package/cjs/sdkClient/client.js
CHANGED
|
@@ -112,13 +112,11 @@ function clientFactory(params) {
|
|
|
112
112
|
}
|
|
113
113
|
// Internal function
|
|
114
114
|
function processEvaluation(evaluation, featureFlagName, key, withConfig, invokingMethodName, queue, options) {
|
|
115
|
-
var _a;
|
|
116
115
|
if (options === void 0) { options = {}; }
|
|
117
116
|
var matchingKey = (0, key_1.getMatching)(key);
|
|
118
117
|
var bucketingKey = (0, key_1.getBucketing)(key);
|
|
119
118
|
var definition = evaluation.definition;
|
|
120
|
-
var treatment = evaluation.treatment, label = evaluation.label;
|
|
121
|
-
var config = ((_a = definition === null || definition === void 0 ? void 0 : definition.configurations) === null || _a === void 0 ? void 0 : _a[treatment]) || null;
|
|
119
|
+
var treatment = evaluation.treatment, label = evaluation.label, _a = evaluation.config, config = _a === void 0 ? null : _a;
|
|
122
120
|
if (treatment === constants_1.CONTROL) {
|
|
123
121
|
var fallbackTreatment = fallbackCalculator(featureFlagName, label);
|
|
124
122
|
treatment = fallbackTreatment.treatment;
|
|
@@ -135,10 +133,10 @@ function clientFactory(params) {
|
|
|
135
133
|
time: Date.now(),
|
|
136
134
|
bucketingKey: bucketingKey,
|
|
137
135
|
label: label,
|
|
138
|
-
changeNumber: definition.changeNumber,
|
|
136
|
+
changeNumber: definition === null || definition === void 0 ? void 0 : definition.changeNumber,
|
|
139
137
|
properties: stringify(options.properties)
|
|
140
138
|
},
|
|
141
|
-
disabled: options.impressionsDisabled || definition.impressionsDisabled
|
|
139
|
+
disabled: options.impressionsDisabled || (definition === null || definition === void 0 ? void 0 : definition.impressionsDisabled)
|
|
142
140
|
});
|
|
143
141
|
}
|
|
144
142
|
if (withConfig) {
|
|
@@ -61,6 +61,8 @@ function sdkLifecycleFactory(params, isSharedClient) {
|
|
|
61
61
|
}
|
|
62
62
|
// Stop background jobs
|
|
63
63
|
syncManager && syncManager.stop();
|
|
64
|
+
// Stop the service API (the auth provider backoff retries specifically) before flushing,
|
|
65
|
+
// so that a pending/never-resolved auth retry doesn't block the flush forever
|
|
64
66
|
serviceApi && serviceApi.stop();
|
|
65
67
|
return __flush().then(function () {
|
|
66
68
|
// Cleanup storage
|
|
@@ -9,32 +9,35 @@ var SKEW_SECONDS = 30;
|
|
|
9
9
|
function isExpired(credential) {
|
|
10
10
|
return Date.now() / 1000 + SKEW_SECONDS >= credential.decodedToken.exp;
|
|
11
11
|
}
|
|
12
|
+
function noCachedCredentialError() {
|
|
13
|
+
return new Error(constants_1.LOG_PREFIX_SYNC_AUTH + 'stopped without a cached credential');
|
|
14
|
+
}
|
|
12
15
|
/**
|
|
13
16
|
* Factory of AuthProvider, which provides JWT credentials for authenticated HTTP requests.
|
|
14
17
|
* Credentials are fetched lazily on demand, cached in memory, and retried with backoff on failure.
|
|
15
18
|
*/
|
|
16
19
|
function authProviderFactory(settings, splitHttpClient, telemetryTracker) {
|
|
17
20
|
var urls = settings.urls, log = settings.log;
|
|
18
|
-
|
|
21
|
+
var fetchAuth = (0, AuthClient_1.fetchAuthFactory)(function () {
|
|
19
22
|
var url = "".concat(urls.auth, "/api/v3/auth?capabilities=config,aiconfig");
|
|
20
23
|
return splitHttpClient(url, undefined, telemetryTracker.trackHttp(constants_2.TOKEN), false, true);
|
|
21
|
-
}
|
|
22
|
-
var authenticate = (0, AuthClient_1.authenticateFactory)(fetchAuth);
|
|
24
|
+
});
|
|
23
25
|
var backoff = new Backoff_1.Backoff(fetchCredential);
|
|
24
26
|
var cachedCredential;
|
|
25
27
|
var inFlightPromise;
|
|
26
28
|
var stopped = false;
|
|
27
29
|
function fetchCredential() {
|
|
28
|
-
return
|
|
30
|
+
return fetchAuth().then(function (credential) {
|
|
29
31
|
log.info(constants_1.LOG_PREFIX_SYNC_AUTH + 'credential fetched successfully');
|
|
30
32
|
cachedCredential = credential;
|
|
31
33
|
inFlightPromise = undefined;
|
|
32
34
|
backoff.reset();
|
|
33
35
|
return credential;
|
|
34
36
|
}).catch(function (error) {
|
|
35
|
-
// Avoid
|
|
37
|
+
// Avoid unnecessary retries after stop(): fall back to any cached credential, or reject
|
|
38
|
+
// (never resolve with `undefined`, which callers would otherwise send as an unauthenticated request).
|
|
36
39
|
if (stopped)
|
|
37
|
-
return cachedCredential;
|
|
40
|
+
return cachedCredential || Promise.reject(noCachedCredentialError());
|
|
38
41
|
if (error.statusCode && error.statusCode >= 400 && error.statusCode < 500) {
|
|
39
42
|
log.error(constants_1.LOG_PREFIX_SYNC_AUTH + 'non-retryable error fetching credential (status ' + error.statusCode + '): ' + error.message);
|
|
40
43
|
inFlightPromise = undefined;
|
|
@@ -49,18 +52,28 @@ function authProviderFactory(settings, splitHttpClient, telemetryTracker) {
|
|
|
49
52
|
if (cachedCredential && !isExpired(cachedCredential)) {
|
|
50
53
|
return Promise.resolve(cachedCredential);
|
|
51
54
|
}
|
|
55
|
+
// Reuse any fetch already in flight (e.g. one started just before stop()), instead of firing a redundant one.
|
|
56
|
+
if (inFlightPromise)
|
|
57
|
+
return inFlightPromise;
|
|
58
|
+
// No cached credential to fall back to and stopped: reject so callers don't send an unauthenticated request.
|
|
59
|
+
if (stopped && !cachedCredential) {
|
|
60
|
+
return Promise.reject(noCachedCredentialError());
|
|
61
|
+
}
|
|
52
62
|
if (cachedCredential)
|
|
53
63
|
log.debug(constants_1.LOG_PREFIX_SYNC_AUTH + 'cached credential expired');
|
|
54
|
-
|
|
64
|
+
// If stopped, this is a last, bounded refetch: fetchCredential()'s catch short-circuits (no backoff retry)
|
|
65
|
+
// once stopped, so it's a single attempt that falls back to the stale cached credential on failure.
|
|
66
|
+
return inFlightPromise = fetchCredential();
|
|
55
67
|
},
|
|
56
68
|
invalidate: function () {
|
|
57
69
|
cachedCredential = undefined;
|
|
58
70
|
},
|
|
59
71
|
stop: function () {
|
|
60
72
|
stopped = true;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
backoff.
|
|
73
|
+
// Keep any in-flight fetch and already-cached credential usable (e.g. by a final flush on destroy).
|
|
74
|
+
// If a retry is only pending (not actively in flight), settle it immediately instead of waiting out the
|
|
75
|
+
// backoff delay: with the cached credential if there is one, or rejected otherwise.
|
|
76
|
+
backoff.reset(cachedCredential ? { value: cachedCredential } : { error: noCachedCredentialError() });
|
|
64
77
|
}
|
|
65
78
|
};
|
|
66
79
|
}
|
|
@@ -2,31 +2,28 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.secureSplitHttpClientFactory = void 0;
|
|
4
4
|
var tslib_1 = require("tslib");
|
|
5
|
-
var splitHttpClient_1 = require("./splitHttpClient");
|
|
6
|
-
var authProvider_1 = require("./authProvider");
|
|
7
5
|
/**
|
|
8
6
|
* Factory of Secure HTTP client, which authenticates requests using a JWT token.
|
|
9
7
|
* On 401 responses, invalidates the cached credential and retries once with a fresh token.
|
|
10
8
|
*
|
|
11
|
-
* @param
|
|
12
|
-
* @param
|
|
13
|
-
* @param fetchAuth - function to fetch auth credentials from the /v2/auth endpoint
|
|
9
|
+
* @param splitHttpClient - `splitHttpClientFactory` to use for making requests
|
|
10
|
+
* @param authProvider - `authProviderFactory` to use for authentication
|
|
14
11
|
*/
|
|
15
|
-
function secureSplitHttpClientFactory(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
function makeRequest(url, options, latencyTracker, logErrorsAsInfo, token) {
|
|
19
|
-
return splitHttpClient(url, tslib_1.__assign(tslib_1.__assign({}, options), { headers: tslib_1.__assign(tslib_1.__assign({}, options === null || options === void 0 ? void 0 : options.headers), { Authorization: "Bearer ".concat(token) }) }), latencyTracker, logErrorsAsInfo, true);
|
|
12
|
+
function secureSplitHttpClientFactory(splitHttpClient, authProvider) {
|
|
13
|
+
function makeRequest(url, options, latencyTracker, logErrorsAsInfo, newVersionHeader, token) {
|
|
14
|
+
return splitHttpClient(url, token ? tslib_1.__assign(tslib_1.__assign({}, options), { headers: tslib_1.__assign(tslib_1.__assign({}, options === null || options === void 0 ? void 0 : options.headers), { Authorization: "Bearer ".concat(token) }) }) : options, latencyTracker, logErrorsAsInfo, newVersionHeader);
|
|
20
15
|
}
|
|
21
|
-
var httpClient = function (url, options, latencyTracker, logErrorsAsInfo) {
|
|
16
|
+
var httpClient = function (url, options, latencyTracker, logErrorsAsInfo, newVersionHeader, useJwt) {
|
|
17
|
+
if (newVersionHeader === void 0) { newVersionHeader = true; }
|
|
18
|
+
if (useJwt === void 0) { useJwt = true; }
|
|
22
19
|
return authProvider.credential().then(function (credential) {
|
|
23
|
-
return makeRequest(url, options, latencyTracker, logErrorsAsInfo, credential.token)
|
|
20
|
+
return makeRequest(url, options, latencyTracker, logErrorsAsInfo, newVersionHeader, useJwt ? credential.token : undefined)
|
|
24
21
|
.catch(function (error) {
|
|
25
22
|
if (error.statusCode === 401) {
|
|
26
23
|
// retry once for 401, in case the token has just expired
|
|
27
24
|
authProvider.invalidate();
|
|
28
25
|
return authProvider.credential().then(function (newCredential) {
|
|
29
|
-
return makeRequest(url, options, latencyTracker, logErrorsAsInfo, newCredential.token);
|
|
26
|
+
return makeRequest(url, options, latencyTracker, logErrorsAsInfo, newVersionHeader, useJwt ? newCredential.token : undefined);
|
|
30
27
|
});
|
|
31
28
|
}
|
|
32
29
|
throw error;
|
|
@@ -5,25 +5,36 @@ var splitHttpClient_1 = require("./splitHttpClient");
|
|
|
5
5
|
var objectAssign_1 = require("../utils/lang/objectAssign");
|
|
6
6
|
var constants_1 = require("../utils/constants");
|
|
7
7
|
var constants_2 = require("../logger/constants");
|
|
8
|
+
var authProvider_1 = require("./authProvider");
|
|
9
|
+
var secureSplitHttpClient_1 = require("./secureSplitHttpClient");
|
|
8
10
|
var noCacheHeaderOptions = { headers: { 'Cache-Control': 'no-cache' } };
|
|
9
|
-
function userKeyToQueryParam(userKey) {
|
|
10
|
-
return 'users=' + encodeURIComponent(userKey); // no need to check availability of `encodeURIComponent`, since it is a global highly supported.
|
|
11
|
-
}
|
|
12
11
|
/**
|
|
13
12
|
* Factory of ServiceApi objects, which group the collection of HTTP endpoints used by the SDKs
|
|
14
13
|
*
|
|
15
14
|
* @param settings - validated settings object
|
|
16
15
|
* @param platform - object containing environment-specific dependencies
|
|
17
16
|
* @param telemetryTracker - telemetry tracker
|
|
18
|
-
* @param secureSplitHttpClientFactory - factory of SecureSplitHttpClient objects
|
|
19
17
|
*/
|
|
20
|
-
function serviceApiFactory(settings, platform, telemetryTracker
|
|
18
|
+
function serviceApiFactory(settings, platform, telemetryTracker) {
|
|
21
19
|
var urls = settings.urls;
|
|
22
20
|
var filterQueryString = settings.sync.__splitFiltersValidation && settings.sync.__splitFiltersValidation.queryString;
|
|
23
21
|
var SplitSDKImpressionsMode = settings.sync.impressionsMode;
|
|
24
22
|
var splitHttpClient = (0, splitHttpClient_1.splitHttpClientFactory)(settings, platform);
|
|
25
|
-
|
|
23
|
+
// Shared authProvider so that the SSE authentication (via `fetchAuth`,
|
|
24
|
+
// used by `pushManager`) and every authenticated HTTP request to the Configs
|
|
25
|
+
// API reuse the same cached credential instead of each fetching their own.
|
|
26
|
+
var authProvider = (0, authProvider_1.authProviderFactory)(settings, splitHttpClient, telemetryTracker);
|
|
27
|
+
var secureSplitHttpClient = (0, secureSplitHttpClient_1.secureSplitHttpClientFactory)(splitHttpClient, authProvider);
|
|
28
|
+
var initialAuth = true;
|
|
26
29
|
return {
|
|
30
|
+
fetchAuth: function () {
|
|
31
|
+
// Guard condition to avoid invalidating the cached credential on the first pushManager authentication
|
|
32
|
+
if (initialAuth)
|
|
33
|
+
initialAuth = false;
|
|
34
|
+
else
|
|
35
|
+
authProvider.invalidate();
|
|
36
|
+
return authProvider.credential();
|
|
37
|
+
},
|
|
27
38
|
// @TODO throw errors if health check requests fail, to log them in the Synchronizer
|
|
28
39
|
getSdkAPIHealthCheck: function () {
|
|
29
40
|
var url = "".concat(urls.sdk, "/api/version");
|
|
@@ -33,15 +44,6 @@ function serviceApiFactory(settings, platform, telemetryTracker, secureSplitHttp
|
|
|
33
44
|
var url = "".concat(urls.events, "/api/version");
|
|
34
45
|
return splitHttpClient(url).then(function () { return true; }).catch(function () { return false; });
|
|
35
46
|
},
|
|
36
|
-
fetchAuth: function (userMatchingKeys) {
|
|
37
|
-
var url = "".concat(urls.auth, "/api/v2/auth?s=").concat(settings.sync.flagSpecVersion);
|
|
38
|
-
if (userMatchingKeys) { // `userMatchingKeys` is undefined in server-side
|
|
39
|
-
var queryParams = userMatchingKeys.map(userKeyToQueryParam).join('&');
|
|
40
|
-
if (queryParams)
|
|
41
|
-
url += '&' + queryParams;
|
|
42
|
-
}
|
|
43
|
-
return splitHttpClient(url, undefined, telemetryTracker.trackHttp(constants_1.TOKEN));
|
|
44
|
-
},
|
|
45
47
|
fetchSplitChanges: function (since, noCache, till, rbSince) {
|
|
46
48
|
var url = "".concat(urls.sdk, "/api/splitChanges?s=").concat(settings.sync.flagSpecVersion, "&since=").concat(since).concat(rbSince ? '&rbSince=' + rbSince : '').concat(filterQueryString || '').concat(till ? '&till=' + till : '');
|
|
47
49
|
return splitHttpClient(url, noCache ? noCacheHeaderOptions : undefined, telemetryTracker.trackHttp(constants_1.SPLITS))
|
|
@@ -82,7 +84,7 @@ function serviceApiFactory(settings, platform, telemetryTracker, secureSplitHttp
|
|
|
82
84
|
*/
|
|
83
85
|
postEventsBulk: function (body, headers) {
|
|
84
86
|
var url = "".concat(urls.events, "/api/events/bulk");
|
|
85
|
-
return
|
|
87
|
+
return secureSplitHttpClient(url, { method: 'POST', body: body, headers: headers }, telemetryTracker.trackHttp(constants_1.EVENTS), false, false, false);
|
|
86
88
|
},
|
|
87
89
|
/**
|
|
88
90
|
* Post impressions.
|
|
@@ -92,12 +94,12 @@ function serviceApiFactory(settings, platform, telemetryTracker, secureSplitHttp
|
|
|
92
94
|
*/
|
|
93
95
|
postTestImpressionsBulk: function (body, headers) {
|
|
94
96
|
var url = "".concat(urls.events, "/api/testImpressions/bulk");
|
|
95
|
-
return
|
|
97
|
+
return secureSplitHttpClient(url, {
|
|
96
98
|
// Adding extra headers to send impressions in OPTIMIZED or DEBUG modes.
|
|
97
99
|
method: 'POST',
|
|
98
100
|
body: body,
|
|
99
101
|
headers: (0, objectAssign_1.objectAssign)({ SplitSDKImpressionsMode: SplitSDKImpressionsMode }, headers)
|
|
100
|
-
}, telemetryTracker.trackHttp(constants_1.IMPRESSIONS));
|
|
102
|
+
}, telemetryTracker.trackHttp(constants_1.IMPRESSIONS), false, false, false);
|
|
101
103
|
},
|
|
102
104
|
/**
|
|
103
105
|
* Post impressions counts.
|
|
@@ -107,7 +109,7 @@ function serviceApiFactory(settings, platform, telemetryTracker, secureSplitHttp
|
|
|
107
109
|
*/
|
|
108
110
|
postTestImpressionsCount: function (body, headers) {
|
|
109
111
|
var url = "".concat(urls.events, "/api/testImpressions/count");
|
|
110
|
-
return
|
|
112
|
+
return secureSplitHttpClient(url, { method: 'POST', body: body, headers: headers }, telemetryTracker.trackHttp(constants_1.IMPRESSIONS_COUNT), false, false, false);
|
|
111
113
|
},
|
|
112
114
|
/**
|
|
113
115
|
* Post unique keys for client side.
|
|
@@ -117,7 +119,7 @@ function serviceApiFactory(settings, platform, telemetryTracker, secureSplitHttp
|
|
|
117
119
|
*/
|
|
118
120
|
postUniqueKeysBulkCs: function (body, headers) {
|
|
119
121
|
var url = "".concat(urls.telemetry, "/api/v1/keys/cs");
|
|
120
|
-
return
|
|
122
|
+
return secureSplitHttpClient(url, { method: 'POST', body: body, headers: headers }, telemetryTracker.trackHttp(constants_1.TELEMETRY), false, false, false);
|
|
121
123
|
},
|
|
122
124
|
/**
|
|
123
125
|
* Post unique keys for server side.
|
|
@@ -127,7 +129,7 @@ function serviceApiFactory(settings, platform, telemetryTracker, secureSplitHttp
|
|
|
127
129
|
*/
|
|
128
130
|
postUniqueKeysBulkSs: function (body, headers) {
|
|
129
131
|
var url = "".concat(urls.telemetry, "/api/v1/keys/ss");
|
|
130
|
-
return
|
|
132
|
+
return secureSplitHttpClient(url, { method: 'POST', body: body, headers: headers }, telemetryTracker.trackHttp(constants_1.TELEMETRY), false, false, false);
|
|
131
133
|
},
|
|
132
134
|
postMetricsConfig: function (body, headers) {
|
|
133
135
|
var url = "".concat(urls.telemetry, "/api/v1/metrics/config");
|
package/cjs/services/splitApi.js
CHANGED
|
@@ -5,6 +5,7 @@ var splitHttpClient_1 = require("./splitHttpClient");
|
|
|
5
5
|
var objectAssign_1 = require("../utils/lang/objectAssign");
|
|
6
6
|
var constants_1 = require("../utils/constants");
|
|
7
7
|
var constants_2 = require("../logger/constants");
|
|
8
|
+
var AuthClient_1 = require("../sync/streaming/AuthClient");
|
|
8
9
|
var noCacheHeaderOptions = { headers: { 'Cache-Control': 'no-cache' } };
|
|
9
10
|
function userKeyToQueryParam(userKey) {
|
|
10
11
|
return 'users=' + encodeURIComponent(userKey); // no need to check availability of `encodeURIComponent`, since it is a global highly supported.
|
|
@@ -23,6 +24,15 @@ function splitApiFactory(settings, platform, telemetryTracker) {
|
|
|
23
24
|
var SplitSDKImpressionsMode = settings.sync.impressionsMode;
|
|
24
25
|
var splitHttpClient = (0, splitHttpClient_1.splitHttpClientFactory)(settings, platform);
|
|
25
26
|
return {
|
|
27
|
+
fetchAuth: (0, AuthClient_1.fetchAuthFactory)(function (userMatchingKeys) {
|
|
28
|
+
var url = "".concat(urls.auth, "/v2/auth?s=").concat(settings.sync.flagSpecVersion);
|
|
29
|
+
if (userMatchingKeys) { // `userMatchingKeys` is undefined in server-side
|
|
30
|
+
var queryParams = userMatchingKeys.map(userKeyToQueryParam).join('&');
|
|
31
|
+
if (queryParams)
|
|
32
|
+
url += '&' + queryParams;
|
|
33
|
+
}
|
|
34
|
+
return splitHttpClient(url, undefined, telemetryTracker.trackHttp(constants_1.TOKEN));
|
|
35
|
+
}),
|
|
26
36
|
// @TODO throw errors if health check requests fail, to log them in the Synchronizer
|
|
27
37
|
getSdkAPIHealthCheck: function () {
|
|
28
38
|
var url = "".concat(urls.sdk, "/version");
|
|
@@ -32,15 +42,6 @@ function splitApiFactory(settings, platform, telemetryTracker) {
|
|
|
32
42
|
var url = "".concat(urls.events, "/version");
|
|
33
43
|
return splitHttpClient(url).then(function () { return true; }).catch(function () { return false; });
|
|
34
44
|
},
|
|
35
|
-
fetchAuth: function (userMatchingKeys) {
|
|
36
|
-
var url = "".concat(urls.auth, "/v2/auth?s=").concat(settings.sync.flagSpecVersion);
|
|
37
|
-
if (userMatchingKeys) { // `userMatchingKeys` is undefined in server-side
|
|
38
|
-
var queryParams = userMatchingKeys.map(userKeyToQueryParam).join('&');
|
|
39
|
-
if (queryParams)
|
|
40
|
-
url += '&' + queryParams;
|
|
41
|
-
}
|
|
42
|
-
return splitHttpClient(url, undefined, telemetryTracker.trackHttp(constants_1.TOKEN));
|
|
43
|
-
},
|
|
44
45
|
fetchSplitChanges: function (since, noCache, till, rbSince) {
|
|
45
46
|
var url = "".concat(urls.sdk, "/splitChanges?s=").concat(settings.sync.flagSpecVersion, "&since=").concat(since).concat(rbSince ? '&rbSince=' + rbSince : '').concat(filterQueryString || '').concat(till ? '&till=' + till : '');
|
|
46
47
|
return splitHttpClient(url, noCache ? noCacheHeaderOptions : undefined, telemetryTracker.trackHttp(constants_1.SPLITS))
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.RedisAdapter = void 0;
|
|
4
4
|
var tslib_1 = require("tslib");
|
|
5
5
|
// Dynamically require ioredis to prevent strict TypeScript binding
|
|
6
|
-
// and handle module export differences between v4 and
|
|
6
|
+
// and handle module export differences between v4 and later major versions.
|
|
7
7
|
var RedisConstructor;
|
|
8
8
|
try {
|
|
9
9
|
var ioredisLib = require('ioredis');
|
|
@@ -11,7 +11,7 @@ try {
|
|
|
11
11
|
}
|
|
12
12
|
catch (e) {
|
|
13
13
|
// If we reach here, the peer dependency is missing
|
|
14
|
-
throw new Error('ioredis is missing. Please install ioredis v4 or
|
|
14
|
+
throw new Error('ioredis is missing. Please install ioredis v4, v5 or v6.');
|
|
15
15
|
}
|
|
16
16
|
var lang_1 = require("../../utils/lang");
|
|
17
17
|
var thenable_1 = require("../../utils/promise/thenable");
|
|
@@ -31,13 +31,16 @@ var DEFAULT_LIBRARY_OPTIONS = {
|
|
|
31
31
|
enableOfflineQueue: false,
|
|
32
32
|
connectTimeout: DEFAULT_OPTIONS.connectionTimeout,
|
|
33
33
|
lazyConnect: false,
|
|
34
|
-
// CRITICAL: v5
|
|
35
|
-
// v4 defaulted to 5000. We explicitly set it here to ensure v5
|
|
34
|
+
// CRITICAL: v5 and v6 default this to 0 (disabled), which breaks dynamic clusters.
|
|
35
|
+
// v4 defaulted to 5000. We explicitly set it here to ensure v5 and v6 work like v4.
|
|
36
36
|
slotsRefreshInterval: 5000,
|
|
37
|
+
// v6 defaults to RESP3 (`protocol: 3`). The SDK doesn't use any RESP3 feature,
|
|
38
|
+
// so we stick to RESP2 to keep the v4 and v5 behavior. Ignored by v4 and v5.
|
|
39
|
+
protocol: 2,
|
|
37
40
|
};
|
|
38
41
|
/**
|
|
39
42
|
* Redis adapter on top of the library of choice (written with ioredis) for some extra control.
|
|
40
|
-
* Refactored to use Composition instead of Inheritance to support
|
|
43
|
+
* Refactored to use Composition instead of Inheritance to support v4, v5 and v6.
|
|
41
44
|
*/
|
|
42
45
|
var RedisAdapter = /** @class */ (function () {
|
|
43
46
|
function RedisAdapter(log, storageSettings) {
|
|
@@ -8,93 +8,98 @@ var key_1 = require("../../utils/key");
|
|
|
8
8
|
var constants_1 = require("../../readiness/constants");
|
|
9
9
|
var constants_2 = require("../../logger/constants");
|
|
10
10
|
var AbstractDefinitionsCacheSync_1 = require("../../storages/AbstractDefinitionsCacheSync");
|
|
11
|
+
var splitChangesFetcher_1 = require("./fetchers/splitChangesFetcher");
|
|
11
12
|
/**
|
|
12
13
|
* Expose start / stop mechanism for polling data from services.
|
|
13
14
|
* For client-side API with multiple clients.
|
|
14
15
|
*/
|
|
15
|
-
function pollingManagerCSFactory(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
(0,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (mySegmentsSyncTask.isRunning())
|
|
31
|
-
mySegmentsSyncTask.stop();
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
// smart pausing
|
|
35
|
-
readiness.definitions.on(constants_1.SDK_DEFINITIONS_ARRIVED, function () {
|
|
36
|
-
if (!definitionsSyncTask.isRunning())
|
|
37
|
-
return; // noop if not doing polling
|
|
38
|
-
var usingSegments = (0, AbstractDefinitionsCacheSync_1.usesSegmentsSync)(storage);
|
|
39
|
-
if (usingSegments !== mySegmentsSyncTask.isRunning()) {
|
|
40
|
-
log.info(constants_2.POLLING_SMART_PAUSING, [usingSegments ? 'ON' : 'OFF']);
|
|
41
|
-
if (usingSegments) {
|
|
42
|
-
startMySegmentsSyncTasks();
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
stopMySegmentsSyncTasks();
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
function add(matchingKey, readiness, storage) {
|
|
50
|
-
var mySegmentsSyncTask = (0, mySegmentsSyncTask_1.mySegmentsSyncTaskFactory)(serviceApi.fetchMemberships, storage, readiness, settings, matchingKey);
|
|
51
|
-
// smart ready
|
|
52
|
-
function smartReady(metadata) {
|
|
53
|
-
if (!readiness.isReady() && !(0, AbstractDefinitionsCacheSync_1.usesSegmentsSync)(storage))
|
|
54
|
-
readiness.segments.emit(constants_1.SDK_SEGMENTS_ARRIVED, metadata);
|
|
16
|
+
function pollingManagerCSFactory(definitionFetcherFactory) {
|
|
17
|
+
if (definitionFetcherFactory === void 0) { definitionFetcherFactory = splitChangesFetcher_1.splitChangesFetcherFactory; }
|
|
18
|
+
return function (params) {
|
|
19
|
+
var serviceApi = params.serviceApi, storage = params.storage, readiness = params.readiness, settings = params.settings;
|
|
20
|
+
var log = settings.log;
|
|
21
|
+
var definitionChangesFetcher = definitionFetcherFactory(params);
|
|
22
|
+
var definitionsSyncTask = (0, definitionsSyncTask_1.definitionsSyncTaskFactory)(definitionChangesFetcher, storage, readiness, settings);
|
|
23
|
+
// Map of matching keys to their corresponding MySegmentsSyncTask.
|
|
24
|
+
var mySegmentsSyncTasks = {};
|
|
25
|
+
var matchingKey = (0, key_1.getMatching)(settings.core.key);
|
|
26
|
+
var mySegmentsSyncTask = add(matchingKey, readiness, storage);
|
|
27
|
+
function startMySegmentsSyncTasks() {
|
|
28
|
+
(0, lang_1.forOwn)(mySegmentsSyncTasks, function (mySegmentsSyncTask) {
|
|
29
|
+
mySegmentsSyncTask.start();
|
|
30
|
+
});
|
|
55
31
|
}
|
|
56
|
-
|
|
57
|
-
setTimeout(smartReady, 0);
|
|
58
|
-
else
|
|
59
|
-
readiness.definitions.once(constants_1.SDK_DEFINITIONS_ARRIVED, smartReady);
|
|
60
|
-
mySegmentsSyncTasks[matchingKey] = mySegmentsSyncTask;
|
|
61
|
-
return mySegmentsSyncTask;
|
|
62
|
-
}
|
|
63
|
-
return {
|
|
64
|
-
definitionsSyncTask: definitionsSyncTask,
|
|
65
|
-
segmentsSyncTask: mySegmentsSyncTask,
|
|
66
|
-
// Start periodic fetching (polling)
|
|
67
|
-
start: function () {
|
|
68
|
-
log.info(constants_2.POLLING_START);
|
|
69
|
-
definitionsSyncTask.start();
|
|
70
|
-
if ((0, AbstractDefinitionsCacheSync_1.usesSegmentsSync)(storage))
|
|
71
|
-
startMySegmentsSyncTasks();
|
|
72
|
-
},
|
|
73
|
-
// Stop periodic fetching (polling)
|
|
74
|
-
stop: function () {
|
|
75
|
-
log.info(constants_2.POLLING_STOP);
|
|
76
|
-
if (definitionsSyncTask.isRunning())
|
|
77
|
-
definitionsSyncTask.stop();
|
|
78
|
-
stopMySegmentsSyncTasks();
|
|
79
|
-
},
|
|
80
|
-
// Used by SyncManager to know if running in polling mode.
|
|
81
|
-
isRunning: definitionsSyncTask.isRunning,
|
|
82
|
-
// fetch definitions and segments
|
|
83
|
-
syncAll: function () {
|
|
84
|
-
var promises = [definitionsSyncTask.execute()];
|
|
32
|
+
function stopMySegmentsSyncTasks() {
|
|
85
33
|
(0, lang_1.forOwn)(mySegmentsSyncTasks, function (mySegmentsSyncTask) {
|
|
86
|
-
|
|
34
|
+
if (mySegmentsSyncTask.isRunning())
|
|
35
|
+
mySegmentsSyncTask.stop();
|
|
87
36
|
});
|
|
88
|
-
return Promise.all(promises);
|
|
89
|
-
},
|
|
90
|
-
// Support for handling mySegments sync of multiple clients
|
|
91
|
-
add: add,
|
|
92
|
-
remove: function (matchingKey) {
|
|
93
|
-
delete mySegmentsSyncTasks[matchingKey];
|
|
94
|
-
},
|
|
95
|
-
get: function (matchingKey) {
|
|
96
|
-
return mySegmentsSyncTasks[matchingKey];
|
|
97
37
|
}
|
|
38
|
+
// smart pausing
|
|
39
|
+
readiness.definitions.on(constants_1.SDK_DEFINITIONS_ARRIVED, function () {
|
|
40
|
+
if (!definitionsSyncTask.isRunning())
|
|
41
|
+
return; // noop if not doing polling
|
|
42
|
+
var usingSegments = (0, AbstractDefinitionsCacheSync_1.usesSegmentsSync)(storage);
|
|
43
|
+
if (usingSegments !== mySegmentsSyncTask.isRunning()) {
|
|
44
|
+
log.info(constants_2.POLLING_SMART_PAUSING, [usingSegments ? 'ON' : 'OFF']);
|
|
45
|
+
if (usingSegments) {
|
|
46
|
+
startMySegmentsSyncTasks();
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
stopMySegmentsSyncTasks();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
function add(matchingKey, readiness, storage) {
|
|
54
|
+
var mySegmentsSyncTask = (0, mySegmentsSyncTask_1.mySegmentsSyncTaskFactory)(serviceApi.fetchMemberships, storage, readiness, settings, matchingKey);
|
|
55
|
+
// smart ready
|
|
56
|
+
function smartReady(metadata) {
|
|
57
|
+
if (!readiness.isReady() && !(0, AbstractDefinitionsCacheSync_1.usesSegmentsSync)(storage))
|
|
58
|
+
readiness.segments.emit(constants_1.SDK_SEGMENTS_ARRIVED, metadata);
|
|
59
|
+
}
|
|
60
|
+
if (!(0, AbstractDefinitionsCacheSync_1.usesSegmentsSync)(storage))
|
|
61
|
+
setTimeout(smartReady, 0);
|
|
62
|
+
else
|
|
63
|
+
readiness.definitions.once(constants_1.SDK_DEFINITIONS_ARRIVED, smartReady);
|
|
64
|
+
mySegmentsSyncTasks[matchingKey] = mySegmentsSyncTask;
|
|
65
|
+
return mySegmentsSyncTask;
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
definitionsSyncTask: definitionsSyncTask,
|
|
69
|
+
segmentsSyncTask: mySegmentsSyncTask,
|
|
70
|
+
// Start periodic fetching (polling)
|
|
71
|
+
start: function () {
|
|
72
|
+
log.info(constants_2.POLLING_START);
|
|
73
|
+
definitionsSyncTask.start();
|
|
74
|
+
if ((0, AbstractDefinitionsCacheSync_1.usesSegmentsSync)(storage))
|
|
75
|
+
startMySegmentsSyncTasks();
|
|
76
|
+
},
|
|
77
|
+
// Stop periodic fetching (polling)
|
|
78
|
+
stop: function () {
|
|
79
|
+
log.info(constants_2.POLLING_STOP);
|
|
80
|
+
if (definitionsSyncTask.isRunning())
|
|
81
|
+
definitionsSyncTask.stop();
|
|
82
|
+
stopMySegmentsSyncTasks();
|
|
83
|
+
},
|
|
84
|
+
// Used by SyncManager to know if running in polling mode.
|
|
85
|
+
isRunning: definitionsSyncTask.isRunning,
|
|
86
|
+
// fetch definitions and segments
|
|
87
|
+
syncAll: function () {
|
|
88
|
+
var promises = [definitionsSyncTask.execute()];
|
|
89
|
+
(0, lang_1.forOwn)(mySegmentsSyncTasks, function (mySegmentsSyncTask) {
|
|
90
|
+
promises.push(mySegmentsSyncTask.execute());
|
|
91
|
+
});
|
|
92
|
+
return Promise.all(promises);
|
|
93
|
+
},
|
|
94
|
+
// Support for handling mySegments sync of multiple clients
|
|
95
|
+
add: add,
|
|
96
|
+
remove: function (matchingKey) {
|
|
97
|
+
delete mySegmentsSyncTasks[matchingKey];
|
|
98
|
+
},
|
|
99
|
+
get: function (matchingKey) {
|
|
100
|
+
return mySegmentsSyncTasks[matchingKey];
|
|
101
|
+
}
|
|
102
|
+
};
|
|
98
103
|
};
|
|
99
104
|
}
|
|
100
105
|
exports.pollingManagerCSFactory = pollingManagerCSFactory;
|