@splitsoftware/splitio-commons 3.1.1-rc.0 → 3.1.1-rc.10
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 +10 -1
- package/cjs/evaluator/condition/index.js +1 -1
- package/cjs/evaluator/fallbackTreatmentsCalculator/fallbackSanitizer/index.js +8 -5
- package/cjs/evaluator/index.js +69 -81
- package/cjs/listeners/browser.js +1 -2
- package/cjs/listeners/node.js +4 -4
- package/cjs/logger/constants.js +20 -15
- package/cjs/logger/messages/debug.js +6 -5
- package/cjs/logger/messages/error.js +3 -3
- package/cjs/logger/messages/info.js +4 -3
- package/cjs/logger/messages/warn.js +7 -6
- package/cjs/presets/serverSide.js +2 -2
- package/cjs/readiness/constants.js +4 -5
- package/cjs/readiness/readinessManager.js +23 -23
- package/cjs/readiness/sdkReadinessManager.js +17 -1
- package/cjs/sdkClient/client.js +34 -9
- package/cjs/sdkClient/clientInputValidation.js +17 -6
- package/cjs/sdkClient/sdkClient.js +60 -3
- package/cjs/sdkFactory/index.js +15 -11
- package/cjs/sdkManager/index.js +5 -5
- package/cjs/services/decorateHeaders.js +1 -2
- package/cjs/services/splitApi.js +1 -14
- package/cjs/services/splitHttpClient.js +5 -3
- package/cjs/storages/AbstractSplitsCacheAsync.js +51 -0
- package/cjs/storages/AbstractSplitsCacheSync.js +76 -0
- package/cjs/storages/KeyBuilder.js +6 -6
- package/cjs/storages/KeyBuilderCS.js +5 -5
- package/cjs/storages/KeyBuilderSS.js +2 -2
- package/cjs/storages/getRolloutPlan.js +3 -3
- package/cjs/storages/inLocalStorage/MySegmentsCacheInLocal.js +2 -1
- package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +10 -10
- package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +194 -0
- package/cjs/storages/inLocalStorage/index.js +6 -6
- package/cjs/storages/inLocalStorage/validateCache.js +3 -3
- package/cjs/storages/inMemory/InMemoryStorage.js +4 -4
- package/cjs/storages/inMemory/InMemoryStorageCS.js +5 -5
- package/cjs/storages/inMemory/RBSegmentsCacheInMemory.js +5 -5
- package/cjs/storages/inMemory/SegmentsCacheInMemory.js +1 -1
- package/cjs/storages/inMemory/SplitsCacheInMemory.js +121 -0
- package/cjs/storages/inMemory/TelemetryCacheInMemory.js +5 -5
- package/cjs/storages/inRedis/RBSegmentsCacheInRedis.js +1 -1
- package/cjs/storages/inRedis/RedisAdapter.js +8 -5
- package/cjs/storages/inRedis/{DefinitionsCacheInRedis.js → SplitsCacheInRedis.js} +77 -77
- package/cjs/storages/inRedis/index.js +2 -2
- package/cjs/storages/pluggable/RBSegmentsCachePluggable.js +1 -1
- package/cjs/storages/pluggable/SplitsCachePluggable.js +212 -0
- package/cjs/storages/pluggable/index.js +2 -2
- package/cjs/storages/setRolloutPlan.js +4 -4
- package/cjs/storages/utils.js +0 -1
- package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +5 -5
- package/cjs/sync/polling/fetchers/splitChangesFetcher.js +15 -24
- package/cjs/sync/polling/pollingManagerCS.js +20 -20
- package/cjs/sync/polling/pollingManagerSS.js +15 -15
- package/cjs/sync/polling/syncTasks/segmentsSyncTask.js +3 -2
- package/cjs/sync/polling/syncTasks/splitsSyncTask.js +13 -0
- package/cjs/sync/polling/updaters/mySegmentsUpdater.js +3 -3
- package/cjs/sync/polling/updaters/segmentChangesUpdater.js +6 -6
- package/cjs/sync/polling/updaters/{definitionChangesUpdater.js → splitChangesUpdater.js} +68 -65
- package/cjs/sync/streaming/AuthClient/index.js +1 -1
- package/cjs/sync/streaming/SSEHandler/NotificationKeeper.js +1 -1
- package/cjs/sync/streaming/SSEHandler/index.js +1 -1
- package/cjs/sync/streaming/UpdateWorkers/{DefinitionsUpdateWorker.js → SplitsUpdateWorker.js} +19 -16
- package/cjs/sync/streaming/constants.js +3 -3
- package/cjs/sync/streaming/parseUtils.js +11 -11
- package/cjs/sync/streaming/pushManager.js +16 -16
- package/cjs/sync/submitters/eventsSubmitter.js +1 -1
- package/cjs/sync/submitters/impressionCountsSubmitter.js +1 -1
- package/cjs/sync/submitters/impressionsSubmitter.js +4 -5
- package/cjs/sync/submitters/telemetrySubmitter.js +4 -3
- package/cjs/sync/submitters/uniqueKeysSubmitter.js +1 -1
- package/cjs/sync/syncManagerOnline.js +7 -12
- package/cjs/utils/Backoff.js +0 -20
- package/cjs/utils/inputValidation/index.js +7 -7
- package/cjs/utils/inputValidation/isOperational.js +4 -4
- package/cjs/utils/inputValidation/{definition.js → split.js} +10 -10
- package/cjs/utils/inputValidation/{definitionExistence.js → splitExistence.js} +6 -6
- package/cjs/utils/inputValidation/splits.js +25 -0
- package/cjs/utils/labels/index.js +3 -3
- package/cjs/utils/settingsValidation/index.js +0 -2
- package/cjs/utils/settingsValidation/splitFilters.js +3 -3
- package/cjs/utils/settingsValidation/url.js +2 -6
- package/esm/evaluator/condition/index.js +1 -1
- package/esm/evaluator/fallbackTreatmentsCalculator/fallbackSanitizer/index.js +8 -5
- package/esm/evaluator/index.js +69 -80
- package/esm/listeners/browser.js +1 -2
- package/esm/listeners/node.js +4 -4
- package/esm/logger/constants.js +17 -12
- package/esm/logger/messages/debug.js +6 -5
- package/esm/logger/messages/error.js +3 -3
- package/esm/logger/messages/info.js +4 -3
- package/esm/logger/messages/warn.js +7 -6
- package/esm/presets/serverSide.js +2 -2
- package/esm/readiness/constants.js +3 -4
- package/esm/readiness/readinessManager.js +24 -24
- package/esm/readiness/sdkReadinessManager.js +18 -2
- package/esm/sdkClient/client.js +35 -9
- package/esm/sdkClient/clientInputValidation.js +19 -8
- package/esm/sdkClient/sdkClient.js +60 -3
- package/esm/sdkFactory/index.js +16 -12
- package/esm/sdkManager/index.js +6 -6
- package/esm/services/decorateHeaders.js +1 -2
- package/esm/services/splitApi.js +1 -14
- package/esm/services/splitHttpClient.js +5 -3
- package/esm/storages/AbstractSplitsCacheAsync.js +48 -0
- package/esm/storages/AbstractSplitsCacheSync.js +71 -0
- package/esm/storages/KeyBuilder.js +6 -6
- package/esm/storages/KeyBuilderCS.js +5 -5
- package/esm/storages/KeyBuilderSS.js +2 -2
- package/esm/storages/getRolloutPlan.js +3 -3
- package/esm/storages/inLocalStorage/MySegmentsCacheInLocal.js +2 -1
- package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +7 -7
- package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +191 -0
- package/esm/storages/inLocalStorage/index.js +6 -6
- package/esm/storages/inLocalStorage/validateCache.js +3 -3
- package/esm/storages/inMemory/InMemoryStorage.js +4 -4
- package/esm/storages/inMemory/InMemoryStorageCS.js +5 -5
- package/esm/storages/inMemory/RBSegmentsCacheInMemory.js +2 -2
- package/esm/storages/inMemory/SegmentsCacheInMemory.js +1 -1
- package/esm/storages/inMemory/SplitsCacheInMemory.js +118 -0
- package/esm/storages/inMemory/TelemetryCacheInMemory.js +5 -5
- package/esm/storages/inRedis/RBSegmentsCacheInRedis.js +1 -1
- package/esm/storages/inRedis/RedisAdapter.js +8 -5
- package/esm/storages/inRedis/{DefinitionsCacheInRedis.js → SplitsCacheInRedis.js} +76 -76
- package/esm/storages/inRedis/index.js +2 -2
- package/esm/storages/pluggable/RBSegmentsCachePluggable.js +1 -1
- package/esm/storages/pluggable/SplitsCachePluggable.js +209 -0
- package/esm/storages/pluggable/index.js +2 -2
- package/esm/storages/setRolloutPlan.js +4 -4
- package/esm/storages/utils.js +0 -1
- package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +6 -6
- package/esm/sync/polling/fetchers/splitChangesFetcher.js +16 -25
- package/esm/sync/polling/pollingManagerCS.js +17 -17
- package/esm/sync/polling/pollingManagerSS.js +15 -15
- package/esm/sync/polling/syncTasks/segmentsSyncTask.js +3 -2
- package/esm/sync/polling/syncTasks/splitsSyncTask.js +9 -0
- package/esm/sync/polling/updaters/mySegmentsUpdater.js +3 -3
- package/esm/sync/polling/updaters/segmentChangesUpdater.js +7 -7
- package/esm/sync/polling/updaters/{definitionChangesUpdater.js → splitChangesUpdater.js} +68 -65
- package/esm/sync/streaming/AuthClient/index.js +1 -1
- package/esm/sync/streaming/SSEHandler/NotificationKeeper.js +2 -2
- package/esm/sync/streaming/SSEHandler/index.js +2 -2
- package/esm/sync/streaming/UpdateWorkers/{DefinitionsUpdateWorker.js → SplitsUpdateWorker.js} +18 -15
- package/esm/sync/streaming/constants.js +2 -2
- package/esm/sync/streaming/parseUtils.js +9 -9
- package/esm/sync/streaming/pushManager.js +18 -18
- package/esm/sync/submitters/eventsSubmitter.js +1 -1
- package/esm/sync/submitters/impressionCountsSubmitter.js +1 -1
- package/esm/sync/submitters/impressionsSubmitter.js +4 -5
- package/esm/sync/submitters/telemetrySubmitter.js +4 -3
- package/esm/sync/submitters/uniqueKeysSubmitter.js +1 -1
- package/esm/sync/syncManagerOnline.js +6 -11
- package/esm/utils/Backoff.js +0 -20
- package/esm/utils/inputValidation/index.js +3 -3
- package/esm/utils/inputValidation/isOperational.js +4 -4
- package/esm/utils/inputValidation/{definition.js → split.js} +8 -8
- package/esm/utils/inputValidation/splitExistence.js +15 -0
- package/esm/utils/inputValidation/splits.js +21 -0
- package/esm/utils/labels/index.js +2 -2
- package/esm/utils/settingsValidation/index.js +0 -2
- package/esm/utils/settingsValidation/splitFilters.js +3 -3
- package/esm/utils/settingsValidation/url.js +2 -6
- package/package.json +6 -7
- package/src/dtos/types.ts +1 -1
- package/src/evaluator/Engine.ts +3 -3
- package/src/evaluator/condition/index.ts +2 -2
- package/src/evaluator/index.ts +27 -35
- package/src/evaluator/types.ts +2 -6
- package/src/presets/serverSide.ts +3 -3
- package/src/sdkClient/client.ts +22 -24
- 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/cjs/platform/browser.js +0 -15
- package/cjs/platform/getEventSource/browser.js +0 -8
- package/cjs/platform/getEventSource/eventsource.js +0 -480
- package/cjs/platform/getEventSource/node.js +0 -26
- package/cjs/platform/getFetch/browser.js +0 -9
- package/cjs/platform/getFetch/node.js +0 -27
- package/cjs/platform/getOptions/node.js +0 -25
- package/cjs/platform/node.js +0 -17
- package/cjs/sdkClient/sdkLifecycle.js +0 -72
- package/cjs/sdkClient/trackMethod.js +0 -56
- package/cjs/services/authProvider.js +0 -67
- package/cjs/services/secureSplitHttpClient.js +0 -39
- package/cjs/services/serviceApi.js +0 -145
- package/cjs/storages/AbstractDefinitionsCacheAsync.js +0 -51
- package/cjs/storages/AbstractDefinitionsCacheSync.js +0 -76
- package/cjs/storages/inLocalStorage/DefinitionsCacheInLocal.js +0 -194
- package/cjs/storages/inMemory/DefinitionsCacheInMemory.js +0 -121
- package/cjs/storages/pluggable/DefinitionsCachePluggable.js +0 -212
- package/cjs/sync/polling/syncTasks/definitionsSyncTask.js +0 -13
- package/cjs/utils/EventEmitter.js +0 -391
- package/cjs/utils/filter/bloomFilter.js +0 -37
- package/cjs/utils/inputValidation/definitions.js +0 -25
- package/cjs/utils/inputValidation/target.js +0 -22
- package/esm/platform/browser.js +0 -12
- package/esm/platform/getEventSource/browser.js +0 -4
- package/esm/platform/getEventSource/eventsource.js +0 -480
- package/esm/platform/getEventSource/node.js +0 -20
- package/esm/platform/getFetch/browser.js +0 -5
- package/esm/platform/getFetch/node.js +0 -22
- package/esm/platform/getOptions/node.js +0 -20
- package/esm/platform/node.js +0 -14
- package/esm/sdkClient/sdkLifecycle.js +0 -68
- package/esm/sdkClient/trackMethod.js +0 -52
- package/esm/services/authProvider.js +0 -63
- package/esm/services/secureSplitHttpClient.js +0 -35
- package/esm/services/serviceApi.js +0 -141
- package/esm/storages/AbstractDefinitionsCacheAsync.js +0 -48
- package/esm/storages/AbstractDefinitionsCacheSync.js +0 -71
- package/esm/storages/inLocalStorage/DefinitionsCacheInLocal.js +0 -191
- package/esm/storages/inMemory/DefinitionsCacheInMemory.js +0 -118
- package/esm/storages/pluggable/DefinitionsCachePluggable.js +0 -209
- package/esm/sync/polling/syncTasks/definitionsSyncTask.js +0 -9
- package/esm/utils/EventEmitter.js +0 -387
- package/esm/utils/filter/bloomFilter.js +0 -33
- package/esm/utils/inputValidation/definitionExistence.js +0 -15
- package/esm/utils/inputValidation/definitions.js +0 -21
- package/esm/utils/inputValidation/target.js +0 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@splitsoftware/splitio-commons",
|
|
3
|
-
"version": "3.1.1-rc.
|
|
3
|
+
"version": "3.1.1-rc.10",
|
|
4
4
|
"description": "Split JavaScript SDK common components",
|
|
5
5
|
"main": "cjs/index.js",
|
|
6
6
|
"module": "esm/index.js",
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"check:lint": "eslint src types --ext .js,.ts",
|
|
21
21
|
"check:types": "tsc --noEmit",
|
|
22
22
|
"build": "npm run build:cjs && npm run build:esm",
|
|
23
|
-
"build:esm": "rimraf esm && tsc -m es2015 --outDir esm",
|
|
24
|
-
"build:cjs": "rimraf cjs && tsc -m CommonJS --outDir cjs",
|
|
23
|
+
"build:esm": "rimraf esm && tsc -p tsconfig.build.json -m es2015 --outDir esm",
|
|
24
|
+
"build:cjs": "rimraf cjs && tsc -p tsconfig.build.json -m CommonJS --outDir cjs",
|
|
25
25
|
"test": "jest",
|
|
26
26
|
"test:coverage": "jest --coverage",
|
|
27
27
|
"all": "npm run check && npm run build && npm run test",
|
|
@@ -45,12 +45,12 @@
|
|
|
45
45
|
"bugs": "https://github.com/splitio/javascript-commons/issues",
|
|
46
46
|
"homepage": "https://github.com/splitio/javascript-commons#readme",
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@types/
|
|
48
|
+
"@types/node": ">=14.0.0",
|
|
49
49
|
"tslib": "^2.3.1"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"bloom-filters": "^3.0.0",
|
|
53
|
-
"ioredis": "^4.28.0 || ^5.0.0",
|
|
53
|
+
"ioredis": "^4.28.0 || ^5.0.0 || ^6.0.0",
|
|
54
54
|
"node-fetch": "^2.7.0"
|
|
55
55
|
},
|
|
56
56
|
"peerDependenciesMeta": {
|
|
@@ -65,7 +65,6 @@
|
|
|
65
65
|
}
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
-
"@types/ioredis": "^4.28.0",
|
|
69
68
|
"@types/jest": "^27.0.0",
|
|
70
69
|
"@types/lodash": "^4.14.162",
|
|
71
70
|
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
@@ -77,7 +76,7 @@
|
|
|
77
76
|
"eslint-plugin-import": "^2.25.3",
|
|
78
77
|
"eslint-plugin-tsdoc": "^0.3.0",
|
|
79
78
|
"fetch-mock": "^9.11.0",
|
|
80
|
-
"ioredis": "^
|
|
79
|
+
"ioredis": "^6.0.0",
|
|
81
80
|
"jest": "^27.2.3",
|
|
82
81
|
"jest-localstorage-mock": "^2.4.3",
|
|
83
82
|
"lodash": "^4.17.21",
|
package/src/dtos/types.ts
CHANGED
|
@@ -236,7 +236,7 @@ export interface IDefinition extends TargetingEntity {
|
|
|
236
236
|
} | null;
|
|
237
237
|
/** Definition classification. Absent means a feature flag. */
|
|
238
238
|
type?: ConfigType;
|
|
239
|
-
/** Only meaningful when `type === '
|
|
239
|
+
/** Only meaningful when `type === 'ai'`. */
|
|
240
240
|
subtype?: ConfigSubtype;
|
|
241
241
|
}
|
|
242
242
|
|
package/src/evaluator/Engine.ts
CHANGED
|
@@ -7,12 +7,12 @@ import { CONTROL } from '../utils/constants';
|
|
|
7
7
|
import { IDefinition, MaybeThenable } from '../dtos/types';
|
|
8
8
|
import SplitIO from '../../types/splitio';
|
|
9
9
|
import { IStorageAsync, IStorageSync } from '../storages/types';
|
|
10
|
-
import { IEvaluation,
|
|
10
|
+
import { IEvaluation, IDefinitionEvaluator } from './types';
|
|
11
11
|
import { ILogger } from '../logger/types';
|
|
12
12
|
import { ENGINE_DEFAULT } from '../logger/constants';
|
|
13
13
|
import { prerequisitesMatcherContext } from './matchers/prerequisites';
|
|
14
14
|
|
|
15
|
-
function evaluationResult(result: IEvaluation | undefined, defaultTreatment: string):
|
|
15
|
+
function evaluationResult(result: IEvaluation | undefined, defaultTreatment: string): IEvaluation {
|
|
16
16
|
return {
|
|
17
17
|
treatment: get(result, 'treatment', defaultTreatment),
|
|
18
18
|
label: get(result, 'label', NO_CONDITION_MATCH)
|
|
@@ -29,7 +29,7 @@ export function engineParser(log: ILogger, split: IDefinition, storage: IStorage
|
|
|
29
29
|
|
|
30
30
|
return {
|
|
31
31
|
|
|
32
|
-
getTreatment(key: SplitIO.SplitKey, attributes: SplitIO.Attributes | undefined, splitEvaluator: IDefinitionEvaluator): MaybeThenable<
|
|
32
|
+
getTreatment(key: SplitIO.SplitKey, attributes: SplitIO.Attributes | undefined, splitEvaluator: IDefinitionEvaluator): MaybeThenable<IEvaluation> {
|
|
33
33
|
|
|
34
34
|
const parsedKey = keyParser(key);
|
|
35
35
|
|
|
@@ -29,9 +29,9 @@ export function conditionContext(log: ILogger, matcherEvaluator: (key: SplitIO.S
|
|
|
29
29
|
// Whitelisting has more priority than traffic allocation, so we don't apply this filtering to those conditions.
|
|
30
30
|
if (conditionType === 'ROLLOUT' && !shouldApplyRollout(trafficAllocation!, key.bucketingKey, trafficAllocationSeed!)) {
|
|
31
31
|
return {
|
|
32
|
-
|
|
32
|
+
// treatment value is assigned later, at Engine's evaluationResult
|
|
33
33
|
label: NOT_IN_SPLIT
|
|
34
|
-
};
|
|
34
|
+
} as IEvaluation;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
// matcherEvaluator could be Async, this relays on matchers return value, so we need
|
package/src/evaluator/index.ts
CHANGED
|
@@ -4,11 +4,12 @@ import { EXCEPTION, NO_CONDITION_MATCH, DEFINITION_NOT_FOUND } from '../utils/la
|
|
|
4
4
|
import { CONTROL } from '../utils/constants';
|
|
5
5
|
import { IDefinition, MaybeThenable } from '../dtos/types';
|
|
6
6
|
import { IStorageAsync, IStorageSync } from '../storages/types';
|
|
7
|
-
import {
|
|
7
|
+
import { IEvaluation } from './types';
|
|
8
8
|
import SplitIO from '../../types/splitio';
|
|
9
9
|
import { ILogger } from '../logger/types';
|
|
10
10
|
import { returnSetsUnion, setToArray } from '../utils/lang/sets';
|
|
11
11
|
import { WARN_FLAGSET_WITHOUT_FLAGS } from '../logger/constants';
|
|
12
|
+
import { objectAssign } from '../utils/lang/objectAssign';
|
|
12
13
|
|
|
13
14
|
const EVALUATION_EXCEPTION = {
|
|
14
15
|
treatment: CONTROL,
|
|
@@ -23,21 +24,27 @@ const EVALUATION_DEFINITION_NOT_FOUND = {
|
|
|
23
24
|
};
|
|
24
25
|
|
|
25
26
|
function treatmentsException(definitionNames: string[]) {
|
|
26
|
-
const evaluations: Record<string,
|
|
27
|
+
const evaluations: Record<string, IEvaluation> = {};
|
|
27
28
|
definitionNames.forEach(definitionName => {
|
|
28
29
|
evaluations[definitionName] = EVALUATION_EXCEPTION;
|
|
29
30
|
});
|
|
30
31
|
return evaluations;
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
function setEvaluationDataFromDefinition(evaluation: IEvaluation, definition: IDefinition): IEvaluation {
|
|
35
|
+
return objectAssign(evaluation, {
|
|
36
|
+
definition,
|
|
37
|
+
config: definition.configurations && definition.configurations[evaluation.treatment] || null
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
33
41
|
export function evaluateFeature(
|
|
34
42
|
log: ILogger,
|
|
35
43
|
key: SplitIO.SplitKey,
|
|
36
44
|
definitionName: string,
|
|
37
45
|
attributes: SplitIO.Attributes | undefined,
|
|
38
46
|
storage: IStorageSync | IStorageAsync,
|
|
39
|
-
|
|
40
|
-
): MaybeThenable<IEvaluationResult> {
|
|
47
|
+
): MaybeThenable<IEvaluation> {
|
|
41
48
|
let definition;
|
|
42
49
|
|
|
43
50
|
try {
|
|
@@ -48,13 +55,13 @@ export function evaluateFeature(
|
|
|
48
55
|
}
|
|
49
56
|
|
|
50
57
|
return thenable(definition) ?
|
|
51
|
-
definition.then((definition) => getEvaluation(log, key, definition, attributes, storage
|
|
58
|
+
definition.then((definition) => getEvaluation(log, key, definition, attributes, storage))
|
|
52
59
|
.catch(
|
|
53
60
|
// Exception on async storage. For example, when the storage is redis or
|
|
54
61
|
// pluggable and there is a connection issue and we can't retrieve the split to be evaluated
|
|
55
62
|
() => EVALUATION_EXCEPTION
|
|
56
63
|
) :
|
|
57
|
-
getEvaluation(log, key, definition, attributes, storage
|
|
64
|
+
getEvaluation(log, key, definition, attributes, storage);
|
|
58
65
|
}
|
|
59
66
|
|
|
60
67
|
export function evaluateFeatures(
|
|
@@ -63,8 +70,7 @@ export function evaluateFeatures(
|
|
|
63
70
|
definitionNames: string[],
|
|
64
71
|
attributes: SplitIO.Attributes | undefined,
|
|
65
72
|
storage: IStorageSync | IStorageAsync,
|
|
66
|
-
|
|
67
|
-
): MaybeThenable<Record<string, IEvaluationResult>> {
|
|
73
|
+
): MaybeThenable<Record<string, IEvaluation>> {
|
|
68
74
|
let definitions;
|
|
69
75
|
|
|
70
76
|
try {
|
|
@@ -75,13 +81,13 @@ export function evaluateFeatures(
|
|
|
75
81
|
}
|
|
76
82
|
|
|
77
83
|
return thenable(definitions) ?
|
|
78
|
-
definitions.then(definitions => getEvaluations(log, key, definitionNames, definitions, attributes, storage
|
|
84
|
+
definitions.then(definitions => getEvaluations(log, key, definitionNames, definitions, attributes, storage))
|
|
79
85
|
.catch(
|
|
80
86
|
// Exception on async storage. For example, when the storage is redis or
|
|
81
87
|
// pluggable and there is a connection issue and we can't retrieve the split to be evaluated
|
|
82
88
|
() => treatmentsException(definitionNames)
|
|
83
89
|
) :
|
|
84
|
-
getEvaluations(log, key, definitionNames, definitions, attributes, storage
|
|
90
|
+
getEvaluations(log, key, definitionNames, definitions, attributes, storage);
|
|
85
91
|
}
|
|
86
92
|
|
|
87
93
|
export function evaluateFeaturesByFlagSets(
|
|
@@ -91,8 +97,7 @@ export function evaluateFeaturesByFlagSets(
|
|
|
91
97
|
attributes: SplitIO.Attributes | undefined,
|
|
92
98
|
storage: IStorageSync | IStorageAsync,
|
|
93
99
|
method: string,
|
|
94
|
-
|
|
95
|
-
): MaybeThenable<Record<string, IEvaluationResult>> {
|
|
100
|
+
): MaybeThenable<Record<string, IEvaluation>> {
|
|
96
101
|
let storedFlagNames: MaybeThenable<Set<string>[]>;
|
|
97
102
|
|
|
98
103
|
function evaluate(featureFlagsByFlagSets: Set<string>[]) {
|
|
@@ -107,7 +112,7 @@ export function evaluateFeaturesByFlagSets(
|
|
|
107
112
|
}
|
|
108
113
|
|
|
109
114
|
return featureFlags.size ?
|
|
110
|
-
evaluateFeatures(log, key, setToArray(featureFlags), attributes, storage
|
|
115
|
+
evaluateFeatures(log, key, setToArray(featureFlags), attributes, storage) :
|
|
111
116
|
{};
|
|
112
117
|
}
|
|
113
118
|
|
|
@@ -126,33 +131,21 @@ export function evaluateFeaturesByFlagSets(
|
|
|
126
131
|
evaluate(storedFlagNames);
|
|
127
132
|
}
|
|
128
133
|
|
|
129
|
-
function setEvaluationDataFromDefinition(evaluation: IEvaluationResult, definition: IDefinition, options?: SplitIO.EvaluationOptions): IEvaluationResult {
|
|
130
|
-
evaluation.changeNumber = definition.changeNumber;
|
|
131
|
-
evaluation.config = definition.configurations && definition.configurations[evaluation.treatment] || null;
|
|
132
|
-
evaluation.type = definition.type;
|
|
133
|
-
evaluation.subtype = definition.subtype;
|
|
134
|
-
// @ts-expect-error impressionsDisabled is not exposed in the public typings yet.
|
|
135
|
-
evaluation.impressionsDisabled = options?.impressionsDisabled || definition.impressionsDisabled;
|
|
136
|
-
|
|
137
|
-
return evaluation;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
134
|
function getEvaluation(
|
|
141
135
|
log: ILogger,
|
|
142
136
|
key: SplitIO.SplitKey,
|
|
143
137
|
definition: IDefinition | null,
|
|
144
138
|
attributes: SplitIO.Attributes | undefined,
|
|
145
139
|
storage: IStorageSync | IStorageAsync,
|
|
146
|
-
|
|
147
|
-
): MaybeThenable<IEvaluationResult> {
|
|
140
|
+
): MaybeThenable<IEvaluation> {
|
|
148
141
|
|
|
149
142
|
if (definition) {
|
|
150
143
|
const split = engineParser(log, definition, storage);
|
|
151
144
|
const evaluation = split.getTreatment(key, attributes, evaluateFeature);
|
|
152
145
|
|
|
153
146
|
return thenable(evaluation) ?
|
|
154
|
-
evaluation.then(result => setEvaluationDataFromDefinition(result, definition
|
|
155
|
-
setEvaluationDataFromDefinition(evaluation, definition
|
|
147
|
+
evaluation.then(result => setEvaluationDataFromDefinition(result, definition)) :
|
|
148
|
+
setEvaluationDataFromDefinition(evaluation, definition);
|
|
156
149
|
}
|
|
157
150
|
|
|
158
151
|
return EVALUATION_DEFINITION_NOT_FOUND;
|
|
@@ -165,12 +158,11 @@ function getEvaluations(
|
|
|
165
158
|
definitions: Record<string, IDefinition | null>,
|
|
166
159
|
attributes: SplitIO.Attributes | undefined,
|
|
167
160
|
storage: IStorageSync | IStorageAsync,
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const result: Record<string, IEvaluationResult> = {};
|
|
161
|
+
): MaybeThenable<Record<string, IEvaluation>> {
|
|
162
|
+
const result: Record<string, IEvaluation> = {};
|
|
171
163
|
const thenables: Promise<void>[] = [];
|
|
172
164
|
definitionNames.forEach(definitionName => {
|
|
173
|
-
const evaluation = getEvaluation(log, key, definitions[definitionName], attributes, storage
|
|
165
|
+
const evaluation = getEvaluation(log, key, definitions[definitionName], attributes, storage);
|
|
174
166
|
thenable(evaluation) ?
|
|
175
167
|
thenables.push(evaluation.then(res => {
|
|
176
168
|
result[definitionName] = res;
|
|
@@ -184,7 +176,7 @@ function getEvaluations(
|
|
|
184
176
|
export function evaluateDefaultTreatment(
|
|
185
177
|
definitionName: string,
|
|
186
178
|
storage: IStorageSync | IStorageAsync,
|
|
187
|
-
): MaybeThenable<
|
|
179
|
+
): MaybeThenable<IEvaluation> {
|
|
188
180
|
let definition;
|
|
189
181
|
|
|
190
182
|
try {
|
|
@@ -200,11 +192,11 @@ export function evaluateDefaultTreatment(
|
|
|
200
192
|
|
|
201
193
|
function getDefaultTreatment(
|
|
202
194
|
definition: IDefinition | null,
|
|
203
|
-
): MaybeThenable<
|
|
195
|
+
): MaybeThenable<IEvaluation> {
|
|
204
196
|
if (definition) {
|
|
205
197
|
return setEvaluationDataFromDefinition({
|
|
206
198
|
treatment: definition.defaultTreatment,
|
|
207
|
-
label: NO_CONDITION_MATCH // "default rule"
|
|
199
|
+
label: NO_CONDITION_MATCH, // "default rule"
|
|
208
200
|
}, definition);
|
|
209
201
|
}
|
|
210
202
|
|
package/src/evaluator/types.ts
CHANGED
|
@@ -19,16 +19,12 @@ export interface IMatcherDto {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export interface IEvaluation {
|
|
22
|
-
treatment
|
|
22
|
+
treatment: string,
|
|
23
23
|
label: string,
|
|
24
|
-
|
|
24
|
+
definition?: IDefinition
|
|
25
25
|
config?: string | null | SplitIO.JsonObject
|
|
26
|
-
type?: IDefinition['type']
|
|
27
|
-
subtype?: IDefinition['subtype']
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
export type IEvaluationResult = IEvaluation & { treatment: string; impressionsDisabled?: boolean }
|
|
31
|
-
|
|
32
28
|
export type IDefinitionEvaluator = (log: ILogger, key: SplitIO.SplitKey, definitionName: string, attributes: SplitIO.Attributes | undefined, storage: IStorageSync | IStorageAsync) => MaybeThenable<IEvaluation>
|
|
33
29
|
|
|
34
30
|
export type IEvaluator = (key: SplitIO.SplitKeyObject, seed?: number, trafficAllocation?: number, trafficAllocationSeed?: number, attributes?: SplitIO.Attributes, splitEvaluator?: IDefinitionEvaluator) => MaybeThenable<IEvaluation | boolean | undefined>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { splitApiFactory } from '../services/splitApi';
|
|
2
2
|
import { syncManagerOnlineFactory } from '../sync/syncManagerOnline';
|
|
3
3
|
import { pushManagerFactory } from '../sync/streaming/pushManager';
|
|
4
4
|
import { pollingManagerSSFactory } from '../sync/polling/pollingManagerSS';
|
|
@@ -7,11 +7,11 @@ import { sdkManagerFactory } from '../sdkManager';
|
|
|
7
7
|
import { sdkClientMethodFactory } from '../sdkClient/sdkClientMethod';
|
|
8
8
|
import { impressionObserverSSFactory } from '../trackers/impressionObserver/impressionObserverSS';
|
|
9
9
|
|
|
10
|
-
const syncManagerOnlineSSFactory = syncManagerOnlineFactory(pollingManagerSSFactory, pushManagerFactory);
|
|
10
|
+
const syncManagerOnlineSSFactory = syncManagerOnlineFactory(pollingManagerSSFactory(), pushManagerFactory);
|
|
11
11
|
|
|
12
12
|
export const serverSideModules = {
|
|
13
13
|
storageFactory: InMemoryStorageFactory,
|
|
14
|
-
serviceApiFactory,
|
|
14
|
+
serviceApiFactory: splitApiFactory,
|
|
15
15
|
syncManagerFactory: syncManagerOnlineSSFactory,
|
|
16
16
|
sdkManagerFactory,
|
|
17
17
|
sdkClientMethodFactory,
|
package/src/sdkClient/client.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { getMatching, getBucketing } from '../utils/key';
|
|
|
4
4
|
import { validateDefinitionExistence } from '../utils/inputValidation/definitionExistence';
|
|
5
5
|
import { SDK_NOT_READY } from '../utils/labels';
|
|
6
6
|
import { CONTROL, TREATMENT, TREATMENTS, TREATMENT_WITH_CONFIG, TREATMENTS_WITH_CONFIG, TREATMENTS_WITH_CONFIG_BY_FLAGSETS, TREATMENTS_BY_FLAGSETS, TREATMENTS_BY_FLAGSET, TREATMENTS_WITH_CONFIG_BY_FLAGSET, GET_TREATMENTS_WITH_CONFIG, GET_TREATMENTS_BY_FLAG_SETS, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, GET_TREATMENTS_BY_FLAG_SET, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, GET_TREATMENT_WITH_CONFIG, GET_TREATMENT, GET_TREATMENTS } from '../utils/constants';
|
|
7
|
-
import {
|
|
7
|
+
import { IEvaluation } from '../evaluator/types';
|
|
8
8
|
import SplitIO from '../../types/splitio';
|
|
9
9
|
import { IMPRESSION_QUEUEING } from '../logger/constants';
|
|
10
10
|
import { ISdkFactoryContext } from '../sdkFactory/types';
|
|
@@ -16,17 +16,17 @@ import { trackMethodFactory } from './trackMethod';
|
|
|
16
16
|
const treatmentNotReady = { treatment: CONTROL, label: SDK_NOT_READY };
|
|
17
17
|
|
|
18
18
|
function treatmentsNotReady(featureFlagNames: string[]) {
|
|
19
|
-
const evaluations: Record<string,
|
|
19
|
+
const evaluations: Record<string, IEvaluation> = {};
|
|
20
20
|
featureFlagNames.forEach(featureFlagName => {
|
|
21
21
|
evaluations[featureFlagName] = treatmentNotReady;
|
|
22
22
|
});
|
|
23
23
|
return evaluations;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export function stringify(
|
|
27
|
-
if (
|
|
26
|
+
export function stringify(properties?: SplitIO.Properties) {
|
|
27
|
+
if (properties) {
|
|
28
28
|
try {
|
|
29
|
-
return JSON.stringify(
|
|
29
|
+
return JSON.stringify(properties);
|
|
30
30
|
} catch { /* JSON.stringify should never throw with validated options, but handling just in case */ }
|
|
31
31
|
}
|
|
32
32
|
}
|
|
@@ -42,9 +42,9 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
42
42
|
function getTreatment(key: SplitIO.SplitKey, featureFlagName: string, attributes?: SplitIO.Attributes, options?: SplitIO.EvaluationOptions, withConfig = false, methodName = GET_TREATMENT) {
|
|
43
43
|
const stopTelemetryTracker = telemetryTracker.trackEval(withConfig ? TREATMENT_WITH_CONFIG : TREATMENT);
|
|
44
44
|
|
|
45
|
-
const wrapUp = (evaluationResult:
|
|
45
|
+
const wrapUp = (evaluationResult: IEvaluation) => {
|
|
46
46
|
const queue: ImpressionDecorated[] = [];
|
|
47
|
-
const treatment = processEvaluation(evaluationResult, featureFlagName, key,
|
|
47
|
+
const treatment = processEvaluation(evaluationResult, featureFlagName, key, withConfig, methodName, queue, options);
|
|
48
48
|
impressionsTracker.track(queue, attributes);
|
|
49
49
|
|
|
50
50
|
stopTelemetryTracker(queue[0] && queue[0].imp.label);
|
|
@@ -52,7 +52,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
const evaluation = readinessManager.isReadyFromCache() ?
|
|
55
|
-
evaluateFeature(log, key, featureFlagName, attributes, storage
|
|
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) :
|
|
58
58
|
treatmentNotReady;
|
|
@@ -67,12 +67,11 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
67
67
|
function getTreatments(key: SplitIO.SplitKey, featureFlagNames: string[], attributes?: SplitIO.Attributes, options?: SplitIO.EvaluationOptions, withConfig = false, methodName = GET_TREATMENTS) {
|
|
68
68
|
const stopTelemetryTracker = telemetryTracker.trackEval(withConfig ? TREATMENTS_WITH_CONFIG : TREATMENTS);
|
|
69
69
|
|
|
70
|
-
const wrapUp = (evaluationResults: Record<string,
|
|
70
|
+
const wrapUp = (evaluationResults: Record<string, IEvaluation>) => {
|
|
71
71
|
const queue: ImpressionDecorated[] = [];
|
|
72
72
|
const treatments: SplitIO.Treatments | SplitIO.TreatmentsWithConfig = {};
|
|
73
|
-
const properties = stringify(options);
|
|
74
73
|
Object.keys(evaluationResults).forEach(featureFlagName => {
|
|
75
|
-
treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key,
|
|
74
|
+
treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key, withConfig, methodName, queue, options);
|
|
76
75
|
});
|
|
77
76
|
impressionsTracker.track(queue, attributes);
|
|
78
77
|
|
|
@@ -81,7 +80,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
81
80
|
};
|
|
82
81
|
|
|
83
82
|
const evaluations = readinessManager.isReadyFromCache() ?
|
|
84
|
-
evaluateFeatures(log, key, featureFlagNames, attributes, storage
|
|
83
|
+
evaluateFeatures(log, key, featureFlagNames, attributes, storage) :
|
|
85
84
|
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
85
|
Promise.resolve(treatmentsNotReady(featureFlagNames)) :
|
|
87
86
|
treatmentsNotReady(featureFlagNames);
|
|
@@ -96,12 +95,11 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
96
95
|
function getTreatmentsByFlagSets(key: SplitIO.SplitKey, flagSetNames: string[], attributes?: SplitIO.Attributes, options?: SplitIO.EvaluationOptions, withConfig = false, method: Method = TREATMENTS_BY_FLAGSETS, methodName = GET_TREATMENTS_BY_FLAG_SETS) {
|
|
97
96
|
const stopTelemetryTracker = telemetryTracker.trackEval(method);
|
|
98
97
|
|
|
99
|
-
const wrapUp = (evaluationResults: Record<string,
|
|
98
|
+
const wrapUp = (evaluationResults: Record<string, IEvaluation>) => {
|
|
100
99
|
const queue: ImpressionDecorated[] = [];
|
|
101
100
|
const treatments: SplitIO.Treatments | SplitIO.TreatmentsWithConfig = {};
|
|
102
|
-
const properties = stringify(options);
|
|
103
101
|
Object.keys(evaluationResults).forEach(featureFlagName => {
|
|
104
|
-
treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key,
|
|
102
|
+
treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key, withConfig, methodName, queue, options);
|
|
105
103
|
});
|
|
106
104
|
impressionsTracker.track(queue, attributes);
|
|
107
105
|
|
|
@@ -110,7 +108,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
110
108
|
};
|
|
111
109
|
|
|
112
110
|
const evaluations = readinessManager.isReadyFromCache() ?
|
|
113
|
-
evaluateFeaturesByFlagSets(log, key, flagSetNames, attributes, storage, methodName
|
|
111
|
+
evaluateFeaturesByFlagSets(log, key, flagSetNames, attributes, storage, methodName) :
|
|
114
112
|
isAsync ?
|
|
115
113
|
Promise.resolve({}) :
|
|
116
114
|
{};
|
|
@@ -132,18 +130,18 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
132
130
|
|
|
133
131
|
// Internal function
|
|
134
132
|
function processEvaluation(
|
|
135
|
-
evaluation:
|
|
133
|
+
evaluation: IEvaluation,
|
|
136
134
|
featureFlagName: string,
|
|
137
135
|
key: SplitIO.SplitKey,
|
|
138
|
-
properties: string | undefined,
|
|
139
136
|
withConfig: boolean,
|
|
140
137
|
invokingMethodName: string,
|
|
141
|
-
queue: ImpressionDecorated[]
|
|
138
|
+
queue: ImpressionDecorated[],
|
|
139
|
+
options: SplitIO.EvaluationOptions = {}
|
|
142
140
|
): SplitIO.Treatment | SplitIO.TreatmentWithConfig {
|
|
143
141
|
const matchingKey = getMatching(key);
|
|
144
142
|
const bucketingKey = getBucketing(key);
|
|
145
143
|
|
|
146
|
-
const {
|
|
144
|
+
const { definition } = evaluation;
|
|
147
145
|
let { treatment, label, config = null } = evaluation;
|
|
148
146
|
|
|
149
147
|
if (treatment === CONTROL) {
|
|
@@ -163,10 +161,10 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
163
161
|
time: Date.now(),
|
|
164
162
|
bucketingKey,
|
|
165
163
|
label,
|
|
166
|
-
changeNumber: changeNumber
|
|
167
|
-
properties
|
|
168
|
-
},
|
|
169
|
-
disabled: impressionsDisabled
|
|
164
|
+
changeNumber: definition?.changeNumber,
|
|
165
|
+
properties: stringify(options.properties)
|
|
166
|
+
}, // @ts-expect-error impressionsDisabled is not exposed in the public typings yet.
|
|
167
|
+
disabled: options.impressionsDisabled || definition?.impressionsDisabled
|
|
170
168
|
});
|
|
171
169
|
}
|
|
172
170
|
|
|
@@ -69,6 +69,9 @@ export function sdkLifecycleFactory(params: ISdkFactoryContext, isSharedClient?:
|
|
|
69
69
|
|
|
70
70
|
// Stop background jobs
|
|
71
71
|
syncManager && syncManager.stop();
|
|
72
|
+
|
|
73
|
+
// Stop the service API (the auth provider backoff retries specifically) before flushing,
|
|
74
|
+
// so that a pending/never-resolved auth retry doesn't block the flush forever
|
|
72
75
|
serviceApi && serviceApi.stop();
|
|
73
76
|
|
|
74
77
|
return __flush().then(() => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ISplitHttpClient, NetworkError } from './types';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { IJwtCredential } from '../sync/streaming/AuthClient/types';
|
|
3
|
+
import { fetchAuthFactory } from '../sync/streaming/AuthClient';
|
|
4
4
|
import { Backoff } from '../utils/Backoff';
|
|
5
5
|
import { LOG_PREFIX_SYNC_AUTH } from '../logger/constants';
|
|
6
6
|
import { ISettings } from '../types';
|
|
@@ -9,12 +9,24 @@ import { ITelemetryTracker } from '../trackers/types';
|
|
|
9
9
|
|
|
10
10
|
const SKEW_SECONDS = 30;
|
|
11
11
|
|
|
12
|
-
function isExpired(credential:
|
|
12
|
+
function isExpired(credential: IJwtCredential): boolean {
|
|
13
13
|
return Date.now() / 1000 + SKEW_SECONDS >= credential.decodedToken.exp;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
function noCachedCredentialError() {
|
|
17
|
+
return new Error(LOG_PREFIX_SYNC_AUTH + 'stopped without a cached credential');
|
|
18
|
+
}
|
|
19
|
+
|
|
16
20
|
export interface IAuthProvider {
|
|
17
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Returns the cached credential, or fetches a new one if there isn't one cached or it's expired,
|
|
23
|
+
* retrying with backoff on recoverable errors. Used by `secureSplitHttpClient` and `serviceApi.fetchAuth`.
|
|
24
|
+
*/
|
|
25
|
+
credential(): Promise<IJwtCredential>;
|
|
26
|
+
/**
|
|
27
|
+
* Invalidates/clears the cached credential. Used by `secureSplitHttpClient` in the special case of 401 error,
|
|
28
|
+
* and by `serviceApi.fetchAuth` to force a credential/token refresh.
|
|
29
|
+
*/
|
|
18
30
|
invalidate(): void;
|
|
19
31
|
stop(): void;
|
|
20
32
|
}
|
|
@@ -27,28 +39,27 @@ export function authProviderFactory(settings: ISettings, splitHttpClient: ISplit
|
|
|
27
39
|
|
|
28
40
|
const { urls, log } = settings;
|
|
29
41
|
|
|
30
|
-
|
|
42
|
+
const fetchAuth = fetchAuthFactory(() => {
|
|
31
43
|
let url = `${urls.auth}/api/v3/auth?capabilities=config,aiconfig`;
|
|
32
44
|
return splitHttpClient(url, undefined, telemetryTracker.trackHttp(TOKEN), false, true);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const authenticate = authenticateFactory(fetchAuth);
|
|
45
|
+
});
|
|
36
46
|
const backoff = new Backoff(fetchCredential);
|
|
37
47
|
|
|
38
|
-
let cachedCredential:
|
|
39
|
-
let inFlightPromise: Promise<
|
|
48
|
+
let cachedCredential: IJwtCredential | undefined;
|
|
49
|
+
let inFlightPromise: Promise<IJwtCredential> | undefined;
|
|
40
50
|
let stopped = false;
|
|
41
51
|
|
|
42
|
-
function fetchCredential(): Promise<
|
|
43
|
-
return
|
|
52
|
+
function fetchCredential(): Promise<IJwtCredential> {
|
|
53
|
+
return fetchAuth().then((credential: IJwtCredential) => {
|
|
44
54
|
log.info(LOG_PREFIX_SYNC_AUTH + 'credential fetched successfully');
|
|
45
55
|
cachedCredential = credential;
|
|
46
56
|
inFlightPromise = undefined;
|
|
47
57
|
backoff.reset();
|
|
48
58
|
return credential;
|
|
49
59
|
}).catch((error: NetworkError) => {
|
|
50
|
-
// Avoid
|
|
51
|
-
|
|
60
|
+
// Avoid unnecessary retries after stop(): fall back to any cached credential, or reject
|
|
61
|
+
// (never resolve with `undefined`, which callers would otherwise send as an unauthenticated request).
|
|
62
|
+
if (stopped) return cachedCredential || Promise.reject(noCachedCredentialError());
|
|
52
63
|
|
|
53
64
|
if (error.statusCode && error.statusCode >= 400 && error.statusCode < 500) {
|
|
54
65
|
log.error(LOG_PREFIX_SYNC_AUTH + 'non-retryable error fetching credential (status ' + error.statusCode + '): ' + error.message);
|
|
@@ -62,14 +73,24 @@ export function authProviderFactory(settings: ISettings, splitHttpClient: ISplit
|
|
|
62
73
|
}
|
|
63
74
|
|
|
64
75
|
return {
|
|
65
|
-
credential(): Promise<
|
|
76
|
+
credential(): Promise<IJwtCredential> {
|
|
66
77
|
if (cachedCredential && !isExpired(cachedCredential)) {
|
|
67
78
|
return Promise.resolve(cachedCredential);
|
|
68
79
|
}
|
|
69
80
|
|
|
81
|
+
// Reuse any fetch already in flight (e.g. one started just before stop()), instead of firing a redundant one.
|
|
82
|
+
if (inFlightPromise) return inFlightPromise;
|
|
83
|
+
|
|
84
|
+
// No cached credential to fall back to and stopped: reject so callers don't send an unauthenticated request.
|
|
85
|
+
if (stopped && !cachedCredential) {
|
|
86
|
+
return Promise.reject(noCachedCredentialError());
|
|
87
|
+
}
|
|
88
|
+
|
|
70
89
|
if (cachedCredential) log.debug(LOG_PREFIX_SYNC_AUTH + 'cached credential expired');
|
|
71
90
|
|
|
72
|
-
|
|
91
|
+
// If stopped, this is a last, bounded refetch: fetchCredential()'s catch short-circuits (no backoff retry)
|
|
92
|
+
// once stopped, so it's a single attempt that falls back to the stale cached credential on failure.
|
|
93
|
+
return inFlightPromise = fetchCredential();
|
|
73
94
|
},
|
|
74
95
|
|
|
75
96
|
invalidate() {
|
|
@@ -78,9 +99,10 @@ export function authProviderFactory(settings: ISettings, splitHttpClient: ISplit
|
|
|
78
99
|
|
|
79
100
|
stop() {
|
|
80
101
|
stopped = true;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
backoff.
|
|
102
|
+
// Keep any in-flight fetch and already-cached credential usable (e.g. by a final flush on destroy).
|
|
103
|
+
// If a retry is only pending (not actively in flight), settle it immediately instead of waiting out the
|
|
104
|
+
// backoff delay: with the cached credential if there is one, or rejected otherwise.
|
|
105
|
+
backoff.reset(cachedCredential ? { value: cachedCredential } : { error: noCachedCredentialError() });
|
|
84
106
|
}
|
|
85
107
|
};
|
|
86
108
|
}
|
|
@@ -1,36 +1,28 @@
|
|
|
1
|
-
import { IRequestOptions, IResponse, ISecureSplitHttpClient, NetworkError } from './types';
|
|
2
|
-
import {
|
|
3
|
-
import { IPlatform } from '../sdkFactory/types';
|
|
4
|
-
import { splitHttpClientFactory } from './splitHttpClient';
|
|
5
|
-
import { authProviderFactory } from './authProvider';
|
|
6
|
-
import { ITelemetryTracker } from '../trackers/types';
|
|
1
|
+
import { IRequestOptions, IResponse, ISecureSplitHttpClient, ISplitHttpClient, NetworkError } from './types';
|
|
2
|
+
import { IAuthProvider } from './authProvider';
|
|
7
3
|
|
|
8
4
|
/**
|
|
9
5
|
* Factory of Secure HTTP client, which authenticates requests using a JWT token.
|
|
10
6
|
* On 401 responses, invalidates the cached credential and retries once with a fresh token.
|
|
11
7
|
*
|
|
12
|
-
* @param
|
|
13
|
-
* @param
|
|
14
|
-
* @param fetchAuth - function to fetch auth credentials from the /v2/auth endpoint
|
|
8
|
+
* @param splitHttpClient - `splitHttpClientFactory` to use for making requests
|
|
9
|
+
* @param authProvider - `authProviderFactory` to use for authentication
|
|
15
10
|
*/
|
|
16
|
-
export function secureSplitHttpClientFactory(
|
|
11
|
+
export function secureSplitHttpClientFactory(splitHttpClient: ISplitHttpClient, authProvider: IAuthProvider): ISecureSplitHttpClient {
|
|
17
12
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
function makeRequest(url: string, options: IRequestOptions | undefined, latencyTracker: ((error?: NetworkError) => void) | undefined, logErrorsAsInfo: boolean | undefined, token: string): Promise<IResponse> {
|
|
22
|
-
return splitHttpClient(url, { ...options, headers: { ...options?.headers, Authorization: `Bearer ${token}` } }, latencyTracker, logErrorsAsInfo, true);
|
|
13
|
+
function makeRequest(url: string, options: IRequestOptions | undefined, latencyTracker?: (error?: NetworkError) => void, logErrorsAsInfo?: boolean, newVersionHeader?: boolean, token?: string): Promise<IResponse> {
|
|
14
|
+
return splitHttpClient(url, token ? { ...options, headers: { ...options?.headers, Authorization: `Bearer ${token}` } } : options, latencyTracker, logErrorsAsInfo, newVersionHeader);
|
|
23
15
|
}
|
|
24
16
|
|
|
25
|
-
const httpClient = function (url: string, options?: IRequestOptions, latencyTracker?: (error?: NetworkError) => void, logErrorsAsInfo?: boolean): Promise<IResponse> {
|
|
17
|
+
const httpClient = function (url: string, options?: IRequestOptions, latencyTracker?: (error?: NetworkError) => void, logErrorsAsInfo?: boolean, newVersionHeader = true, useJwt = true): Promise<IResponse> {
|
|
26
18
|
return authProvider.credential().then(credential => {
|
|
27
|
-
return makeRequest(url, options, latencyTracker, logErrorsAsInfo, credential.token)
|
|
19
|
+
return makeRequest(url, options, latencyTracker, logErrorsAsInfo, newVersionHeader, useJwt ? credential.token : undefined)
|
|
28
20
|
.catch((error: NetworkError) => {
|
|
29
21
|
if (error.statusCode === 401) {
|
|
30
22
|
// retry once for 401, in case the token has just expired
|
|
31
23
|
authProvider.invalidate();
|
|
32
24
|
return authProvider.credential().then(newCredential => {
|
|
33
|
-
return makeRequest(url, options, latencyTracker, logErrorsAsInfo, newCredential.token);
|
|
25
|
+
return makeRequest(url, options, latencyTracker, logErrorsAsInfo, newVersionHeader, useJwt ? newCredential.token : undefined);
|
|
34
26
|
});
|
|
35
27
|
}
|
|
36
28
|
throw error;
|